Import brillo_update_payload from Chrome OS dev-utils.
brillo_update_payload was developed in Chrome OS' dev-utils repo. This
CL merges it here to make it available from an AOSP checkout.
Bug: 25631934
TEST=File is present on the checkout.
Change-Id: Idc6eac200b4f725b103847c3f77260a25ea55a7b
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d6dd317
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# Build/test generated files
+/*.pub.pem
+/app.info
+/delta_generator
+/html/
+/test_http_server
+/update_engine
+/update_engine.dbusclient.h
+/update_engine.dbusserver.h
+/update_engine_client
+/update_engine_unittests
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..265ed23
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,484 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(my-dir)
+
+# Default values for the USE flags. Override these USE flags from your product.
+BRILLO_USE_DBUS ?= 1
+BRILLO_USE_HWID_OVERRIDE ?= 0
+BRILLO_USE_MTD ?= 0
+BRILLO_USE_POWER_MANAGEMENT ?= 0
+
+ue_common_cflags := \
+ -DUSE_DBUS=$(BRILLO_USE_DBUS) \
+ -DUSE_HWID_OVERRIDE=$(BRILLO_USE_HWID_OVERRIDE) \
+ -DUSE_MTD=$(BRILLO_USE_MTD) \
+ -DUSE_POWER_MANAGEMENT=$(BRILLO_USE_POWER_MANAGEMENT) \
+ -D_FILE_OFFSET_BITS=64 \
+ -D_POSIX_C_SOURCE=199309L \
+ -Wa,--noexecstack \
+ -Wall \
+ -Werror \
+ -Wextra \
+ -Wformat=2 \
+ -Wno-psabi \
+ -Wno-unused-parameter \
+ -ffunction-sections \
+ -fstack-protector-strong \
+ -fvisibility=hidden
+ue_common_cppflags := \
+ -Wnon-virtual-dtor \
+ -fno-strict-aliasing \
+ -std=gnu++11
+ue_common_ldflags := \
+ -Wl,--gc-sections
+ue_common_c_includes := \
+ $(LOCAL_PATH)/client_library/include \
+ external/gtest/include \
+ system
+ue_common_shared_libraries := \
+ libbrillo \
+ libbrillo-http \
+ libbrillo-stream \
+ libchrome
+
+ifeq ($(BRILLO_USE_DBUS),1)
+ue_common_shared_libraries += \
+ libbrillo-dbus \
+ libchrome-dbus
+endif # BRILLO_USE_DBUS == 1
+
+
+ifeq ($(BRILLO_USE_DBUS),1)
+
+# update_engine_client-dbus-proxies (from generate-dbus-proxies.gypi)
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine_client-dbus-proxies
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_SRC_FILES := \
+ dbus_bindings/dbus-service-config.json \
+ dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
+LOCAL_DBUS_PROXY_PREFIX := update_engine
+include $(BUILD_STATIC_LIBRARY)
+
+endif # BRILLO_USE_DBUS == 1
+
+# update_metadata-protos (type: static_library)
+# ========================================================
+# Protobufs.
+ue_update_metadata_protos_exported_static_libraries := \
+ update_metadata-protos
+ue_update_metadata_protos_exported_shared_libraries := \
+ libprotobuf-cpp-lite-rtti
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_metadata-protos
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+generated_sources_dir := $(call local-generated-sources-dir)
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(generated_sources_dir)/proto/system
+LOCAL_SRC_FILES := \
+ update_metadata.proto
+include $(BUILD_STATIC_LIBRARY)
+
+ifeq ($(BRILLO_USE_DBUS),1)
+
+# update_engine-dbus-adaptor (from generate-dbus-adaptors.gypi)
+# ========================================================
+# Chrome D-Bus bindings.
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine-dbus-adaptor
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_SRC_FILES := \
+ dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
+include $(BUILD_STATIC_LIBRARY)
+
+# update_engine-dbus-libcros-client (from generate-dbus-proxies.gypi)
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine-dbus-libcros-client
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_SRC_FILES := \
+ dbus_bindings/org.chromium.LibCrosService.dbus-xml
+LOCAL_DBUS_PROXY_PREFIX := libcros
+include $(BUILD_STATIC_LIBRARY)
+
+endif # BRILLO_USE_DBUS == 1
+
+# libpayload_consumer (type: static_library)
+# ========================================================
+# The payload application component and common dependencies.
+ue_libpayload_consumer_exported_static_libraries := \
+ update_metadata-protos \
+ libxz \
+ libbz \
+ $(ue_update_metadata_protos_exported_static_libraries)
+ue_libpayload_consumer_exported_shared_libraries := \
+ libcrypto \
+ libcurl \
+ libssl \
+ $(ue_update_metadata_protos_exported_shared_libraries)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libpayload_consumer
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_RTTI_FLAG := -frtti
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+ $(ue_common_c_includes) \
+ external/e2fsprogs/lib
+LOCAL_STATIC_LIBRARIES := \
+ update_metadata-protos \
+ $(ue_libpayload_consumer_exported_static_libraries) \
+ $(ue_update_metadata_protos_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES := \
+ $(ue_common_shared_libraries) \
+ $(ue_libpayload_consumer_exported_shared_libraries) \
+ $(ue_update_metadata_protos_exported_shared_libraries)
+LOCAL_SRC_FILES := \
+ common/action_processor.cc \
+ common/boot_control_stub.cc \
+ common/certificate_checker.cc \
+ common/clock.cc \
+ common/constants.cc \
+ common/hash_calculator.cc \
+ common/http_common.cc \
+ common/http_fetcher.cc \
+ common/hwid_override.cc \
+ common/libcurl_http_fetcher.cc \
+ common/multi_range_http_fetcher.cc \
+ common/platform_constants_android.cc \
+ common/prefs.cc \
+ common/subprocess.cc \
+ common/terminator.cc \
+ common/utils.cc \
+ payload_consumer/bzip_extent_writer.cc \
+ payload_consumer/delta_performer.cc \
+ payload_consumer/download_action.cc \
+ payload_consumer/extent_writer.cc \
+ payload_consumer/file_descriptor.cc \
+ payload_consumer/file_writer.cc \
+ payload_consumer/filesystem_verifier_action.cc \
+ payload_consumer/install_plan.cc \
+ payload_consumer/payload_constants.cc \
+ payload_consumer/payload_verifier.cc \
+ payload_consumer/postinstall_runner_action.cc \
+ payload_consumer/xz_extent_writer.cc
+include $(BUILD_STATIC_LIBRARY)
+
+ifeq ($(BRILLO_USE_DBUS),1)
+
+# libupdate_engine (type: static_library)
+# ========================================================
+# The main daemon static_library with all the code used to check for updates
+# with Omaha and expose a DBus daemon.
+ue_libupdate_engine_exported_c_includes := \
+ $(LOCAL_PATH)/include \
+ external/cros/system_api/dbus
+ue_libupdate_engine_exported_static_libraries := \
+ libpayload_consumer \
+ update_metadata-protos \
+ update_engine-dbus-adaptor \
+ update_engine-dbus-libcros-client \
+ update_engine_client-dbus-proxies \
+ libbz \
+ libfs_mgr \
+ $(ue_libpayload_consumer_exported_static_libraries) \
+ $(ue_update_metadata_protos_exported_static_libraries)
+ue_libupdate_engine_exported_shared_libraries := \
+ libdbus \
+ libmetrics \
+ libshill-client \
+ libexpat \
+ libbrillo-policy \
+ libhardware \
+ libcutils \
+ $(ue_libpayload_consumer_exported_shared_libraries) \
+ $(ue_update_metadata_protos_exported_shared_libraries)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libupdate_engine
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_RTTI_FLAG := -frtti
+LOCAL_CLANG := true
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(ue_libupdate_engine_exported_c_includes)
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+ $(ue_common_c_includes) \
+ $(ue_libupdate_engine_exported_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+ libpayload_consumer \
+ update_metadata-protos \
+ update_engine-dbus-adaptor \
+ update_engine-dbus-libcros-client \
+ update_engine_client-dbus-proxies \
+ $(ue_libupdate_engine_exported_static_libraries) \
+ $(ue_libpayload_consumer_exported_static_libraries) \
+ $(ue_update_metadata_protos_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES := \
+ $(ue_common_shared_libraries) \
+ $(ue_libupdate_engine_exported_shared_libraries) \
+ $(ue_libpayload_consumer_exported_shared_libraries) \
+ $(ue_update_metadata_protos_exported_shared_libraries)
+LOCAL_SRC_FILES := \
+ boot_control_android.cc \
+ chrome_browser_proxy_resolver.cc \
+ connection_manager.cc \
+ daemon.cc \
+ dbus_service.cc \
+ hardware_android.cc \
+ image_properties_android.cc \
+ libcros_proxy.cc \
+ metrics.cc \
+ metrics_utils.cc \
+ omaha_request_action.cc \
+ omaha_request_params.cc \
+ omaha_response_handler_action.cc \
+ p2p_manager.cc \
+ payload_state.cc \
+ proxy_resolver.cc \
+ real_system_state.cc \
+ shill_proxy.cc \
+ update_attempter.cc \
+ update_manager/boxed_value.cc \
+ update_manager/chromeos_policy.cc \
+ update_manager/default_policy.cc \
+ update_manager/evaluation_context.cc \
+ update_manager/policy.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/state_factory.cc \
+ update_manager/update_manager.cc \
+ update_status_utils.cc
+include $(BUILD_STATIC_LIBRARY)
+
+endif # BRILLO_USE_DBUS == 1
+
+# update_engine (type: executable)
+# ========================================================
+# update_engine daemon.
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_REQUIRED_MODULES := \
+ bspatch \
+ cacerts_google
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+ $(ue_common_c_includes)
+
+ifdef BRILLO
+
+LOCAL_C_INCLUDES += \
+ $(ue_libupdate_engine_exported_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+ libupdate_engine \
+ $(ue_libupdate_engine_exported_static_libraries)
+
+LOCAL_RTTI_FLAG := -frtti
+LOCAL_SHARED_LIBRARIES := \
+ $(ue_common_shared_libraries) \
+ $(ue_libupdate_engine_exported_shared_libraries)
+LOCAL_SRC_FILES := \
+ main.cc
+
+else # !defined(BRILLO)
+
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/binder_bindings
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ liblog \
+ libutils
+LOCAL_SRC_FILES := \
+ binder_bindings/android/os/IUpdateEngine.aidl \
+ binder_bindings/android/os/IUpdateEnginePayloadApplicationCallback.aidl \
+ binder_main.cc \
+ binder_service.cc
+
+endif # defined(BRILLO)
+
+LOCAL_INIT_RC := update_engine.rc
+include $(BUILD_EXECUTABLE)
+
+ifeq ($(BRILLO_USE_DBUS),1)
+
+# update_engine_client (type: executable)
+# ========================================================
+# update_engine console client.
+include $(CLEAR_VARS)
+LOCAL_MODULE := update_engine_client
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_RTTI_FLAG := -frtti
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := \
+ $(ue_common_c_includes) \
+ $(LOCAL_PATH)/include
+LOCAL_STATIC_LIBRARIES := update_engine_client-dbus-proxies
+LOCAL_SHARED_LIBRARIES := $(ue_common_shared_libraries)
+LOCAL_SRC_FILES := \
+ update_engine_client.cc
+include $(BUILD_EXECUTABLE)
+
+endif # BRILLO_USE_DBUS == 1
+
+# libpayload_generator (type: static_library)
+# ========================================================
+# server-side code. This is used for delta_generator and unittests but not
+# for any client code.
+ue_libpayload_generator_exported_static_libraries := \
+ libpayload_consumer \
+ update_metadata-protos \
+ $(ue_libpayload_consumer_exported_static_libraries) \
+ $(ue_update_metadata_protos_exported_static_libraries)
+ue_libpayload_generator_exported_shared_libraries := \
+ libext2fs \
+ $(ue_libpayload_consumer_exported_shared_libraries) \
+ $(ue_update_metadata_protos_exported_shared_libraries)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := libpayload_generator
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_RTTI_FLAG := -frtti
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := $(ue_common_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+ libpayload_consumer \
+ update_metadata-protos \
+ $(ue_libpayload_consumer_exported_static_libraries) \
+ $(ue_update_metadata_protos_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES := \
+ $(ue_common_shared_libraries) \
+ $(ue_libpayload_generator_exported_shared_libraries) \
+ $(ue_libpayload_consumer_exported_shared_libraries) \
+ $(ue_update_metadata_protos_exported_shared_libraries)
+LOCAL_SRC_FILES := \
+ payload_generator/ab_generator.cc \
+ payload_generator/annotated_operation.cc \
+ payload_generator/blob_file_writer.cc \
+ payload_generator/block_mapping.cc \
+ payload_generator/bzip.cc \
+ payload_generator/cycle_breaker.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/payload_file.cc \
+ payload_generator/payload_generation_config.cc \
+ payload_generator/payload_signer.cc \
+ payload_generator/raw_filesystem.cc \
+ payload_generator/tarjan.cc \
+ payload_generator/topological_sort.cc
+include $(BUILD_STATIC_LIBRARY)
+
+# delta_generator (type: executable)
+# ========================================================
+# server-side delta generator.
+include $(CLEAR_VARS)
+LOCAL_MODULE := delta_generator
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_RTTI_FLAG := -frtti
+LOCAL_CLANG := true
+LOCAL_CFLAGS := $(ue_common_cflags)
+LOCAL_CPPFLAGS := $(ue_common_cppflags)
+LOCAL_LDFLAGS := $(ue_common_ldflags)
+LOCAL_C_INCLUDES := $(ue_common_c_includes)
+LOCAL_STATIC_LIBRARIES := \
+ libpayload_consumer \
+ libpayload_generator \
+ $(ue_libpayload_consumer_exported_static_libraries) \
+ $(ue_libpayload_generator_exported_static_libraries)
+LOCAL_SHARED_LIBRARIES := \
+ $(ue_common_shared_libraries) \
+ $(ue_libpayload_consumer_exported_shared_libraries) \
+ $(ue_libpayload_generator_exported_shared_libraries)
+LOCAL_SRC_FILES := \
+ payload_generator/generate_delta_main.cc
+include $(BUILD_EXECUTABLE)
+
+ifeq ($(BRILLO_USE_DBUS),1)
+
+# libupdate_engine_client
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libupdate_engine_client
+LOCAL_RTTI_FLAG := -frtti
+LOCAL_CFLAGS := \
+ -Wall \
+ -Werror \
+ -Wno-unused-parameter
+LOCAL_CLANG := true
+LOCAL_CPP_EXTENSION := .cc
+LOCAL_C_INCLUDES := \
+ $(LOCAL_PATH)/client_library/include \
+ external/cros/system_api/dbus \
+ system \
+ external/gtest/include
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/client_library/include
+LOCAL_SHARED_LIBRARIES := \
+ libchrome \
+ libchrome-dbus \
+ libbrillo \
+ libbrillo-dbus
+LOCAL_STATIC_LIBRARIES := \
+ update_engine_client-dbus-proxies
+LOCAL_SRC_FILES := \
+ client_library/client.cc \
+ client_library/client_impl.cc \
+ update_status_utils.cc
+include $(BUILD_SHARED_LIBRARY)
+
+endif # BRILLO_USE_DBUS == 1
+
+# Update payload signing public key.
+# ========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := brillo-update-payload-key
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/update_engine
+LOCAL_MODULE_STEM := update-payload-key.pub.pem
+LOCAL_SRC_FILES := update_payload_key/brillo-update-payload-key.pub.pem
+LOCAL_BUILT_MODULE_STEM := update_payload_key/brillo-update-payload-key.pub.pem
+include $(BUILD_PREBUILT)
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..fafbecc
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+deymo@chromium.org
+garnold@chromium.org
+zeuthen@chromium.org
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
new file mode 100644
index 0000000..087dfa3
--- /dev/null
+++ b/PRESUBMIT.cfg
@@ -0,0 +1,3 @@
+[Hook Overrides]
+cros_license_check: false
+aosp_license_check: true
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
new file mode 100644
index 0000000..8a91607
--- /dev/null
+++ b/UpdateEngine.conf
@@ -0,0 +1,64 @@
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+ "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+ <policy user="root">
+ <allow own="org.chromium.UpdateEngine" />
+ <allow send_destination="org.chromium.UpdateEngine" />
+ </policy>
+ <policy user="chronos">
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="AttemptUpdate"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="AttemptUpdateWithFlags"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="AttemptRollback"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="CanRollback"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetRollbackPartition"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="ResetStatus"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetStatus"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="RebootIfNeeded"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="SetChannel"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetChannel"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="SetP2PUpdatePermission"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetP2PUpdatePermission"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="SetUpdateOverCellularPermission"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetUpdateOverCellularPermission"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetDurationSinceUpdate"/>
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetPrevVersion"/>
+ <allow send_interface="org.chromium.UpdateEngineLibcrosProxyResolvedInterface" />
+ </policy>
+ <policy user="power">
+ <allow send_destination="org.chromium.UpdateEngine"
+ send_interface="org.chromium.UpdateEngineInterface"
+ send_member="GetStatus"/>
+ </policy>
+</busconfig>
diff --git a/WATCHLISTS b/WATCHLISTS
new file mode 100644
index 0000000..bcce0de
--- /dev/null
+++ b/WATCHLISTS
@@ -0,0 +1,14 @@
+# See http://dev.chromium.org/developers/contributing-code/watchlists for
+# a description of this file's format.
+# Please keep these keys in alphabetical order.
+
+{
+ 'WATCHLIST_DEFINITIONS': {
+ 'all': {
+ 'filepath': '.',
+ },
+ },
+ 'WATCHLISTS': {
+ 'all': ['adlr@chromium.org', 'petkov@chromium.org']
+ },
+}
diff --git a/binder_bindings/android/os/IUpdateEngine.aidl b/binder_bindings/android/os/IUpdateEngine.aidl
new file mode 100644
index 0000000..ebc3ffb
--- /dev/null
+++ b/binder_bindings/android/os/IUpdateEngine.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+package android.os;
+
+import android.os.IUpdateEnginePayloadApplicationCallback;
+
+interface IUpdateEngine {
+ int applyPayload(String url,
+ in String[] headerKeyValuePairs,
+ IUpdateEnginePayloadApplicationCallback callback);
+ void suspend();
+ void resume();
+ void cancel();
+}
diff --git a/binder_bindings/android/os/IUpdateEnginePayloadApplicationCallback.aidl b/binder_bindings/android/os/IUpdateEnginePayloadApplicationCallback.aidl
new file mode 100644
index 0000000..871ef1d
--- /dev/null
+++ b/binder_bindings/android/os/IUpdateEnginePayloadApplicationCallback.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+package android.os;
+
+oneway interface IUpdateEnginePayloadApplicationCallback {
+ void onStatusUpdate(int status_code, float percentage);
+ void onPayloadApplicationComplete(int error_code);
+}
diff --git a/binder_main.cc b/binder_main.cc
new file mode 100644
index 0000000..7d5c975
--- /dev/null
+++ b/binder_main.cc
@@ -0,0 +1,91 @@
+//
+// 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 <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/Looper.h>
+#include <utils/StrongPointer.h>
+
+#include "update_engine/binder_service.h"
+
+// Log to logcat as update_engine.
+#undef LOG_TAG
+#define LOG_TAG "update_engine"
+
+namespace android {
+namespace {
+
+class BinderCallback : public LooperCallback {
+ public:
+ BinderCallback() {}
+ ~BinderCallback() override {}
+
+ int handleEvent(int /* fd */, int /* events */, void* /* data */) override {
+ IPCThreadState::self()->handlePolledCommands();
+ return 1; // Continue receiving callbacks.
+ }
+};
+
+bool run(const sp<IBinder>& service) {
+ sp<Looper> looper(Looper::prepare(0 /* opts */));
+
+ ALOGD("Connecting to binder driver");
+ int binder_fd = -1;
+ ProcessState::self()->setThreadPoolMaxThreadCount(0);
+ IPCThreadState::self()->disableBackgroundScheduling(true);
+ IPCThreadState::self()->setupPolling(&binder_fd);
+ if (binder_fd < 0) {
+ return false;
+ }
+
+ sp<BinderCallback> cb(new BinderCallback);
+ if (looper->addFd(binder_fd, Looper::POLL_CALLBACK, Looper::EVENT_INPUT, cb,
+ nullptr) != 1) {
+ ALOGE("Failed to add binder FD to Looper");
+ return false;
+ }
+
+ ALOGD("Registering update_engine with the service manager");
+ status_t status = defaultServiceManager()->addService(
+ service->getInterfaceDescriptor(), service);
+ if (status != android::OK) {
+ ALOGE("Failed to register update_engine with the service manager.");
+ return false;
+ }
+
+ ALOGD("Entering update_engine mainloop");
+ while (true) {
+ const int result = looper->pollAll(-1 /* timeoutMillis */);
+ ALOGD("Looper returned %d", result);
+ }
+ // We should never get here.
+ return false;
+}
+
+} // namespace
+} // namespace android
+
+int main(int argc, char** argv) {
+ android::sp<android::IBinder> service(
+ new chromeos_update_engine::BinderService);
+ if (!android::run(service)) {
+ return 1;
+ }
+ return 0;
+}
diff --git a/binder_service.cc b/binder_service.cc
new file mode 100644
index 0000000..8029505
--- /dev/null
+++ b/binder_service.cc
@@ -0,0 +1,49 @@
+//
+// 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.h"
+
+using android::OK;
+using android::String16;
+using android::os::IUpdateEnginePayloadApplicationCallback;
+using android::sp;
+using android::binder::Status;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+Status BinderService::applyPayload(
+ const String16& url,
+ const vector<String16>& header_kv_pairs,
+ const sp<IUpdateEnginePayloadApplicationCallback>& callback,
+ int32_t* return_value) {
+ *return_value = 0;
+ return Status::ok();
+}
+
+Status BinderService::suspend() {
+ return Status::ok();
+}
+
+Status BinderService::resume() {
+ return Status::ok();
+}
+
+Status BinderService::cancel() {
+ return Status::ok();
+}
+
+} // namespace chromeos_update_engine
diff --git a/binder_service.h b/binder_service.h
new file mode 100644
index 0000000..bbbe5b6
--- /dev/null
+++ b/binder_service.h
@@ -0,0 +1,52 @@
+//
+// 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_BINDER_SERVICE_H_
+#define UPDATE_ENGINE_BINDER_SERVICE_H_
+
+#include <vector>
+
+#include <utils/Errors.h>
+#include <utils/String16.h>
+#include <utils/StrongPointer.h>
+
+#include "android/os/BnUpdateEngine.h"
+#include "android/os/IUpdateEnginePayloadApplicationCallback.h"
+
+namespace chromeos_update_engine {
+
+class BinderService : public android::os::BnUpdateEngine {
+ public:
+ BinderService() = default;
+ virtual ~BinderService() = default;
+
+ android::binder::Status applyPayload(
+ const android::String16& url,
+ const std::vector<android::String16>& header_kv_pairs,
+ const android::sp<android::os::IUpdateEnginePayloadApplicationCallback>&
+ callback,
+ int32_t* return_value) override;
+
+ android::binder::Status suspend() override;
+
+ android::binder::Status resume() override;
+
+ android::binder::Status cancel() override;
+}; // class BinderService
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_BINDER_SERVICE_H_
diff --git a/boot_control_android.cc b/boot_control_android.cc
new file mode 100644
index 0000000..275d2aa
--- /dev/null
+++ b/boot_control_android.cc
@@ -0,0 +1,200 @@
+//
+// 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/boot_control_android.h"
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/message_loop.h>
+#include <cutils/properties.h>
+#include <fs_mgr.h>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace {
+
+// Open the appropriate fstab file and fallback to /fstab.device if
+// that's what's being used.
+static struct fstab* OpenFSTab() {
+ char propbuf[PROPERTY_VALUE_MAX];
+ struct fstab* fstab;
+
+ property_get("ro.hardware", propbuf, "");
+ string fstab_name = string("/fstab.") + propbuf;
+ fstab = fs_mgr_read_fstab(fstab_name.c_str());
+ if (fstab != nullptr)
+ return fstab;
+
+ fstab = fs_mgr_read_fstab("/fstab.device");
+ return fstab;
+}
+
+} // namespace
+
+
+namespace chromeos_update_engine {
+
+namespace boot_control {
+
+// Factory defined in boot_control.h.
+std::unique_ptr<BootControlInterface> CreateBootControl() {
+ std::unique_ptr<BootControlAndroid> boot_control(new BootControlAndroid());
+ if (!boot_control->Init()) {
+ return nullptr;
+ }
+ return brillo::make_unique_ptr(boot_control.release());
+}
+
+} // namespace boot_control
+
+bool BootControlAndroid::Init() {
+ const hw_module_t* hw_module;
+ int ret;
+
+ ret = hw_get_module(BOOT_CONTROL_HARDWARE_MODULE_ID, &hw_module);
+ if (ret != 0) {
+ LOG(ERROR) << "Error loading boot_control HAL implementation.";
+ return false;
+ }
+
+ module_ = reinterpret_cast<boot_control_module_t*>(const_cast<hw_module_t*>(hw_module));
+ module_->init(module_);
+
+ LOG(INFO) << "Loaded boot_control HAL "
+ << "'" << hw_module->name << "' "
+ << "version " << (hw_module->module_api_version>>8) << "."
+ << (hw_module->module_api_version&0xff) << " "
+ << "authored by '" << hw_module->author << "'.";
+ return true;
+}
+
+unsigned int BootControlAndroid::GetNumSlots() const {
+ return module_->getNumberSlots(module_);
+}
+
+BootControlInterface::Slot BootControlAndroid::GetCurrentSlot() const {
+ return module_->getCurrentSlot(module_);
+}
+
+bool BootControlAndroid::GetPartitionDevice(const string& partition_name,
+ Slot slot,
+ string* device) const {
+ struct fstab* fstab;
+ struct fstab_rec* record;
+
+ // We can't use fs_mgr to look up |partition_name| because fstab
+ // doesn't list every slot partition (it uses the slotselect option
+ // to mask the suffix).
+ //
+ // We can however assume that there's an entry for the /misc mount
+ // point and use that to get the device file for the misc
+ // partition. This helps us locate the disk that |partition_name|
+ // resides on. From there we'll assume that a by-name scheme is used
+ // so we can just replace the trailing "misc" by the given
+ // |partition_name| and suffix corresponding to |slot|, e.g.
+ //
+ // /dev/block/platform/soc.0/7824900.sdhci/by-name/misc ->
+ // /dev/block/platform/soc.0/7824900.sdhci/by-name/boot_a
+ //
+ // If needed, it's possible to relax the by-name assumption in the
+ // future by trawling /sys/block looking for the appropriate sibling
+ // of misc and then finding an entry in /dev matching the sysfs
+ // entry.
+
+ fstab = OpenFSTab();
+ if (fstab == nullptr) {
+ LOG(ERROR) << "Error opening fstab file.";
+ return false;
+ }
+ record = fs_mgr_get_entry_for_mount_point(fstab, "/misc");
+ if (record == nullptr) {
+ LOG(ERROR) << "Error finding /misc entry in fstab file.";
+ fs_mgr_free_fstab(fstab);
+ return false;
+ }
+
+ base::FilePath misc_device = base::FilePath(record->blk_device);
+ fs_mgr_free_fstab(fstab);
+
+ if (misc_device.BaseName() != base::FilePath("misc")) {
+ LOG(ERROR) << "Device file " << misc_device.value() << " for /misc "
+ << "is not in the expected format.";
+ return false;
+ }
+
+ const char* suffix = module_->getSuffix(module_, slot);
+ if (suffix == nullptr) {
+ LOG(ERROR) << "boot_control impl returned no suffix for slot "
+ << SlotName(slot);
+ return false;
+ }
+
+ base::FilePath path = misc_device.DirName().Append(partition_name + suffix);
+ if (!base::PathExists(path)) {
+ LOG(ERROR) << "Device file " << path.value() << " does not exist.";
+ return false;
+ }
+
+ *device = path.value();
+ return true;
+}
+
+bool BootControlAndroid::IsSlotBootable(Slot slot) const {
+ int ret = module_->isSlotBootable(module_, slot);
+ if (ret < 0) {
+ LOG(ERROR) << "Unable to determine if slot " << SlotName(slot)
+ << " is bootable: " << strerror(-ret);
+ return false;
+ }
+ return ret == 1;
+}
+
+bool BootControlAndroid::MarkSlotUnbootable(Slot slot) {
+ int ret = module_->setSlotAsUnbootable(module_, slot);
+ if (ret < 0) {
+ LOG(ERROR) << "Unable to mark slot " << SlotName(slot)
+ << " as bootable: " << strerror(-ret);
+ return false;
+ }
+ return ret == 0;
+}
+
+bool BootControlAndroid::SetActiveBootSlot(Slot slot) {
+ int ret = module_->setActiveBootSlot(module_, slot);
+ if (ret < 0) {
+ LOG(ERROR) << "Unable to set the active slot to slot " << SlotName(slot)
+ << ": " << strerror(-ret);
+ }
+ return ret == 0;
+}
+
+bool BootControlAndroid::MarkBootSuccessfulAsync(
+ base::Callback<void(bool)> callback) {
+ int ret = module_->markBootSuccessful(module_);
+ if (ret < 0) {
+ LOG(ERROR) << "Unable to mark boot successful: " << strerror(-ret);
+ }
+ return brillo::MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(callback, ret == 0)) !=
+ brillo::MessageLoop::kTaskIdNull;
+}
+
+} // namespace chromeos_update_engine
diff --git a/boot_control_android.h b/boot_control_android.h
new file mode 100644
index 0000000..a5a6255
--- /dev/null
+++ b/boot_control_android.h
@@ -0,0 +1,61 @@
+//
+// 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_BOOT_CONTROL_ANDROID_H_
+#define UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_
+
+#include <string>
+
+#include <hardware/boot_control.h>
+#include <hardware/hardware.h>
+
+#include "update_engine/common/boot_control.h"
+
+namespace chromeos_update_engine {
+
+// The Android implementation of the BootControlInterface. This implementation
+// uses the libhardware's boot_control HAL to access the bootloader.
+class BootControlAndroid : public BootControlInterface {
+ public:
+ BootControlAndroid() = default;
+ ~BootControlAndroid() = default;
+
+ // Load boot_control HAL implementation using libhardware and
+ // initializes it. Returns false if an error occurred.
+ bool Init();
+
+ // BootControlInterface overrides.
+ unsigned int GetNumSlots() const override;
+ BootControlInterface::Slot GetCurrentSlot() const override;
+ bool GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ std::string* device) const override;
+ bool IsSlotBootable(BootControlInterface::Slot slot) const override;
+ bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
+ bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
+ bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+
+ private:
+ // NOTE: There is no way to release/unload HAL implementations so
+ // this is essentially leaked on object destruction.
+ boot_control_module_t* module_;
+
+ DISALLOW_COPY_AND_ASSIGN(BootControlAndroid);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_BOOT_CONTROL_ANDROID_H_
diff --git a/boot_control_chromeos.cc b/boot_control_chromeos.cc
new file mode 100644
index 0000000..fd248ab
--- /dev/null
+++ b/boot_control_chromeos.cc
@@ -0,0 +1,304 @@
+//
+// 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/boot_control_chromeos.h"
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <brillo/make_unique_ptr.h>
+#include <rootdev/rootdev.h>
+
+extern "C" {
+#include <vboot/vboot_host.h>
+}
+
+#include "update_engine/common/boot_control.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace {
+
+const char* kChromeOSPartitionNameKernel = "kernel";
+const char* kChromeOSPartitionNameRoot = "root";
+const char* kAndroidPartitionNameKernel = "boot";
+const char* kAndroidPartitionNameRoot = "system";
+
+// Returns the currently booted rootfs partition. "/dev/sda3", for example.
+string GetBootDevice() {
+ char boot_path[PATH_MAX];
+ // Resolve the boot device path fully, including dereferencing through
+ // dm-verity.
+ int ret = rootdev(boot_path, sizeof(boot_path), true, false);
+ if (ret < 0) {
+ LOG(ERROR) << "rootdev failed to find the root device";
+ return "";
+ }
+ LOG_IF(WARNING, ret > 0) << "rootdev found a device name with no device node";
+
+ // This local variable is used to construct the return string and is not
+ // passed around after use.
+ return boot_path;
+}
+
+// ExecCallback called when the execution of setgoodkernel finishes. Notifies
+// the caller of MarkBootSuccessfullAsync() by calling |callback| with the
+// result.
+void OnMarkBootSuccessfulDone(base::Callback<void(bool)> callback,
+ int return_code,
+ const string& output) {
+ callback.Run(return_code == 0);
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace boot_control {
+
+// Factory defined in boot_control.h.
+std::unique_ptr<BootControlInterface> CreateBootControl() {
+ std::unique_ptr<BootControlChromeOS> boot_control_chromeos(
+ new BootControlChromeOS());
+ if (!boot_control_chromeos->Init()) {
+ LOG(ERROR) << "Ignoring BootControlChromeOS failure. We won't run updates.";
+ }
+ return brillo::make_unique_ptr(boot_control_chromeos.release());
+}
+
+} // namespace boot_control
+
+bool BootControlChromeOS::Init() {
+ string boot_device = GetBootDevice();
+ if (boot_device.empty())
+ return false;
+
+ int partition_num;
+ if (!utils::SplitPartitionName(boot_device, &boot_disk_name_, &partition_num))
+ return false;
+
+ // All installed Chrome OS devices have two slots. We don't update removable
+ // devices, so we will pretend we have only one slot in that case.
+ if (IsRemovableDevice(boot_disk_name_)) {
+ LOG(INFO)
+ << "Booted from a removable device, pretending we have only one slot.";
+ num_slots_ = 1;
+ } else {
+ // TODO(deymo): Look at the actual number of slots reported in the GPT.
+ num_slots_ = 2;
+ }
+
+ // Search through the slots to see which slot has the partition_num we booted
+ // from. This should map to one of the existing slots, otherwise something is
+ // very wrong.
+ current_slot_ = 0;
+ while (current_slot_ < num_slots_ &&
+ partition_num !=
+ GetPartitionNumber(kChromeOSPartitionNameRoot, current_slot_)) {
+ current_slot_++;
+ }
+ 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.";
+ num_slots_ = 1;
+ current_slot_ = BootControlInterface::kInvalidSlot;
+ return false;
+ }
+
+ LOG(INFO) << "Booted from slot " << current_slot_ << " (slot "
+ << SlotName(current_slot_) << ") of " << num_slots_
+ << " slots present on disk " << boot_disk_name_;
+ return true;
+}
+
+unsigned int BootControlChromeOS::GetNumSlots() const {
+ return num_slots_;
+}
+
+BootControlInterface::Slot BootControlChromeOS::GetCurrentSlot() const {
+ return current_slot_;
+}
+
+bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
+ unsigned int slot,
+ string* device) const {
+ int partition_num = GetPartitionNumber(partition_name, slot);
+ if (partition_num < 0)
+ return false;
+
+ string part_device = utils::MakePartitionName(boot_disk_name_, partition_num);
+ if (part_device.empty())
+ return false;
+
+ *device = part_device;
+ return true;
+}
+
+bool BootControlChromeOS::IsSlotBootable(Slot slot) const {
+ int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+ if (partition_num < 0)
+ return false;
+
+ CgptAddParams params;
+ memset(¶ms, '\0', sizeof(params));
+ params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ params.partition = partition_num;
+
+ int retval = CgptGetPartitionDetails(¶ms);
+ if (retval != CGPT_OK)
+ return false;
+
+ return params.successful || params.tries > 0;
+}
+
+bool BootControlChromeOS::MarkSlotUnbootable(Slot slot) {
+ LOG(INFO) << "Marking slot " << SlotName(slot) << " unbootable";
+
+ if (slot == current_slot_) {
+ LOG(ERROR) << "Refusing to mark current slot as unbootable.";
+ return false;
+ }
+
+ int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+ if (partition_num < 0)
+ return false;
+
+ CgptAddParams params;
+ memset(¶ms, 0, sizeof(params));
+
+ params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ params.partition = partition_num;
+
+ params.successful = false;
+ params.set_successful = true;
+
+ params.tries = 0;
+ params.set_tries = true;
+
+ int retval = CgptSetAttributes(¶ms);
+ if (retval != CGPT_OK) {
+ LOG(ERROR) << "Marking kernel unbootable failed.";
+ return false;
+ }
+
+ return true;
+}
+
+bool BootControlChromeOS::SetActiveBootSlot(Slot slot) {
+ LOG(INFO) << "Marking slot " << SlotName(slot) << " active.";
+
+ int partition_num = GetPartitionNumber(kChromeOSPartitionNameKernel, slot);
+ if (partition_num < 0)
+ return false;
+
+ CgptPrioritizeParams prio_params;
+ memset(&prio_params, 0, sizeof(prio_params));
+
+ prio_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ prio_params.set_partition = partition_num;
+
+ prio_params.max_priority = 0;
+
+ int retval = CgptPrioritize(&prio_params);
+ if (retval != CGPT_OK) {
+ LOG(ERROR) << "Unable to set highest priority for slot " << SlotName(slot)
+ << " (partition " << partition_num << ").";
+ return false;
+ }
+
+ CgptAddParams add_params;
+ memset(&add_params, 0, sizeof(add_params));
+
+ add_params.drive_name = const_cast<char*>(boot_disk_name_.c_str());
+ add_params.partition = partition_num;
+
+ add_params.tries = 6;
+ add_params.set_tries = true;
+
+ retval = CgptSetAttributes(&add_params);
+ if (retval != CGPT_OK) {
+ LOG(ERROR) << "Unable to set NumTriesLeft to " << add_params.tries
+ << " for slot " << SlotName(slot) << " (partition "
+ << partition_num << ").";
+ return false;
+ }
+
+ return true;
+}
+
+bool BootControlChromeOS::MarkBootSuccessfulAsync(
+ base::Callback<void(bool)> callback) {
+ return Subprocess::Get().Exec(
+ {"/usr/sbin/chromeos-setgoodkernel"},
+ base::Bind(&OnMarkBootSuccessfulDone, callback)) != 0;
+}
+
+// static
+string BootControlChromeOS::SysfsBlockDevice(const string& device) {
+ base::FilePath device_path(device);
+ if (device_path.DirName().value() != "/dev") {
+ return "";
+ }
+ return base::FilePath("/sys/block").Append(device_path.BaseName()).value();
+}
+
+// static
+bool BootControlChromeOS::IsRemovableDevice(const string& device) {
+ string sysfs_block = SysfsBlockDevice(device);
+ string removable;
+ if (sysfs_block.empty() ||
+ !base::ReadFileToString(base::FilePath(sysfs_block).Append("removable"),
+ &removable)) {
+ return false;
+ }
+ base::TrimWhitespaceASCII(removable, base::TRIM_ALL, &removable);
+ return removable == "1";
+}
+
+int BootControlChromeOS::GetPartitionNumber(
+ const string partition_name,
+ BootControlInterface::Slot slot) const {
+ if (slot >= num_slots_) {
+ LOG(ERROR) << "Invalid slot number: " << slot << ", we only have "
+ << num_slots_ << " slot(s)";
+ return -1;
+ }
+
+ // In Chrome OS, the partition numbers are hard-coded:
+ // KERNEL-A=2, ROOT-A=3, KERNEL-B=4, ROOT-B=4, ...
+ // To help compatibility between different we accept both lowercase and
+ // uppercase names in the ChromeOS or Brillo standard names.
+ // See http://www.chromium.org/chromium-os/chromiumos-design-docs/disk-format
+ string partition_lower = base::StringToLowerASCII(partition_name);
+ int base_part_num = 2 + 2 * slot;
+ if (partition_lower == kChromeOSPartitionNameKernel ||
+ partition_lower == kAndroidPartitionNameKernel)
+ return base_part_num + 0;
+ if (partition_lower == kChromeOSPartitionNameRoot ||
+ partition_lower == kAndroidPartitionNameRoot)
+ return base_part_num + 1;
+ LOG(ERROR) << "Unknown Chrome OS partition name \"" << partition_name << "\"";
+ return -1;
+}
+
+} // namespace chromeos_update_engine
diff --git a/boot_control_chromeos.h b/boot_control_chromeos.h
new file mode 100644
index 0000000..a1d57fe
--- /dev/null
+++ b/boot_control_chromeos.h
@@ -0,0 +1,85 @@
+//
+// 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_BOOT_CONTROL_CHROMEOS_H_
+#define UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_
+
+#include <string>
+
+#include <base/callback.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// The Chrome OS implementation of the BootControlInterface. This interface
+// assumes the partition names and numbers used in Chrome OS devices.
+class BootControlChromeOS : public BootControlInterface {
+ public:
+ BootControlChromeOS() = default;
+ ~BootControlChromeOS() = default;
+
+ // Initialize the BootControl instance loading the constant values. Returns
+ // whether the operation succeeded. In case of failure, normally meaning
+ // some critical failure such as we couldn't determine the slot that we
+ // booted from, the implementation will pretend that there's only one slot and
+ // therefore A/B updates are disabled.
+ bool Init();
+
+ // BootControlInterface overrides.
+ unsigned int GetNumSlots() const override;
+ BootControlInterface::Slot GetCurrentSlot() const override;
+ bool GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ std::string* device) const override;
+ bool IsSlotBootable(BootControlInterface::Slot slot) const override;
+ bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
+ bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
+ bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+
+ private:
+ friend class BootControlChromeOSTest;
+ FRIEND_TEST(BootControlChromeOSTest, SysfsBlockDeviceTest);
+ FRIEND_TEST(BootControlChromeOSTest, GetPartitionNumberTest);
+
+ // Returns the sysfs block device for a root block device. For example,
+ // SysfsBlockDevice("/dev/sda") returns "/sys/block/sda". Returns an empty
+ // string if the input device is not of the "/dev/xyz" form.
+ static std::string SysfsBlockDevice(const std::string& device);
+
+ // Returns true if the root |device| (e.g., "/dev/sdb") is known to be
+ // removable, false otherwise.
+ static bool IsRemovableDevice(const std::string& device);
+
+ // Return the hard-coded partition number used in Chrome OS for the passed
+ // |partition_name| and |slot|. In case of invalid data, returns -1.
+ int GetPartitionNumber(const std::string partition_name,
+ BootControlInterface::Slot slot) const;
+
+ // Cached values for GetNumSlots() and GetCurrentSlot().
+ BootControlInterface::Slot num_slots_{1};
+ BootControlInterface::Slot current_slot_{BootControlInterface::kInvalidSlot};
+
+ // The block device of the disk we booted from, without the partition number.
+ std::string boot_disk_name_;
+
+ DISALLOW_COPY_AND_ASSIGN(BootControlChromeOS);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_BOOT_CONTROL_CHROMEOS_H_
diff --git a/chrome_browser_proxy_resolver.cc b/chrome_browser_proxy_resolver.cc
new file mode 100644
index 0000000..4971d74
--- /dev/null
+++ b/chrome_browser_proxy_resolver.cc
@@ -0,0 +1,194 @@
+//
+// 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/chrome_browser_proxy_resolver.h"
+
+#include <deque>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <base/bind.h>
+#include <base/strings/string_tokenizer.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+using base::StringTokenizer;
+using base::TimeDelta;
+using brillo::MessageLoop;
+using std::deque;
+using std::make_pair;
+using std::pair;
+using std::string;
+
+const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
+const char kLibCrosProxyResolveName[] = "ProxyResolved";
+const char kLibCrosProxyResolveSignalInterface[] =
+ "org.chromium.UpdateEngineLibcrosProxyResolvedInterface";
+
+namespace {
+
+const int kTimeout = 5; // seconds
+
+} // namespace
+
+ChromeBrowserProxyResolver::ChromeBrowserProxyResolver(
+ LibCrosProxy* libcros_proxy)
+ : libcros_proxy_(libcros_proxy), timeout_(kTimeout) {}
+
+bool ChromeBrowserProxyResolver::Init() {
+ libcros_proxy_->ue_proxy_resolved_interface()
+ ->RegisterProxyResolvedSignalHandler(
+ base::Bind(&ChromeBrowserProxyResolver::OnProxyResolvedSignal,
+ base::Unretained(this)),
+ base::Bind(&ChromeBrowserProxyResolver::OnSignalConnected,
+ base::Unretained(this)));
+ return true;
+}
+
+ChromeBrowserProxyResolver::~ChromeBrowserProxyResolver() {
+ // Kill outstanding timers.
+ for (auto& timer : timers_) {
+ MessageLoop::current()->CancelTask(timer.second);
+ timer.second = MessageLoop::kTaskIdNull;
+ }
+}
+
+bool ChromeBrowserProxyResolver::GetProxiesForUrl(const string& url,
+ ProxiesResolvedFn callback,
+ void* data) {
+ int timeout = timeout_;
+ brillo::ErrorPtr error;
+ if (!libcros_proxy_->service_interface_proxy()->ResolveNetworkProxy(
+ url.c_str(),
+ kLibCrosProxyResolveSignalInterface,
+ kLibCrosProxyResolveName,
+ &error)) {
+ LOG(WARNING) << "Can't resolve the proxy. Continuing with no proxy.";
+ timeout = 0;
+ }
+
+ callbacks_.insert(make_pair(url, make_pair(callback, data)));
+ MessageLoop::TaskId timer = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ChromeBrowserProxyResolver::HandleTimeout,
+ base::Unretained(this),
+ url),
+ TimeDelta::FromSeconds(timeout));
+ timers_.insert(make_pair(url, timer));
+ return true;
+}
+
+bool ChromeBrowserProxyResolver::DeleteUrlState(
+ const string& source_url,
+ bool delete_timer,
+ pair<ProxiesResolvedFn, void*>* callback) {
+ {
+ CallbacksMap::iterator it = callbacks_.lower_bound(source_url);
+ TEST_AND_RETURN_FALSE(it != callbacks_.end());
+ TEST_AND_RETURN_FALSE(it->first == source_url);
+ if (callback)
+ *callback = it->second;
+ callbacks_.erase(it);
+ }
+ {
+ TimeoutsMap::iterator it = timers_.lower_bound(source_url);
+ TEST_AND_RETURN_FALSE(it != timers_.end());
+ TEST_AND_RETURN_FALSE(it->first == source_url);
+ if (delete_timer)
+ MessageLoop::current()->CancelTask(it->second);
+ timers_.erase(it);
+ }
+ return true;
+}
+
+void ChromeBrowserProxyResolver::OnSignalConnected(const string& interface_name,
+ const string& signal_name,
+ bool successful) {
+ if (!successful) {
+ LOG(ERROR) << "Couldn't connect to the signal " << interface_name << "."
+ << signal_name;
+ }
+}
+
+void ChromeBrowserProxyResolver::OnProxyResolvedSignal(
+ const string& source_url,
+ const string& proxy_info,
+ const string& error_message) {
+ pair<ProxiesResolvedFn, void*> callback;
+ TEST_AND_RETURN(DeleteUrlState(source_url, true, &callback));
+ if (!error_message.empty()) {
+ LOG(WARNING) << "ProxyResolved error: " << error_message;
+ }
+ (*callback.first)(ParseProxyString(proxy_info), callback.second);
+}
+
+void ChromeBrowserProxyResolver::HandleTimeout(string source_url) {
+ LOG(INFO) << "Timeout handler called. Seems Chrome isn't responding.";
+ pair<ProxiesResolvedFn, void*> callback;
+ TEST_AND_RETURN(DeleteUrlState(source_url, false, &callback));
+ deque<string> proxies;
+ proxies.push_back(kNoProxy);
+ (*callback.first)(proxies, callback.second);
+}
+
+deque<string> ChromeBrowserProxyResolver::ParseProxyString(
+ const string& input) {
+ deque<string> ret;
+ // Some of this code taken from
+ // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_server.cc and
+ // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list.cc
+ StringTokenizer entry_tok(input, ";");
+ while (entry_tok.GetNext()) {
+ string token = entry_tok.token();
+ base::TrimWhitespaceASCII(token, base::TRIM_ALL, &token);
+
+ // Start by finding the first space (if any).
+ string::iterator space;
+ for (space = token.begin(); space != token.end(); ++space) {
+ if (IsAsciiWhitespace(*space)) {
+ break;
+ }
+ }
+
+ string scheme = string(token.begin(), space);
+ base::StringToLowerASCII(&scheme);
+ // Chrome uses "socks" to mean socks4 and "proxy" to mean http.
+ if (scheme == "socks")
+ scheme += "4";
+ else if (scheme == "proxy")
+ scheme = "http";
+ else if (scheme != "https" &&
+ scheme != "socks4" &&
+ scheme != "socks5" &&
+ scheme != "direct")
+ continue; // Invalid proxy scheme
+
+ string host_and_port = string(space, token.end());
+ base::TrimWhitespaceASCII(host_and_port, base::TRIM_ALL, &host_and_port);
+ if (scheme != "direct" && host_and_port.empty())
+ continue; // Must supply host/port when non-direct proxy used.
+ ret.push_back(scheme + "://" + host_and_port);
+ }
+ if (ret.empty() || *ret.rbegin() != kNoProxy)
+ ret.push_back(kNoProxy);
+ return ret;
+}
+
+} // namespace chromeos_update_engine
diff --git a/chrome_browser_proxy_resolver.h b/chrome_browser_proxy_resolver.h
new file mode 100644
index 0000000..84b0c28
--- /dev/null
+++ b/chrome_browser_proxy_resolver.h
@@ -0,0 +1,98 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_
+#define UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <utility>
+
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/libcros_proxy.h"
+#include "update_engine/proxy_resolver.h"
+
+namespace chromeos_update_engine {
+
+extern const char kLibCrosServiceName[];
+extern const char kLibCrosProxyResolveName[];
+extern const char kLibCrosProxyResolveSignalInterface[];
+
+class ChromeBrowserProxyResolver : public ProxyResolver {
+ public:
+ explicit ChromeBrowserProxyResolver(LibCrosProxy* libcros_proxy);
+ ~ChromeBrowserProxyResolver() override;
+
+ // Initialize the ProxyResolver using the provided DBus proxies.
+ bool Init();
+
+ bool GetProxiesForUrl(const std::string& url,
+ ProxiesResolvedFn callback,
+ void* data) override;
+
+ private:
+ FRIEND_TEST(ChromeBrowserProxyResolverTest, ParseTest);
+ FRIEND_TEST(ChromeBrowserProxyResolverTest, SuccessTest);
+ typedef std::multimap<std::string, std::pair<ProxiesResolvedFn, void*>>
+ CallbacksMap;
+ typedef std::multimap<std::string, brillo::MessageLoop::TaskId> TimeoutsMap;
+
+ // Called when the signal in UpdateEngineLibcrosProxyResolvedInterface is
+ // connected.
+ void OnSignalConnected(const std::string& interface_name,
+ const std::string& signal_name,
+ bool successful);
+
+ // Handle a reply from Chrome:
+ void OnProxyResolvedSignal(const std::string& source_url,
+ const std::string& proxy_info,
+ const std::string& error_message);
+
+ // Handle no reply:
+ void HandleTimeout(std::string source_url);
+
+ // Parses a string-encoded list of proxies and returns a deque
+ // of individual proxies. The last one will always be kNoProxy.
+ static std::deque<std::string> ParseProxyString(const std::string& input);
+
+ // Deletes internal state for the first instance of url in the state.
+ // If delete_timer is set, calls CancelTask on the timer id.
+ // Returns the callback in an out parameter. Returns true on success.
+ bool DeleteUrlState(const std::string& url,
+ bool delete_timer,
+ std::pair<ProxiesResolvedFn, void*>* callback);
+
+ // Shutdown the dbus proxy object.
+ void Shutdown();
+
+ // DBus proxies to request a HTTP proxy resolution. The request is done in the
+ // service_interface_proxy() interface and the response is received as a
+ // signal in the ue_proxy_resolved_interface().
+ LibCrosProxy* libcros_proxy_;
+
+ int timeout_;
+ TimeoutsMap timers_;
+ CallbacksMap callbacks_;
+ DISALLOW_COPY_AND_ASSIGN(ChromeBrowserProxyResolver);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CHROME_BROWSER_PROXY_RESOLVER_H_
diff --git a/chrome_browser_proxy_resolver_unittest.cc b/chrome_browser_proxy_resolver_unittest.cc
new file mode 100644
index 0000000..7a4de3d
--- /dev/null
+++ b/chrome_browser_proxy_resolver_unittest.cc
@@ -0,0 +1,211 @@
+//
+// 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/chrome_browser_proxy_resolver.h"
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <base/bind.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/fake_message_loop.h>
+
+#include "libcros/dbus-proxies.h"
+#include "libcros/dbus-proxy-mocks.h"
+#include "update_engine/dbus_test_utils.h"
+
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::_;
+using brillo::MessageLoop;
+using org::chromium::LibCrosServiceInterfaceProxyMock;
+using org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyMock;
+using std::deque;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class ChromeBrowserProxyResolverTest : public ::testing::Test {
+ protected:
+ ChromeBrowserProxyResolverTest()
+ : service_interface_mock_(new LibCrosServiceInterfaceProxyMock()),
+ ue_proxy_resolved_interface_mock_(
+ new UpdateEngineLibcrosProxyResolvedInterfaceProxyMock()),
+ libcros_proxy_(
+ brillo::make_unique_ptr(service_interface_mock_),
+ brillo::make_unique_ptr(ue_proxy_resolved_interface_mock_)) {}
+
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ // The ProxyResolved signal should be subscribed to.
+ MOCK_SIGNAL_HANDLER_EXPECT_SIGNAL_HANDLER(
+ ue_proxy_resolved_signal_,
+ *ue_proxy_resolved_interface_mock_,
+ ProxyResolved);
+
+ EXPECT_TRUE(resolver_.Init());
+ // Run the loop once to dispatch the successfully registered signal handler.
+ EXPECT_TRUE(loop_.RunOnce(false));
+ }
+
+ void TearDown() override {
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ // Send the signal to the callback passed during registration of the
+ // ProxyResolved.
+ void SendReplySignal(const string& source_url,
+ const string& proxy_info,
+ const string& error_message);
+
+ void RunTest(bool chrome_replies, bool chrome_alive);
+
+ private:
+ brillo::FakeMessageLoop loop_{nullptr};
+
+ // Local pointers to the mocks. The instances are owned by the
+ // |libcros_proxy_|.
+ LibCrosServiceInterfaceProxyMock* service_interface_mock_;
+ UpdateEngineLibcrosProxyResolvedInterfaceProxyMock*
+ ue_proxy_resolved_interface_mock_;
+
+ // The registered signal handler for the signal
+ // UpdateEngineLibcrosProxyResolvedInterface.ProxyResolved.
+ chromeos_update_engine::dbus_test_utils::MockSignalHandler<
+ void(const string&, const string&, const string&)>
+ ue_proxy_resolved_signal_;
+
+ LibCrosProxy libcros_proxy_;
+ ChromeBrowserProxyResolver resolver_{&libcros_proxy_};
+};
+
+
+void ChromeBrowserProxyResolverTest::SendReplySignal(
+ const string& source_url,
+ const string& proxy_info,
+ const string& error_message) {
+ ASSERT_TRUE(ue_proxy_resolved_signal_.IsHandlerRegistered());
+ ue_proxy_resolved_signal_.signal_callback().Run(
+ source_url, proxy_info, error_message);
+}
+
+namespace {
+void CheckResponseResolved(const deque<string>& proxies,
+ void* /* pirv_data */) {
+ EXPECT_EQ(2, proxies.size());
+ EXPECT_EQ("socks5://192.168.52.83:5555", proxies[0]);
+ EXPECT_EQ(kNoProxy, proxies[1]);
+ MessageLoop::current()->BreakLoop();
+}
+
+void CheckResponseNoReply(const deque<string>& proxies, void* /* pirv_data */) {
+ EXPECT_EQ(1, proxies.size());
+ EXPECT_EQ(kNoProxy, proxies[0]);
+ MessageLoop::current()->BreakLoop();
+}
+} // namespace
+
+// chrome_replies should be set to whether or not we fake a reply from
+// chrome. If there's no reply, the resolver should time out.
+// If chrome_alive is false, assume that sending to chrome fails.
+void ChromeBrowserProxyResolverTest::RunTest(bool chrome_replies,
+ bool chrome_alive) {
+ char kUrl[] = "http://example.com/blah";
+ char kProxyConfig[] = "SOCKS5 192.168.52.83:5555;DIRECT";
+
+ EXPECT_CALL(*service_interface_mock_,
+ ResolveNetworkProxy(StrEq(kUrl),
+ StrEq(kLibCrosProxyResolveSignalInterface),
+ StrEq(kLibCrosProxyResolveName),
+ _,
+ _))
+ .WillOnce(Return(chrome_alive));
+
+ ProxiesResolvedFn get_proxies_response = &CheckResponseNoReply;
+ if (chrome_replies) {
+ get_proxies_response = &CheckResponseResolved;
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ChromeBrowserProxyResolverTest::SendReplySignal,
+ base::Unretained(this),
+ kUrl,
+ kProxyConfig,
+ ""),
+ base::TimeDelta::FromSeconds(1));
+ }
+
+ EXPECT_TRUE(resolver_.GetProxiesForUrl(kUrl, get_proxies_response, nullptr));
+ MessageLoop::current()->Run();
+}
+
+
+TEST_F(ChromeBrowserProxyResolverTest, ParseTest) {
+ // Test ideas from
+ // http://src.chromium.org/svn/trunk/src/net/proxy/proxy_list_unittest.cc
+ vector<string> inputs = {
+ "PROXY foopy:10",
+ " DIRECT", // leading space.
+ "PROXY foopy1 ; proxy foopy2;\t DIRECT",
+ "proxy foopy1 ; SOCKS foopy2",
+ "DIRECT ; proxy foopy1 ; DIRECT ; SOCKS5 foopy2;DIRECT ",
+ "DIRECT ; proxy foopy1:80; DIRECT ; DIRECT",
+ "PROXY-foopy:10",
+ "PROXY",
+ "PROXY foopy1 ; JUNK ; JUNK ; SOCKS5 foopy2 ; ;",
+ "HTTP foopy1; SOCKS5 foopy2"};
+ vector<deque<string>> outputs = {
+ {"http://foopy:10", kNoProxy},
+ {kNoProxy},
+ {"http://foopy1", "http://foopy2", kNoProxy},
+ {"http://foopy1", "socks4://foopy2", kNoProxy},
+ {kNoProxy, "http://foopy1", kNoProxy, "socks5://foopy2", kNoProxy},
+ {kNoProxy, "http://foopy1:80", kNoProxy, kNoProxy},
+ {kNoProxy},
+ {kNoProxy},
+ {"http://foopy1", "socks5://foopy2", kNoProxy},
+ {"socks5://foopy2", kNoProxy}};
+ ASSERT_EQ(inputs.size(), outputs.size());
+
+ for (size_t i = 0; i < inputs.size(); i++) {
+ deque<string> results =
+ ChromeBrowserProxyResolver::ParseProxyString(inputs[i]);
+ deque<string>& expected = outputs[i];
+ EXPECT_EQ(results.size(), expected.size()) << "i = " << i;
+ if (expected.size() != results.size())
+ continue;
+ for (size_t j = 0; j < expected.size(); j++) {
+ EXPECT_EQ(expected[j], results[j]) << "i = " << i;
+ }
+ }
+}
+
+TEST_F(ChromeBrowserProxyResolverTest, SuccessTest) {
+ RunTest(true, true);
+}
+
+TEST_F(ChromeBrowserProxyResolverTest, NoReplyTest) {
+ RunTest(false, true);
+}
+
+TEST_F(ChromeBrowserProxyResolverTest, NoChromeTest) {
+ RunTest(false, false);
+}
+
+} // namespace chromeos_update_engine
diff --git a/client_library/client.cc b/client_library/client.cc
new file mode 100644
index 0000000..d6e7382
--- /dev/null
+++ b/client_library/client.cc
@@ -0,0 +1,31 @@
+//
+// 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>
+
+#include "update_engine/client_library/client_impl.h"
+
+using std::unique_ptr;
+
+namespace update_engine {
+
+std::unique_ptr<UpdateEngineClient> UpdateEngineClient::CreateInstance() {
+ return unique_ptr<UpdateEngineClient>{new internal::UpdateEngineClientImpl{}};
+}
+
+} // namespace update_engine
diff --git a/client_library/client_impl.cc b/client_library/client_impl.cc
new file mode 100644
index 0000000..84ca184
--- /dev/null
+++ b/client_library/client_impl.cc
@@ -0,0 +1,91 @@
+//
+// 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_impl.h"
+
+#include <dbus/bus.h>
+#include <update_engine/dbus-constants.h>
+
+#include "update_engine/update_status_utils.h"
+
+using chromeos_update_engine::StringToUpdateStatus;
+using dbus::Bus;
+using org::chromium::UpdateEngineInterfaceProxy;
+using std::string;
+
+namespace update_engine {
+namespace internal {
+
+UpdateEngineClientImpl::UpdateEngineClientImpl() {
+ Bus::Options options;
+ options.bus_type = Bus::SYSTEM;
+ scoped_refptr<Bus> bus{new Bus{options}};
+ proxy_.reset(new UpdateEngineInterfaceProxy{bus});
+}
+
+bool UpdateEngineClientImpl::AttemptUpdate(const string& in_app_version,
+ const string& in_omaha_url,
+ bool at_user_request) {
+ return proxy_->AttemptUpdateWithFlags(
+ in_app_version,
+ in_omaha_url,
+ (at_user_request) ? 0 : kAttemptUpdateFlagNonInteractive,
+ nullptr);
+}
+
+bool UpdateEngineClientImpl::GetStatus(int64_t* out_last_checked_time,
+ double* out_progress,
+ UpdateStatus* out_update_status,
+ string* out_new_version,
+ int64_t* out_new_size) {
+ 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) {
+ return false;
+ }
+
+ return StringToUpdateStatus(status_as_string, out_update_status);
+}
+
+bool UpdateEngineClientImpl::SetTargetChannel(const string& in_target_channel) {
+ return proxy_->SetChannel(
+ in_target_channel,
+ true,
+ nullptr);
+}
+
+bool UpdateEngineClientImpl::GetTargetChannel(string* out_channel) {
+ return proxy_->GetChannel(
+ false, // Get the target channel.
+ out_channel,
+ nullptr);
+}
+
+bool UpdateEngineClientImpl::GetChannel(string* out_channel) {
+ return proxy_->GetChannel(
+ true, // Get the current channel.
+ out_channel,
+ nullptr);
+}
+
+} // namespace internal
+} // namespace update_engine
diff --git a/client_library/client_impl.h b/client_library/client_impl.h
new file mode 100644
index 0000000..e6194d2
--- /dev/null
+++ b/client_library/client_impl.h
@@ -0,0 +1,62 @@
+//
+// 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_CLIENT_LIBRARY_CLIENT_IMPL_H_
+#define UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_IMPL_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+
+#include "update_engine/client_library/include/update_engine/client.h"
+#include "update_engine/dbus-proxies.h"
+
+namespace update_engine {
+namespace internal {
+
+class UpdateEngineClientImpl : public UpdateEngineClient {
+ public:
+ UpdateEngineClientImpl();
+ virtual ~UpdateEngineClientImpl() = default;
+
+ bool AttemptUpdate(const std::string& app_version,
+ const std::string& omaha_url,
+ bool at_user_request) 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) override;
+
+ bool SetTargetChannel(const std::string& target_channel) override;
+
+ bool GetTargetChannel(std::string* out_channel) override;
+
+ bool GetChannel(std::string* out_channel) override;
+
+ private:
+ std::unique_ptr<org::chromium::UpdateEngineInterfaceProxy> proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateEngineClientImpl);
+}; // class UpdateEngineClientImpl
+
+} // namespace internal
+} // namespace update_engine
+
+#endif // UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_IMPL_H_
diff --git a/client_library/include/update_engine/client.h b/client_library/include/update_engine/client.h
new file mode 100644
index 0000000..b3cef66
--- /dev/null
+++ b/client_library/include/update_engine/client.h
@@ -0,0 +1,89 @@
+//
+// 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_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_CLIENT_H_
+#define UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_CLIENT_H_
+
+#include <cstdint>
+#include <memory>
+#include <string>
+
+#include "update_engine/update_status.h"
+
+namespace update_engine {
+
+class UpdateEngineClient {
+ public:
+ static std::unique_ptr<UpdateEngineClient> CreateInstance();
+
+ virtual ~UpdateEngineClient() = default;
+
+ // Force the update_engine to attempt an update.
+ // |app_version|
+ // Attempt to update to this version. An empty string indicates that
+ // update engine should pick the most recent image on the current channel.
+ // |omaha_url|
+ // Force update_engine to look for updates from the given server. Passing
+ // empty indicates update_engine should get this parameter from its
+ // config. Note that update_engine will ignore this parameter in
+ // production mode to avoid pulling untrusted updates.
+ // |at_user_request|
+ // This update was directly requested by the user.
+ virtual bool AttemptUpdate(const std::string& app_version,
+ const std::string& omaha_url,
+ bool at_user_request) = 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) = 0;
+
+ // Changes the current channel of the device to the target channel.
+ virtual bool SetTargetChannel(const std::string& target_channel) = 0;
+
+ // Get the channel the device will switch to on reboot.
+ virtual bool GetTargetChannel(std::string* out_channel) = 0;
+
+ // Get the channel the device is currently on.
+ virtual bool GetChannel(std::string* out_channel) = 0;
+
+ protected:
+ // Use CreateInstance().
+ UpdateEngineClient() = default;
+
+ private:
+ UpdateEngineClient(const UpdateEngineClient&) = delete;
+ void operator=(const UpdateEngineClient&) = delete;
+}; // class UpdateEngineClient
+
+} // namespace update_engine
+
+#endif // UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_CLIENT_H_
diff --git a/client_library/include/update_engine/update_status.h b/client_library/include/update_engine/update_status.h
new file mode 100644
index 0000000..3e9af5b
--- /dev/null
+++ b/client_library/include/update_engine/update_status.h
@@ -0,0 +1,37 @@
+//
+// 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_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_UPDATE_STATUS_H_
+#define UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_UPDATE_STATUS_H_
+
+namespace update_engine {
+
+enum class UpdateStatus {
+ IDLE = 0,
+ CHECKING_FOR_UPDATE,
+ UPDATE_AVAILABLE,
+ DOWNLOADING,
+ VERIFYING,
+ FINALIZING,
+ UPDATED_NEED_REBOOT,
+ REPORTING_ERROR_EVENT,
+ ATTEMPTING_ROLLBACK,
+ DISABLED,
+};
+
+} // namespace update_engine
+
+#endif // UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_UPDATE_ENGINE_UPDATE_STATUS_H_
diff --git a/common/action.h b/common/action.h
new file mode 100644
index 0000000..d8049ac
--- /dev/null
+++ b/common/action.h
@@ -0,0 +1,218 @@
+//
+// 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_COMMON_ACTION_H_
+#define UPDATE_ENGINE_COMMON_ACTION_H_
+
+#include <stdio.h>
+
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/action_processor.h"
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+//
+// Readers may want to consult this wiki page from the Update Engine site:
+// http://code.google.com/p/update-engine/wiki/ActionProcessor
+// Although it's referring to the Objective-C KSAction* classes, much
+// applies here as well.
+//
+// How it works:
+//
+// First off, there is only one thread and all I/O should be asynchronous.
+// A message loop blocks whenever there is no work to be done. This happens
+// where there is no CPU work to be done and no I/O ready to transfer in or
+// out. Two kinds of events can wake up the message loop: timer alarm or file
+// descriptors. If either of these happens, the message loop finds out the owner
+// of what fired and calls the appropriate code to handle it. As such, all the
+// code in the Action* classes and the code that is calls is non-blocking.
+//
+// An ActionProcessor contains a queue of Actions to perform. When
+// ActionProcessor::StartProcessing() is called, it executes the first action.
+// Each action tells the processor when it has completed, which causes the
+// Processor to execute the next action. ActionProcessor may have a delegate
+// (an object of type ActionProcessorDelegate). If it does, the delegate
+// is called to be notified of events as they happen.
+//
+// ActionPipe classes
+//
+// See action_pipe.h
+//
+// ActionTraits
+//
+// We need to use an extra class ActionTraits. ActionTraits is a simple
+// templated class that contains only two typedefs: OutputObjectType and
+// InputObjectType. Each action class also has two typedefs of the same name
+// that are of the same type. So, to get the input/output types of, e.g., the
+// DownloadAction class, we look at the type of
+// DownloadAction::InputObjectType.
+//
+// Each concrete Action class derives from Action<T>. This means that during
+// template instantiation of Action<T>, T is declared but not defined, which
+// means that T::InputObjectType (and OutputObjectType) is not defined.
+// However, the traits class is constructed in such a way that it will be
+// template instantiated first, so Action<T> *can* find the types it needs by
+// consulting ActionTraits<T>::InputObjectType (and OutputObjectType).
+// This is why the ActionTraits classes are needed.
+
+namespace chromeos_update_engine {
+
+// It is handy to have a non-templated base class of all Actions.
+class AbstractAction {
+ public:
+ AbstractAction() : processor_(nullptr) {}
+ virtual ~AbstractAction() = default;
+
+ // Begin performing the action. Since this code is asynchronous, when this
+ // method returns, it means only that the action has started, not necessarily
+ // completed. However, it's acceptable for this method to perform the
+ // action synchronously; Action authors should understand the implications
+ // of synchronously performing, though, because this is a single-threaded
+ // app, the entire process will be blocked while the action performs.
+ //
+ // When the action is complete, it must call
+ // ActionProcessor::ActionComplete(this); to notify the processor that it's
+ // done.
+ virtual void PerformAction() = 0;
+
+ // Called on ActionProcess::ActionComplete() by ActionProcessor.
+ virtual void ActionCompleted(ErrorCode code) {}
+
+ // Called by the ActionProcessor to tell this Action which processor
+ // it belongs to.
+ void SetProcessor(ActionProcessor* processor) {
+ if (processor)
+ CHECK(!processor_);
+ else
+ CHECK(processor_);
+ processor_ = processor;
+ }
+
+ // Returns true iff the action is the current action of its ActionProcessor.
+ bool IsRunning() const {
+ if (!processor_)
+ return false;
+ return processor_->current_action() == this;
+ }
+
+ // Called on asynchronous actions if canceled. Actions may implement if
+ // there's any cleanup to do. There is no need to call
+ // ActionProcessor::ActionComplete() because the processor knows this
+ // action is terminating.
+ // Only the ActionProcessor should call this.
+ virtual void TerminateProcessing() {}
+
+ // These methods are useful for debugging. TODO(adlr): consider using
+ // std::type_info for this?
+ // Type() returns a string of the Action type. I.e., for DownloadAction,
+ // Type() would return "DownloadAction".
+ virtual std::string Type() const = 0;
+
+ protected:
+ // A weak pointer to the processor that owns this Action.
+ ActionProcessor* processor_;
+};
+
+// Forward declare a couple classes we use.
+template<typename T>
+class ActionPipe;
+template<typename T>
+class ActionTraits;
+
+template<typename SubClass>
+class Action : public AbstractAction {
+ public:
+ ~Action() override {}
+
+ // Attaches an input pipe to this Action. This is optional; an Action
+ // doesn't need to have an input pipe. The input pipe must be of the type
+ // of object that this class expects.
+ // This is generally called by ActionPipe::Bond()
+ void set_in_pipe(
+ // this type is a fancy way of saying: a shared_ptr to an
+ // ActionPipe<InputObjectType>.
+ const std::shared_ptr<ActionPipe<
+ typename ActionTraits<SubClass>::InputObjectType>>& in_pipe) {
+ in_pipe_ = in_pipe;
+ }
+
+ // Attaches an output pipe to this Action. This is optional; an Action
+ // doesn't need to have an output pipe. The output pipe must be of the type
+ // of object that this class expects.
+ // This is generally called by ActionPipe::Bond()
+ void set_out_pipe(
+ // this type is a fancy way of saying: a shared_ptr to an
+ // ActionPipe<OutputObjectType>.
+ const std::shared_ptr<ActionPipe<
+ typename ActionTraits<SubClass>::OutputObjectType>>& out_pipe) {
+ out_pipe_ = out_pipe;
+ }
+
+ // Returns true iff there is an associated input pipe. If there's an input
+ // pipe, there's an input object, but it may have been constructed with the
+ // default ctor if the previous action didn't call SetOutputObject().
+ bool HasInputObject() const { return in_pipe_.get(); }
+
+ // returns a const reference to the object in the input pipe.
+ const typename ActionTraits<SubClass>::InputObjectType& GetInputObject()
+ const {
+ CHECK(HasInputObject());
+ return in_pipe_->contents();
+ }
+
+ // Returns true iff there's an output pipe.
+ bool HasOutputPipe() const {
+ return out_pipe_.get();
+ }
+
+ // Copies the object passed into the output pipe. It will be accessible to
+ // the next Action via that action's input pipe (which is the same as this
+ // Action's output pipe).
+ void SetOutputObject(
+ const typename ActionTraits<SubClass>::OutputObjectType& out_obj) {
+ CHECK(HasOutputPipe());
+ out_pipe_->set_contents(out_obj);
+ }
+
+ // Returns a reference to the object sitting in the output pipe.
+ const typename ActionTraits<SubClass>::OutputObjectType& GetOutputObject() {
+ CHECK(HasOutputPipe());
+ return out_pipe_->contents();
+ }
+
+ protected:
+ // We use a shared_ptr to the pipe. shared_ptr objects destroy what they
+ // point to when the last such shared_ptr object dies. We consider the
+ // Actions on either end of a pipe to "own" the pipe. When the last Action
+ // of the two dies, the ActionPipe will die, too.
+ std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::InputObjectType>>
+ in_pipe_;
+ std::shared_ptr<ActionPipe<typename ActionTraits<SubClass>::OutputObjectType>>
+ out_pipe_;
+};
+
+}; // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_ACTION_H_
diff --git a/common/action_pipe.h b/common/action_pipe.h
new file mode 100644
index 0000000..362817d
--- /dev/null
+++ b/common/action_pipe.h
@@ -0,0 +1,101 @@
+//
+// 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_COMMON_ACTION_PIPE_H_
+#define UPDATE_ENGINE_COMMON_ACTION_PIPE_H_
+
+#include <stdio.h>
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/logging.h>
+#include <base/macros.h>
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+
+// This class serves as a temporary holding area for an object passed out
+// from one Action and into another Action. It's templated so that it may
+// contain any type of object that an Action outputs/inputs. Actions
+// cannot be bonded (i.e., connected with a pipe) if their output/input
+// object types differ (a compiler error will result).
+//
+// An ActionPipe is generally created with the Bond() method and owned by
+// the two Action objects. a shared_ptr is used so that when the last Action
+// pointing to an ActionPipe dies, the ActionPipe dies, too.
+
+namespace chromeos_update_engine {
+
+// Used by Actions an InputObjectType or OutputObjectType to specify that
+// for that type, no object is taken/given.
+class NoneType {};
+
+template<typename T>
+class Action;
+
+template<typename ObjectType>
+class ActionPipe {
+ public:
+ virtual ~ActionPipe() {}
+
+ // This should be called by an Action on its input pipe.
+ // Returns a reference to the stored object.
+ const ObjectType& contents() const { return contents_; }
+
+ // This should be called by an Action on its output pipe.
+ // Stores a copy of the passed object in this pipe.
+ void set_contents(const ObjectType& contents) { contents_ = contents; }
+
+ // Bonds two Actions together with a new ActionPipe. The ActionPipe is
+ // jointly owned by the two Actions and will be automatically destroyed
+ // when the last Action is destroyed.
+ template<typename FromAction, typename ToAction>
+ static void Bond(FromAction* from, ToAction* to) {
+ std::shared_ptr<ActionPipe<ObjectType>> pipe(new ActionPipe<ObjectType>);
+ from->set_out_pipe(pipe);
+
+ to->set_in_pipe(pipe); // If you get an error on this line, then
+ // it most likely means that the From object's OutputObjectType is
+ // different from the To object's InputObjectType.
+ }
+
+ private:
+ ObjectType contents_;
+
+ // The ctor is private. This is because this class should construct itself
+ // via the static Bond() method.
+ ActionPipe() {}
+ DISALLOW_COPY_AND_ASSIGN(ActionPipe);
+};
+
+// Utility function
+template<typename FromAction, typename ToAction>
+void BondActions(FromAction* from, ToAction* to) {
+ // TODO(adlr): find something like this that the compiler accepts:
+ // COMPILE_ASSERT(typeof(typename FromAction::OutputObjectType) ==
+ // typeof(typename ToAction::InputObjectType),
+ // FromAction_OutputObjectType_doesnt_match_ToAction_InputObjectType);
+ ActionPipe<typename FromAction::OutputObjectType>::Bond(from, to);
+}
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_ACTION_PIPE_H_
diff --git a/common/action_pipe_unittest.cc b/common/action_pipe_unittest.cc
new file mode 100644
index 0000000..9bfbc83
--- /dev/null
+++ b/common/action_pipe_unittest.cc
@@ -0,0 +1,60 @@
+//
+// 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/common/action_pipe.h"
+
+#include <gtest/gtest.h>
+#include <string>
+#include "update_engine/common/action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionPipeTestAction;
+
+template<>
+class ActionTraits<ActionPipeTestAction> {
+ public:
+ typedef string OutputObjectType;
+ typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionPipeTestAction : public Action<ActionPipeTestAction> {
+ public:
+ typedef string InputObjectType;
+ typedef string OutputObjectType;
+ ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+ ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+ void PerformAction() {}
+ string Type() const { return "ActionPipeTestAction"; }
+};
+
+class ActionPipeTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionPipeTest, SimpleTest) {
+ ActionPipeTestAction a, b;
+ BondActions(&a, &b);
+ a.out_pipe()->set_contents("foo");
+ EXPECT_EQ("foo", b.in_pipe()->contents());
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/action_processor.cc b/common/action_processor.cc
new file mode 100644
index 0000000..7ccdfbd
--- /dev/null
+++ b/common/action_processor.cc
@@ -0,0 +1,100 @@
+//
+// 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/common/action_processor.h"
+
+#include <string>
+
+#include <base/logging.h>
+
+#include "update_engine/common/action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+ActionProcessor::ActionProcessor()
+ : current_action_(nullptr), delegate_(nullptr) {}
+
+ActionProcessor::~ActionProcessor() {
+ if (IsRunning())
+ StopProcessing();
+ for (auto action : actions_)
+ action->SetProcessor(nullptr);
+}
+
+void ActionProcessor::EnqueueAction(AbstractAction* action) {
+ actions_.push_back(action);
+ action->SetProcessor(this);
+}
+
+void ActionProcessor::StartProcessing() {
+ CHECK(!IsRunning());
+ if (!actions_.empty()) {
+ current_action_ = actions_.front();
+ LOG(INFO) << "ActionProcessor::StartProcessing: "
+ << current_action_->Type();
+ actions_.pop_front();
+ current_action_->PerformAction();
+ }
+}
+
+void ActionProcessor::StopProcessing() {
+ CHECK(IsRunning());
+ CHECK(current_action_);
+ current_action_->TerminateProcessing();
+ CHECK(current_action_);
+ current_action_->SetProcessor(nullptr);
+ LOG(INFO) << "ActionProcessor::StopProcessing: aborted "
+ << current_action_->Type();
+ current_action_ = nullptr;
+ if (delegate_)
+ delegate_->ProcessingStopped(this);
+}
+
+void ActionProcessor::ActionComplete(AbstractAction* actionptr,
+ ErrorCode code) {
+ CHECK_EQ(actionptr, current_action_);
+ if (delegate_)
+ delegate_->ActionCompleted(this, actionptr, code);
+ string old_type = current_action_->Type();
+ current_action_->ActionCompleted(code);
+ current_action_->SetProcessor(nullptr);
+ current_action_ = nullptr;
+ if (actions_.empty()) {
+ LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
+ " type " << old_type;
+ } else if (code != ErrorCode::kSuccess) {
+ LOG(INFO) << "ActionProcessor::ActionComplete: " << old_type
+ << " action failed. Aborting processing.";
+ actions_.clear();
+ }
+ if (actions_.empty()) {
+ LOG(INFO) << "ActionProcessor::ActionComplete: finished last action of"
+ " type " << old_type;
+ if (delegate_) {
+ delegate_->ProcessingDone(this, code);
+ }
+ return;
+ }
+ current_action_ = actions_.front();
+ actions_.pop_front();
+ LOG(INFO) << "ActionProcessor::ActionComplete: finished " << old_type
+ << ", starting " << current_action_->Type();
+ current_action_->PerformAction();
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/action_processor.h b/common/action_processor.h
new file mode 100644
index 0000000..d61e12d
--- /dev/null
+++ b/common/action_processor.h
@@ -0,0 +1,116 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_ACTION_PROCESSOR_H_
+#define UPDATE_ENGINE_COMMON_ACTION_PROCESSOR_H_
+
+#include <deque>
+
+#include <base/macros.h>
+
+#include "update_engine/common/error_code.h"
+
+// The structure of these classes (Action, ActionPipe, ActionProcessor, etc.)
+// is based on the KSAction* classes from the Google Update Engine code at
+// http://code.google.com/p/update-engine/ . The author of this file sends
+// a big thanks to that team for their high quality design, implementation,
+// and documentation.
+
+// See action.h for an overview of this class and other Action* classes.
+
+// An ActionProcessor keeps a queue of Actions and processes them in order.
+
+namespace chromeos_update_engine {
+
+class AbstractAction;
+class ActionProcessorDelegate;
+
+class ActionProcessor {
+ public:
+ ActionProcessor();
+
+ virtual ~ActionProcessor();
+
+ // Starts processing the first Action in the queue. If there's a delegate,
+ // when all processing is complete, ProcessingDone() will be called on the
+ // delegate.
+ virtual void StartProcessing();
+
+ // Aborts processing. If an Action is running, it will have
+ // TerminateProcessing() called on it. The Action that was running
+ // will be lost and must be re-enqueued if this Processor is to use it.
+ void StopProcessing();
+
+ // Returns true iff an Action is currently processing.
+ bool IsRunning() const { return nullptr != current_action_; }
+
+ // Adds another Action to the end of the queue.
+ virtual void EnqueueAction(AbstractAction* action);
+
+ // Sets/gets the current delegate. Set to null to remove a delegate.
+ ActionProcessorDelegate* delegate() const { return delegate_; }
+ void set_delegate(ActionProcessorDelegate *delegate) {
+ delegate_ = delegate;
+ }
+
+ // Returns a pointer to the current Action that's processing.
+ AbstractAction* current_action() const {
+ return current_action_;
+ }
+
+ // Called by an action to notify processor that it's done. Caller passes self.
+ void ActionComplete(AbstractAction* actionptr, ErrorCode code);
+
+ private:
+ // Actions that have not yet begun processing, in the order in which
+ // they'll be processed.
+ std::deque<AbstractAction*> actions_;
+
+ // A pointer to the currently processing Action, if any.
+ AbstractAction* current_action_;
+
+ // A pointer to the delegate, or null if none.
+ ActionProcessorDelegate *delegate_;
+ DISALLOW_COPY_AND_ASSIGN(ActionProcessor);
+};
+
+// A delegate object can be used to be notified of events that happen
+// in an ActionProcessor. An instance of this class can be passed to an
+// ActionProcessor to register itself.
+class ActionProcessorDelegate {
+ public:
+ virtual ~ActionProcessorDelegate() = default;
+
+ // Called when all processing in an ActionProcessor has completed. A pointer
+ // to the ActionProcessor is passed. |code| is set to the exit code of the
+ // last completed action.
+ virtual void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) {}
+
+ // Called when processing has stopped. Does not mean that all Actions have
+ // completed. If/when all Actions complete, ProcessingDone() will be called.
+ virtual void ProcessingStopped(const ActionProcessor* processor) {}
+
+ // Called whenever an action has finished processing, either successfully
+ // or otherwise.
+ virtual void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {}
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_ACTION_PROCESSOR_H_
diff --git a/common/action_processor_unittest.cc b/common/action_processor_unittest.cc
new file mode 100644
index 0000000..8285470
--- /dev/null
+++ b/common/action_processor_unittest.cc
@@ -0,0 +1,190 @@
+//
+// 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/common/action_processor.h"
+
+#include <gtest/gtest.h>
+#include <string>
+#include "update_engine/common/action.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionProcessorTestAction;
+
+template<>
+class ActionTraits<ActionProcessorTestAction> {
+ public:
+ typedef string OutputObjectType;
+ typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionProcessorTestAction : public Action<ActionProcessorTestAction> {
+ public:
+ typedef string InputObjectType;
+ typedef string OutputObjectType;
+ ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+ ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+ ActionProcessor* processor() { return processor_; }
+ void PerformAction() {}
+ void CompleteAction() {
+ ASSERT_TRUE(processor());
+ processor()->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ string Type() const { return "ActionProcessorTestAction"; }
+};
+
+class ActionProcessorTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionProcessorTest, SimpleTest) {
+ ActionProcessorTestAction action;
+ ActionProcessor action_processor;
+ EXPECT_FALSE(action_processor.IsRunning());
+ action_processor.EnqueueAction(&action);
+ EXPECT_FALSE(action_processor.IsRunning());
+ EXPECT_FALSE(action.IsRunning());
+ action_processor.StartProcessing();
+ EXPECT_TRUE(action_processor.IsRunning());
+ EXPECT_TRUE(action.IsRunning());
+ EXPECT_EQ(action_processor.current_action(), &action);
+ action.CompleteAction();
+ EXPECT_FALSE(action_processor.IsRunning());
+ EXPECT_FALSE(action.IsRunning());
+}
+
+namespace {
+class MyActionProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ explicit MyActionProcessorDelegate(const ActionProcessor* processor)
+ : processor_(processor),
+ processing_done_called_(false),
+ processing_stopped_called_(false),
+ action_completed_called_(false),
+ action_exit_code_(ErrorCode::kError) {}
+
+ virtual void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) {
+ EXPECT_EQ(processor_, processor);
+ EXPECT_FALSE(processing_done_called_);
+ processing_done_called_ = true;
+ }
+ virtual void ProcessingStopped(const ActionProcessor* processor) {
+ EXPECT_EQ(processor_, processor);
+ EXPECT_FALSE(processing_stopped_called_);
+ processing_stopped_called_ = true;
+ }
+ virtual void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ EXPECT_EQ(processor_, processor);
+ EXPECT_FALSE(action_completed_called_);
+ action_completed_called_ = true;
+ action_exit_code_ = code;
+ }
+
+ const ActionProcessor* processor_;
+ bool processing_done_called_;
+ bool processing_stopped_called_;
+ bool action_completed_called_;
+ ErrorCode action_exit_code_;
+};
+} // namespace
+
+TEST(ActionProcessorTest, DelegateTest) {
+ ActionProcessorTestAction action;
+ ActionProcessor action_processor;
+ MyActionProcessorDelegate delegate(&action_processor);
+ action_processor.set_delegate(&delegate);
+
+ action_processor.EnqueueAction(&action);
+ action_processor.StartProcessing();
+ action.CompleteAction();
+ action_processor.set_delegate(nullptr);
+ EXPECT_TRUE(delegate.processing_done_called_);
+ EXPECT_TRUE(delegate.action_completed_called_);
+}
+
+TEST(ActionProcessorTest, StopProcessingTest) {
+ ActionProcessorTestAction action;
+ ActionProcessor action_processor;
+ MyActionProcessorDelegate delegate(&action_processor);
+ action_processor.set_delegate(&delegate);
+
+ action_processor.EnqueueAction(&action);
+ action_processor.StartProcessing();
+ action_processor.StopProcessing();
+ action_processor.set_delegate(nullptr);
+ EXPECT_TRUE(delegate.processing_stopped_called_);
+ EXPECT_FALSE(delegate.action_completed_called_);
+ EXPECT_FALSE(action_processor.IsRunning());
+ EXPECT_EQ(nullptr, action_processor.current_action());
+}
+
+TEST(ActionProcessorTest, ChainActionsTest) {
+ ActionProcessorTestAction action1, action2;
+ ActionProcessor action_processor;
+ action_processor.EnqueueAction(&action1);
+ action_processor.EnqueueAction(&action2);
+ action_processor.StartProcessing();
+ EXPECT_EQ(&action1, action_processor.current_action());
+ EXPECT_TRUE(action_processor.IsRunning());
+ action1.CompleteAction();
+ EXPECT_EQ(&action2, action_processor.current_action());
+ EXPECT_TRUE(action_processor.IsRunning());
+ action2.CompleteAction();
+ EXPECT_EQ(nullptr, action_processor.current_action());
+ EXPECT_FALSE(action_processor.IsRunning());
+}
+
+TEST(ActionProcessorTest, DtorTest) {
+ ActionProcessorTestAction action1, action2;
+ {
+ ActionProcessor action_processor;
+ action_processor.EnqueueAction(&action1);
+ action_processor.EnqueueAction(&action2);
+ action_processor.StartProcessing();
+ }
+ EXPECT_EQ(nullptr, action1.processor());
+ EXPECT_FALSE(action1.IsRunning());
+ EXPECT_EQ(nullptr, action2.processor());
+ EXPECT_FALSE(action2.IsRunning());
+}
+
+TEST(ActionProcessorTest, DefaultDelegateTest) {
+ // Just make sure it doesn't crash
+ ActionProcessorTestAction action;
+ ActionProcessor action_processor;
+ ActionProcessorDelegate delegate;
+ action_processor.set_delegate(&delegate);
+
+ action_processor.EnqueueAction(&action);
+ action_processor.StartProcessing();
+ action.CompleteAction();
+
+ action_processor.EnqueueAction(&action);
+ action_processor.StartProcessing();
+ action_processor.StopProcessing();
+
+ action_processor.set_delegate(nullptr);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/action_unittest.cc b/common/action_unittest.cc
new file mode 100644
index 0000000..dcdce17
--- /dev/null
+++ b/common/action_unittest.cc
@@ -0,0 +1,76 @@
+//
+// 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/common/action.h"
+
+#include <gtest/gtest.h>
+#include <string>
+#include "update_engine/common/action_processor.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+using chromeos_update_engine::ActionPipe;
+
+class ActionTestAction;
+
+template<>
+class ActionTraits<ActionTestAction> {
+ public:
+ typedef string OutputObjectType;
+ typedef string InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class ActionTestAction : public Action<ActionTestAction> {
+ public:
+ typedef string InputObjectType;
+ typedef string OutputObjectType;
+ ActionPipe<string>* in_pipe() { return in_pipe_.get(); }
+ ActionPipe<string>* out_pipe() { return out_pipe_.get(); }
+ ActionProcessor* processor() { return processor_; }
+ void PerformAction() {}
+ void CompleteAction() {
+ ASSERT_TRUE(processor());
+ processor()->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ string Type() const { return "ActionTestAction"; }
+};
+
+class ActionTest : public ::testing::Test { };
+
+// This test creates two simple Actions and sends a message via an ActionPipe
+// from one to the other.
+TEST(ActionTest, SimpleTest) {
+ ActionTestAction action;
+
+ EXPECT_FALSE(action.in_pipe());
+ EXPECT_FALSE(action.out_pipe());
+ EXPECT_FALSE(action.processor());
+ EXPECT_FALSE(action.IsRunning());
+
+ ActionProcessor action_processor;
+ action_processor.EnqueueAction(&action);
+ EXPECT_EQ(&action_processor, action.processor());
+
+ action_processor.StartProcessing();
+ EXPECT_TRUE(action.IsRunning());
+ action.CompleteAction();
+ EXPECT_FALSE(action.IsRunning());
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/boot_control.h b/common/boot_control.h
new file mode 100644
index 0000000..1463015
--- /dev/null
+++ b/common/boot_control.h
@@ -0,0 +1,35 @@
+//
+// 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_COMMON_BOOT_CONTROL_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_H_
+
+#include <memory>
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+namespace boot_control {
+
+// The real BootControlInterface is platform-specific. This factory function
+// creates a new BootControlInterface instance for the current platform. If
+// this fails nullptr is returned.
+std::unique_ptr<BootControlInterface> CreateBootControl();
+
+} // namespace boot_control
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_BOOT_CONTROL_H_
diff --git a/common/boot_control_chromeos_unittest.cc b/common/boot_control_chromeos_unittest.cc
new file mode 100644
index 0000000..4c218c0
--- /dev/null
+++ b/common/boot_control_chromeos_unittest.cc
@@ -0,0 +1,70 @@
+//
+// 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/common/boot_control_chromeos.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+class BootControlChromeOSTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // We don't run Init() for bootctl_, we set its internal values instead.
+ bootctl_.num_slots_ = 2;
+ bootctl_.current_slot_ = 0;
+ bootctl_.boot_disk_name_ = "/dev/null";
+ }
+
+ BootControlChromeOS bootctl_; // BootControlChromeOS under test.
+};
+
+TEST_F(BootControlChromeOSTest, SysfsBlockDeviceTest) {
+ EXPECT_EQ("/sys/block/sda", bootctl_.SysfsBlockDevice("/dev/sda"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("/foo/sda"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("/dev/foo/bar"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("/"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice("./"));
+ EXPECT_EQ("", bootctl_.SysfsBlockDevice(""));
+}
+
+TEST_F(BootControlChromeOSTest, GetPartitionNumberTest) {
+ // The partition name should not be case-sensitive.
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("kernel", 0));
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("boot", 0));
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("KERNEL", 0));
+ EXPECT_EQ(2, bootctl_.GetPartitionNumber("BOOT", 0));
+
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("ROOT", 0));
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("system", 0));
+
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("ROOT", 0));
+ EXPECT_EQ(3, bootctl_.GetPartitionNumber("system", 0));
+
+ // Slot B.
+ EXPECT_EQ(4, bootctl_.GetPartitionNumber("KERNEL", 1));
+ EXPECT_EQ(5, bootctl_.GetPartitionNumber("ROOT", 1));
+
+ // Slot C doesn't exists.
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("KERNEL", 2));
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("ROOT", 2));
+
+ // Non A/B partitions are ignored.
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("OEM", 0));
+ EXPECT_EQ(-1, bootctl_.GetPartitionNumber("A little panda", 0));
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/boot_control_interface.h b/common/boot_control_interface.h
new file mode 100644
index 0000000..659b388
--- /dev/null
+++ b/common/boot_control_interface.h
@@ -0,0 +1,98 @@
+//
+// 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_COMMON_BOOT_CONTROL_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_INTERFACE_H_
+
+#include <climits>
+#include <string>
+
+#include <base/callback.h>
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+// The abstract boot control interface defines the interaction with the
+// platform's bootloader hiding vendor-specific details from the rest of
+// update_engine. This interface is used for controlling where the device should
+// boot from.
+class BootControlInterface {
+ public:
+ using Slot = unsigned int;
+
+ static const Slot kInvalidSlot = UINT_MAX;
+
+ virtual ~BootControlInterface() = default;
+
+ // Return the number of update slots in the system. A system will normally
+ // have two slots, named "A" and "B" in the documentation, but sometimes
+ // images running from other media can have only one slot, like some USB
+ // image. Systems with only one slot won't be able to update.
+ virtual unsigned int GetNumSlots() const = 0;
+
+ // Return the slot where we are running the system from. On success, the
+ // result is a number between 0 and GetNumSlots() - 1. Otherwise, log an error
+ // and return kInvalidSlot.
+ virtual Slot GetCurrentSlot() const = 0;
+
+ // Determines the block device for the given partition name and slot number.
+ // The |slot| number must be between 0 and GetNumSlots() - 1 and the
+ // |partition_name| is a platform-specific name that identifies a partition on
+ // every slot. On success, returns true and stores the block device in
+ // |device|.
+ virtual bool GetPartitionDevice(const std::string& partition_name,
+ Slot slot,
+ std::string* device) 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;
+
+ // Mark the specified slot unbootable. No other slot flags are modified.
+ // Returns true on success.
+ virtual bool MarkSlotUnbootable(Slot slot) = 0;
+
+ // Set the passed |slot| as the preferred boot slot. Returns whether it
+ // succeeded setting the active slot. If succeeded, on next boot the
+ // bootloader will attempt to load the |slot| marked as active. Note that this
+ // method doesn't change the value of GetCurrentSlot() on the current boot.
+ virtual bool SetActiveBootSlot(Slot slot) = 0;
+
+ // Mark the current slot as successfully booted asynchronously. No other slot
+ // flags are modified. Returns false if it was not able to schedule the
+ // operation, otherwise, returns true and calls the |callback| with the result
+ // of the operation.
+ virtual bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) = 0;
+
+ // Return a human-readable slot name used for logging.
+ static std::string SlotName(Slot slot) {
+ if (slot == kInvalidSlot)
+ return "INVALID";
+ if (slot < 26)
+ return std::string(1, 'A' + slot);
+ return "TOO_BIG";
+ }
+
+ protected:
+ BootControlInterface() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BootControlInterface);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_BOOT_CONTROL_INTERFACE_H_
diff --git a/common/boot_control_stub.cc b/common/boot_control_stub.cc
new file mode 100644
index 0000000..2de0c82
--- /dev/null
+++ b/common/boot_control_stub.cc
@@ -0,0 +1,62 @@
+//
+// 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/common/boot_control_stub.h"
+
+#include <base/logging.h>
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+unsigned int BootControlStub::GetNumSlots() const {
+ return 0;
+}
+
+BootControlInterface::Slot BootControlStub::GetCurrentSlot() const {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return 0;
+}
+
+bool BootControlStub::GetPartitionDevice(const string& partition_name,
+ Slot slot,
+ string* device) const {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return false;
+}
+
+bool BootControlStub::IsSlotBootable(Slot slot) const {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return false;
+}
+
+bool BootControlStub::MarkSlotUnbootable(Slot slot) {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return false;
+}
+
+bool BootControlStub::SetActiveBootSlot(Slot slot) {
+ LOG(ERROR) << __FUNCTION__ << " should never be called.";
+ return false;
+}
+
+bool BootControlStub::MarkBootSuccessfulAsync(
+ base::Callback<void(bool)> callback) {
+ // This is expected to be called on update_engine startup.
+ return false;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/boot_control_stub.h b/common/boot_control_stub.h
new file mode 100644
index 0000000..7832adc
--- /dev/null
+++ b/common/boot_control_stub.h
@@ -0,0 +1,55 @@
+//
+// 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_COMMON_BOOT_CONTROL_STUB_H_
+#define UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_
+
+#include <string>
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// An implementation of the BootControlInterface that does nothing,
+// typically used when e.g. an underlying HAL implementation cannot be
+// loaded or doesn't exist.
+//
+// You are gauranteed that the implementation of GetNumSlots() method
+// always returns 0. This can be used to identify that this
+// implementation is in use.
+class BootControlStub : public BootControlInterface {
+ public:
+ BootControlStub() = default;
+ ~BootControlStub() = default;
+
+ // BootControlInterface overrides.
+ unsigned int GetNumSlots() const override;
+ BootControlInterface::Slot GetCurrentSlot() const override;
+ bool GetPartitionDevice(const std::string& partition_name,
+ BootControlInterface::Slot slot,
+ std::string* device) const override;
+ bool IsSlotBootable(BootControlInterface::Slot slot) const override;
+ bool MarkSlotUnbootable(BootControlInterface::Slot slot) override;
+ bool SetActiveBootSlot(BootControlInterface::Slot slot) override;
+ bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BootControlStub);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_BOOT_CONTROL_STUB_H_
diff --git a/common/certificate_checker.cc b/common/certificate_checker.cc
new file mode 100644
index 0000000..86df950
--- /dev/null
+++ b/common/certificate_checker.cc
@@ -0,0 +1,204 @@
+//
+// 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/common/certificate_checker.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <curl/curl.h>
+#include <openssl/evp.h>
+#include <openssl/ssl.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+bool OpenSSLWrapper::GetCertificateDigest(X509_STORE_CTX* x509_ctx,
+ int* out_depth,
+ unsigned int* out_digest_length,
+ uint8_t* out_digest) const {
+ TEST_AND_RETURN_FALSE(out_digest);
+ X509* certificate = X509_STORE_CTX_get_current_cert(x509_ctx);
+ TEST_AND_RETURN_FALSE(certificate);
+ int depth = X509_STORE_CTX_get_error_depth(x509_ctx);
+ if (out_depth)
+ *out_depth = depth;
+
+ unsigned int len;
+ const EVP_MD* digest_function = EVP_sha256();
+ bool success = X509_digest(certificate, digest_function, out_digest, &len);
+
+ if (success && out_digest_length)
+ *out_digest_length = len;
+ return success;
+}
+
+// static
+CertificateChecker* CertificateChecker::cert_checker_singleton_ = nullptr;
+
+CertificateChecker::CertificateChecker(PrefsInterface* prefs,
+ OpenSSLWrapper* openssl_wrapper)
+ : prefs_(prefs), openssl_wrapper_(openssl_wrapper) {
+}
+
+CertificateChecker::~CertificateChecker() {
+ if (cert_checker_singleton_ == this)
+ cert_checker_singleton_ = nullptr;
+}
+
+void CertificateChecker::Init() {
+ CHECK(cert_checker_singleton_ == nullptr);
+ cert_checker_singleton_ = this;
+}
+
+// static
+CURLcode CertificateChecker::ProcessSSLContext(CURL* curl_handle,
+ SSL_CTX* ssl_ctx,
+ void* ptr) {
+ ServerToCheck* server_to_check = reinterpret_cast<ServerToCheck*>(ptr);
+
+ if (!cert_checker_singleton_) {
+ DLOG(WARNING) << "No CertificateChecker singleton initialized.";
+ return CURLE_FAILED_INIT;
+ }
+
+ // From here we set the SSL_CTX to another callback, from the openssl library,
+ // which will be called after each server certificate is validated. However,
+ // since openssl does not allow us to pass our own data pointer to the
+ // callback, the certificate check will have to be done statically. Since we
+ // need to know which update server we are using in order to check the
+ // certificate, we hardcode Chrome OS's two known update servers here, and
+ // define a different static callback for each. Since this code should only
+ // run in official builds, this should not be a problem. However, if an update
+ // server different from the ones listed here is used, the check will not
+ // take place.
+ int (*verify_callback)(int, X509_STORE_CTX*);
+ switch (*server_to_check) {
+ case ServerToCheck::kDownload:
+ verify_callback = &CertificateChecker::VerifySSLCallbackDownload;
+ break;
+ case ServerToCheck::kUpdate:
+ verify_callback = &CertificateChecker::VerifySSLCallbackUpdate;
+ break;
+ case ServerToCheck::kNone:
+ verify_callback = nullptr;
+ break;
+ }
+
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, verify_callback);
+ return CURLE_OK;
+}
+
+// static
+int CertificateChecker::VerifySSLCallbackDownload(int preverify_ok,
+ X509_STORE_CTX* x509_ctx) {
+ return VerifySSLCallback(preverify_ok, x509_ctx, ServerToCheck::kDownload);
+}
+
+// static
+int CertificateChecker::VerifySSLCallbackUpdate(int preverify_ok,
+ X509_STORE_CTX* x509_ctx) {
+ return VerifySSLCallback(preverify_ok, x509_ctx, ServerToCheck::kUpdate);
+}
+
+// static
+int CertificateChecker::VerifySSLCallback(int preverify_ok,
+ X509_STORE_CTX* x509_ctx,
+ ServerToCheck server_to_check) {
+ CHECK(cert_checker_singleton_ != nullptr);
+ return cert_checker_singleton_->CheckCertificateChange(
+ preverify_ok, x509_ctx, server_to_check) ? 1 : 0;
+}
+
+bool CertificateChecker::CheckCertificateChange(int preverify_ok,
+ X509_STORE_CTX* x509_ctx,
+ ServerToCheck server_to_check) {
+ TEST_AND_RETURN_FALSE(prefs_ != nullptr);
+
+ // If pre-verification failed, we are not interested in the current
+ // certificate. We store a report to UMA and just propagate the fail result.
+ if (!preverify_ok) {
+ NotifyCertificateChecked(server_to_check, CertificateCheckResult::kFailed);
+ return false;
+ }
+
+ int depth;
+ unsigned int digest_length;
+ uint8_t digest[EVP_MAX_MD_SIZE];
+
+ if (!openssl_wrapper_->GetCertificateDigest(x509_ctx,
+ &depth,
+ &digest_length,
+ digest)) {
+ LOG(WARNING) << "Failed to generate digest of X509 certificate "
+ << "from update server.";
+ NotifyCertificateChecked(server_to_check, CertificateCheckResult::kValid);
+ return true;
+ }
+
+ // We convert the raw bytes of the digest to an hex string, for storage in
+ // prefs.
+ string digest_string = base::HexEncode(digest, digest_length);
+
+ string storage_key =
+ base::StringPrintf("%s-%d-%d", kPrefsUpdateServerCertificate,
+ static_cast<int>(server_to_check), depth);
+ string stored_digest;
+ // If there's no stored certificate, we just store the current one and return.
+ if (!prefs_->GetString(storage_key, &stored_digest)) {
+ if (!prefs_->SetString(storage_key, digest_string)) {
+ LOG(WARNING) << "Failed to store server certificate on storage key "
+ << storage_key;
+ }
+ NotifyCertificateChecked(server_to_check, CertificateCheckResult::kValid);
+ return true;
+ }
+
+ // Certificate changed, we store a report to UMA and store the most recent
+ // certificate.
+ if (stored_digest != digest_string) {
+ if (!prefs_->SetString(storage_key, digest_string)) {
+ LOG(WARNING) << "Failed to store server certificate on storage key "
+ << storage_key;
+ }
+ LOG(INFO) << "Certificate changed from " << stored_digest << " to "
+ << digest_string << ".";
+ NotifyCertificateChecked(server_to_check,
+ CertificateCheckResult::kValidChanged);
+ return true;
+ }
+
+ NotifyCertificateChecked(server_to_check, CertificateCheckResult::kValid);
+ // Since we don't perform actual SSL verification, we return success.
+ return true;
+}
+
+void CertificateChecker::NotifyCertificateChecked(
+ ServerToCheck server_to_check,
+ CertificateCheckResult result) {
+ if (observer_)
+ observer_->CertificateChecked(server_to_check, result);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/certificate_checker.h b/common/certificate_checker.h
new file mode 100644
index 0000000..c785192
--- /dev/null
+++ b/common/certificate_checker.h
@@ -0,0 +1,175 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_CERTIFICATE_CHECKER_H_
+#define UPDATE_ENGINE_COMMON_CERTIFICATE_CHECKER_H_
+
+#include <curl/curl.h>
+#include <openssl/ssl.h>
+
+#include <string>
+
+#include <base/macros.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+namespace chromeos_update_engine {
+
+class PrefsInterface;
+
+// Wrapper for openssl operations with the certificates.
+class OpenSSLWrapper {
+ public:
+ OpenSSLWrapper() = default;
+ virtual ~OpenSSLWrapper() = default;
+
+ // Takes an openssl X509_STORE_CTX, extracts the corresponding certificate
+ // from it and calculates its fingerprint (SHA256 digest). Returns true on
+ // success and false otherwise.
+ //
+ // |x509_ctx| is the pointer to the openssl object that holds the certificate.
+ // |out_depth| is the depth of the current certificate, in the certificate
+ // chain.
+ // |out_digest_length| is the length of the generated digest.
+ // |out_digest| is the byte array where the digest itself will be written.
+ // It should be big enough to hold a SHA1 digest (e.g. EVP_MAX_MD_SIZE).
+ virtual bool GetCertificateDigest(X509_STORE_CTX* x509_ctx,
+ int* out_depth,
+ unsigned int* out_digest_length,
+ uint8_t* out_digest) const;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(OpenSSLWrapper);
+};
+
+// The values in this enum are replicated in the metrics server. See metrics.h
+// for instructions on how to update these values in the server side.
+enum class CertificateCheckResult {
+ // The certificate is valid and the same as seen before or the first time we
+ // see a certificate.
+ kValid,
+ // The certificate is valid, but is different than a previously seen
+ // certificate for the selected server.
+ kValidChanged,
+ // The certificate validation failed.
+ kFailed,
+
+ // This value must be the last entry.
+ kNumConstants
+};
+
+// These values are used to generate the keys of files persisted via prefs.
+// This means that changing these will cause loss of information on metrics
+// reporting, during the transition. These values are also mapped to a metric
+// name in metrics.cc, so adding values here requires to assign a new metric
+// name in that file.
+enum class ServerToCheck {
+ kUpdate = 0,
+ kDownload,
+
+ // Ignore this server.
+ kNone,
+};
+
+// Responsible for checking whether update server certificates change, and
+// reporting to UMA when this happens. Since all state information is persisted,
+// and openssl forces us to use a static callback with no data pointer, this
+// class is entirely static.
+class CertificateChecker {
+ public:
+ class Observer {
+ public:
+ virtual ~Observer() = default;
+
+ // Called whenever a certificate is checked for the server |server_to_check|
+ // passing the result of said certificate check.
+ virtual void CertificateChecked(ServerToCheck server_to_check,
+ CertificateCheckResult result) = 0;
+ };
+
+ CertificateChecker(PrefsInterface* prefs, OpenSSLWrapper* openssl_wrapper);
+ ~CertificateChecker();
+
+ // This callback is called by libcurl just before the initialization of an
+ // SSL connection after having processed all other SSL related options. Used
+ // to check if server certificates change. |cert_checker| is expected to be a
+ // pointer to the CertificateChecker instance.
+ static CURLcode ProcessSSLContext(CURL* curl_handle,
+ SSL_CTX* ssl_ctx,
+ void* cert_checker);
+
+ // Initialize this class as the singleton instance. Only one instance can be
+ // initialized at a time and it should be initialized before other methods
+ // can be used.
+ void Init();
+
+ // Set the certificate observer to the passed instance. To remove the
+ // observer, pass a nullptr. The |observer| instance must be valid while this
+ // CertificateChecker verifies certificates.
+ void SetObserver(Observer* observer) { observer_ = observer; }
+
+ private:
+ FRIEND_TEST(CertificateCheckerTest, NewCertificate);
+ FRIEND_TEST(CertificateCheckerTest, SameCertificate);
+ FRIEND_TEST(CertificateCheckerTest, ChangedCertificate);
+ FRIEND_TEST(CertificateCheckerTest, FailedCertificate);
+
+ // These callbacks are asynchronously called by openssl after initial SSL
+ // verification. They are used to perform any additional security verification
+ // on the connection, but we use them here to get hold of the server
+ // certificate, in order to determine if it has changed since the last
+ // connection. Since openssl forces us to do this statically, we define two
+ // different callbacks for the two different official update servers, and only
+ // assign the correspondent one. The assigned callback is then called once per
+ // each certificate on the server and returns 1 for success and 0 for failure.
+ static int VerifySSLCallbackDownload(int preverify_ok,
+ X509_STORE_CTX* x509_ctx);
+ static int VerifySSLCallbackUpdate(int preverify_ok,
+ X509_STORE_CTX* x509_ctx);
+ static int VerifySSLCallback(int preverify_ok,
+ X509_STORE_CTX* x509_ctx,
+ ServerToCheck server_to_check);
+
+ // Checks if server certificate stored in |x509_ctx| has changed since last
+ // connection to that same server, specified by |server_to_check|.
+ // This is called by the callbacks defined above. The result of the
+ // certificate check is passed to the observer, if any. Returns true on
+ // success and false otherwise.
+ bool CheckCertificateChange(int preverify_ok,
+ X509_STORE_CTX* x509_ctx,
+ ServerToCheck server_to_check);
+
+ // Notifies the observer, if any, of a certificate checking.
+ void NotifyCertificateChecked(ServerToCheck server_to_check,
+ CertificateCheckResult result);
+
+ // The CertificateChecker singleton instance.
+ static CertificateChecker* cert_checker_singleton_;
+
+ // Prefs instance used to store the certificates seen in the past.
+ PrefsInterface* prefs_;
+
+ // The wrapper for openssl operations.
+ OpenSSLWrapper* openssl_wrapper_;
+
+ // The observer called whenever a certificate is checked, if not null.
+ Observer* observer_{nullptr};
+
+ DISALLOW_COPY_AND_ASSIGN(CertificateChecker);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_CERTIFICATE_CHECKER_H_
diff --git a/common/certificate_checker_unittest.cc b/common/certificate_checker_unittest.cc
new file mode 100644
index 0000000..c30acc5
--- /dev/null
+++ b/common/certificate_checker_unittest.cc
@@ -0,0 +1,140 @@
+//
+// 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/common/certificate_checker.h"
+
+#include <string>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/mock_certificate_checker.h"
+#include "update_engine/common/mock_prefs.h"
+
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SetArgumentPointee;
+using ::testing::SetArrayArgument;
+using ::testing::_;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class MockCertificateCheckObserver : public CertificateChecker::Observer {
+ public:
+ MOCK_METHOD2(CertificateChecked,
+ void(ServerToCheck server_to_check,
+ CertificateCheckResult result));
+};
+
+class CertificateCheckerTest : public testing::Test {
+ protected:
+ void SetUp() override {
+ cert_key_ = base::StringPrintf("%s-%d-%d",
+ cert_key_prefix_.c_str(),
+ static_cast<int>(server_to_check_),
+ depth_);
+ cert_checker.Init();
+ cert_checker.SetObserver(&observer_);
+ }
+
+ void TearDown() override {
+ cert_checker.SetObserver(nullptr);
+ }
+
+ MockPrefs prefs_;
+ MockOpenSSLWrapper openssl_wrapper_;
+ // Parameters of our mock certificate digest.
+ int depth_{0};
+ unsigned int length_{4};
+ uint8_t digest_[4]{0x17, 0x7D, 0x07, 0x5F};
+ string digest_hex_{"177D075F"};
+ string diff_digest_hex_{"1234ABCD"};
+ string cert_key_prefix_{kPrefsUpdateServerCertificate};
+ ServerToCheck server_to_check_{ServerToCheck::kUpdate};
+ string cert_key_;
+
+ testing::StrictMock<MockCertificateCheckObserver> observer_;
+ CertificateChecker cert_checker{&prefs_, &openssl_wrapper_};
+};
+
+// check certificate change, new
+TEST_F(CertificateCheckerTest, NewCertificate) {
+ EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(depth_),
+ SetArgumentPointee<2>(length_),
+ SetArrayArgument<3>(digest_, digest_ + 4),
+ Return(true)));
+ EXPECT_CALL(prefs_, GetString(cert_key_, _)).WillOnce(Return(false));
+ EXPECT_CALL(prefs_, SetString(cert_key_, digest_hex_)).WillOnce(Return(true));
+ EXPECT_CALL(observer_,
+ CertificateChecked(server_to_check_,
+ CertificateCheckResult::kValid));
+ ASSERT_TRUE(
+ cert_checker.CheckCertificateChange(1, nullptr, server_to_check_));
+}
+
+// check certificate change, unchanged
+TEST_F(CertificateCheckerTest, SameCertificate) {
+ EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(depth_),
+ SetArgumentPointee<2>(length_),
+ SetArrayArgument<3>(digest_, digest_ + 4),
+ Return(true)));
+ EXPECT_CALL(prefs_, GetString(cert_key_, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(digest_hex_), Return(true)));
+ EXPECT_CALL(prefs_, SetString(_, _)).Times(0);
+ EXPECT_CALL(observer_,
+ CertificateChecked(server_to_check_,
+ CertificateCheckResult::kValid));
+ ASSERT_TRUE(
+ cert_checker.CheckCertificateChange(1, nullptr, server_to_check_));
+}
+
+// check certificate change, changed
+TEST_F(CertificateCheckerTest, ChangedCertificate) {
+ EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(nullptr, _, _, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<1>(depth_),
+ SetArgumentPointee<2>(length_),
+ SetArrayArgument<3>(digest_, digest_ + 4),
+ Return(true)));
+ EXPECT_CALL(prefs_, GetString(cert_key_, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(diff_digest_hex_), Return(true)));
+ EXPECT_CALL(observer_,
+ CertificateChecked(server_to_check_,
+ CertificateCheckResult::kValidChanged));
+ EXPECT_CALL(prefs_, SetString(cert_key_, digest_hex_)).WillOnce(Return(true));
+ ASSERT_TRUE(
+ cert_checker.CheckCertificateChange(1, nullptr, server_to_check_));
+}
+
+// check certificate change, failed
+TEST_F(CertificateCheckerTest, FailedCertificate) {
+ EXPECT_CALL(observer_, CertificateChecked(server_to_check_,
+ CertificateCheckResult::kFailed));
+ EXPECT_CALL(prefs_, GetString(_, _)).Times(0);
+ EXPECT_CALL(openssl_wrapper_, GetCertificateDigest(_, _, _, _)).Times(0);
+ ASSERT_FALSE(
+ cert_checker.CheckCertificateChange(0, nullptr, server_to_check_));
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/clock.cc b/common/clock.cc
new file mode 100644
index 0000000..f0eff44
--- /dev/null
+++ b/common/clock.cc
@@ -0,0 +1,58 @@
+//
+// 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.
+//
+
+#include "update_engine/common/clock.h"
+
+#include <time.h>
+
+namespace chromeos_update_engine {
+
+base::Time Clock::GetWallclockTime() {
+ return base::Time::Now();
+}
+
+base::Time Clock::GetMonotonicTime() {
+ 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
+ // often and we don't want to fill up the disk. Note that this
+ // only fails if running on ancient kernels (CLOCK_MONOTONIC_RAW
+ // was added in Linux 2.6.28) so it never fails on a ChromeOS
+ // device.
+ return base::Time();
+ }
+ struct timeval now_tv;
+ now_tv.tv_sec = now_ts.tv_sec;
+ now_tv.tv_usec = now_ts.tv_nsec/base::Time::kNanosecondsPerMicrosecond;
+ return base::Time::FromTimeVal(now_tv);
+}
+
+base::Time Clock::GetBootTime() {
+ 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
+ // often and we don't want to fill up the disk. Note that this
+ // only fails if running on ancient kernels (CLOCK_BOOTTIME was
+ // added in Linux 2.6.39) so it never fails on a ChromeOS device.
+ return base::Time();
+ }
+ struct timeval now_tv;
+ now_tv.tv_sec = now_ts.tv_sec;
+ now_tv.tv_usec = now_ts.tv_nsec/base::Time::kNanosecondsPerMicrosecond;
+ return base::Time::FromTimeVal(now_tv);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/clock.h b/common/clock.h
new file mode 100644
index 0000000..2f373a7
--- /dev/null
+++ b/common/clock.h
@@ -0,0 +1,41 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_CLOCK_H_
+#define UPDATE_ENGINE_COMMON_CLOCK_H_
+
+#include "update_engine/common/clock_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a clock.
+class Clock : public ClockInterface {
+ public:
+ Clock() {}
+
+ base::Time GetWallclockTime() override;
+
+ base::Time GetMonotonicTime() override;
+
+ base::Time GetBootTime() override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Clock);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_CLOCK_H_
diff --git a/common/clock_interface.h b/common/clock_interface.h
new file mode 100644
index 0000000..2228983
--- /dev/null
+++ b/common/clock_interface.h
@@ -0,0 +1,54 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_CLOCK_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_CLOCK_INTERFACE_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+namespace chromeos_update_engine {
+
+// The clock interface allows access to various system clocks. The
+// sole reason for providing this as an interface is unit testing.
+// Additionally, the sole reason for the methods not being static
+// is also unit testing.
+class ClockInterface {
+ public:
+ virtual ~ClockInterface() = default;
+
+ // Gets the current time e.g. similar to base::Time::Now().
+ virtual base::Time GetWallclockTime() = 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;
+
+ // 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;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_CLOCK_INTERFACE_H_
diff --git a/common/constants.cc b/common/constants.cc
new file mode 100644
index 0000000..fe4e643
--- /dev/null
+++ b/common/constants.cc
@@ -0,0 +1,90 @@
+//
+// 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.
+//
+
+#include "update_engine/common/constants.h"
+
+namespace chromeos_update_engine {
+
+const char kPowerwashMarkerFile[] =
+ "/mnt/stateful_partition/factory_install_reset";
+
+const char kPowerwashCommand[] = "safe fast keepimg reason=update_engine\n";
+
+const char kPowerwashSafePrefsSubDirectory[] = "update_engine/prefs";
+
+const char kPrefsSubDirectory[] = "prefs";
+
+const char kStatefulPartition[] = "/mnt/stateful_partition";
+
+// Constants defining keys for the persisted state of update engine.
+const char kPrefsAttemptInProgress[] = "attempt-in-progress";
+const char kPrefsBackoffExpiryTime[] = "backoff-expiry-time";
+const char kPrefsBootId[] = "boot-id";
+const char kPrefsCurrentBytesDownloaded[] = "current-bytes-downloaded";
+const char kPrefsCurrentResponseSignature[] = "current-response-signature";
+const char kPrefsCurrentUrlFailureCount[] = "current-url-failure-count";
+const char kPrefsCurrentUrlIndex[] = "current-url-index";
+const char kPrefsDailyMetricsLastReportedAt[] =
+ "daily-metrics-last-reported-at";
+const char kPrefsDeltaUpdateFailures[] = "delta-update-failures";
+const char kPrefsFullPayloadAttemptNumber[] = "full-payload-attempt-number";
+const char kPrefsInstallDateDays[] = "install-date-days";
+const char kPrefsLastActivePingDay[] = "last-active-ping-day";
+const char kPrefsLastRollCallPingDay[] = "last-roll-call-ping-day";
+const char kPrefsManifestMetadataSize[] = "manifest-metadata-size";
+const char kPrefsMetricsAttemptLastReportingTime[] =
+ "metrics-attempt-last-reporting-time";
+const char kPrefsMetricsCheckLastReportingTime[] =
+ "metrics-check-last-reporting-time";
+const char kPrefsNumReboots[] = "num-reboots";
+const char kPrefsNumResponsesSeen[] = "num-responses-seen";
+const char kPrefsOmahaCohort[] = "omaha-cohort";
+const char kPrefsOmahaCohortHint[] = "omaha-cohort-hint";
+const char kPrefsOmahaCohortName[] = "omaha-cohort-name";
+const char kPrefsP2PEnabled[] = "p2p-enabled";
+const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
+const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
+const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
+const char kPrefsPreviousVersion[] = "previous-version";
+const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
+const char kPrefsRollbackVersion[] = "rollback-version";
+const char kPrefsChannelOnSlotPrefix[] = "channel-on-slot-";
+const char kPrefsSystemUpdatedMarker[] = "system-updated-marker";
+const char kPrefsTargetVersionAttempt[] = "target-version-attempt";
+const char kPrefsTargetVersionInstalledFrom[] = "target-version-installed-from";
+const char kPrefsTargetVersionUniqueId[] = "target-version-unique-id";
+const char kPrefsTotalBytesDownloaded[] = "total-bytes-downloaded";
+const char kPrefsUpdateCheckCount[] = "update-check-count";
+const char kPrefsUpdateCheckResponseHash[] = "update-check-response-hash";
+const char kPrefsUpdateCompletedBootTime[] = "update-completed-boot-time";
+const char kPrefsUpdateCompletedOnBootId[] = "update-completed-on-boot-id";
+const char kPrefsUpdateDurationUptime[] = "update-duration-uptime";
+const char kPrefsUpdateFirstSeenAt[] = "update-first-seen-at";
+const char kPrefsUpdateOverCellularPermission[] =
+ "update-over-cellular-permission";
+const char kPrefsUpdateServerCertificate[] = "update-server-cert";
+const char kPrefsUpdateStateNextDataLength[] = "update-state-next-data-length";
+const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
+const char kPrefsUpdateStateNextOperation[] = "update-state-next-operation";
+const char kPrefsUpdateStateSHA256Context[] = "update-state-sha-256-context";
+const char kPrefsUpdateStateSignatureBlob[] = "update-state-signature-blob";
+const char kPrefsUpdateStateSignedSHA256Context[] =
+ "update-state-signed-sha-256-context";
+const char kPrefsUpdateTimestampStart[] = "update-timestamp-start";
+const char kPrefsUrlSwitchCount[] = "url-switch-count";
+const char kPrefsWallClockWaitPeriod[] = "wall-clock-wait-period";
+
+} // namespace chromeos_update_engine
diff --git a/common/constants.h b/common/constants.h
new file mode 100644
index 0000000..f20de8e
--- /dev/null
+++ b/common/constants.h
@@ -0,0 +1,180 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_CONSTANTS_H_
+#define UPDATE_ENGINE_COMMON_CONSTANTS_H_
+
+namespace chromeos_update_engine {
+
+// The name of the marker file used to trigger powerwash when post-install
+// completes successfully so that the device is powerwashed on next reboot.
+extern const char kPowerwashMarkerFile[];
+
+// The contents of the powerwash marker file.
+extern const char kPowerwashCommand[];
+
+// Directory for AU prefs that are preserved across powerwash.
+extern const char kPowerwashSafePrefsSubDirectory[];
+
+// The location where we store the AU preferences (state etc).
+extern const char kPrefsSubDirectory[];
+
+// Path to the stateful partition on the root filesystem.
+extern const char kStatefulPartition[];
+
+// Constants related to preferences.
+extern const char kPrefsAttemptInProgress[];
+extern const char kPrefsBackoffExpiryTime[];
+extern const char kPrefsBootId[];
+extern const char kPrefsCurrentBytesDownloaded[];
+extern const char kPrefsCurrentResponseSignature[];
+extern const char kPrefsCurrentUrlFailureCount[];
+extern const char kPrefsCurrentUrlIndex[];
+extern const char kPrefsDailyMetricsLastReportedAt[];
+extern const char kPrefsDeltaUpdateFailures[];
+extern const char kPrefsFullPayloadAttemptNumber[];
+extern const char kPrefsInstallDateDays[];
+extern const char kPrefsLastActivePingDay[];
+extern const char kPrefsLastRollCallPingDay[];
+extern const char kPrefsManifestMetadataSize[];
+extern const char kPrefsMetricsAttemptLastReportingTime[];
+extern const char kPrefsMetricsCheckLastReportingTime[];
+extern const char kPrefsNumReboots[];
+extern const char kPrefsNumResponsesSeen[];
+extern const char kPrefsOmahaCohort[];
+extern const char kPrefsOmahaCohortHint[];
+extern const char kPrefsOmahaCohortName[];
+extern const char kPrefsP2PEnabled[];
+extern const char kPrefsP2PFirstAttemptTimestamp[];
+extern const char kPrefsP2PNumAttempts[];
+extern const char kPrefsPayloadAttemptNumber[];
+extern const char kPrefsPreviousVersion[];
+extern const char kPrefsResumedUpdateFailures[];
+extern const char kPrefsRollbackVersion[];
+extern const char kPrefsChannelOnSlotPrefix[];
+extern const char kPrefsSystemUpdatedMarker[];
+extern const char kPrefsTargetVersionAttempt[];
+extern const char kPrefsTargetVersionInstalledFrom[];
+extern const char kPrefsTargetVersionUniqueId[];
+extern const char kPrefsTotalBytesDownloaded[];
+extern const char kPrefsUpdateCheckCount[];
+extern const char kPrefsUpdateCheckResponseHash[];
+extern const char kPrefsUpdateCompletedBootTime[];
+extern const char kPrefsUpdateCompletedOnBootId[];
+extern const char kPrefsUpdateDurationUptime[];
+extern const char kPrefsUpdateFirstSeenAt[];
+extern const char kPrefsUpdateOverCellularPermission[];
+extern const char kPrefsUpdateServerCertificate[];
+extern const char kPrefsUpdateStateNextDataLength[];
+extern const char kPrefsUpdateStateNextDataOffset[];
+extern const char kPrefsUpdateStateNextOperation[];
+extern const char kPrefsUpdateStateSHA256Context[];
+extern const char kPrefsUpdateStateSignatureBlob[];
+extern const char kPrefsUpdateStateSignedSHA256Context[];
+extern const char kPrefsUpdateTimestampStart[];
+extern const char kPrefsUrlSwitchCount[];
+extern const char kPrefsWallClockWaitPeriod[];
+
+// 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.
+typedef enum {
+ kDownloadSourceHttpsServer, // UMA Binary representation: 0001
+ kDownloadSourceHttpServer, // UMA Binary representation: 0010
+ kDownloadSourceHttpPeer, // UMA Binary representation: 0100
+
+ // Note: Add new sources only above this line.
+ kNumDownloadSources
+} DownloadSource;
+
+// A payload can be a Full or Delta payload. In some cases, a Full payload is
+// used even when a Delta payload was available for the update, called here
+// ForcedFull. The PayloadType enum is only used to send UMA metrics about the
+// successfully applied payload.
+typedef enum {
+ kPayloadTypeFull,
+ kPayloadTypeDelta,
+ kPayloadTypeForcedFull,
+
+ // Note: Add new payload types only above this line.
+ kNumPayloadTypes
+} PayloadType;
+
+// Maximum number of times we'll allow using p2p for the same update payload.
+const 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;
+
+// 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;
+
+// The maximum number of payload files to keep in /var/cache/p2p.
+const int kMaxP2PFilesToKeep = 3;
+
+// The maximum number of days to keep a p2p file;
+const int kMaxP2PFileAgeDays = 5;
+
+// The default number of UMA buckets for metrics.
+const int kNumDefaultUmaBuckets = 50;
+
+// General constants
+const int kNumBytesInOneMiB = 1024 * 1024;
+
+// Number of redirects allowed when downloading.
+const int kDownloadMaxRedirects = 10;
+
+// The minimum average speed that downloads must sustain...
+//
+// This is set low because some devices may have very poor
+// 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;
+
+// ... measured over this period.
+//
+// For non-official builds (e.g. typically built on a developer's
+// workstation and served via devserver) bump this since it takes time
+// for the workstation to generate the payload. For p2p, make this
+// relatively low since we want to fail fast.
+const int kDownloadLowSpeedTimeSeconds = 90;
+const int kDownloadDevModeLowSpeedTimeSeconds = 180;
+const 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 kDownloadP2PMaxRetryCount = 5;
+
+// The connect timeout, in seconds.
+//
+// This is set high because some devices may have very poor
+// 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;
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_CONSTANTS_H_
diff --git a/common/error_code.h b/common/error_code.h
new file mode 100644
index 0000000..2bbdcfa
--- /dev/null
+++ b/common/error_code.h
@@ -0,0 +1,135 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_ERROR_CODE_H_
+#define UPDATE_ENGINE_COMMON_ERROR_CODE_H_
+
+#include <ostream> // NOLINT(readability/streams)
+
+namespace chromeos_update_engine {
+
+// Action exit codes.
+enum class ErrorCode : int {
+ kSuccess = 0,
+ kError = 1,
+ kOmahaRequestError = 2,
+ kOmahaResponseHandlerError = 3,
+ kFilesystemCopierError = 4,
+ kPostinstallRunnerError = 5,
+ kPayloadMismatchedType = 6,
+ kInstallDeviceOpenError = 7,
+ kKernelDeviceOpenError = 8,
+ kDownloadTransferError = 9,
+ kPayloadHashMismatchError = 10,
+ kPayloadSizeMismatchError = 11,
+ kDownloadPayloadVerificationError = 12,
+ kDownloadNewPartitionInfoError = 13,
+ kDownloadWriteError = 14,
+ kNewRootfsVerificationError = 15,
+ kNewKernelVerificationError = 16,
+ kSignedDeltaPayloadExpectedError = 17,
+ kDownloadPayloadPubKeyVerificationError = 18,
+ kPostinstallBootedFromFirmwareB = 19,
+ kDownloadStateInitializationError = 20,
+ kDownloadInvalidMetadataMagicString = 21,
+ kDownloadSignatureMissingInManifest = 22,
+ kDownloadManifestParseError = 23,
+ kDownloadMetadataSignatureError = 24,
+ kDownloadMetadataSignatureVerificationError = 25,
+ kDownloadMetadataSignatureMismatch = 26,
+ kDownloadOperationHashVerificationError = 27,
+ kDownloadOperationExecutionError = 28,
+ kDownloadOperationHashMismatch = 29,
+ kOmahaRequestEmptyResponseError = 30,
+ kOmahaRequestXMLParseError = 31,
+ kDownloadInvalidMetadataSize = 32,
+ kDownloadInvalidMetadataSignature = 33,
+ kOmahaResponseInvalid = 34,
+ kOmahaUpdateIgnoredPerPolicy = 35,
+ kOmahaUpdateDeferredPerPolicy = 36,
+ kOmahaErrorInHTTPResponse = 37,
+ kDownloadOperationHashMissingError = 38,
+ kDownloadMetadataSignatureMissingError = 39,
+ kOmahaUpdateDeferredForBackoff = 40,
+ kPostinstallPowerwashError = 41,
+ kUpdateCanceledByChannelChange = 42,
+ kPostinstallFirmwareRONotUpdatable = 43,
+ kUnsupportedMajorPayloadVersion = 44,
+ kUnsupportedMinorPayloadVersion = 45,
+ kOmahaRequestXMLHasEntityDecl = 46,
+ kFilesystemVerifierError = 47,
+
+ // VERY IMPORTANT! When adding new error codes:
+ //
+ // 1) Update tools/metrics/histograms/histograms.xml in Chrome.
+ //
+ // 2) Update the assorted switch statements in update_engine which won't
+ // build until this case is added.
+
+ // Any code above this is sent to both Omaha and UMA as-is, except
+ // kOmahaErrorInHTTPResponse (see error code 2000 for more details).
+ // Codes/flags below this line is sent only to Omaha and not to UMA.
+
+ // kUmaReportedMax is not an error code per se, it's just the count
+ // of the number of enums above. Add any new errors above this line if you
+ // want them to show up on UMA. Stuff below this line will not be sent to UMA
+ // but is used for other errors that are sent to Omaha. We don't assign any
+ // particular value for this enum so that it's just one more than the last
+ // one above and thus always represents the correct count of UMA metrics
+ // buckets, even when new enums are added above this line in future. See
+ // metrics::ReportUpdateAttemptMetrics() on how this enum is used.
+ kUmaReportedMax,
+
+ // use the 2xxx range to encode HTTP errors. These errors are available in
+ // Dremel with the individual granularity. But for UMA purposes, all these
+ // errors are aggregated into one: kOmahaErrorInHTTPResponse.
+ kOmahaRequestHTTPResponseBase = 2000, // + HTTP response code
+
+ // TODO(jaysri): Move out all the bit masks into separate constants
+ // outside the enum as part of fixing bug 34369.
+ // Bit flags. Remember to update the mask below for new bits.
+
+ // Set if boot mode not normal.
+ // TODO(garnold) This is very debatable value to use, knowing that the
+ // underlying type is a signed int (often, 32-bit). However, at this point
+ // there are parts of the ecosystem that expect this to be a negative value,
+ // so we preserve this semantics. This should be reconsidered if/when we
+ // modify the implementation of ErrorCode into a properly encapsulated class.
+ kDevModeFlag = 1 << 31,
+
+ // Set if resuming an interruped update.
+ kResumedFlag = 1 << 30,
+
+ // Set if using a dev/test image as opposed to an MP-signed image.
+ kTestImageFlag = 1 << 29,
+
+ // Set if using devserver or Omaha sandbox (using crosh autest).
+ kTestOmahaUrlFlag = 1 << 28,
+
+ // Mask that indicates bit positions that are used to indicate special flags
+ // that are embedded in the error code to provide additional context about
+ // the system in which the error was encountered.
+ kSpecialFlags = (kDevModeFlag | kResumedFlag | kTestImageFlag |
+ kTestOmahaUrlFlag)
+};
+
+inline std::ostream& operator<<(std::ostream& os, ErrorCode val) {
+ return os << static_cast<int>(val);
+}
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_ERROR_CODE_H_
diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h
new file mode 100644
index 0000000..5c6c160
--- /dev/null
+++ b/common/fake_boot_control.h
@@ -0,0 +1,112 @@
+//
+// 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_COMMON_FAKE_BOOT_CONTROL_H_
+#define UPDATE_ENGINE_COMMON_FAKE_BOOT_CONTROL_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake bootloader control interface used for testing.
+class FakeBootControl : public BootControlInterface {
+ public:
+ FakeBootControl() {
+ SetNumSlots(num_slots_);
+ // The current slot should be bootable.
+ is_bootable_[current_slot_] = true;
+ }
+
+ // BootControlInterface overrides.
+ unsigned int GetNumSlots() const override { return num_slots_; }
+ BootControlInterface::Slot GetCurrentSlot() const override {
+ return current_slot_;
+ }
+
+ 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())
+ return false;
+ *device = part_it->second;
+ return true;
+ }
+
+ bool IsSlotBootable(BootControlInterface::Slot slot) const override {
+ return slot < num_slots_ && is_bootable_[slot];
+ }
+
+ bool MarkSlotUnbootable(BootControlInterface::Slot slot) override {
+ if (slot >= num_slots_)
+ return false;
+ is_bootable_[slot] = false;
+ return true;
+ }
+
+ bool SetActiveBootSlot(Slot slot) override { return true; }
+
+ bool MarkBootSuccessfulAsync(base::Callback<void(bool)> callback) override {
+ // We run the callback directly from here to avoid having to setup a message
+ // loop in the test environment.
+ callback.Run(true);
+ return true;
+ }
+
+ // Setters
+ void SetNumSlots(unsigned int num_slots) {
+ num_slots_ = num_slots;
+ is_bootable_.resize(num_slots_, false);
+ devices_.resize(num_slots_);
+ }
+
+ void SetCurrentSlot(BootControlInterface::Slot slot) {
+ current_slot_ = slot;
+ }
+
+ void SetPartitionDevice(const std::string partition_name,
+ BootControlInterface::Slot slot,
+ const std::string device) {
+ DCHECK(slot < num_slots_);
+ devices_[slot][partition_name] = device;
+ }
+
+ void SetSlotBootable(BootControlInterface::Slot slot, bool bootable) {
+ DCHECK(slot < num_slots_);
+ is_bootable_[slot] = bootable;
+ }
+
+ private:
+ BootControlInterface::Slot num_slots_{2};
+ BootControlInterface::Slot current_slot_{0};
+
+ std::vector<bool> is_bootable_;
+ std::vector<std::map<std::string, std::string>> devices_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeBootControl);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_FAKE_BOOT_CONTROL_H_
diff --git a/common/fake_clock.h b/common/fake_clock.h
new file mode 100644
index 0000000..3d3bad8
--- /dev/null
+++ b/common/fake_clock.h
@@ -0,0 +1,63 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_FAKE_CLOCK_H_
+#define UPDATE_ENGINE_COMMON_FAKE_CLOCK_H_
+
+#include "update_engine/common/clock_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a clock that can be made to tell any time you want.
+class FakeClock : public ClockInterface {
+ public:
+ FakeClock() {}
+
+ base::Time GetWallclockTime() override {
+ return wallclock_time_;
+ }
+
+ base::Time GetMonotonicTime() override {
+ return monotonic_time_;
+ }
+
+ base::Time GetBootTime() override {
+ return boot_time_;
+ }
+
+ void SetWallclockTime(const base::Time &time) {
+ wallclock_time_ = time;
+ }
+
+ void SetMonotonicTime(const base::Time &time) {
+ monotonic_time_ = time;
+ }
+
+ void SetBootTime(const base::Time &time) {
+ boot_time_ = time;
+ }
+
+ private:
+ base::Time wallclock_time_;
+ base::Time monotonic_time_;
+ base::Time boot_time_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeClock);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_FAKE_CLOCK_H_
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
new file mode 100644
index 0000000..23d6498
--- /dev/null
+++ b/common/fake_hardware.h
@@ -0,0 +1,124 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_FAKE_HARDWARE_H_
+#define UPDATE_ENGINE_COMMON_FAKE_HARDWARE_H_
+
+#include <map>
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake hardware interface used for testing.
+class FakeHardware : public HardwareInterface {
+ public:
+ // Value used to signal that the powerwash_count file is not present. When
+ // this value is used in SetPowerwashCount(), GetPowerwashCount() will return
+ // false.
+ static const int kPowerwashCountNotSet = -1;
+
+ FakeHardware()
+ : is_official_build_(true),
+ is_normal_boot_mode_(true),
+ is_oobe_complete_(false),
+ hardware_class_("Fake HWID BLAH-1234"),
+ firmware_version_("Fake Firmware v1.0.1"),
+ ec_version_("Fake EC v1.0a"),
+ powerwash_count_(kPowerwashCountNotSet) {}
+
+ // HardwareInterface methods.
+ bool IsOfficialBuild() const override { return is_official_build_; }
+
+ bool IsNormalBootMode() const override { return is_normal_boot_mode_; }
+
+ bool IsOOBEComplete(base::Time* out_time_of_oobe) const override {
+ if (out_time_of_oobe != nullptr)
+ *out_time_of_oobe = oobe_timestamp_;
+ return is_oobe_complete_;
+ }
+
+ std::string GetHardwareClass() const override { return hardware_class_; }
+
+ std::string GetFirmwareVersion() const override { return firmware_version_; }
+
+ std::string GetECVersion() const override { return ec_version_; }
+
+ int GetPowerwashCount() const override { return powerwash_count_; }
+
+ bool GetNonVolatileDirectory(base::FilePath* path) const override {
+ return false;
+ }
+
+ bool GetPowerwashSafeDirectory(base::FilePath* path) const override {
+ return false;
+ }
+
+ // Setters
+ void SetIsOfficialBuild(bool is_official_build) {
+ is_official_build_ = is_official_build;
+ }
+
+ void SetIsNormalBootMode(bool is_normal_boot_mode) {
+ is_normal_boot_mode_ = is_normal_boot_mode;
+ }
+
+ // Sets the IsOOBEComplete to True with the given timestamp.
+ void SetIsOOBEComplete(base::Time oobe_timestamp) {
+ is_oobe_complete_ = true;
+ oobe_timestamp_ = oobe_timestamp;
+ }
+
+ // Sets the IsOOBEComplete to False.
+ void UnsetIsOOBEComplete() {
+ is_oobe_complete_ = false;
+ }
+
+ void SetHardwareClass(std::string hardware_class) {
+ hardware_class_ = hardware_class;
+ }
+
+ void SetFirmwareVersion(std::string firmware_version) {
+ firmware_version_ = firmware_version;
+ }
+
+ void SetECVersion(std::string ec_version) {
+ ec_version_ = ec_version;
+ }
+
+ void SetPowerwashCount(int powerwash_count) {
+ powerwash_count_ = powerwash_count;
+ }
+
+ private:
+ bool is_official_build_;
+ bool is_normal_boot_mode_;
+ bool is_oobe_complete_;
+ base::Time oobe_timestamp_;
+ std::string hardware_class_;
+ std::string firmware_version_;
+ std::string ec_version_;
+ int powerwash_count_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeHardware);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_FAKE_HARDWARE_H_
diff --git a/common/fake_prefs.cc b/common/fake_prefs.cc
new file mode 100644
index 0000000..5a0a3af
--- /dev/null
+++ b/common/fake_prefs.cc
@@ -0,0 +1,168 @@
+//
+// 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/common/fake_prefs.h"
+
+#include <algorithm>
+
+#include <gtest/gtest.h>
+
+using std::string;
+
+using chromeos_update_engine::FakePrefs;
+
+namespace {
+
+void CheckNotNull(const string& key, void* ptr) {
+ EXPECT_NE(nullptr, ptr)
+ << "Called Get*() for key \"" << key << "\" with a null parameter.";
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+FakePrefs::~FakePrefs() {
+ EXPECT_TRUE(observers_.empty());
+}
+
+// Compile-time type-dependent constants definitions.
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<string>::type =
+ FakePrefs::PrefType::kString;
+template<>
+string FakePrefs::PrefValue::* const // NOLINT(runtime/string), not static str.
+ FakePrefs::PrefConsts<string>::member = &FakePrefs::PrefValue::as_str;
+
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<int64_t>::type =
+ FakePrefs::PrefType::kInt64;
+template<>
+int64_t FakePrefs::PrefValue::* const FakePrefs::PrefConsts<int64_t>::member =
+ &FakePrefs::PrefValue::as_int64;
+
+template<>
+FakePrefs::PrefType const FakePrefs::PrefConsts<bool>::type =
+ FakePrefs::PrefType::kBool;
+template<>
+bool FakePrefs::PrefValue::* const FakePrefs::PrefConsts<bool>::member =
+ &FakePrefs::PrefValue::as_bool;
+
+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);
+ return true;
+}
+
+bool FakePrefs::GetInt64(const string& key, int64_t* value) const {
+ return GetValue(key, value);
+}
+
+bool FakePrefs::SetInt64(const string& key, const int64_t value) {
+ SetValue(key, value);
+ return true;
+}
+
+bool FakePrefs::GetBoolean(const string& key, bool* value) const {
+ return GetValue(key, value);
+}
+
+bool FakePrefs::SetBoolean(const string& key, const bool value) {
+ SetValue(key, value);
+ return true;
+}
+
+bool FakePrefs::Exists(const string& key) const {
+ return values_.find(key) != values_.end();
+}
+
+bool FakePrefs::Delete(const string& key) {
+ if (values_.find(key) == values_.end())
+ return false;
+ values_.erase(key);
+ const auto observers_for_key = observers_.find(key);
+ if (observers_for_key != observers_.end()) {
+ std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+ for (ObserverInterface* observer : copy_observers)
+ observer->OnPrefDeleted(key);
+ }
+ return true;
+}
+
+string FakePrefs::GetTypeName(PrefType type) {
+ switch (type) {
+ case PrefType::kString:
+ return "string";
+ case PrefType::kInt64:
+ return "int64_t";
+ case PrefType::kBool:
+ return "bool";
+ }
+ return "Unknown";
+}
+
+void FakePrefs::CheckKeyType(const string& key, PrefType type) const {
+ auto it = values_.find(key);
+ EXPECT_TRUE(it == values_.end() || it->second.type == type)
+ << "Key \"" << key << "\" if defined as " << GetTypeName(it->second.type)
+ << " but is accessed as a " << GetTypeName(type);
+}
+
+template<typename T>
+void FakePrefs::SetValue(const string& key, const T& value) {
+ CheckKeyType(key, PrefConsts<T>::type);
+ values_[key].type = PrefConsts<T>::type;
+ values_[key].value.*(PrefConsts<T>::member) = value;
+ const auto observers_for_key = observers_.find(key);
+ if (observers_for_key != observers_.end()) {
+ std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+ for (ObserverInterface* observer : copy_observers)
+ observer->OnPrefSet(key);
+ }
+}
+
+template<typename T>
+bool FakePrefs::GetValue(const string& key, T* value) const {
+ CheckKeyType(key, PrefConsts<T>::type);
+ auto it = values_.find(key);
+ if (it == values_.end())
+ return false;
+ CheckNotNull(key, value);
+ *value = it->second.value.*(PrefConsts<T>::member);
+ return true;
+}
+
+void FakePrefs::AddObserver(const string& key, ObserverInterface* observer) {
+ observers_[key].push_back(observer);
+}
+
+void FakePrefs::RemoveObserver(const string& key, ObserverInterface* observer) {
+ std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+ auto observer_it =
+ std::find(observers_for_key.begin(), observers_for_key.end(), observer);
+ EXPECT_NE(observer_it, observers_for_key.end())
+ << "Trying to remove an observer instance not watching the key "
+ << key;
+ if (observer_it != observers_for_key.end())
+ observers_for_key.erase(observer_it);
+ if (observers_for_key.empty())
+ observers_.erase(key);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/fake_prefs.h b/common/fake_prefs.h
new file mode 100644
index 0000000..d194060
--- /dev/null
+++ b/common/fake_prefs.h
@@ -0,0 +1,113 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
+#define UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/common/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a fake preference store by storing the value associated with
+// a key in a std::map, suitable for testing. It doesn't allow to set a value on
+// a key with a different type than the previously set type. This enforces the
+// type of a given key to be fixed. Also the class checks that the Get*()
+// methods aren't called on a key set with a different type.
+
+class FakePrefs : public PrefsInterface {
+ public:
+ FakePrefs() = default;
+ ~FakePrefs();
+
+ // 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 GetInt64(const std::string& key, int64_t* value) const override;
+ bool SetInt64(const std::string& key, const int64_t value) override;
+ bool GetBoolean(const std::string& key, bool* value) const override;
+ bool SetBoolean(const std::string& key, const bool value) override;
+
+ bool Exists(const std::string& key) const override;
+ bool Delete(const std::string& key) override;
+
+ void AddObserver(const std::string& key,
+ ObserverInterface* observer) override;
+ void RemoveObserver(const std::string& key,
+ ObserverInterface* observer) override;
+
+ private:
+ enum class PrefType {
+ kString,
+ kInt64,
+ kBool,
+ };
+ struct PrefValue {
+ std::string as_str;
+ int64_t as_int64;
+ bool as_bool;
+ };
+
+ struct PrefTypeValue {
+ PrefType type;
+ PrefValue value;
+ };
+
+ // Class to store compile-time type-dependent constants.
+ template<typename T>
+ class PrefConsts {
+ public:
+ // The PrefType associated with T.
+ static FakePrefs::PrefType const type;
+
+ // The data member pointer to PrefValue associated with T.
+ static T FakePrefs::PrefValue::* const member;
+ };
+
+ // Returns a string representation of the PrefType useful for logging.
+ static std::string GetTypeName(PrefType type);
+
+ // Checks that the |key| is either not present or has the given |type|.
+ void CheckKeyType(const std::string& key, PrefType type) const;
+
+ // 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);
+
+ // Helper function to get a value from the map checking for invalid calls.
+ // The function fails the test if you attempt to read a value defined as a
+ // different type. Returns whether the get succeeded.
+ template<typename T>
+ bool GetValue(const std::string& key, T* value) const;
+
+ // Container for all the key/value pairs.
+ std::map<std::string, PrefTypeValue> values_;
+
+ // The registered observers watching for changes.
+ std::map<std::string, std::vector<ObserverInterface*>> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakePrefs);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_FAKE_PREFS_H_
diff --git a/common/hardware.h b/common/hardware.h
new file mode 100644
index 0000000..f1365e0
--- /dev/null
+++ b/common/hardware.h
@@ -0,0 +1,34 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_HARDWARE_H_
+#define UPDATE_ENGINE_COMMON_HARDWARE_H_
+
+#include <memory>
+
+#include "update_engine/common/hardware_interface.h"
+
+namespace chromeos_update_engine {
+namespace hardware {
+
+// The real HardwareInterface is platform-specific. This factory function
+// creates a new HardwareInterface instance for the current platform.
+std::unique_ptr<HardwareInterface> CreateHardware();
+
+} // namespace hardware
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HARDWARE_H_
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
new file mode 100644
index 0000000..17ce694
--- /dev/null
+++ b/common/hardware_interface.h
@@ -0,0 +1,81 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/time/time.h>
+
+namespace chromeos_update_engine {
+
+// The hardware interface allows access to the crossystem exposed properties,
+// such as the firmware version, hwid, verified boot mode.
+// These stateless functions are tied together in this interface to facilitate
+// unit testing.
+class HardwareInterface {
+ public:
+ virtual ~HardwareInterface() {}
+
+ // Returns whether this is an official build. Official build means that the
+ // server maintains and updates the build, so update_engine should run and
+ // periodically check for updates.
+ virtual bool IsOfficialBuild() const = 0;
+
+ // Returns true if the boot mode is normal or if it's unable to
+ // determine the boot mode. Returns false if the boot mode is
+ // developer. A dev-mode boot will allow the user to access developer-only
+ // features.
+ virtual bool IsNormalBootMode() const = 0;
+
+ // Returns true if the OOBE process has been completed and EULA accepted,
+ // False otherwise. If True is returned, and |out_time_of_oobe| isn't null,
+ // the time-stamp of when OOBE happened is stored at |out_time_of_oobe|.
+ virtual bool IsOOBEComplete(base::Time* out_time_of_oobe) const = 0;
+
+ // 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 powerwash_count from the stateful. If the file is not found
+ // or is invalid, returns -1. Brand new machines out of the factory or after
+ // recovery don't have this value set.
+ virtual int GetPowerwashCount() const = 0;
+
+ // Store in |path| the path to a non-volatile directory (persisted across
+ // reboots) available for this daemon. In case of an error, such as no
+ // directory available, returns false.
+ virtual bool GetNonVolatileDirectory(base::FilePath* path) const = 0;
+
+ // Store in |path| the path to a non-volatile directory persisted across
+ // powerwash cycles. In case of an error, such as no directory available,
+ // returns false.
+ virtual bool GetPowerwashSafeDirectory(base::FilePath* path) const = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HARDWARE_INTERFACE_H_
diff --git a/common/hash_calculator.cc b/common/hash_calculator.cc
new file mode 100644
index 0000000..de6e0f9
--- /dev/null
+++ b/common/hash_calculator.cc
@@ -0,0 +1,143 @@
+//
+// 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/common/hash_calculator.h"
+
+#include <fcntl.h>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <brillo/data_encoding.h>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+HashCalculator::HashCalculator() : valid_(false) {
+ valid_ = (SHA256_Init(&ctx_) == 1);
+ LOG_IF(ERROR, !valid_) << "SHA256_Init failed";
+}
+
+// Update is called with all of the data that should be hashed in order.
+// Mostly just passes the data through to OpenSSL's SHA256_Update()
+bool HashCalculator::Update(const void* data, size_t length) {
+ TEST_AND_RETURN_FALSE(valid_);
+ TEST_AND_RETURN_FALSE(hash_.empty());
+ static_assert(sizeof(size_t) <= sizeof(unsigned long), // NOLINT(runtime/int)
+ "length param may be truncated in SHA256_Update");
+ TEST_AND_RETURN_FALSE(SHA256_Update(&ctx_, data, length) == 1);
+ return true;
+}
+
+off_t HashCalculator::UpdateFile(const string& name, off_t length) {
+ int fd = HANDLE_EINTR(open(name.c_str(), O_RDONLY));
+ if (fd < 0) {
+ return -1;
+ }
+
+ const int kBufferSize = 128 * 1024; // 128 KiB
+ brillo::Blob buffer(kBufferSize);
+ off_t bytes_processed = 0;
+ while (length < 0 || bytes_processed < length) {
+ off_t bytes_to_read = buffer.size();
+ if (length >= 0 && bytes_to_read > length - bytes_processed) {
+ bytes_to_read = length - bytes_processed;
+ }
+ ssize_t rc = HANDLE_EINTR(read(fd, buffer.data(), bytes_to_read));
+ if (rc == 0) { // EOF
+ break;
+ }
+ if (rc < 0 || !Update(buffer.data(), rc)) {
+ bytes_processed = -1;
+ break;
+ }
+ bytes_processed += rc;
+ }
+ IGNORE_EINTR(close(fd));
+ return bytes_processed;
+}
+
+// Call Finalize() when all data has been passed in. This mostly just
+// calls OpenSSL's SHA256_Final() and then base64 encodes the hash.
+bool HashCalculator::Finalize() {
+ TEST_AND_RETURN_FALSE(hash_.empty());
+ TEST_AND_RETURN_FALSE(raw_hash_.empty());
+ raw_hash_.resize(SHA256_DIGEST_LENGTH);
+ TEST_AND_RETURN_FALSE(SHA256_Final(raw_hash_.data(), &ctx_) == 1);
+
+ // Convert raw_hash_ to base64 encoding and store it in hash_.
+ hash_ = brillo::data_encoding::Base64Encode(raw_hash_.data(),
+ raw_hash_.size());
+ return true;
+}
+
+bool HashCalculator::RawHashOfBytes(const void* data,
+ size_t length,
+ brillo::Blob* out_hash) {
+ HashCalculator calc;
+ TEST_AND_RETURN_FALSE(calc.Update(data, length));
+ TEST_AND_RETURN_FALSE(calc.Finalize());
+ *out_hash = calc.raw_hash();
+ return true;
+}
+
+bool HashCalculator::RawHashOfData(const brillo::Blob& data,
+ brillo::Blob* out_hash) {
+ return RawHashOfBytes(data.data(), data.size(), out_hash);
+}
+
+off_t HashCalculator::RawHashOfFile(const string& name, off_t length,
+ brillo::Blob* out_hash) {
+ HashCalculator calc;
+ off_t res = calc.UpdateFile(name, length);
+ if (res < 0) {
+ return res;
+ }
+ if (!calc.Finalize()) {
+ return -1;
+ }
+ *out_hash = calc.raw_hash();
+ return res;
+}
+
+string HashCalculator::HashOfBytes(const void* data, size_t length) {
+ HashCalculator calc;
+ calc.Update(data, length);
+ calc.Finalize();
+ return calc.hash();
+}
+
+string HashCalculator::HashOfString(const string& str) {
+ return HashOfBytes(str.data(), str.size());
+}
+
+string HashCalculator::HashOfData(const brillo::Blob& data) {
+ return HashOfBytes(data.data(), data.size());
+}
+
+string HashCalculator::GetContext() const {
+ return string(reinterpret_cast<const char*>(&ctx_), sizeof(ctx_));
+}
+
+bool HashCalculator::SetContext(const string& context) {
+ TEST_AND_RETURN_FALSE(context.size() == sizeof(ctx_));
+ memcpy(&ctx_, context.data(), sizeof(ctx_));
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/hash_calculator.h b/common/hash_calculator.h
new file mode 100644
index 0000000..f749585
--- /dev/null
+++ b/common/hash_calculator.h
@@ -0,0 +1,107 @@
+//
+// 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_COMMON_HASH_CALCULATOR_H_
+#define UPDATE_ENGINE_COMMON_HASH_CALCULATOR_H_
+
+#include <openssl/sha.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+// Omaha uses base64 encoded SHA-256 as the hash. This class provides a simple
+// wrapper around OpenSSL providing such a formatted hash of data passed in.
+// The methods of this class must be called in a very specific order: First the
+// ctor (of course), then 0 or more calls to Update(), then Finalize(), then 0
+// or more calls to hash().
+
+namespace chromeos_update_engine {
+
+class HashCalculator {
+ public:
+ HashCalculator();
+
+ // Update is called with all of the data that should be hashed in order.
+ // Update will read |length| bytes of |data|.
+ // Returns true on success.
+ bool Update(const void* data, size_t length);
+
+ // Updates the hash with up to |length| bytes of data from |file|. If |length|
+ // is negative, reads in and updates with the whole file. Returns the number
+ // of bytes that the hash was updated with, or -1 on error.
+ off_t UpdateFile(const std::string& name, off_t length);
+
+ // Call Finalize() when all data has been passed in. This method tells
+ // OpenSSl that no more data will come in and base64 encodes the resulting
+ // hash.
+ // Returns true on success.
+ bool Finalize();
+
+ // Gets the hash. Finalize() must have been called.
+ const std::string& hash() const {
+ DCHECK(!hash_.empty()) << "Call Finalize() first";
+ return hash_;
+ }
+
+ const brillo::Blob& raw_hash() const {
+ DCHECK(!raw_hash_.empty()) << "Call Finalize() first";
+ return raw_hash_;
+ }
+
+ // Gets the current hash context. Note that the string will contain binary
+ // data (including \0 characters).
+ std::string GetContext() const;
+
+ // Sets the current hash context. |context| must the string returned by a
+ // previous HashCalculator::GetContext method call. Returns true on success,
+ // and false otherwise.
+ bool SetContext(const std::string& context);
+
+ static bool RawHashOfBytes(const void* data,
+ size_t length,
+ brillo::Blob* out_hash);
+ static bool RawHashOfData(const brillo::Blob& data,
+ brillo::Blob* out_hash);
+ static off_t RawHashOfFile(const std::string& name, off_t length,
+ brillo::Blob* out_hash);
+
+ // Used by tests
+ static std::string HashOfBytes(const void* data, size_t length);
+ static std::string HashOfString(const std::string& str);
+ static std::string HashOfData(const brillo::Blob& data);
+
+ private:
+ // If non-empty, the final base64 encoded hash and the raw hash. Will only be
+ // set to non-empty when Finalize is called.
+ std::string hash_;
+ brillo::Blob raw_hash_;
+
+ // Init success
+ bool valid_;
+
+ // The hash state used by OpenSSL
+ SHA256_CTX ctx_;
+ DISALLOW_COPY_AND_ASSIGN(HashCalculator);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HASH_CALCULATOR_H_
diff --git a/common/hash_calculator_unittest.cc b/common/hash_calculator_unittest.cc
new file mode 100644
index 0000000..27dbc56
--- /dev/null
+++ b/common/hash_calculator_unittest.cc
@@ -0,0 +1,174 @@
+//
+// 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/common/hash_calculator.h"
+
+#include <math.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/libcurl_http_fetcher.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// Generated by running this on a linux shell:
+// $ echo -n hi | openssl dgst -sha256 -binary | openssl base64
+static const char kExpectedHash[] =
+ "j0NDRmSPa5bfid2pAcUXaxCm2Dlh3TwayItZstwyeqQ=";
+static const uint8_t kExpectedRawHash[] = {
+ 0x8f, 0x43, 0x43, 0x46, 0x64, 0x8f, 0x6b, 0x96,
+ 0xdf, 0x89, 0xdd, 0xa9, 0x01, 0xc5, 0x17, 0x6b,
+ 0x10, 0xa6, 0xd8, 0x39, 0x61, 0xdd, 0x3c, 0x1a,
+ 0xc8, 0x8b, 0x59, 0xb2, 0xdc, 0x32, 0x7a, 0xa4
+};
+
+class HashCalculatorTest : public ::testing::Test {
+ public:
+ HashCalculatorTest() {}
+};
+
+TEST_F(HashCalculatorTest, SimpleTest) {
+ HashCalculator calc;
+ calc.Update("hi", 2);
+ calc.Finalize();
+ EXPECT_EQ(kExpectedHash, calc.hash());
+ brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc.raw_hash());
+}
+
+TEST_F(HashCalculatorTest, MultiUpdateTest) {
+ HashCalculator calc;
+ calc.Update("h", 1);
+ calc.Update("i", 1);
+ calc.Finalize();
+ EXPECT_EQ(kExpectedHash, calc.hash());
+ brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc.raw_hash());
+}
+
+TEST_F(HashCalculatorTest, ContextTest) {
+ HashCalculator calc;
+ calc.Update("h", 1);
+ string calc_context = calc.GetContext();
+ calc.Finalize();
+ HashCalculator calc_next;
+ calc_next.SetContext(calc_context);
+ calc_next.Update("i", 1);
+ calc_next.Finalize();
+ EXPECT_EQ(kExpectedHash, calc_next.hash());
+ brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc_next.raw_hash());
+}
+
+TEST_F(HashCalculatorTest, BigTest) {
+ HashCalculator calc;
+
+ int digit_count = 1;
+ int next_overflow = 10;
+ for (int i = 0; i < 1000000; i++) {
+ char buf[8];
+ if (i == next_overflow) {
+ next_overflow *= 10;
+ digit_count++;
+ }
+ ASSERT_EQ(digit_count, snprintf(buf, sizeof(buf), "%d", i)) << " i = " << i;
+ calc.Update(buf, strlen(buf));
+ }
+ calc.Finalize();
+
+ // Hash constant generated by running this on a linux shell:
+ // $ C=0
+ // $ while [ $C -lt 1000000 ]; do
+ // echo -n $C
+ // let C=C+1
+ // done | openssl dgst -sha256 -binary | openssl base64
+ EXPECT_EQ("NZf8k6SPBkYMvhaX8YgzuMgbkLP1XZ+neM8K5wcSsf8=", calc.hash());
+}
+
+TEST_F(HashCalculatorTest, UpdateFileSimpleTest) {
+ string data_path;
+ ASSERT_TRUE(
+ utils::MakeTempFile("data.XXXXXX", &data_path, nullptr));
+ ScopedPathUnlinker data_path_unlinker(data_path);
+ ASSERT_TRUE(utils::WriteFile(data_path.c_str(), "hi", 2));
+
+ static const int kLengths[] = { -1, 2, 10 };
+ for (size_t i = 0; i < arraysize(kLengths); i++) {
+ HashCalculator calc;
+ EXPECT_EQ(2, calc.UpdateFile(data_path, kLengths[i]));
+ EXPECT_TRUE(calc.Finalize());
+ EXPECT_EQ(kExpectedHash, calc.hash());
+ brillo::Blob raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ EXPECT_TRUE(raw_hash == calc.raw_hash());
+ }
+
+ HashCalculator calc;
+ EXPECT_EQ(0, calc.UpdateFile(data_path, 0));
+ EXPECT_EQ(1, calc.UpdateFile(data_path, 1));
+ EXPECT_TRUE(calc.Finalize());
+ // echo -n h | openssl dgst -sha256 -binary | openssl base64
+ EXPECT_EQ("qqlAJmTxpB9A67xSyZk+tmrrNmYClY/fqig7ceZNsSM=", calc.hash());
+}
+
+TEST_F(HashCalculatorTest, RawHashOfFileSimpleTest) {
+ string data_path;
+ ASSERT_TRUE(
+ utils::MakeTempFile("data.XXXXXX", &data_path, nullptr));
+ ScopedPathUnlinker data_path_unlinker(data_path);
+ ASSERT_TRUE(utils::WriteFile(data_path.c_str(), "hi", 2));
+
+ static const int kLengths[] = { -1, 2, 10 };
+ for (size_t i = 0; i < arraysize(kLengths); i++) {
+ brillo::Blob exp_raw_hash(std::begin(kExpectedRawHash),
+ std::end(kExpectedRawHash));
+ brillo::Blob raw_hash;
+ EXPECT_EQ(2, HashCalculator::RawHashOfFile(data_path,
+ kLengths[i],
+ &raw_hash));
+ EXPECT_TRUE(exp_raw_hash == raw_hash);
+ }
+}
+
+TEST_F(HashCalculatorTest, UpdateFileNonexistentTest) {
+ HashCalculator calc;
+ EXPECT_EQ(-1, calc.UpdateFile("/some/non-existent/file", -1));
+}
+
+TEST_F(HashCalculatorTest, AbortTest) {
+ // Just make sure we don't crash and valgrind doesn't detect memory leaks
+ {
+ HashCalculator calc;
+ }
+ {
+ HashCalculator calc;
+ calc.Update("h", 1);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/http_common.cc b/common/http_common.cc
new file mode 100644
index 0000000..7d98889
--- /dev/null
+++ b/common/http_common.cc
@@ -0,0 +1,86 @@
+//
+// 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.
+//
+
+// Implementation of common HTTP related functions.
+
+#include "update_engine/common/http_common.h"
+
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+const char *GetHttpResponseDescription(HttpResponseCode code) {
+ static const struct {
+ HttpResponseCode code;
+ const char* description;
+ } http_response_table[] = {
+ { kHttpResponseOk, "OK" },
+ { kHttpResponseCreated, "Created" },
+ { kHttpResponseAccepted, "Accepted" },
+ { kHttpResponseNonAuthInfo, "Non-Authoritative Information" },
+ { kHttpResponseNoContent, "No Content" },
+ { kHttpResponseResetContent, "Reset Content" },
+ { kHttpResponsePartialContent, "Partial Content" },
+ { kHttpResponseMultipleChoices, "Multiple Choices" },
+ { kHttpResponseMovedPermanently, "Moved Permanently" },
+ { kHttpResponseFound, "Found" },
+ { kHttpResponseSeeOther, "See Other" },
+ { kHttpResponseNotModified, "Not Modified" },
+ { kHttpResponseUseProxy, "Use Proxy" },
+ { kHttpResponseTempRedirect, "Temporary Redirect" },
+ { kHttpResponseBadRequest, "Bad Request" },
+ { kHttpResponseUnauth, "Unauthorized" },
+ { kHttpResponseForbidden, "Forbidden" },
+ { kHttpResponseNotFound, "Not Found" },
+ { kHttpResponseRequestTimeout, "Request Timeout" },
+ { kHttpResponseInternalServerError, "Internal Server Error" },
+ { kHttpResponseNotImplemented, "Not Implemented" },
+ { kHttpResponseServiceUnavailable, "Service Unavailable" },
+ { kHttpResponseVersionNotSupported, "HTTP Version Not Supported" },
+ };
+
+ bool is_found = false;
+ size_t i;
+ for (i = 0; i < arraysize(http_response_table); i++)
+ if ((is_found = (http_response_table[i].code == code)))
+ break;
+
+ return (is_found ? http_response_table[i].description : "(unsupported)");
+}
+
+HttpResponseCode StringToHttpResponseCode(const char *s) {
+ return static_cast<HttpResponseCode>(strtoul(s, nullptr, 10));
+}
+
+
+const char *GetHttpContentTypeString(HttpContentType type) {
+ static const struct {
+ HttpContentType type;
+ const char* str;
+ } http_content_type_table[] = {
+ { kHttpContentTypeTextXml, "text/xml" },
+ };
+
+ bool is_found = false;
+ size_t i;
+ for (i = 0; i < arraysize(http_content_type_table); i++)
+ if ((is_found = (http_content_type_table[i].type == type)))
+ break;
+
+ return (is_found ? http_content_type_table[i].str : nullptr);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/http_common.h b/common/http_common.h
new file mode 100644
index 0000000..041cad5
--- /dev/null
+++ b/common/http_common.h
@@ -0,0 +1,74 @@
+//
+// 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.
+//
+
+// This file contains general definitions used in implementing, testing and
+// emulating communication over HTTP.
+
+#ifndef UPDATE_ENGINE_COMMON_HTTP_COMMON_H_
+#define UPDATE_ENGINE_COMMON_HTTP_COMMON_H_
+
+#include <cstdlib>
+
+namespace chromeos_update_engine {
+
+// Enumeration type for HTTP response codes.
+enum HttpResponseCode {
+ kHttpResponseUndefined = 0,
+ kHttpResponseOk = 200,
+ kHttpResponseCreated = 201,
+ kHttpResponseAccepted = 202,
+ kHttpResponseNonAuthInfo = 203,
+ kHttpResponseNoContent = 204,
+ kHttpResponseResetContent = 205,
+ kHttpResponsePartialContent = 206,
+ kHttpResponseMultipleChoices = 300,
+ kHttpResponseMovedPermanently = 301,
+ kHttpResponseFound = 302,
+ kHttpResponseSeeOther = 303,
+ kHttpResponseNotModified = 304,
+ kHttpResponseUseProxy = 305,
+ kHttpResponseTempRedirect = 307,
+ kHttpResponseBadRequest = 400,
+ kHttpResponseUnauth = 401,
+ kHttpResponseForbidden = 403,
+ kHttpResponseNotFound = 404,
+ kHttpResponseRequestTimeout = 408,
+ kHttpResponseReqRangeNotSat = 416,
+ kHttpResponseInternalServerError = 500,
+ kHttpResponseNotImplemented = 501,
+ kHttpResponseServiceUnavailable = 503,
+ kHttpResponseVersionNotSupported = 505,
+};
+
+// Returns a standard HTTP status line string for a given response code.
+const char *GetHttpResponseDescription(HttpResponseCode code);
+
+// Converts a string beginning with an HTTP error code into numerical value.
+HttpResponseCode StringToHttpResponseCode(const char *s);
+
+
+// Enumeration type for HTTP Content-Type.
+enum HttpContentType {
+ kHttpContentTypeUnspecified = 0,
+ kHttpContentTypeTextXml,
+};
+
+// Returns a standard HTTP Content-Type string.
+const char *GetHttpContentTypeString(HttpContentType type);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HTTP_COMMON_H_
diff --git a/common/http_fetcher.cc b/common/http_fetcher.cc
new file mode 100644
index 0000000..400b43c
--- /dev/null
+++ b/common/http_fetcher.cc
@@ -0,0 +1,82 @@
+//
+// 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/common/http_fetcher.h"
+
+#include <base/bind.h>
+
+using base::Closure;
+using brillo::MessageLoop;
+using std::deque;
+using std::string;
+
+namespace chromeos_update_engine {
+
+HttpFetcher::~HttpFetcher() {
+ if (no_resolver_idle_id_ != MessageLoop::kTaskIdNull) {
+ MessageLoop::current()->CancelTask(no_resolver_idle_id_);
+ no_resolver_idle_id_ = MessageLoop::kTaskIdNull;
+ }
+}
+
+void HttpFetcher::SetPostData(const void* data, size_t size,
+ HttpContentType type) {
+ post_data_set_ = true;
+ post_data_.clear();
+ const char* char_data = reinterpret_cast<const char*>(data);
+ post_data_.insert(post_data_.end(), char_data, char_data + size);
+ post_content_type_ = type;
+}
+
+void HttpFetcher::SetPostData(const void* data, size_t size) {
+ SetPostData(data, size, kHttpContentTypeUnspecified);
+}
+
+// Proxy methods to set the proxies, then to pop them off.
+bool HttpFetcher::ResolveProxiesForUrl(const string& url,
+ const Closure& callback) {
+ CHECK_EQ(static_cast<Closure*>(nullptr), callback_.get());
+ callback_.reset(new Closure(callback));
+
+ if (!proxy_resolver_) {
+ LOG(INFO) << "Not resolving proxies (no proxy resolver).";
+ no_resolver_idle_id_ = MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&HttpFetcher::NoProxyResolverCallback,
+ base::Unretained(this)));
+ return true;
+ }
+ return proxy_resolver_->GetProxiesForUrl(url,
+ &HttpFetcher::StaticProxiesResolved,
+ this);
+}
+
+void HttpFetcher::NoProxyResolverCallback() {
+ ProxiesResolved(deque<string>());
+}
+
+void HttpFetcher::ProxiesResolved(const deque<string>& proxies) {
+ no_resolver_idle_id_ = MessageLoop::kTaskIdNull;
+ if (!proxies.empty())
+ SetProxies(proxies);
+ CHECK_NE(static_cast<Closure*>(nullptr), callback_.get());
+ Closure* callback = callback_.release();
+ // This may indirectly call back into ResolveProxiesForUrl():
+ callback->Run();
+ delete callback;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/http_fetcher.h b/common/http_fetcher.h
new file mode 100644
index 0000000..11e8e9f
--- /dev/null
+++ b/common/http_fetcher.h
@@ -0,0 +1,197 @@
+//
+// 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_COMMON_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_HTTP_FETCHER_H_
+
+#include <deque>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/http_common.h"
+#include "update_engine/proxy_resolver.h"
+
+// This class is a simple wrapper around an HTTP library (libcurl). We can
+// easily mock out this interface for testing.
+
+// Implementations of this class should use asynchronous i/o. They can access
+// the MessageLoop to request callbacks when timers or file descriptors change.
+
+namespace chromeos_update_engine {
+
+class HttpFetcherDelegate;
+
+class HttpFetcher {
+ public:
+ // |proxy_resolver| is the resolver that will be consulted for proxy
+ // settings. It may be null, in which case direct connections will
+ // be used. Does not take ownership of the resolver.
+ HttpFetcher(ProxyResolver* proxy_resolver)
+ : post_data_set_(false),
+ http_response_code_(0),
+ delegate_(nullptr),
+ proxies_(1, kNoProxy),
+ proxy_resolver_(proxy_resolver),
+ callback_(nullptr) {}
+ virtual ~HttpFetcher();
+
+ void set_delegate(HttpFetcherDelegate* delegate) { delegate_ = delegate; }
+ HttpFetcherDelegate* delegate() const { return delegate_; }
+ int http_response_code() const { return http_response_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.
+ void SetPostData(const void* data, size_t size, HttpContentType type);
+
+ // Same without a specified Content-Type.
+ void SetPostData(const void* data, size_t size);
+
+ // Proxy methods to set the proxies, then to pop them off.
+ // Returns true on success.
+ bool ResolveProxiesForUrl(const std::string& url,
+ const base::Closure& callback);
+
+ void SetProxies(const std::deque<std::string>& proxies) {
+ proxies_ = proxies;
+ }
+ const std::string& GetCurrentProxy() const {
+ return proxies_.front();
+ }
+ bool HasProxy() const { return !proxies_.empty(); }
+ void PopProxy() { proxies_.pop_front(); }
+
+ // Downloading should resume from this offset
+ virtual void SetOffset(off_t offset) = 0;
+
+ // Set/unset the length of the range to be downloaded.
+ virtual void SetLength(size_t length) = 0;
+ virtual void UnsetLength() = 0;
+
+ // Begins the transfer to the specified URL. This fetcher instance should not
+ // be destroyed until either TransferComplete, or TransferTerminated is
+ // called.
+ virtual void BeginTransfer(const std::string& url) = 0;
+
+ // Aborts the transfer. The transfer may not abort right away -- delegate's
+ // TransferTerminated() will be called when the transfer is actually done.
+ virtual void TerminateTransfer() = 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.
+ virtual void Pause() = 0;
+
+ // Used to unpause an HttpFetcher and let the bytes stream in again.
+ // If a delegate is set, ReceivedBytes() may be called on it before
+ // Unpause() returns
+ virtual void Unpause() = 0;
+
+ // These two function are overloaded in LibcurlHttp fetcher to speed
+ // testing.
+ virtual void set_idle_seconds(int seconds) {}
+ virtual void set_retry_seconds(int seconds) {}
+
+ // Sets the values used to time out the connection if the transfer
+ // rate is less than |low_speed_bps| bytes/sec for more than
+ // |low_speed_sec| seconds.
+ virtual void set_low_speed_limit(int low_speed_bps, int low_speed_sec) = 0;
+
+ // Sets the connect timeout, e.g. the maximum amount of time willing
+ // to wait for establishing a connection to the server.
+ virtual void set_connect_timeout(int connect_timeout_seconds) = 0;
+
+ // Sets the number of allowed retries.
+ virtual void set_max_retry_count(int max_retry_count) = 0;
+
+ // Get the total number of bytes downloaded by fetcher.
+ virtual size_t GetBytesDownloaded() = 0;
+
+ ProxyResolver* proxy_resolver() const { return proxy_resolver_; }
+
+ protected:
+ // The URL we're actively fetching from
+ std::string url_;
+
+ // POST data for the transfer, and whether or not it was ever set
+ bool post_data_set_;
+ brillo::Blob post_data_;
+ HttpContentType post_content_type_;
+
+ // The server's HTTP response code from the last transfer. This
+ // field should be set to 0 when a new transfer is initiated, and
+ // set to the response code when the transfer is complete.
+ int http_response_code_;
+
+ // The delegate; may be null.
+ HttpFetcherDelegate* delegate_;
+
+ // Proxy servers
+ std::deque<std::string> proxies_;
+
+ ProxyResolver* const proxy_resolver_;
+
+ // The ID of the idle callback, used when we have no proxy resolver.
+ brillo::MessageLoop::TaskId no_resolver_idle_id_{
+ brillo::MessageLoop::kTaskIdNull};
+
+ // Callback for when we are resolving proxies
+ std::unique_ptr<base::Closure> callback_;
+
+ private:
+ // Callback from the proxy resolver
+ void ProxiesResolved(const std::deque<std::string>& proxies);
+ static void StaticProxiesResolved(const std::deque<std::string>& proxies,
+ void* data) {
+ reinterpret_cast<HttpFetcher*>(data)->ProxiesResolved(proxies);
+ }
+
+ // Callback used to run the proxy resolver callback when there is no
+ // |proxy_resolver_|.
+ void NoProxyResolverCallback();
+
+ DISALLOW_COPY_AND_ASSIGN(HttpFetcher);
+};
+
+// Interface for delegates
+class HttpFetcherDelegate {
+ public:
+ virtual ~HttpFetcherDelegate() = default;
+
+ // Called every time bytes are received.
+ virtual void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes,
+ size_t length) = 0;
+
+ // Called if the fetcher seeks to a particular offset.
+ virtual void SeekToOffset(off_t offset) {}
+
+ // When a transfer has completed, exactly one of these two methods will be
+ // called. TransferTerminated is called when the transfer has been aborted
+ // through TerminateTransfer. TransferComplete is called in all other
+ // situations. It's OK to destroy the |fetcher| object in this callback.
+ virtual void TransferComplete(HttpFetcher* fetcher, bool successful) = 0;
+ virtual void TransferTerminated(HttpFetcher* fetcher) {}
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HTTP_FETCHER_H_
diff --git a/common/http_fetcher_unittest.cc b/common/http_fetcher_unittest.cc
new file mode 100644
index 0000000..17e360e
--- /dev/null
+++ b/common/http_fetcher_unittest.cc
@@ -0,0 +1,1138 @@
+//
+// 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 <netinet/in.h>
+#include <netinet/ip.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/message_loop/message_loop.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/bind_lambda.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 <brillo/process.h>
+#include <brillo/streams/file_stream.h>
+#include <brillo/streams/stream.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/http_common.h"
+#include "update_engine/common/libcurl_http_fetcher.h"
+#include "update_engine/common/mock_http_fetcher.h"
+#include "update_engine/common/multi_range_http_fetcher.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/proxy_resolver.h"
+
+using brillo::MessageLoop;
+using std::make_pair;
+using std::pair;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace {
+
+const int kBigLength = 100000;
+const int kMediumLength = 1000;
+const int kFlakyTruncateLength = 29000;
+const int kFlakySleepEvery = 3;
+const int kFlakySleepSecs = 10;
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+static const char *kUnusedUrl = "unused://unused";
+
+static inline string LocalServerUrlForPath(in_port_t port,
+ const string& path) {
+ string port_str = (port ? base::StringPrintf(":%hu", port) : "");
+ return base::StringPrintf("http://127.0.0.1%s%s", port_str.c_str(),
+ path.c_str());
+}
+
+//
+// Class hierarchy for HTTP server implementations.
+//
+
+class HttpServer {
+ public:
+ // This makes it an abstract class (dirty but works).
+ virtual ~HttpServer() = 0;
+
+ virtual in_port_t GetPort() const {
+ return 0;
+ }
+
+ bool started_;
+};
+
+HttpServer::~HttpServer() {}
+
+
+class NullHttpServer : public HttpServer {
+ public:
+ NullHttpServer() {
+ started_ = true;
+ }
+};
+
+
+class PythonHttpServer : public HttpServer {
+ public:
+ PythonHttpServer() : port_(0) {
+ started_ = false;
+
+ // Spawn the server process.
+ unique_ptr<brillo::Process> http_server(new brillo::ProcessImpl());
+ base::FilePath test_server_path =
+ test_utils::GetBuildArtifactsPath().Append("test_http_server");
+ http_server->AddArg(test_server_path.value());
+ http_server->RedirectUsingPipe(STDOUT_FILENO, false);
+
+ if (!http_server->Start()) {
+ ADD_FAILURE() << "failed to spawn http server process";
+ return;
+ }
+ LOG(INFO) << "started http server with pid " << http_server->pid();
+
+ // Wait for server to begin accepting connections, obtain its port.
+ brillo::StreamPtr stdout = brillo::FileStream::FromFileDescriptor(
+ http_server->GetPipe(STDOUT_FILENO), false /* own */, nullptr);
+ if (!stdout)
+ return;
+
+ vector<char> buf(128);
+ string line;
+ while (line.find('\n') == string::npos) {
+ size_t read;
+ if (!stdout->ReadBlocking(buf.data(), buf.size(), &read, nullptr)) {
+ ADD_FAILURE() << "error reading http server stdout";
+ return;
+ }
+ line.append(buf.data(), read);
+ if (read == 0)
+ break;
+ }
+ // Parse the port from the output line.
+ const size_t listening_msg_prefix_len = strlen(kServerListeningMsgPrefix);
+ if (line.size() < listening_msg_prefix_len) {
+ ADD_FAILURE() << "server output too short";
+ return;
+ }
+
+ EXPECT_EQ(kServerListeningMsgPrefix,
+ line.substr(0, listening_msg_prefix_len));
+ string port_str = line.substr(listening_msg_prefix_len);
+ port_str.resize(port_str.find('\n'));
+ EXPECT_TRUE(base::StringToUint(port_str, &port_));
+
+ started_ = true;
+ LOG(INFO) << "server running, listening on port " << port_;
+
+ // Any failure before this point will SIGKILL the test server if started
+ // when the |http_server| goes out of scope.
+ http_server_ = std::move(http_server);
+ }
+
+ ~PythonHttpServer() {
+ // If there's no process, do nothing.
+ if (!http_server_)
+ return;
+ // Wait up to 10 seconds for the process to finish. Destroying the process
+ // will kill it with a SIGKILL otherwise.
+ http_server_->Kill(SIGTERM, 10);
+ }
+
+ in_port_t GetPort() const override {
+ return port_;
+ }
+
+ private:
+ static const char* kServerListeningMsgPrefix;
+
+ unique_ptr<brillo::Process> http_server_;
+ unsigned int port_;
+};
+
+const char* PythonHttpServer::kServerListeningMsgPrefix = "listening on port ";
+
+//
+// Class hierarchy for HTTP fetcher test wrappers.
+//
+
+class AnyHttpFetcherTest {
+ public:
+ AnyHttpFetcherTest() {}
+ virtual ~AnyHttpFetcherTest() {}
+
+ virtual HttpFetcher* NewLargeFetcher(size_t num_proxies) = 0;
+ HttpFetcher* NewLargeFetcher() {
+ return NewLargeFetcher(1);
+ }
+
+ virtual HttpFetcher* NewSmallFetcher(size_t num_proxies) = 0;
+ HttpFetcher* NewSmallFetcher() {
+ return NewSmallFetcher(1);
+ }
+
+ virtual string BigUrl(in_port_t port) const { return kUnusedUrl; }
+ virtual string SmallUrl(in_port_t port) const { return kUnusedUrl; }
+ virtual string ErrorUrl(in_port_t port) const { return kUnusedUrl; }
+
+ virtual bool IsMock() const = 0;
+ virtual bool IsMulti() const = 0;
+
+ virtual void IgnoreServerAborting(HttpServer* server) const {}
+
+ virtual HttpServer* CreateServer() = 0;
+
+ FakeHardware* fake_hardware() {
+ return &fake_hardware_;
+ }
+
+ protected:
+ DirectProxyResolver proxy_resolver_;
+ FakeHardware fake_hardware_;
+};
+
+class MockHttpFetcherTest : public AnyHttpFetcherTest {
+ public:
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewLargeFetcher;
+ HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+ brillo::Blob big_data(1000000);
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ return new MockHttpFetcher(
+ big_data.data(),
+ big_data.size(),
+ reinterpret_cast<ProxyResolver*>(&proxy_resolver_));
+ }
+
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewSmallFetcher;
+ HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ return new MockHttpFetcher(
+ "x",
+ 1,
+ reinterpret_cast<ProxyResolver*>(&proxy_resolver_));
+ }
+
+ bool IsMock() const override { return true; }
+ bool IsMulti() const override { return false; }
+
+ HttpServer* CreateServer() override {
+ return new NullHttpServer;
+ }
+};
+
+class LibcurlHttpFetcherTest : public AnyHttpFetcherTest {
+ public:
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewLargeFetcher;
+ HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ LibcurlHttpFetcher *ret = new
+ LibcurlHttpFetcher(reinterpret_cast<ProxyResolver*>(&proxy_resolver_),
+ &fake_hardware_);
+ // Speed up test execution.
+ ret->set_idle_seconds(1);
+ ret->set_retry_seconds(1);
+ fake_hardware_.SetIsOfficialBuild(false);
+ return ret;
+ }
+
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewSmallFetcher;
+ HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+ return NewLargeFetcher(num_proxies);
+ }
+
+ string BigUrl(in_port_t port) const override {
+ return LocalServerUrlForPath(port,
+ base::StringPrintf("/download/%d",
+ kBigLength));
+ }
+ string SmallUrl(in_port_t port) const override {
+ return LocalServerUrlForPath(port, "/foo");
+ }
+ string ErrorUrl(in_port_t port) const override {
+ return LocalServerUrlForPath(port, "/error");
+ }
+
+ bool IsMock() const override { return false; }
+ bool IsMulti() const override { return false; }
+
+ void IgnoreServerAborting(HttpServer* server) const override {
+ // Nothing to do.
+ }
+
+ HttpServer* CreateServer() override {
+ return new PythonHttpServer;
+ }
+};
+
+class MultiRangeHttpFetcherTest : public LibcurlHttpFetcherTest {
+ public:
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewLargeFetcher;
+ HttpFetcher* NewLargeFetcher(size_t num_proxies) override {
+ CHECK_GT(num_proxies, 0u);
+ proxy_resolver_.set_num_proxies(num_proxies);
+ ProxyResolver* resolver =
+ reinterpret_cast<ProxyResolver*>(&proxy_resolver_);
+ MultiRangeHttpFetcher *ret =
+ new MultiRangeHttpFetcher(
+ new LibcurlHttpFetcher(resolver, &fake_hardware_));
+ ret->ClearRanges();
+ ret->AddRange(0);
+ // Speed up test execution.
+ ret->set_idle_seconds(1);
+ ret->set_retry_seconds(1);
+ fake_hardware_.SetIsOfficialBuild(false);
+ return ret;
+ }
+
+ // Necessary to unhide the definition in the base class.
+ using AnyHttpFetcherTest::NewSmallFetcher;
+ HttpFetcher* NewSmallFetcher(size_t num_proxies) override {
+ return NewLargeFetcher(num_proxies);
+ }
+
+ bool IsMulti() const override { return true; }
+};
+
+
+//
+// Infrastructure for type tests of HTTP fetcher.
+// See: http://code.google.com/p/googletest/wiki/AdvancedGuide#Typed_Tests
+//
+
+// Fixture class template. We use an explicit constraint to guarantee that it
+// can only be instantiated with an AnyHttpFetcherTest type, see:
+// http://www2.research.att.com/~bs/bs_faq2.html#constraints
+template <typename T>
+class HttpFetcherTest : public ::testing::Test {
+ public:
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop loop_{&base_loop_};
+
+ T test_;
+
+ protected:
+ HttpFetcherTest() {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1));
+ }
+
+ private:
+ static void TypeConstraint(T* a) {
+ AnyHttpFetcherTest *b = a;
+ if (b == 0) // Silence compiler warning of unused variable.
+ *b = a;
+ }
+};
+
+// Test case types list.
+typedef ::testing::Types<LibcurlHttpFetcherTest,
+ MockHttpFetcherTest,
+ MultiRangeHttpFetcherTest> HttpFetcherTestTypes;
+TYPED_TEST_CASE(HttpFetcherTest, HttpFetcherTestTypes);
+
+
+namespace {
+class HttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ HttpFetcherTestDelegate() :
+ is_expect_error_(false), times_transfer_complete_called_(0),
+ times_transfer_terminated_called_(0), times_received_bytes_called_(0) {}
+
+ void ReceivedBytes(HttpFetcher* /* fetcher */,
+ const void* /* bytes */, size_t /* length */) override {
+ // Update counters
+ times_received_bytes_called_++;
+ }
+
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ if (is_expect_error_)
+ EXPECT_EQ(kHttpResponseNotFound, fetcher->http_response_code());
+ else
+ EXPECT_EQ(kHttpResponseOk, fetcher->http_response_code());
+ MessageLoop::current()->BreakLoop();
+
+ // Update counter
+ times_transfer_complete_called_++;
+ }
+
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ times_transfer_terminated_called_++;
+ }
+
+ // Are we expecting an error response? (default: no)
+ bool is_expect_error_;
+
+ // Counters for callback invocations.
+ int times_transfer_complete_called_;
+ int times_transfer_terminated_called_;
+ int times_received_bytes_called_;
+};
+
+
+void StartTransfer(HttpFetcher* http_fetcher, const string& url) {
+ http_fetcher->BeginTransfer(url);
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, SimpleTest) {
+ HttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ this->test_.SmallUrl(server->GetPort())));
+ this->loop_.Run();
+}
+
+TYPED_TEST(HttpFetcherTest, SimpleBigTest) {
+ HttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ this->test_.BigUrl(server->GetPort())));
+ this->loop_.Run();
+}
+
+// Issue #9648: when server returns an error HTTP response, the fetcher needs to
+// terminate transfer prematurely, rather than try to process the error payload.
+TYPED_TEST(HttpFetcherTest, ErrorTest) {
+ if (this->test_.IsMock() || this->test_.IsMulti())
+ return;
+ HttpFetcherTestDelegate delegate;
+
+ // Delegate should expect an error response.
+ delegate.is_expect_error_ = true;
+
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ this->test_.ErrorUrl(server->GetPort())));
+ this->loop_.Run();
+
+ // Make sure that no bytes were received.
+ CHECK_EQ(delegate.times_received_bytes_called_, 0);
+ CHECK_EQ(fetcher->GetBytesDownloaded(), static_cast<size_t>(0));
+
+ // Make sure that transfer completion was signaled once, and no termination
+ // was signaled.
+ CHECK_EQ(delegate.times_transfer_complete_called_, 1);
+ CHECK_EQ(delegate.times_transfer_terminated_called_, 0);
+}
+
+namespace {
+class PausingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* /* bytes */, size_t /* length */) override {
+ CHECK(!paused_);
+ paused_ = true;
+ fetcher->Pause();
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ void Unpause() {
+ CHECK(paused_);
+ paused_ = false;
+ fetcher_->Unpause();
+ }
+ bool paused_;
+ HttpFetcher* fetcher_;
+};
+
+void UnpausingTimeoutCallback(PausingHttpFetcherTestDelegate* delegate,
+ MessageLoop::TaskId* my_id) {
+ if (delegate->paused_)
+ delegate->Unpause();
+ // Update the task id with the new scheduled callback.
+ *my_id = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UnpausingTimeoutCallback, delegate, my_id),
+ base::TimeDelta::FromMilliseconds(200));
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, PauseTest) {
+ {
+ PausingHttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher());
+ delegate.paused_ = false;
+ delegate.fetcher_ = fetcher.get();
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ MessageLoop::TaskId callback_id;
+ callback_id = this->loop_.PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UnpausingTimeoutCallback, &delegate, &callback_id),
+ base::TimeDelta::FromMilliseconds(200));
+ fetcher->BeginTransfer(this->test_.BigUrl(server->GetPort()));
+
+ this->loop_.Run();
+ EXPECT_TRUE(this->loop_.CancelTask(callback_id));
+ }
+}
+
+namespace {
+class AbortingHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {}
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ ADD_FAILURE(); // We should never get here
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ EXPECT_EQ(fetcher, fetcher_.get());
+ EXPECT_FALSE(once_);
+ EXPECT_TRUE(callback_once_);
+ callback_once_ = false;
+ // The fetcher could have a callback scheduled on the ProxyResolver that
+ // can fire after this callback. We wait until the end of the test to
+ // delete the fetcher.
+ }
+ void TerminateTransfer() {
+ CHECK(once_);
+ once_ = false;
+ fetcher_->TerminateTransfer();
+ }
+ void EndLoop() {
+ MessageLoop::current()->BreakLoop();
+ }
+ bool once_;
+ bool callback_once_;
+ unique_ptr<HttpFetcher> fetcher_;
+};
+
+void AbortingTimeoutCallback(AbortingHttpFetcherTestDelegate* delegate,
+ MessageLoop::TaskId* my_id) {
+ if (delegate->once_) {
+ delegate->TerminateTransfer();
+ *my_id = MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(AbortingTimeoutCallback, delegate, my_id));
+ } else {
+ delegate->EndLoop();
+ *my_id = MessageLoop::kTaskIdNull;
+ }
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, AbortTest) {
+ AbortingHttpFetcherTestDelegate delegate;
+ delegate.fetcher_.reset(this->test_.NewLargeFetcher());
+ delegate.once_ = true;
+ delegate.callback_once_ = true;
+ delegate.fetcher_->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ this->test_.IgnoreServerAborting(server.get());
+ ASSERT_TRUE(server->started_);
+
+ MessageLoop::TaskId task_id = MessageLoop::kTaskIdNull;
+
+ task_id = this->loop_.PostTask(
+ FROM_HERE,
+ base::Bind(AbortingTimeoutCallback, &delegate, &task_id));
+ delegate.fetcher_->BeginTransfer(this->test_.BigUrl(server->GetPort()));
+
+ this->loop_.Run();
+ CHECK(!delegate.once_);
+ CHECK(!delegate.callback_once_);
+ this->loop_.CancelTask(task_id);
+}
+
+namespace {
+class FlakyHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ data.append(reinterpret_cast<const char*>(bytes), length);
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_TRUE(successful);
+ EXPECT_EQ(kHttpResponsePartialContent, fetcher->http_response_code());
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ string data;
+};
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, FlakyTest) {
+ if (this->test_.IsMock())
+ return;
+ {
+ FlakyHttpFetcherTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ &StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/flaky/%d/%d/%d/%d",
+ kBigLength,
+ kFlakyTruncateLength,
+ kFlakySleepEvery,
+ kFlakySleepSecs))));
+ this->loop_.Run();
+
+ // verify the data we get back
+ ASSERT_EQ(kBigLength, delegate.data.size());
+ for (int i = 0; i < kBigLength; i += 10) {
+ // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+ ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+ }
+ }
+}
+
+namespace {
+// This delegate kills the server attached to it after receiving any bytes.
+// This can be used for testing what happens when you try to fetch data and
+// the server dies.
+class FailureHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ explicit FailureHttpFetcherTestDelegate(PythonHttpServer* server)
+ : server_(server) {}
+
+ ~FailureHttpFetcherTestDelegate() override {
+ if (server_) {
+ LOG(INFO) << "Stopping server in destructor";
+ delete server_;
+ LOG(INFO) << "server stopped";
+ }
+ }
+
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ if (server_) {
+ LOG(INFO) << "Stopping server in ReceivedBytes";
+ delete server_;
+ LOG(INFO) << "server stopped";
+ server_ = nullptr;
+ }
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_FALSE(successful);
+ EXPECT_EQ(0, fetcher->http_response_code());
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ PythonHttpServer* server_;
+};
+} // namespace
+
+
+TYPED_TEST(HttpFetcherTest, FailureTest) {
+ // This test ensures that a fetcher responds correctly when a server isn't
+ // available at all.
+ if (this->test_.IsMock())
+ return;
+ {
+ FailureHttpFetcherTestDelegate delegate(nullptr);
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ this->loop_.PostTask(FROM_HERE,
+ base::Bind(StartTransfer,
+ fetcher.get(),
+ "http://host_doesnt_exist99999999"));
+ this->loop_.Run();
+
+ // Exiting and testing happens in the delegate
+ }
+}
+
+TYPED_TEST(HttpFetcherTest, NoResponseTest) {
+ // This test starts a new http server but the server doesn't respond and just
+ // closes the connection.
+ if (this->test_.IsMock())
+ return;
+
+ PythonHttpServer* server = new PythonHttpServer();
+ int port = server->GetPort();
+ ASSERT_TRUE(server->started_);
+
+ // Handles destruction and claims ownership.
+ FailureHttpFetcherTestDelegate delegate(server);
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+ // The server will not reply at all, so we can limit the execution time of the
+ // test by reducing the low-speed timeout to something small. The test will
+ // finish once the TimeoutCallback() triggers (every second) and the timeout
+ // expired.
+ fetcher->set_low_speed_limit(kDownloadLowSpeedLimitBps, 1);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(port, "/hang")));
+ this->loop_.Run();
+
+ // Check that no other callback runs in the next two seconds. That would
+ // indicate a leaked callback.
+ bool timeout = false;
+ auto callback = base::Bind([&timeout]{ timeout = true;});
+ this->loop_.PostDelayedTask(FROM_HERE, callback,
+ base::TimeDelta::FromSeconds(2));
+ EXPECT_TRUE(this->loop_.RunOnce(true));
+ EXPECT_TRUE(timeout);
+}
+
+TYPED_TEST(HttpFetcherTest, ServerDiesTest) {
+ // This test starts a new http server and kills it after receiving its first
+ // set of bytes. It test whether or not our fetcher eventually gives up on
+ // retries and aborts correctly.
+ if (this->test_.IsMock())
+ return;
+ {
+ PythonHttpServer* server = new PythonHttpServer();
+ int port = server->GetPort();
+ ASSERT_TRUE(server->started_);
+
+ // Handles destruction and claims ownership.
+ FailureHttpFetcherTestDelegate delegate(server);
+ unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher());
+ fetcher->set_delegate(&delegate);
+
+ this->loop_.PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(port,
+ base::StringPrintf("/flaky/%d/%d/%d/%d",
+ kBigLength,
+ kFlakyTruncateLength,
+ kFlakySleepEvery,
+ kFlakySleepSecs))));
+ this->loop_.Run();
+
+ // Exiting and testing happens in the delegate
+ }
+}
+
+namespace {
+const HttpResponseCode kRedirectCodes[] = {
+ kHttpResponseMovedPermanently, kHttpResponseFound, kHttpResponseSeeOther,
+ kHttpResponseTempRedirect
+};
+
+class RedirectHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ explicit RedirectHttpFetcherTestDelegate(bool expected_successful)
+ : expected_successful_(expected_successful) {}
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ data.append(reinterpret_cast<const char*>(bytes), length);
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_EQ(expected_successful_, successful);
+ if (expected_successful_) {
+ EXPECT_EQ(kHttpResponseOk, fetcher->http_response_code());
+ } else {
+ EXPECT_GE(fetcher->http_response_code(), kHttpResponseMovedPermanently);
+ EXPECT_LE(fetcher->http_response_code(), kHttpResponseTempRedirect);
+ }
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+ bool expected_successful_;
+ string data;
+};
+
+// RedirectTest takes ownership of |http_fetcher|.
+void RedirectTest(const HttpServer* server,
+ bool expected_successful,
+ const string& url,
+ HttpFetcher* http_fetcher) {
+ RedirectHttpFetcherTestDelegate delegate(expected_successful);
+ unique_ptr<HttpFetcher> fetcher(http_fetcher);
+ fetcher->set_delegate(&delegate);
+
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(server->GetPort(), url)));
+ MessageLoop::current()->Run();
+ if (expected_successful) {
+ // verify the data we get back
+ ASSERT_EQ(kMediumLength, delegate.data.size());
+ for (int i = 0; i < kMediumLength; i += 10) {
+ // Assert so that we don't flood the screen w/ EXPECT errors on failure.
+ ASSERT_EQ(delegate.data.substr(i, 10), "abcdefghij");
+ }
+ }
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, SimpleRedirectTest) {
+ if (this->test_.IsMock())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ for (size_t c = 0; c < arraysize(kRedirectCodes); ++c) {
+ const string url = base::StringPrintf("/redirect/%d/download/%d",
+ kRedirectCodes[c],
+ kMediumLength);
+ RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
+ }
+}
+
+TYPED_TEST(HttpFetcherTest, MaxRedirectTest) {
+ if (this->test_.IsMock())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ string url;
+ for (int r = 0; r < kDownloadMaxRedirects; r++) {
+ url += base::StringPrintf("/redirect/%d",
+ kRedirectCodes[r % arraysize(kRedirectCodes)]);
+ }
+ url += base::StringPrintf("/download/%d", kMediumLength);
+ RedirectTest(server.get(), true, url, this->test_.NewLargeFetcher());
+}
+
+TYPED_TEST(HttpFetcherTest, BeyondMaxRedirectTest) {
+ if (this->test_.IsMock())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ string url;
+ for (int r = 0; r < kDownloadMaxRedirects + 1; r++) {
+ url += base::StringPrintf("/redirect/%d",
+ kRedirectCodes[r % arraysize(kRedirectCodes)]);
+ }
+ url += base::StringPrintf("/download/%d", kMediumLength);
+ RedirectTest(server.get(), false, url, this->test_.NewLargeFetcher());
+}
+
+namespace {
+class MultiHttpFetcherTestDelegate : public HttpFetcherDelegate {
+ public:
+ explicit MultiHttpFetcherTestDelegate(int expected_response_code)
+ : expected_response_code_(expected_response_code) {}
+
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ EXPECT_EQ(fetcher, fetcher_.get());
+ data.append(reinterpret_cast<const char*>(bytes), length);
+ }
+
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_EQ(fetcher, fetcher_.get());
+ EXPECT_EQ(expected_response_code_ != kHttpResponseUndefined, successful);
+ if (expected_response_code_ != 0)
+ EXPECT_EQ(expected_response_code_, fetcher->http_response_code());
+ // Destroy the fetcher (because we're allowed to).
+ fetcher_.reset(nullptr);
+ MessageLoop::current()->BreakLoop();
+ }
+
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+
+ unique_ptr<HttpFetcher> fetcher_;
+ int expected_response_code_;
+ string data;
+};
+
+void MultiTest(HttpFetcher* fetcher_in,
+ FakeHardware* fake_hardware,
+ const string& url,
+ const vector<pair<off_t, off_t>>& ranges,
+ const string& expected_prefix,
+ off_t expected_size,
+ HttpResponseCode expected_response_code) {
+ MultiHttpFetcherTestDelegate delegate(expected_response_code);
+ delegate.fetcher_.reset(fetcher_in);
+
+ MultiRangeHttpFetcher* multi_fetcher =
+ dynamic_cast<MultiRangeHttpFetcher*>(fetcher_in);
+ ASSERT_TRUE(multi_fetcher);
+ multi_fetcher->ClearRanges();
+ for (vector<pair<off_t, off_t>>::const_iterator it = ranges.begin(),
+ e = ranges.end(); it != e; ++it) {
+ string tmp_str = base::StringPrintf("%jd+", it->first);
+ if (it->second > 0) {
+ base::StringAppendF(&tmp_str, "%jd", it->second);
+ multi_fetcher->AddRange(it->first, it->second);
+ } else {
+ base::StringAppendF(&tmp_str, "?");
+ multi_fetcher->AddRange(it->first);
+ }
+ LOG(INFO) << "added range: " << tmp_str;
+ }
+ fake_hardware->SetIsOfficialBuild(false);
+ multi_fetcher->set_delegate(&delegate);
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(StartTransfer, multi_fetcher, url));
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(expected_size, delegate.data.size());
+ EXPECT_EQ(expected_prefix,
+ string(delegate.data.data(), expected_prefix.size()));
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherSimpleTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 25));
+ ranges.push_back(make_pair(99, 0));
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.fake_hardware(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "abcdefghijabcdefghijabcdejabcdefghijabcdef",
+ kBigLength - (99 - 25),
+ kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherLengthLimitTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 24));
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.fake_hardware(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "abcdefghijabcdefghijabcd",
+ 24,
+ kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherMultiEndTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(kBigLength - 2, 0));
+ ranges.push_back(make_pair(kBigLength - 3, 0));
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.fake_hardware(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "ijhij",
+ 5,
+ kHttpResponsePartialContent);
+}
+
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherInsufficientTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(kBigLength - 2, 4));
+ for (int i = 0; i < 2; ++i) {
+ LOG(INFO) << "i = " << i;
+ MultiTest(this->test_.NewLargeFetcher(),
+ this->test_.fake_hardware(),
+ this->test_.BigUrl(server->GetPort()),
+ ranges,
+ "ij",
+ 2,
+ kHttpResponseUndefined);
+ ranges.push_back(make_pair(0, 5));
+ }
+}
+
+// Issue #18143: when a fetch of a secondary chunk out of a chain, then it
+// should retry with other proxies listed before giving up.
+//
+// (1) successful recovery: The offset fetch will fail twice but succeed with
+// the third proxy.
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherErrorIfOffsetRecoverableTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 25));
+ ranges.push_back(make_pair(99, 0));
+ MultiTest(this->test_.NewLargeFetcher(3),
+ this->test_.fake_hardware(),
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/error-if-offset/%d/2",
+ kBigLength)),
+ ranges,
+ "abcdefghijabcdefghijabcdejabcdefghijabcdef",
+ kBigLength - (99 - 25),
+ kHttpResponsePartialContent);
+}
+
+// (2) unsuccessful recovery: The offset fetch will fail repeatedly. The
+// fetcher will signal a (failed) completed transfer to the delegate.
+TYPED_TEST(HttpFetcherTest, MultiHttpFetcherErrorIfOffsetUnrecoverableTest) {
+ if (!this->test_.IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(this->test_.CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ vector<pair<off_t, off_t>> ranges;
+ ranges.push_back(make_pair(0, 25));
+ ranges.push_back(make_pair(99, 0));
+ MultiTest(this->test_.NewLargeFetcher(2),
+ this->test_.fake_hardware(),
+ LocalServerUrlForPath(server->GetPort(),
+ base::StringPrintf("/error-if-offset/%d/3",
+ kBigLength)),
+ ranges,
+ "abcdefghijabcdefghijabcde", // only received the first chunk
+ 25,
+ kHttpResponseUndefined);
+}
+
+
+
+namespace {
+class BlockedTransferTestDelegate : public HttpFetcherDelegate {
+ public:
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override {
+ ADD_FAILURE();
+ }
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override {
+ EXPECT_FALSE(successful);
+ MessageLoop::current()->BreakLoop();
+ }
+ void TransferTerminated(HttpFetcher* fetcher) override {
+ ADD_FAILURE();
+ }
+};
+
+void BlockedTransferTestHelper(AnyHttpFetcherTest* fetcher_test,
+ bool is_official_build) {
+ if (fetcher_test->IsMock() || fetcher_test->IsMulti())
+ return;
+
+ unique_ptr<HttpServer> server(fetcher_test->CreateServer());
+ ASSERT_TRUE(server->started_);
+
+ BlockedTransferTestDelegate delegate;
+ unique_ptr<HttpFetcher> fetcher(fetcher_test->NewLargeFetcher());
+ LOG(INFO) << "is_official_build: " << is_official_build;
+ // NewLargeFetcher creates the HttpFetcher* with a FakeSystemState.
+ fetcher_test->fake_hardware()->SetIsOfficialBuild(is_official_build);
+ fetcher->set_delegate(&delegate);
+
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ StartTransfer,
+ fetcher.get(),
+ LocalServerUrlForPath(server->GetPort(),
+ fetcher_test->SmallUrl(server->GetPort()))));
+ MessageLoop::current()->Run();
+}
+} // namespace
+
+TYPED_TEST(HttpFetcherTest, BlockedTransferTest) {
+ BlockedTransferTestHelper(&this->test_, false);
+}
+
+TYPED_TEST(HttpFetcherTest, BlockedTransferOfficialBuildTest) {
+ BlockedTransferTestHelper(&this->test_, true);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/hwid_override.cc b/common/hwid_override.cc
new file mode 100644
index 0000000..8800e94
--- /dev/null
+++ b/common/hwid_override.cc
@@ -0,0 +1,46 @@
+//
+// 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.
+//
+
+#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 {
+
+const char HwidOverride::kHwidOverrideKey[] = "HWID_OVERRIDE";
+
+HwidOverride::HwidOverride() {}
+
+HwidOverride::~HwidOverride() {}
+
+string HwidOverride::Read(const base::FilePath& root) {
+ brillo::KeyValueStore lsb_release;
+ lsb_release.Load(base::FilePath(root.value() + "/etc/lsb-release"));
+ string result;
+ if (lsb_release.GetString(kHwidOverrideKey, &result))
+ return result;
+ return "";
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/hwid_override.h b/common/hwid_override.h
new file mode 100644
index 0000000..d39b572
--- /dev/null
+++ b/common/hwid_override.h
@@ -0,0 +1,46 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_HWID_OVERRIDE_H_
+#define UPDATE_ENGINE_COMMON_HWID_OVERRIDE_H_
+
+#include <map>
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+// Class that allows HWID to be read from <root>/etc/lsb-release.
+class HwidOverride {
+ public:
+ HwidOverride();
+ ~HwidOverride();
+
+ // Read HWID from an /etc/lsb-release file under given root.
+ // An empty string is returned if there is any error.
+ static std::string Read(const base::FilePath& root);
+
+ static const char kHwidOverrideKey[];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HwidOverride);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_HWID_OVERRIDE_H_
diff --git a/common/hwid_override_unittest.cc b/common/hwid_override_unittest.cc
new file mode 100644
index 0000000..fff64bc
--- /dev/null
+++ b/common/hwid_override_unittest.cc
@@ -0,0 +1,67 @@
+//
+// 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/common/hwid_override.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>
+
+namespace chromeos_update_engine {
+
+class HwidOverrideTest : public ::testing::Test {
+ public:
+ HwidOverrideTest() {}
+ ~HwidOverrideTest() override = default;
+
+ void SetUp() override {
+ ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
+ ASSERT_TRUE(base::CreateDirectory(tempdir_.path().Append("etc")));
+ }
+
+ protected:
+ base::ScopedTempDir tempdir_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HwidOverrideTest);
+};
+
+TEST_F(HwidOverrideTest, ReadGood) {
+ std::string expected_hwid("expected");
+ std::string keyval(HwidOverride::kHwidOverrideKey);
+ keyval += ("=" + expected_hwid);
+ ASSERT_EQ(base::WriteFile(tempdir_.path().Append("etc/lsb-release"),
+ keyval.c_str(), keyval.length()),
+ keyval.length());
+ EXPECT_EQ(expected_hwid, HwidOverride::Read(tempdir_.path()));
+}
+
+TEST_F(HwidOverrideTest, ReadNothing) {
+ std::string keyval("SOMETHING_ELSE=UNINTERESTING");
+ ASSERT_EQ(base::WriteFile(tempdir_.path().Append("etc/lsb-release"),
+ keyval.c_str(), keyval.length()),
+ keyval.length());
+ EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.path()));
+}
+
+TEST_F(HwidOverrideTest, ReadFailure) {
+ EXPECT_EQ(std::string(), HwidOverride::Read(tempdir_.path()));
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/libcurl_http_fetcher.cc b/common/libcurl_http_fetcher.cc
new file mode 100644
index 0000000..13784fa
--- /dev/null
+++ b/common/libcurl_http_fetcher.cc
@@ -0,0 +1,587 @@
+//
+// 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/common/libcurl_http_fetcher.h"
+
+#include <algorithm>
+#include <string>
+
+#include <base/bind.h>
+#include <base/format_macros.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/certificate_checker.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/platform_constants.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using std::max;
+using std::string;
+
+// This is a concrete implementation of HttpFetcher that uses libcurl to do the
+// http work.
+
+namespace chromeos_update_engine {
+
+namespace {
+const int kNoNetworkRetrySeconds = 10;
+} // namespace
+
+LibcurlHttpFetcher::LibcurlHttpFetcher(ProxyResolver* proxy_resolver,
+ HardwareInterface* hardware)
+ : HttpFetcher(proxy_resolver), hardware_(hardware) {
+ // Dev users want a longer timeout (180 seconds) because they may
+ // be waiting on the dev server to build an image.
+ if (!hardware_->IsOfficialBuild())
+ low_speed_time_seconds_ = kDownloadDevModeLowSpeedTimeSeconds;
+ if (!hardware_->IsOOBEComplete(nullptr))
+ max_retry_count_ = kDownloadMaxRetryCountOobeNotComplete;
+}
+
+LibcurlHttpFetcher::~LibcurlHttpFetcher() {
+ LOG_IF(ERROR, transfer_in_progress_)
+ << "Destroying the fetcher while a transfer is in progress.";
+ CleanUp();
+}
+
+bool LibcurlHttpFetcher::GetProxyType(const string& proxy,
+ curl_proxytype* out_type) {
+ if (base::StartsWithASCII(proxy, "socks5://", true) ||
+ base::StartsWithASCII(proxy, "socks://", true)) {
+ *out_type = CURLPROXY_SOCKS5_HOSTNAME;
+ return true;
+ }
+ if (base::StartsWithASCII(proxy, "socks4://", true)) {
+ *out_type = CURLPROXY_SOCKS4A;
+ return true;
+ }
+ if (base::StartsWithASCII(proxy, "http://", true) ||
+ base::StartsWithASCII(proxy, "https://", true)) {
+ *out_type = CURLPROXY_HTTP;
+ return true;
+ }
+ if (base::StartsWithASCII(proxy, kNoProxy, true)) {
+ // known failure case. don't log.
+ return false;
+ }
+ LOG(INFO) << "Unknown proxy type: " << proxy;
+ return false;
+}
+
+void LibcurlHttpFetcher::ResumeTransfer(const string& url) {
+ LOG(INFO) << "Starting/Resuming transfer";
+ CHECK(!transfer_in_progress_);
+ url_ = url;
+ curl_multi_handle_ = curl_multi_init();
+ CHECK(curl_multi_handle_);
+
+ curl_handle_ = curl_easy_init();
+ CHECK(curl_handle_);
+
+ CHECK(HasProxy());
+ bool is_direct = (GetCurrentProxy() == kNoProxy);
+ LOG(INFO) << "Using proxy: " << (is_direct ? "no" : "yes");
+ if (is_direct) {
+ CHECK_EQ(curl_easy_setopt(curl_handle_,
+ CURLOPT_PROXY,
+ ""), CURLE_OK);
+ } else {
+ CHECK_EQ(curl_easy_setopt(curl_handle_,
+ CURLOPT_PROXY,
+ GetCurrentProxy().c_str()), CURLE_OK);
+ // Curl seems to require us to set the protocol
+ curl_proxytype type;
+ if (GetProxyType(GetCurrentProxy(), &type)) {
+ CHECK_EQ(curl_easy_setopt(curl_handle_,
+ CURLOPT_PROXYTYPE,
+ type), CURLE_OK);
+ }
+ }
+
+ if (post_data_set_) {
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
+ post_data_.data()),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
+ post_data_.size()),
+ CURLE_OK);
+
+ // Set the Content-Type HTTP header, if one was specifically set.
+ CHECK(!curl_http_headers_);
+ if (post_content_type_ != kHttpContentTypeUnspecified) {
+ const string content_type_attr =
+ base::StringPrintf("Content-Type: %s",
+ GetHttpContentTypeString(post_content_type_));
+ curl_http_headers_ = curl_slist_append(nullptr,
+ content_type_attr.c_str());
+ CHECK(curl_http_headers_);
+ CHECK_EQ(
+ curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER,
+ curl_http_headers_),
+ CURLE_OK);
+ } else {
+ LOG(WARNING) << "no content type set, using libcurl default";
+ }
+ }
+
+ if (bytes_downloaded_ > 0 || download_length_) {
+ // Resume from where we left off.
+ resume_offset_ = bytes_downloaded_;
+ CHECK_GE(resume_offset_, 0);
+
+ // Compute end offset, if one is specified. As per HTTP specification, this
+ // is an inclusive boundary. Make sure it doesn't overflow.
+ size_t end_offset = 0;
+ if (download_length_) {
+ end_offset = static_cast<size_t>(resume_offset_) + download_length_ - 1;
+ CHECK_LE((size_t) resume_offset_, end_offset);
+ }
+
+ // Create a string representation of the desired range.
+ string range_str = base::StringPrintf(
+ "%" PRIu64 "-", static_cast<uint64_t>(resume_offset_));
+ if (end_offset)
+ range_str += std::to_string(end_offset);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_RANGE, range_str.c_str()),
+ CURLE_OK);
+ }
+
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
+ StaticLibcurlWrite), CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()),
+ CURLE_OK);
+
+ // If the connection drops under |low_speed_limit_bps_| (10
+ // bytes/sec by default) for |low_speed_time_seconds_| (90 seconds,
+ // 180 on non-official builds), reconnect.
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_LIMIT,
+ low_speed_limit_bps_),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_TIME,
+ low_speed_time_seconds_),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CONNECTTIMEOUT,
+ connect_timeout_seconds_),
+ CURLE_OK);
+
+ // By default, libcurl doesn't follow redirections. Allow up to
+ // |kDownloadMaxRedirects| redirections.
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_FOLLOWLOCATION, 1), CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_MAXREDIRS,
+ kDownloadMaxRedirects),
+ CURLE_OK);
+
+ // Lock down the appropriate curl options for HTTP or HTTPS depending on
+ // the url.
+ if (hardware_->IsOfficialBuild()) {
+ if (base::StartsWithASCII(url_, "http://", false))
+ SetCurlOptionsForHttp();
+ else
+ SetCurlOptionsForHttps();
+ } else {
+ LOG(INFO) << "Not setting http(s) curl options because we are "
+ << "running a dev/test image";
+ }
+
+ CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK);
+ transfer_in_progress_ = true;
+}
+
+// Lock down only the protocol in case of HTTP.
+void LibcurlHttpFetcher::SetCurlOptionsForHttp() {
+ LOG(INFO) << "Setting up curl options for HTTP";
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_HTTP),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS,
+ CURLPROTO_HTTP),
+ CURLE_OK);
+}
+
+// Security lock-down in official builds: makes sure that peer certificate
+// verification is enabled, restricts the set of trusted certificates,
+// restricts protocols to HTTPS, restricts ciphers to HIGH.
+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_CAPATH,
+ constants::kCACertificatesPath),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS,
+ CURLPROTO_HTTPS),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CIPHER_LIST, "HIGH:!ADH"),
+ CURLE_OK);
+ if (server_to_check_ != ServerToCheck::kNone) {
+ CHECK_EQ(
+ curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_DATA, &server_to_check_),
+ CURLE_OK);
+ CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSL_CTX_FUNCTION,
+ CertificateChecker::ProcessSSLContext),
+ CURLE_OK);
+ }
+}
+
+
+// Begins the transfer, which must not have already been started.
+void LibcurlHttpFetcher::BeginTransfer(const string& url) {
+ CHECK(!transfer_in_progress_);
+ url_ = url;
+ auto closure = base::Bind(&LibcurlHttpFetcher::ProxiesResolved,
+ base::Unretained(this));
+ if (!ResolveProxiesForUrl(url_, closure)) {
+ LOG(ERROR) << "Couldn't resolve proxies";
+ if (delegate_)
+ delegate_->TransferComplete(this, false);
+ }
+}
+
+void LibcurlHttpFetcher::ProxiesResolved() {
+ transfer_size_ = -1;
+ resume_offset_ = 0;
+ retry_count_ = 0;
+ no_network_retry_count_ = 0;
+ http_response_code_ = 0;
+ terminate_requested_ = false;
+ sent_byte_ = false;
+ ResumeTransfer(url_);
+ CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::ForceTransferTermination() {
+ CleanUp();
+ if (delegate_) {
+ // Note that after the callback returns this object may be destroyed.
+ delegate_->TransferTerminated(this);
+ }
+}
+
+void LibcurlHttpFetcher::TerminateTransfer() {
+ if (in_write_callback_) {
+ terminate_requested_ = true;
+ } else {
+ ForceTransferTermination();
+ }
+}
+
+void LibcurlHttpFetcher::CurlPerformOnce() {
+ CHECK(transfer_in_progress_);
+ int running_handles = 0;
+ CURLMcode retcode = CURLM_CALL_MULTI_PERFORM;
+
+ // libcurl may request that we immediately call curl_multi_perform after it
+ // returns, so we do. libcurl promises that curl_multi_perform will not block.
+ while (CURLM_CALL_MULTI_PERFORM == retcode) {
+ retcode = curl_multi_perform(curl_multi_handle_, &running_handles);
+ if (terminate_requested_) {
+ ForceTransferTermination();
+ return;
+ }
+ }
+ if (0 == running_handles) {
+ GetHttpResponseCode();
+ if (http_response_code_) {
+ LOG(INFO) << "HTTP response code: " << http_response_code_;
+ no_network_retry_count_ = 0;
+ } else {
+ LOG(ERROR) << "Unable to get http response code.";
+ }
+
+ // we're done!
+ CleanUp();
+
+ // 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
+ // is online/offline and return an appropriate error code.
+ if (!sent_byte_ &&
+ http_response_code_ == 0 &&
+ no_network_retry_count_ < no_network_max_retries_) {
+ no_network_retry_count_++;
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+ base::Unretained(this)),
+ TimeDelta::FromSeconds(kNoNetworkRetrySeconds));
+ LOG(INFO) << "No HTTP response, retry " << no_network_retry_count_;
+ return;
+ }
+
+ if ((!sent_byte_ && !IsHttpResponseSuccess()) || IsHttpResponseError()) {
+ // The transfer completed w/ error and we didn't get any bytes.
+ // If we have another proxy to try, try that.
+ //
+ // TODO(garnold) in fact there are two separate cases here: one case is an
+ // other-than-success return code (including no return code) and no
+ // received bytes, which is necessary due to the way callbacks are
+ // currently processing error conditions; the second is an explicit HTTP
+ // error code, where some data may have been received (as in the case of a
+ // semi-successful multi-chunk fetch). This is a confusing behavior and
+ // should be unified into a complete, coherent interface.
+ LOG(INFO) << "Transfer resulted in an error (" << http_response_code_
+ << "), " << bytes_downloaded_ << " bytes downloaded";
+
+ PopProxy(); // Delete the proxy we just gave up on.
+
+ if (HasProxy()) {
+ // We have another proxy. Retry immediately.
+ LOG(INFO) << "Retrying with next proxy setting";
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+ base::Unretained(this)));
+ } else {
+ // Out of proxies. Give up.
+ LOG(INFO) << "No further proxies, indicating transfer complete";
+ if (delegate_)
+ delegate_->TransferComplete(this, false); // signal fail
+ }
+ } else if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) {
+ retry_count_++;
+ LOG(INFO) << "Transfer interrupted after downloading "
+ << bytes_downloaded_ << " of " << transfer_size_ << " bytes. "
+ << transfer_size_ - bytes_downloaded_ << " bytes remaining "
+ << "after " << retry_count_ << " attempt(s)";
+
+ if (retry_count_ > max_retry_count_) {
+ LOG(INFO) << "Reached max attempts (" << retry_count_ << ")";
+ if (delegate_)
+ delegate_->TransferComplete(this, false); // signal fail
+ } else {
+ // Need to restart transfer
+ LOG(INFO) << "Restarting transfer to download the remaining bytes";
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&LibcurlHttpFetcher::RetryTimeoutCallback,
+ base::Unretained(this)),
+ TimeDelta::FromSeconds(retry_seconds_));
+ }
+ } else {
+ LOG(INFO) << "Transfer completed (" << http_response_code_
+ << "), " << bytes_downloaded_ << " bytes downloaded";
+ if (delegate_) {
+ bool success = IsHttpResponseSuccess();
+ delegate_->TransferComplete(this, success);
+ }
+ }
+ } else {
+ // set up callback
+ SetupMessageLoopSources();
+ }
+}
+
+size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
+ // Update HTTP response first.
+ GetHttpResponseCode();
+ const size_t payload_size = size * nmemb;
+
+ // Do nothing if no payload or HTTP response is an error.
+ if (payload_size == 0 || !IsHttpResponseSuccess()) {
+ LOG(INFO) << "HTTP response unsuccessful (" << http_response_code_
+ << ") or no payload (" << payload_size << "), nothing to do";
+ return 0;
+ }
+
+ sent_byte_ = true;
+ {
+ double transfer_size_double;
+ CHECK_EQ(curl_easy_getinfo(curl_handle_,
+ CURLINFO_CONTENT_LENGTH_DOWNLOAD,
+ &transfer_size_double), CURLE_OK);
+ off_t new_transfer_size = static_cast<off_t>(transfer_size_double);
+ if (new_transfer_size > 0) {
+ transfer_size_ = resume_offset_ + new_transfer_size;
+ }
+ }
+ bytes_downloaded_ += payload_size;
+ in_write_callback_ = true;
+ if (delegate_)
+ delegate_->ReceivedBytes(this, ptr, payload_size);
+ in_write_callback_ = false;
+ return payload_size;
+}
+
+void LibcurlHttpFetcher::Pause() {
+ CHECK(curl_handle_);
+ CHECK(transfer_in_progress_);
+ CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_ALL), CURLE_OK);
+}
+
+void LibcurlHttpFetcher::Unpause() {
+ CHECK(curl_handle_);
+ CHECK(transfer_in_progress_);
+ CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_CONT), CURLE_OK);
+}
+
+// This method sets up callbacks with the MessageLoop.
+void LibcurlHttpFetcher::SetupMessageLoopSources() {
+ fd_set fd_read;
+ fd_set fd_write;
+ fd_set fd_exc;
+
+ FD_ZERO(&fd_read);
+ FD_ZERO(&fd_write);
+ FD_ZERO(&fd_exc);
+
+ int fd_max = 0;
+
+ // Ask libcurl for the set of file descriptors we should track on its
+ // behalf.
+ CHECK_EQ(curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
+ &fd_exc, &fd_max), CURLM_OK);
+
+ // 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 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.
+ 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.
+ bool is_exc = FD_ISSET(fd, &fd_exc) != 0;
+ bool must_track[2] = {
+ 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();
+
+ 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);
+ }
+ continue;
+ }
+
+ // If we are already tracking this fd, continue -- nothing to do.
+ if (tracked)
+ 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)));
+
+ static int io_counter = 0;
+ io_counter++;
+ if (io_counter % 50 == 0) {
+ LOG(INFO) << "io_counter = " << io_counter;
+ }
+ }
+ }
+
+ // Set up a timeout callback for libcurl.
+ if (timeout_id_ == MessageLoop::kTaskIdNull) {
+ LOG(INFO) << "Setting up timeout source: " << idle_seconds_ << " seconds.";
+ timeout_id_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&LibcurlHttpFetcher::TimeoutCallback,
+ base::Unretained(this)),
+ TimeDelta::FromSeconds(idle_seconds_));
+ }
+}
+
+void LibcurlHttpFetcher::RetryTimeoutCallback() {
+ ResumeTransfer(url_);
+ CurlPerformOnce();
+}
+
+void LibcurlHttpFetcher::TimeoutCallback() {
+ // We always re-schedule the callback, even if we don't want to be called
+ // anymore. We will remove the event source separately if we don't want to
+ // be called back.
+ timeout_id_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&LibcurlHttpFetcher::TimeoutCallback, base::Unretained(this)),
+ TimeDelta::FromSeconds(idle_seconds_));
+
+ // CurlPerformOnce() may call CleanUp(), so we need to schedule our callback
+ // first, since it could be canceled by this call.
+ if (transfer_in_progress_)
+ CurlPerformOnce();
+}
+
+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();
+ }
+
+ if (curl_http_headers_) {
+ curl_slist_free_all(curl_http_headers_);
+ curl_http_headers_ = nullptr;
+ }
+ if (curl_handle_) {
+ if (curl_multi_handle_) {
+ CHECK_EQ(curl_multi_remove_handle(curl_multi_handle_, curl_handle_),
+ CURLM_OK);
+ }
+ curl_easy_cleanup(curl_handle_);
+ curl_handle_ = nullptr;
+ }
+ if (curl_multi_handle_) {
+ CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK);
+ curl_multi_handle_ = nullptr;
+ }
+ transfer_in_progress_ = false;
+}
+
+void LibcurlHttpFetcher::GetHttpResponseCode() {
+ long http_response_code = 0; // NOLINT(runtime/int) - curl needs long.
+ if (curl_easy_getinfo(curl_handle_,
+ CURLINFO_RESPONSE_CODE,
+ &http_response_code) == CURLE_OK) {
+ http_response_code_ = static_cast<int>(http_response_code);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/libcurl_http_fetcher.h b/common/libcurl_http_fetcher.h
new file mode 100644
index 0000000..900c973
--- /dev/null
+++ b/common/libcurl_http_fetcher.h
@@ -0,0 +1,249 @@
+//
+// 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_COMMON_LIBCURL_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_LIBCURL_HTTP_FETCHER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+
+#include <curl/curl.h>
+
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/certificate_checker.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/http_fetcher.h"
+
+// This is a concrete implementation of HttpFetcher that uses libcurl to do the
+// http work.
+
+namespace chromeos_update_engine {
+
+class LibcurlHttpFetcher : public HttpFetcher {
+ public:
+ LibcurlHttpFetcher(ProxyResolver* proxy_resolver,
+ HardwareInterface* hardware);
+
+ // Cleans up all internal state. Does not notify delegate
+ ~LibcurlHttpFetcher() override;
+
+ void SetOffset(off_t offset) override { bytes_downloaded_ = offset; }
+
+ void SetLength(size_t length) override { download_length_ = length; }
+ void UnsetLength() override { SetLength(0); }
+
+ // Begins the transfer if it hasn't already begun.
+ void BeginTransfer(const std::string& url) override;
+
+ // If the transfer is in progress, aborts the transfer early. The transfer
+ // cannot be resumed.
+ void TerminateTransfer() override;
+
+ // Suspend the transfer by calling curl_easy_pause(CURLPAUSE_ALL).
+ void Pause() override;
+
+ // Resume the transfer by calling curl_easy_pause(CURLPAUSE_CONT).
+ void Unpause() override;
+
+ // Libcurl sometimes asks to be called back after some time while
+ // leaving that time unspecified. In that case, we pick a reasonable
+ // default of one second, but it can be overridden here. This is
+ // primarily useful for testing.
+ // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html:
+ // if libcurl returns a -1 timeout here, it just means that libcurl
+ // currently has no stored timeout value. You must not wait too long
+ // (more than a few seconds perhaps) before you call
+ // curl_multi_perform() again.
+ void set_idle_seconds(int seconds) override { idle_seconds_ = seconds; }
+
+ // Sets the retry timeout. Useful for testing.
+ void set_retry_seconds(int seconds) override { retry_seconds_ = seconds; }
+
+ void set_no_network_max_retries(int retries) {
+ no_network_max_retries_ = retries;
+ }
+
+ void set_server_to_check(ServerToCheck server_to_check) {
+ server_to_check_ = server_to_check;
+ }
+
+ size_t GetBytesDownloaded() override {
+ return static_cast<size_t>(bytes_downloaded_);
+ }
+
+ void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
+ low_speed_limit_bps_ = low_speed_bps;
+ low_speed_time_seconds_ = low_speed_sec;
+ }
+
+ void set_connect_timeout(int connect_timeout_seconds) override {
+ connect_timeout_seconds_ = connect_timeout_seconds;
+ }
+
+ void set_max_retry_count(int max_retry_count) override {
+ max_retry_count_ = max_retry_count;
+ }
+
+ private:
+ // Callback for when proxy resolution has completed. This begins the
+ // transfer.
+ void ProxiesResolved();
+
+ // Asks libcurl for the http response code and stores it in the object.
+ void GetHttpResponseCode();
+
+ // Checks whether stored HTTP response is within the success range.
+ inline bool IsHttpResponseSuccess() {
+ return (http_response_code_ >= 200 && http_response_code_ < 300);
+ }
+
+ // Checks whether stored HTTP response is within the error range. This
+ // includes both errors with the request (4xx) and server errors (5xx).
+ inline bool IsHttpResponseError() {
+ return (http_response_code_ >= 400 && http_response_code_ < 600);
+ }
+
+ // Resumes a transfer where it left off. This will use the
+ // HTTP Range: header to make a new connection from where the last
+ // left off.
+ virtual void ResumeTransfer(const std::string& url);
+
+ void TimeoutCallback();
+ void RetryTimeoutCallback();
+
+ // Calls into curl_multi_perform to let libcurl do its work. Returns after
+ // curl_multi_perform is finished, which may actually be after more than
+ // one call to curl_multi_perform. This method will set up the message
+ // loop with sources for future work that libcurl will do.
+ // This method will not block.
+ // Returns true if we should resume immediately after this call.
+ void CurlPerformOnce();
+
+ // Sets up message loop sources as needed by libcurl. This is generally
+ // the file descriptor of the socket and a timer in case nothing happens
+ // on the fds.
+ void SetupMessageLoopSources();
+
+ // Callback called by libcurl when new data has arrived on the transfer
+ size_t LibcurlWrite(void *ptr, size_t size, size_t nmemb);
+ static size_t StaticLibcurlWrite(void *ptr, size_t size,
+ size_t nmemb, void *stream) {
+ return reinterpret_cast<LibcurlHttpFetcher*>(stream)->
+ LibcurlWrite(ptr, size, nmemb);
+ }
+
+ // Cleans up the following if they are non-null:
+ // curl(m) handles, fd_task_maps_, timeout_id_.
+ void CleanUp();
+
+ // Force terminate the transfer. This will invoke the delegate's (if any)
+ // TransferTerminated callback so, after returning, this fetcher instance may
+ // be destroyed.
+ void ForceTransferTermination();
+
+ // Sets the curl options for HTTP URL.
+ void SetCurlOptionsForHttp();
+
+ // Sets the curl options for HTTPS URL.
+ void SetCurlOptionsForHttps();
+
+ // Convert a proxy URL into a curl proxy type, if applicable. Returns true iff
+ // conversion was successful, false otherwise (in which case nothing is
+ // written to |out_type|).
+ bool GetProxyType(const std::string& proxy, curl_proxytype* out_type);
+
+ // Hardware interface used to query dev-mode and official build settings.
+ HardwareInterface* hardware_;
+
+ // Handles for the libcurl library
+ CURLM* curl_multi_handle_{nullptr};
+ CURL* curl_handle_{nullptr};
+ struct curl_slist* curl_http_headers_{nullptr};
+
+ // Lists of all read(0)/write(1) file descriptors that we're waiting on from
+ // 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];
+
+ // The TaskId of the timer we're waiting on. kTaskIdNull if we are not waiting
+ // on it.
+ brillo::MessageLoop::TaskId timeout_id_{brillo::MessageLoop::kTaskIdNull};
+
+ bool transfer_in_progress_{false};
+
+ // The transfer size. -1 if not known.
+ off_t transfer_size_{0};
+
+ // How many bytes have been downloaded and sent to the delegate.
+ off_t bytes_downloaded_{0};
+
+ // The remaining maximum number of bytes to download. Zero represents an
+ // unspecified length.
+ size_t download_length_{0};
+
+ // If we resumed an earlier transfer, data offset that we used for the
+ // new connection. 0 otherwise.
+ // In this class, resume refers to resuming a dropped HTTP connection,
+ // not to resuming an interrupted download.
+ off_t resume_offset_{0};
+
+ // Number of resumes performed so far and the max allowed.
+ int retry_count_{0};
+ int max_retry_count_{kDownloadMaxRetryCount};
+
+ // Seconds to wait before retrying a resume.
+ int retry_seconds_{20};
+
+ // Number of resumes due to no network (e.g., HTTP response code 0).
+ int no_network_retry_count_{0};
+ int no_network_max_retries_{0};
+
+ // Seconds to wait before asking libcurl to "perform".
+ int idle_seconds_{1};
+
+ // If true, we are currently performing a write callback on the delegate.
+ bool in_write_callback_{false};
+
+ // If true, we have returned at least one byte in the write callback
+ // to the delegate.
+ bool sent_byte_{false};
+
+ // We can't clean everything up while we're in a write callback, so
+ // if we get a terminate request, queue it until we can handle it.
+ bool terminate_requested_{false};
+
+ // The ServerToCheck used when checking this connection's certificate. If no
+ // certificate check needs to be performed, this should be set to
+ // ServerToCheck::kNone.
+ ServerToCheck server_to_check_{ServerToCheck::kNone};
+
+ int low_speed_limit_bps_{kDownloadLowSpeedLimitBps};
+ int low_speed_time_seconds_{kDownloadLowSpeedTimeSeconds};
+ int connect_timeout_seconds_{kDownloadConnectTimeoutSeconds};
+ int num_max_retries_;
+
+ DISALLOW_COPY_AND_ASSIGN(LibcurlHttpFetcher);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_LIBCURL_HTTP_FETCHER_H_
diff --git a/common/mock_certificate_checker.h b/common/mock_certificate_checker.h
new file mode 100644
index 0000000..1f55ca1
--- /dev/null
+++ b/common/mock_certificate_checker.h
@@ -0,0 +1,38 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_MOCK_CERTIFICATE_CHECKER_H_
+#define UPDATE_ENGINE_COMMON_MOCK_CERTIFICATE_CHECKER_H_
+
+#include <gmock/gmock.h>
+#include <openssl/ssl.h>
+
+#include "update_engine/common/certificate_checker.h"
+
+namespace chromeos_update_engine {
+
+class MockOpenSSLWrapper : public OpenSSLWrapper {
+ public:
+ MOCK_CONST_METHOD4(GetCertificateDigest,
+ bool(X509_STORE_CTX* x509_ctx,
+ int* out_depth,
+ unsigned int* out_digest_length,
+ uint8_t* out_digest));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_MOCK_CERTIFICATE_CHECKER_H_
diff --git a/common/mock_hardware.h b/common/mock_hardware.h
new file mode 100644
index 0000000..451af91
--- /dev/null
+++ b/common/mock_hardware.h
@@ -0,0 +1,90 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_MOCK_HARDWARE_H_
+#define UPDATE_ENGINE_COMMON_MOCK_HARDWARE_H_
+
+#include <string>
+
+#include "update_engine/common/fake_hardware.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+// A mocked, fake implementation of HardwareInterface.
+class MockHardware : public HardwareInterface {
+ public:
+ MockHardware() {
+ // Delegate all calls to the fake instance
+ ON_CALL(*this, IsOfficialBuild())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::IsOfficialBuild));
+ ON_CALL(*this, IsNormalBootMode())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::IsNormalBootMode));
+ ON_CALL(*this, IsOOBEComplete(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::IsOOBEComplete));
+ 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, GetPowerwashCount())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetPowerwashCount));
+ ON_CALL(*this, GetNonVolatileDirectory(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetNonVolatileDirectory));
+ ON_CALL(*this, GetPowerwashSafeDirectory(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeHardware::GetPowerwashSafeDirectory));
+ }
+
+ ~MockHardware() override = default;
+
+ // Hardware overrides.
+ MOCK_CONST_METHOD0(IsOfficialBuild, bool());
+ MOCK_CONST_METHOD0(IsNormalBootMode, 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(GetPowerwashCount, int());
+ MOCK_CONST_METHOD1(GetNonVolatileDirectory, bool(base::FilePath*));
+ MOCK_CONST_METHOD1(GetPowerwashSafeDirectory, bool(base::FilePath*));
+
+ // Returns a reference to the underlying FakeHardware.
+ FakeHardware& fake() {
+ return fake_;
+ }
+
+ private:
+ // The underlying FakeHardware.
+ FakeHardware fake_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockHardware);
+};
+
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_MOCK_HARDWARE_H_
diff --git a/common/mock_http_fetcher.cc b/common/mock_http_fetcher.cc
new file mode 100644
index 0000000..f3fa70d
--- /dev/null
+++ b/common/mock_http_fetcher.cc
@@ -0,0 +1,149 @@
+//
+// 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/common/mock_http_fetcher.h"
+
+#include <algorithm>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+// This is a mock implementation of HttpFetcher which is useful for testing.
+
+using brillo::MessageLoop;
+using std::min;
+
+namespace chromeos_update_engine {
+
+MockHttpFetcher::~MockHttpFetcher() {
+ CHECK(timeout_id_ == MessageLoop::kTaskIdNull) <<
+ "Call TerminateTransfer() before dtor.";
+}
+
+void MockHttpFetcher::BeginTransfer(const std::string& url) {
+ EXPECT_FALSE(never_use_);
+ if (fail_transfer_ || data_.empty()) {
+ // No data to send, just notify of completion..
+ SignalTransferComplete();
+ return;
+ }
+ if (sent_size_ < data_.size())
+ SendData(true);
+}
+
+// Returns false on one condition: If timeout_id_ was already set
+// and it needs to be deleted by the caller. If timeout_id_ is null
+// when this function is called, this function will always return true.
+bool MockHttpFetcher::SendData(bool skip_delivery) {
+ if (fail_transfer_) {
+ SignalTransferComplete();
+ return timeout_id_ != MessageLoop::kTaskIdNull;
+ }
+
+ CHECK_LT(sent_size_, data_.size());
+ if (!skip_delivery) {
+ const size_t chunk_size = min(kMockHttpFetcherChunkSize,
+ data_.size() - sent_size_);
+ CHECK(delegate_);
+ delegate_->ReceivedBytes(this, &data_[sent_size_], chunk_size);
+ // We may get terminated in the callback.
+ if (sent_size_ == data_.size()) {
+ LOG(INFO) << "Terminated in the ReceivedBytes callback.";
+ return timeout_id_ != MessageLoop::kTaskIdNull;
+ }
+ sent_size_ += chunk_size;
+ CHECK_LE(sent_size_, data_.size());
+ if (sent_size_ == data_.size()) {
+ // We've sent all the data. Notify of success.
+ SignalTransferComplete();
+ }
+ }
+
+ if (paused_) {
+ // If we're paused, we should return true if timeout_id_ is set,
+ // since we need the caller to delete it.
+ return timeout_id_ != MessageLoop::kTaskIdNull;
+ }
+
+ if (timeout_id_ != MessageLoop::kTaskIdNull) {
+ // we still need a timeout if there's more data to send
+ return sent_size_ < data_.size();
+ } else if (sent_size_ < data_.size()) {
+ // we don't have a timeout source and we need one
+ timeout_id_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(10));
+ }
+ return true;
+}
+
+void MockHttpFetcher::TimeoutCallback() {
+ CHECK(!paused_);
+ if (SendData(false)) {
+ // We need to re-schedule the timeout.
+ timeout_id_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(10));
+ } else {
+ timeout_id_ = MessageLoop::kTaskIdNull;
+ }
+}
+
+// If the transfer is in progress, aborts the transfer early.
+// The transfer cannot be resumed.
+void MockHttpFetcher::TerminateTransfer() {
+ LOG(INFO) << "Terminating transfer.";
+ sent_size_ = data_.size();
+ // Kill any timeout, it is ok to call with kTaskIdNull.
+ MessageLoop::current()->CancelTask(timeout_id_);
+ timeout_id_ = MessageLoop::kTaskIdNull;
+ delegate_->TransferTerminated(this);
+}
+
+void MockHttpFetcher::Pause() {
+ CHECK(!paused_);
+ paused_ = true;
+ MessageLoop::current()->CancelTask(timeout_id_);
+ timeout_id_ = MessageLoop::kTaskIdNull;
+}
+
+void MockHttpFetcher::Unpause() {
+ CHECK(paused_) << "You must pause before unpause.";
+ paused_ = false;
+ if (sent_size_ < data_.size()) {
+ SendData(false);
+ }
+}
+
+void MockHttpFetcher::FailTransfer(int http_response_code) {
+ fail_transfer_ = true;
+ http_response_code_ = http_response_code;
+}
+
+void MockHttpFetcher::SignalTransferComplete() {
+ // If the transfer has been failed, the HTTP response code should be set
+ // already.
+ if (!fail_transfer_) {
+ http_response_code_ = 200;
+ }
+ delegate_->TransferComplete(this, !fail_transfer_);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/mock_http_fetcher.h b/common/mock_http_fetcher.h
new file mode 100644
index 0000000..90d34dd
--- /dev/null
+++ b/common/mock_http_fetcher.h
@@ -0,0 +1,146 @@
+//
+// 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_COMMON_MOCK_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_MOCK_HTTP_FETCHER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/http_fetcher.h"
+
+// This is a mock implementation of HttpFetcher which is useful for testing.
+// All data must be passed into the ctor. When started, MockHttpFetcher will
+// deliver the data in chunks of size kMockHttpFetcherChunkSize. To simulate
+// a network failure, you can call FailTransfer().
+
+namespace chromeos_update_engine {
+
+// MockHttpFetcher will send a chunk of data down in each call to BeginTransfer
+// and Unpause. For the other chunks of data, a callback is put on the run
+// loop and when that's called, another chunk is sent down.
+const size_t kMockHttpFetcherChunkSize(65536);
+
+class MockHttpFetcher : public HttpFetcher {
+ public:
+ // The data passed in here is copied and then passed to the delegate after
+ // the transfer begins.
+ MockHttpFetcher(const uint8_t* data,
+ size_t size,
+ ProxyResolver* proxy_resolver)
+ : HttpFetcher(proxy_resolver),
+ sent_size_(0),
+ timeout_id_(brillo::MessageLoop::kTaskIdNull),
+ paused_(false),
+ fail_transfer_(false),
+ never_use_(false) {
+ data_.insert(data_.end(), data, data + size);
+ }
+
+ // Constructor overload for string data.
+ MockHttpFetcher(const char* data, size_t size, ProxyResolver* proxy_resolver)
+ : MockHttpFetcher(reinterpret_cast<const uint8_t*>(data), size,
+ proxy_resolver) {}
+
+ // Cleans up all internal state. Does not notify delegate
+ ~MockHttpFetcher() override;
+
+ // Ignores this.
+ void SetOffset(off_t offset) override {
+ sent_size_ = offset;
+ if (delegate_)
+ delegate_->SeekToOffset(offset);
+ }
+
+ // Do nothing.
+ void SetLength(size_t length) override {}
+ void UnsetLength() override {}
+ void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {}
+ 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_;
+ }
+
+ // Begins the transfer if it hasn't already begun.
+ void BeginTransfer(const std::string& url) override;
+
+ // If the transfer is in progress, aborts the transfer early.
+ // The transfer cannot be resumed.
+ void TerminateTransfer() override;
+
+ // Suspend the mock transfer.
+ void Pause() override;
+
+ // Resume the mock transfer.
+ void Unpause() override;
+
+ // Fail the transfer. This simulates a network failure.
+ void FailTransfer(int http_response_code);
+
+ // If set to true, this will EXPECT fail on BeginTransfer
+ void set_never_use(bool never_use) { never_use_ = never_use; }
+
+ const brillo::Blob& post_data() const {
+ return post_data_;
+ }
+
+ private:
+ // Sends data to the delegate and sets up a timeout callback if needed.
+ // There must be a delegate and there must be data to send. If there is
+ // already a timeout callback, and it should be deleted by the caller,
+ // this will return false; otherwise true is returned.
+ // If skip_delivery is true, no bytes will be delivered, but the callbacks
+ // still be set if needed.
+ bool SendData(bool skip_delivery);
+
+ // Callback for when our message loop timeout expires.
+ void TimeoutCallback();
+
+ // Sets the HTTP response code and signals to the delegate that the transfer
+ // is complete.
+ void SignalTransferComplete();
+
+ // 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 TaskId of the timeout callback. After each chunk of data sent, we
+ // time out for 0s just to make sure that run loop services other clients.
+ brillo::MessageLoop::TaskId timeout_id_;
+
+ // True iff the fetcher is paused.
+ bool paused_;
+
+ // Set to true if the transfer should fail.
+ bool fail_transfer_;
+
+ // Set to true if BeginTransfer should EXPECT fail.
+ bool never_use_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockHttpFetcher);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_MOCK_HTTP_FETCHER_H_
diff --git a/common/mock_prefs.h b/common/mock_prefs.h
new file mode 100644
index 0000000..0e639a2
--- /dev/null
+++ b/common/mock_prefs.h
@@ -0,0 +1,51 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_MOCK_PREFS_H_
+#define UPDATE_ENGINE_COMMON_MOCK_PREFS_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+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_CONST_METHOD2(GetInt64, bool(const std::string& key, int64_t* value));
+ MOCK_METHOD2(SetInt64, bool(const std::string& key, const int64_t value));
+
+ MOCK_CONST_METHOD2(GetBoolean, bool(const std::string& key, bool* value));
+ MOCK_METHOD2(SetBoolean, bool(const std::string& key, const bool value));
+
+ MOCK_CONST_METHOD1(Exists, bool(const std::string& key));
+ MOCK_METHOD1(Delete, bool(const std::string& key));
+
+ MOCK_METHOD2(AddObserver, void(const std::string& key, ObserverInterface*));
+ MOCK_METHOD2(RemoveObserver,
+ void(const std::string& key, ObserverInterface*));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_MOCK_PREFS_H_
diff --git a/common/multi_range_http_fetcher.cc b/common/multi_range_http_fetcher.cc
new file mode 100644
index 0000000..0a97b6e
--- /dev/null
+++ b/common/multi_range_http_fetcher.cc
@@ -0,0 +1,190 @@
+//
+// 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/common/multi_range_http_fetcher.h"
+
+#include <base/strings/stringprintf.h>
+
+#include <algorithm>
+#include <string>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+// Begins the transfer to the specified URL.
+// State change: Stopped -> Downloading
+// (corner case: Stopped -> Stopped for an empty request)
+void MultiRangeHttpFetcher::BeginTransfer(const std::string& url) {
+ CHECK(!base_fetcher_active_) << "BeginTransfer but already active.";
+ CHECK(!pending_transfer_ended_) << "BeginTransfer but pending.";
+ CHECK(!terminating_) << "BeginTransfer but terminating.";
+
+ if (ranges_.empty()) {
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferComplete(this, true);
+ return;
+ }
+ url_ = url;
+ current_index_ = 0;
+ bytes_received_this_range_ = 0;
+ LOG(INFO) << "starting first transfer";
+ base_fetcher_->set_delegate(this);
+ StartTransfer();
+}
+
+// State change: Downloading -> Pending transfer ended
+void MultiRangeHttpFetcher::TerminateTransfer() {
+ if (!base_fetcher_active_) {
+ LOG(INFO) << "Called TerminateTransfer but not active.";
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferTerminated(this);
+ return;
+ }
+ terminating_ = true;
+
+ if (!pending_transfer_ended_) {
+ base_fetcher_->TerminateTransfer();
+ }
+}
+
+// State change: Stopped or Downloading -> Downloading
+void MultiRangeHttpFetcher::StartTransfer() {
+ if (current_index_ >= ranges_.size()) {
+ return;
+ }
+
+ Range range = ranges_[current_index_];
+ LOG(INFO) << "starting transfer of range " << range.ToString();
+
+ bytes_received_this_range_ = 0;
+ base_fetcher_->SetOffset(range.offset());
+ if (range.HasLength())
+ base_fetcher_->SetLength(range.length());
+ else
+ base_fetcher_->UnsetLength();
+ if (delegate_)
+ delegate_->SeekToOffset(range.offset());
+ base_fetcher_active_ = true;
+ base_fetcher_->BeginTransfer(url_);
+}
+
+// State change: Downloading -> Downloading or Pending transfer ended
+void MultiRangeHttpFetcher::ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes,
+ size_t length) {
+ CHECK_LT(current_index_, ranges_.size());
+ CHECK_EQ(fetcher, base_fetcher_.get());
+ CHECK(!pending_transfer_ended_);
+ size_t next_size = length;
+ Range range = ranges_[current_index_];
+ if (range.HasLength()) {
+ next_size = std::min(next_size,
+ range.length() - bytes_received_this_range_);
+ }
+ LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
+ if (delegate_) {
+ delegate_->ReceivedBytes(this, bytes, next_size);
+ }
+ bytes_received_this_range_ += length;
+ if (range.HasLength() && bytes_received_this_range_ >= range.length()) {
+ // Terminates the current fetcher. Waits for its TransferTerminated
+ // callback before starting the next range so that we don't end up
+ // signalling the delegate that the whole multi-transfer is complete
+ // before all fetchers are really done and cleaned up.
+ pending_transfer_ended_ = true;
+ LOG(INFO) << "terminating transfer";
+ fetcher->TerminateTransfer();
+ }
+}
+
+// State change: Downloading or Pending transfer ended -> Stopped
+void MultiRangeHttpFetcher::TransferEnded(HttpFetcher* fetcher,
+ bool successful) {
+ CHECK(base_fetcher_active_) << "Transfer ended unexpectedly.";
+ CHECK_EQ(fetcher, base_fetcher_.get());
+ pending_transfer_ended_ = false;
+ http_response_code_ = fetcher->http_response_code();
+ LOG(INFO) << "TransferEnded w/ code " << http_response_code_;
+ if (terminating_) {
+ LOG(INFO) << "Terminating.";
+ Reset();
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferTerminated(this);
+ return;
+ }
+
+ // If we didn't get enough bytes, it's failure
+ Range range = ranges_[current_index_];
+ if (range.HasLength()) {
+ if (bytes_received_this_range_ < range.length()) {
+ // Failure
+ LOG(INFO) << "Didn't get enough bytes. Ending w/ failure.";
+ Reset();
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferComplete(this, false);
+ return;
+ }
+ // We got enough bytes and there were bytes specified, so this is success.
+ successful = true;
+ }
+
+ // If we have another transfer, do that.
+ if (current_index_ + 1 < ranges_.size()) {
+ current_index_++;
+ LOG(INFO) << "Starting next transfer (" << current_index_ << ").";
+ StartTransfer();
+ return;
+ }
+
+ LOG(INFO) << "Done w/ all transfers";
+ Reset();
+ // Note that after the callback returns this object may be destroyed.
+ if (delegate_)
+ delegate_->TransferComplete(this, successful);
+}
+
+void MultiRangeHttpFetcher::TransferComplete(HttpFetcher* fetcher,
+ bool successful) {
+ LOG(INFO) << "Received transfer complete.";
+ TransferEnded(fetcher, successful);
+}
+
+void MultiRangeHttpFetcher::TransferTerminated(HttpFetcher* fetcher) {
+ LOG(INFO) << "Received transfer terminated.";
+ TransferEnded(fetcher, false);
+}
+
+void MultiRangeHttpFetcher::Reset() {
+ base_fetcher_active_ = pending_transfer_ended_ = terminating_ = false;
+ current_index_ = 0;
+ bytes_received_this_range_ = 0;
+}
+
+std::string MultiRangeHttpFetcher::Range::ToString() const {
+ std::string range_str = base::StringPrintf("%jd+", offset());
+ if (HasLength())
+ range_str += std::to_string(length());
+ else
+ range_str += "?";
+ return range_str;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/multi_range_http_fetcher.h b/common/multi_range_http_fetcher.h
new file mode 100644
index 0000000..8158a22
--- /dev/null
+++ b/common/multi_range_http_fetcher.h
@@ -0,0 +1,179 @@
+//
+// 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_COMMON_MULTI_RANGE_HTTP_FETCHER_H_
+#define UPDATE_ENGINE_COMMON_MULTI_RANGE_HTTP_FETCHER_H_
+
+#include <deque>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "update_engine/common/http_fetcher.h"
+
+// This class is a simple wrapper around an HttpFetcher. The client
+// specifies a vector of byte ranges. MultiRangeHttpFetcher will fetch bytes
+// from those offsets, using the same bash fetcher for all ranges. Thus, the
+// fetcher must support beginning a transfer after one has stopped. Pass -1
+// as a length to specify unlimited length. It really only would make sense
+// for the last range specified to have unlimited length, tho it is legal for
+// other entries to have unlimited length.
+
+// There are three states a MultiRangeHttpFetcher object will be in:
+// - Stopped (start state)
+// - Downloading
+// - Pending transfer ended
+// Various functions below that might change state indicate possible
+// state changes.
+
+namespace chromeos_update_engine {
+
+class MultiRangeHttpFetcher : public HttpFetcher, public HttpFetcherDelegate {
+ public:
+ // Takes ownership of the passed in fetcher.
+ explicit MultiRangeHttpFetcher(HttpFetcher* base_fetcher)
+ : HttpFetcher(base_fetcher->proxy_resolver()),
+ base_fetcher_(base_fetcher),
+ base_fetcher_active_(false),
+ pending_transfer_ended_(false),
+ terminating_(false),
+ current_index_(0),
+ bytes_received_this_range_(0) {}
+ ~MultiRangeHttpFetcher() override {}
+
+ void ClearRanges() { ranges_.clear(); }
+
+ void AddRange(off_t offset, size_t size) {
+ CHECK_GT(size, static_cast<size_t>(0));
+ ranges_.push_back(Range(offset, size));
+ }
+
+ void AddRange(off_t offset) {
+ ranges_.push_back(Range(offset));
+ }
+
+ // HttpFetcher overrides.
+ void SetOffset(off_t offset) override {} // for now, doesn't support this
+
+ void SetLength(size_t length) override {} // unsupported
+ void UnsetLength() override {}
+
+ // Begins the transfer to the specified URL.
+ // State change: Stopped -> Downloading
+ // (corner case: Stopped -> Stopped for an empty request)
+ void BeginTransfer(const std::string& url) override;
+
+ // State change: Downloading -> Pending transfer ended
+ void TerminateTransfer() override;
+
+ void Pause() override { base_fetcher_->Pause(); }
+
+ void Unpause() override { base_fetcher_->Unpause(); }
+
+ // These functions are overloaded in LibcurlHttp fetcher for testing purposes.
+ void set_idle_seconds(int seconds) override {
+ base_fetcher_->set_idle_seconds(seconds);
+ }
+ void set_retry_seconds(int seconds) override {
+ base_fetcher_->set_retry_seconds(seconds);
+ }
+ // TODO(deymo): Determine if this method should be virtual in HttpFetcher so
+ // this call is sent to the base_fetcher_.
+ virtual void SetProxies(const std::deque<std::string>& proxies) {
+ base_fetcher_->SetProxies(proxies);
+ }
+
+ inline size_t GetBytesDownloaded() override {
+ return base_fetcher_->GetBytesDownloaded();
+ }
+
+ void set_low_speed_limit(int low_speed_bps, int low_speed_sec) override {
+ base_fetcher_->set_low_speed_limit(low_speed_bps, low_speed_sec);
+ }
+
+ void set_connect_timeout(int connect_timeout_seconds) override {
+ base_fetcher_->set_connect_timeout(connect_timeout_seconds);
+ }
+
+ void set_max_retry_count(int max_retry_count) override {
+ base_fetcher_->set_max_retry_count(max_retry_count);
+ }
+
+ private:
+ // A range object defining the offset and length of a download chunk. Zero
+ // length indicates an unspecified end offset (note that it is impossible to
+ // request a zero-length range in HTTP).
+ class Range {
+ public:
+ Range(off_t offset, size_t length) : offset_(offset), length_(length) {}
+ explicit Range(off_t offset) : offset_(offset), length_(0) {}
+
+ inline off_t offset() const { return offset_; }
+ inline size_t length() const { return length_; }
+
+ inline bool HasLength() const { return (length_ > 0); }
+
+ std::string ToString() const;
+
+ private:
+ off_t offset_;
+ size_t length_;
+ };
+
+ typedef std::vector<Range> RangesVect;
+
+ // State change: Stopped or Downloading -> Downloading
+ void StartTransfer();
+
+ // HttpFetcherDelegate overrides.
+ // State change: Downloading -> Downloading or Pending transfer ended
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes,
+ size_t length) override;
+
+ // State change: Pending transfer ended -> Stopped
+ void TransferEnded(HttpFetcher* fetcher, bool successful);
+ // These two call TransferEnded():
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override;
+ void TransferTerminated(HttpFetcher* fetcher) override;
+
+ void Reset();
+
+ std::unique_ptr<HttpFetcher> base_fetcher_;
+
+ // If true, do not send any more data or TransferComplete to the delegate.
+ bool base_fetcher_active_;
+
+ // If true, the next fetcher needs to be started when TransferTerminated is
+ // received from the current fetcher.
+ bool pending_transfer_ended_;
+
+ // True if we are waiting for base fetcher to terminate b/c we are
+ // ourselves terminating.
+ bool terminating_;
+
+ RangesVect ranges_;
+
+ RangesVect::size_type current_index_; // index into ranges_
+ size_t bytes_received_this_range_;
+
+ DISALLOW_COPY_AND_ASSIGN(MultiRangeHttpFetcher);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_MULTI_RANGE_HTTP_FETCHER_H_
diff --git a/common/platform_constants.h b/common/platform_constants.h
new file mode 100644
index 0000000..d1786ff
--- /dev/null
+++ b/common/platform_constants.h
@@ -0,0 +1,56 @@
+//
+// 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_COMMON_PLATFORM_CONSTANTS_H_
+#define UPDATE_ENGINE_COMMON_PLATFORM_CONSTANTS_H_
+
+namespace chromeos_update_engine {
+namespace constants {
+
+// The default URL used by all products when running in normal mode. The AUTest
+// URL is used when testing normal images against the alternative AUTest server.
+// Note that the URL can be override in run-time in certain cases.
+extern const char kOmahaDefaultProductionURL[];
+extern const char kOmahaDefaultAUTestURL[];
+
+// Our product name used in Omaha. This value must match the one configured in
+// the server side and is sent on every request.
+extern const char kOmahaUpdaterID[];
+
+// The name of the platform as sent to Omaha.
+extern const char kOmahaPlatformName[];
+
+// Path to the location of the public half of the payload key. The payload key
+// is used to sign the contents of the payload binary file: the manifest and the
+// whole payload.
+extern const char kUpdatePayloadPublicKeyPath[];
+
+// Path to the directory containing all the SSL certificates accepted by
+// update_engine when sending requests to Omaha and the download server (if
+// HTTPS is used for that as well).
+extern const char kCACertificatesPath[];
+
+// Path to the file used to notify chrome about the deadline of the last omaha
+// response. Empty if not supported.
+extern const char kOmahaResponseDeadlineFile[];
+
+// The stateful directory used by update_engine.
+extern const char kNonVolatileDirectory[];
+
+} // namespace constants
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_PLATFORM_CONSTANTS_H_
diff --git a/common/platform_constants_android.cc b/common/platform_constants_android.cc
new file mode 100644
index 0000000..4f55106
--- /dev/null
+++ b/common/platform_constants_android.cc
@@ -0,0 +1,36 @@
+//
+// 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/common/platform_constants.h"
+
+namespace chromeos_update_engine {
+namespace constants {
+
+const char kOmahaDefaultProductionURL[] =
+ "https://clients2.google.com/service/update2/brillo";
+const char kOmahaDefaultAUTestURL[] =
+ "https://clients2.google.com/service/update2/brillo";
+const char kOmahaUpdaterID[] = "Brillo";
+const char kOmahaPlatformName[] = "Brillo";
+const char kUpdatePayloadPublicKeyPath[] =
+ "/etc/update_engine/update-payload-key.pub.pem";
+const char kCACertificatesPath[] = "/system/etc/security/cacerts_google";
+// No deadline file API support on Android.
+const char kOmahaResponseDeadlineFile[] = "";
+const char kNonVolatileDirectory[] = "/data/misc/update_engine";
+
+} // namespace constants
+} // namespace chromeos_update_engine
diff --git a/common/platform_constants_chromeos.cc b/common/platform_constants_chromeos.cc
new file mode 100644
index 0000000..d8587ca
--- /dev/null
+++ b/common/platform_constants_chromeos.cc
@@ -0,0 +1,37 @@
+//
+// 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/common/platform_constants.h"
+
+namespace chromeos_update_engine {
+namespace constants {
+
+const char kOmahaDefaultProductionURL[] =
+ "https://tools.google.com/service/update2";
+const char kOmahaDefaultAUTestURL[] =
+ "https://omaha.sandbox.google.com/service/update2";
+const char kOmahaUpdaterID[] = "ChromeOSUpdateEngine";
+const char kOmahaPlatformName[] = "Chrome OS";
+const char kUpdatePayloadPublicKeyPath[] =
+ "/usr/share/update_engine/update-payload-key.pub.pem";
+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";
+
+} // namespace constants
+} // namespace chromeos_update_engine
diff --git a/common/prefs.cc b/common/prefs.cc
new file mode 100644
index 0000000..9d3a30f
--- /dev/null
+++ b/common/prefs.cc
@@ -0,0 +1,143 @@
+//
+// 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/common/prefs.h"
+
+#include <algorithm>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+bool Prefs::Init(const base::FilePath& prefs_dir) {
+ prefs_dir_ = prefs_dir;
+ return true;
+}
+
+bool Prefs::GetString(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::SetString(const string& key, const string& value) {
+ base::FilePath filename;
+ TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+ if (!base::DirectoryExists(filename.DirName())) {
+ // Only attempt to create the directory if it doesn't exist to avoid calls
+ // to parent directories where we might not have permission to write to.
+ TEST_AND_RETURN_FALSE(base::CreateDirectory(filename.DirName()));
+ }
+ TEST_AND_RETURN_FALSE(base::WriteFile(filename, value.data(), value.size()) ==
+ static_cast<int>(value.size()));
+ const auto observers_for_key = observers_.find(key);
+ if (observers_for_key != observers_.end()) {
+ std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+ for (ObserverInterface* observer : copy_observers)
+ observer->OnPrefSet(key);
+ }
+ return true;
+}
+
+bool Prefs::GetInt64(const string& key, int64_t* value) const {
+ string str_value;
+ if (!GetString(key, &str_value))
+ return false;
+ base::TrimWhitespaceASCII(str_value, base::TRIM_ALL, &str_value);
+ TEST_AND_RETURN_FALSE(base::StringToInt64(str_value, value));
+ return true;
+}
+
+bool Prefs::SetInt64(const string& key, const int64_t value) {
+ return SetString(key, base::Int64ToString(value));
+}
+
+bool Prefs::GetBoolean(const string& key, bool* value) const {
+ string str_value;
+ if (!GetString(key, &str_value))
+ return false;
+ base::TrimWhitespaceASCII(str_value, base::TRIM_ALL, &str_value);
+ if (str_value == "false") {
+ *value = false;
+ return true;
+ }
+ if (str_value == "true") {
+ *value = true;
+ return true;
+ }
+ return false;
+}
+
+bool Prefs::SetBoolean(const string& key, const bool value) {
+ return SetString(key, value ? "true" : "false");
+}
+
+bool Prefs::Exists(const string& key) const {
+ base::FilePath filename;
+ TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+ return base::PathExists(filename);
+}
+
+bool Prefs::Delete(const string& key) {
+ base::FilePath filename;
+ TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename));
+ TEST_AND_RETURN_FALSE(base::DeleteFile(filename, false));
+ const auto observers_for_key = observers_.find(key);
+ if (observers_for_key != observers_.end()) {
+ std::vector<ObserverInterface*> copy_observers(observers_for_key->second);
+ for (ObserverInterface* observer : copy_observers)
+ observer->OnPrefDeleted(key);
+ }
+ return true;
+}
+
+void Prefs::AddObserver(const string& key, ObserverInterface* observer) {
+ observers_[key].push_back(observer);
+}
+
+void Prefs::RemoveObserver(const string& key, ObserverInterface* observer) {
+ std::vector<ObserverInterface*>& observers_for_key = observers_[key];
+ auto observer_it =
+ std::find(observers_for_key.begin(), observers_for_key.end(), observer);
+ if (observer_it != observers_for_key.end())
+ observers_for_key.erase(observer_it);
+}
+
+bool Prefs::GetFileNameForKey(const string& key,
+ base::FilePath* filename) const {
+ // 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);
+ TEST_AND_RETURN_FALSE(IsAsciiAlpha(c) || IsAsciiDigit(c) ||
+ c == '_' || c == '-');
+ }
+ *filename = prefs_dir_.Append(key);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/prefs.h b/common/prefs.h
new file mode 100644
index 0000000..f11abc3
--- /dev/null
+++ b/common/prefs.h
@@ -0,0 +1,81 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_PREFS_H_
+#define UPDATE_ENGINE_COMMON_PREFS_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+
+#include "gtest/gtest_prod.h" // for FRIEND_TEST
+#include "update_engine/common/prefs_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements a preference store by storing the value associated with
+// a key in a separate file named after the key under a preference
+// store directory.
+
+class Prefs : public PrefsInterface {
+ public:
+ Prefs() = default;
+
+ // Initializes the store by associating this object with |prefs_dir|
+ // as the preference store directory. Returns true on success, false
+ // otherwise.
+ bool Init(const base::FilePath& prefs_dir);
+
+ // 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 GetInt64(const std::string& key, int64_t* value) const override;
+ bool SetInt64(const std::string& key, const int64_t value) override;
+ bool GetBoolean(const std::string& key, bool* value) const override;
+ bool SetBoolean(const std::string& key, const bool value) override;
+
+ bool Exists(const std::string& key) const override;
+ bool Delete(const std::string& key) override;
+
+ void AddObserver(const std::string& key,
+ ObserverInterface* observer) override;
+ void RemoveObserver(const std::string& key,
+ ObserverInterface* observer) override;
+
+ private:
+ FRIEND_TEST(PrefsTest, GetFileNameForKey);
+ FRIEND_TEST(PrefsTest, GetFileNameForKeyBadCharacter);
+ FRIEND_TEST(PrefsTest, GetFileNameForKeyEmpty);
+
+ // Sets |filename| to the full path to the file containing the data
+ // associated with |key|. Returns true on success, false otherwise.
+ bool GetFileNameForKey(const std::string& key,
+ base::FilePath* filename) const;
+
+ // Preference store directory.
+ base::FilePath prefs_dir_;
+
+ // The registered observers watching for changes.
+ std::map<std::string, std::vector<ObserverInterface*>> observers_;
+
+ DISALLOW_COPY_AND_ASSIGN(Prefs);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_PREFS_H_
diff --git a/common/prefs_interface.h b/common/prefs_interface.h
new file mode 100644
index 0000000..03ae3ec
--- /dev/null
+++ b/common/prefs_interface.h
@@ -0,0 +1,97 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_PREFS_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_PREFS_INTERFACE_H_
+
+#include <stdint.h>
+
+#include <string>
+
+namespace chromeos_update_engine {
+
+// The prefs interface allows access to a persistent preferences
+// store. The two reasons for providing this as an interface are
+// testing as well as easier switching to a new implementation in the
+// future, if necessary.
+
+class PrefsInterface {
+ public:
+ // Observer class to be notified about key value changes.
+ class ObserverInterface {
+ public:
+ virtual ~ObserverInterface() = default;
+
+ // Called when the value is set for the observed |key|.
+ virtual void OnPrefSet(const std::string& key) = 0;
+
+ // Called when the observed |key| is deleted.
+ virtual void OnPrefDeleted(const std::string& key) = 0;
+ };
+
+ virtual ~PrefsInterface() = default;
+
+ // Gets a string |value| associated with |key|. Returns true on
+ // success, false on failure (including when the |key| is not
+ // present in the store).
+ virtual bool GetString(const std::string& key, std::string* value) const = 0;
+
+ // Associates |key| with a string |value|. Returns true on success,
+ // false otherwise.
+ virtual bool SetString(const std::string& key, const std::string& value) = 0;
+
+ // Gets an int64_t |value| associated with |key|. Returns true on
+ // success, false on failure (including when the |key| is not
+ // present in the store).
+ virtual bool GetInt64(const std::string& key, int64_t* value) const = 0;
+
+ // Associates |key| with an int64_t |value|. Returns true on success,
+ // false otherwise.
+ virtual bool SetInt64(const std::string& key, const int64_t value) = 0;
+
+ // Gets a boolean |value| associated with |key|. Returns true on
+ // success, false on failure (including when the |key| is not
+ // present in the store).
+ virtual bool GetBoolean(const std::string& key, bool* value) const = 0;
+
+ // Associates |key| with a boolean |value|. Returns true on success,
+ // false otherwise.
+ virtual bool SetBoolean(const std::string& key, const bool value) = 0;
+
+ // Returns true if the setting exists (i.e. a file with the given key
+ // exists in the prefs directory)
+ virtual bool Exists(const std::string& key) const = 0;
+
+ // Returns true if successfully deleted the file corresponding to
+ // this key. Calling with non-existent keys does nothing.
+ virtual bool Delete(const std::string& key) = 0;
+
+ // Add an observer to watch whenever the given |key| is modified. The
+ // OnPrefSet() and OnPrefDelete() methods will be called whenever any of the
+ // Set*() methods or the Delete() method are called on the given key,
+ // respectively.
+ virtual void AddObserver(const std::string& key,
+ ObserverInterface* observer) = 0;
+
+ // Remove an observer added with AddObserver(). The observer won't be called
+ // anymore for future Set*() and Delete() method calls.
+ virtual void RemoveObserver(const std::string& key,
+ ObserverInterface* observer) = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_PREFS_INTERFACE_H_
diff --git a/common/prefs_unittest.cc b/common/prefs_unittest.cc
new file mode 100644
index 0000000..354b05b
--- /dev/null
+++ b/common/prefs_unittest.cc
@@ -0,0 +1,337 @@
+//
+// 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/common/prefs.h"
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <base/macros.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using std::string;
+using testing::Eq;
+using testing::_;
+
+namespace {
+// Test key used along the tests.
+const char kKey[] = "test-key";
+}
+
+namespace chromeos_update_engine {
+
+class PrefsTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(base::CreateNewTempDirectory("auprefs", &prefs_dir_));
+ ASSERT_TRUE(prefs_.Init(prefs_dir_));
+ }
+
+ void TearDown() override {
+ base::DeleteFile(prefs_dir_, true); // recursive
+ }
+
+ bool SetValue(const string& key, const string& value) {
+ return base::WriteFile(prefs_dir_.Append(key), value.data(),
+ value.length()) == static_cast<int>(value.length());
+ }
+
+ base::FilePath prefs_dir_;
+ Prefs prefs_;
+};
+
+TEST_F(PrefsTest, GetFileNameForKey) {
+ const char kAllvalidCharsKey[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-";
+ base::FilePath path;
+ EXPECT_TRUE(prefs_.GetFileNameForKey(kAllvalidCharsKey, &path));
+ EXPECT_EQ(prefs_dir_.Append(kAllvalidCharsKey).value(), path.value());
+}
+
+TEST_F(PrefsTest, GetFileNameForKeyBadCharacter) {
+ base::FilePath path;
+ EXPECT_FALSE(prefs_.GetFileNameForKey("ABC abc", &path));
+}
+
+TEST_F(PrefsTest, GetFileNameForKeyEmpty) {
+ base::FilePath path;
+ EXPECT_FALSE(prefs_.GetFileNameForKey("", &path));
+}
+
+TEST_F(PrefsTest, GetString) {
+ const string test_data = "test data";
+ ASSERT_TRUE(SetValue(kKey, test_data));
+ string value;
+ EXPECT_TRUE(prefs_.GetString(kKey, &value));
+ EXPECT_EQ(test_data, value);
+}
+
+TEST_F(PrefsTest, GetStringBadKey) {
+ string value;
+ EXPECT_FALSE(prefs_.GetString(",bad", &value));
+}
+
+TEST_F(PrefsTest, GetStringNonExistentKey) {
+ string value;
+ EXPECT_FALSE(prefs_.GetString("non-existent-key", &value));
+}
+
+TEST_F(PrefsTest, SetString) {
+ const char kValue[] = "some test value\non 2 lines";
+ EXPECT_TRUE(prefs_.SetString(kKey, kValue));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ(kValue, value);
+}
+
+TEST_F(PrefsTest, SetStringBadKey) {
+ const char kKeyWithDots[] = ".no-dots";
+ EXPECT_FALSE(prefs_.SetString(kKeyWithDots, "some value"));
+ EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKeyWithDots)));
+}
+
+TEST_F(PrefsTest, SetStringCreateDir) {
+ const char kValue[] = "test value";
+ base::FilePath subdir = prefs_dir_.Append("subdir1").Append("subdir2");
+ EXPECT_TRUE(prefs_.Init(subdir));
+ EXPECT_TRUE(prefs_.SetString(kKey, kValue));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(subdir.Append(kKey), &value));
+ EXPECT_EQ(kValue, value);
+}
+
+TEST_F(PrefsTest, SetStringDirCreationFailure) {
+ EXPECT_TRUE(prefs_.Init(base::FilePath("/dev/null")));
+ EXPECT_FALSE(prefs_.SetString(kKey, "test value"));
+}
+
+TEST_F(PrefsTest, SetStringFileCreationFailure) {
+ base::CreateDirectory(prefs_dir_.Append(kKey));
+ EXPECT_FALSE(prefs_.SetString(kKey, "test value"));
+ EXPECT_TRUE(base::DirectoryExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, GetInt64) {
+ ASSERT_TRUE(SetValue(kKey, " \n 25 \t "));
+ int64_t value;
+ EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+ EXPECT_EQ(25, value);
+}
+
+TEST_F(PrefsTest, GetInt64BadValue) {
+ ASSERT_TRUE(SetValue(kKey, "30a"));
+ int64_t value;
+ EXPECT_FALSE(prefs_.GetInt64(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetInt64Max) {
+ ASSERT_TRUE(SetValue(kKey, base::StringPrintf("%" PRIi64, kint64max)));
+ int64_t value;
+ EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+ EXPECT_EQ(kint64max, value);
+}
+
+TEST_F(PrefsTest, GetInt64Min) {
+ ASSERT_TRUE(SetValue(kKey, base::StringPrintf("%" PRIi64, kint64min)));
+ int64_t value;
+ EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+ EXPECT_EQ(kint64min, value);
+}
+
+TEST_F(PrefsTest, GetInt64Negative) {
+ ASSERT_TRUE(SetValue(kKey, " \t -100 \n "));
+ int64_t value;
+ EXPECT_TRUE(prefs_.GetInt64(kKey, &value));
+ EXPECT_EQ(-100, value);
+}
+
+TEST_F(PrefsTest, GetInt64NonExistentKey) {
+ int64_t value;
+ EXPECT_FALSE(prefs_.GetInt64("random-key", &value));
+}
+
+TEST_F(PrefsTest, SetInt64) {
+ EXPECT_TRUE(prefs_.SetInt64(kKey, -123));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ("-123", value);
+}
+
+TEST_F(PrefsTest, SetInt64BadKey) {
+ const char kKeyWithSpaces[] = "s p a c e s";
+ EXPECT_FALSE(prefs_.SetInt64(kKeyWithSpaces, 20));
+ EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKeyWithSpaces)));
+}
+
+TEST_F(PrefsTest, SetInt64Max) {
+ EXPECT_TRUE(prefs_.SetInt64(kKey, kint64max));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ(base::StringPrintf("%" PRIi64, kint64max), value);
+}
+
+TEST_F(PrefsTest, SetInt64Min) {
+ EXPECT_TRUE(prefs_.SetInt64(kKey, kint64min));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ(base::StringPrintf("%" PRIi64, kint64min), value);
+}
+
+TEST_F(PrefsTest, GetBooleanFalse) {
+ ASSERT_TRUE(SetValue(kKey, " \n false \t "));
+ bool value;
+ EXPECT_TRUE(prefs_.GetBoolean(kKey, &value));
+ EXPECT_FALSE(value);
+}
+
+TEST_F(PrefsTest, GetBooleanTrue) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, " \t true \n "));
+ bool value;
+ EXPECT_TRUE(prefs_.GetBoolean(kKey, &value));
+ EXPECT_TRUE(value);
+}
+
+TEST_F(PrefsTest, GetBooleanBadValue) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, "1"));
+ bool value;
+ EXPECT_FALSE(prefs_.GetBoolean(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetBooleanBadEmptyValue) {
+ const char kKey[] = "test-key";
+ ASSERT_TRUE(SetValue(kKey, ""));
+ bool value;
+ EXPECT_FALSE(prefs_.GetBoolean(kKey, &value));
+}
+
+TEST_F(PrefsTest, GetBooleanNonExistentKey) {
+ bool value;
+ EXPECT_FALSE(prefs_.GetBoolean("random-key", &value));
+}
+
+TEST_F(PrefsTest, SetBooleanTrue) {
+ const char kKey[] = "test-bool";
+ EXPECT_TRUE(prefs_.SetBoolean(kKey, true));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ("true", value);
+}
+
+TEST_F(PrefsTest, SetBooleanFalse) {
+ const char kKey[] = "test-bool";
+ EXPECT_TRUE(prefs_.SetBoolean(kKey, false));
+ string value;
+ EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value));
+ EXPECT_EQ("false", value);
+}
+
+TEST_F(PrefsTest, SetBooleanBadKey) {
+ const char kKey[] = "s p a c e s";
+ EXPECT_FALSE(prefs_.SetBoolean(kKey, true));
+ EXPECT_FALSE(base::PathExists(prefs_dir_.Append(kKey)));
+}
+
+TEST_F(PrefsTest, ExistsWorks) {
+ // test that the key doesn't exist before we set it.
+ EXPECT_FALSE(prefs_.Exists(kKey));
+
+ // test that the key exists after we set it.
+ ASSERT_TRUE(prefs_.SetInt64(kKey, 8));
+ EXPECT_TRUE(prefs_.Exists(kKey));
+}
+
+TEST_F(PrefsTest, DeleteWorks) {
+ // test that it's alright to delete a non-existent key.
+ EXPECT_TRUE(prefs_.Delete(kKey));
+
+ // delete the key after we set it.
+ ASSERT_TRUE(prefs_.SetInt64(kKey, 0));
+ EXPECT_TRUE(prefs_.Delete(kKey));
+
+ // make sure it doesn't exist anymore.
+ EXPECT_FALSE(prefs_.Exists(kKey));
+}
+
+class MockPrefsObserver : public PrefsInterface::ObserverInterface {
+ public:
+ MOCK_METHOD1(OnPrefSet, void(const string&));
+ MOCK_METHOD1(OnPrefDeleted, void(const string& key));
+};
+
+TEST_F(PrefsTest, ObserversCalled) {
+ MockPrefsObserver mock_obserser;
+ prefs_.AddObserver(kKey, &mock_obserser);
+
+ EXPECT_CALL(mock_obserser, OnPrefSet(Eq(kKey)));
+ EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0);
+ prefs_.SetString(kKey, "value");
+ testing::Mock::VerifyAndClearExpectations(&mock_obserser);
+
+ EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0);
+ EXPECT_CALL(mock_obserser, OnPrefDeleted(Eq(kKey)));
+ prefs_.Delete(kKey);
+ testing::Mock::VerifyAndClearExpectations(&mock_obserser);
+
+ prefs_.RemoveObserver(kKey, &mock_obserser);
+}
+
+TEST_F(PrefsTest, OnlyCalledOnObservedKeys) {
+ MockPrefsObserver mock_obserser;
+ const char kUnusedKey[] = "unused-key";
+ prefs_.AddObserver(kUnusedKey, &mock_obserser);
+
+ EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0);
+ EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0);
+ prefs_.SetString(kKey, "value");
+ prefs_.Delete(kKey);
+
+ prefs_.RemoveObserver(kUnusedKey, &mock_obserser);
+}
+
+TEST_F(PrefsTest, RemovedObserversNotCalled) {
+ MockPrefsObserver mock_obserser_a, mock_obserser_b;
+ prefs_.AddObserver(kKey, &mock_obserser_a);
+ prefs_.AddObserver(kKey, &mock_obserser_b);
+ EXPECT_CALL(mock_obserser_a, OnPrefSet(_)).Times(2);
+ EXPECT_CALL(mock_obserser_b, OnPrefSet(_)).Times(1);
+ EXPECT_TRUE(prefs_.SetString(kKey, "value"));
+ prefs_.RemoveObserver(kKey, &mock_obserser_b);
+ EXPECT_TRUE(prefs_.SetString(kKey, "other value"));
+ prefs_.RemoveObserver(kKey, &mock_obserser_a);
+ EXPECT_TRUE(prefs_.SetString(kKey, "yet another value"));
+}
+
+TEST_F(PrefsTest, UnsuccessfulCallsNotObserved) {
+ MockPrefsObserver mock_obserser;
+ const char kInvalidKey[] = "no spaces or .";
+ prefs_.AddObserver(kInvalidKey, &mock_obserser);
+
+ EXPECT_CALL(mock_obserser, OnPrefSet(_)).Times(0);
+ EXPECT_CALL(mock_obserser, OnPrefDeleted(_)).Times(0);
+ EXPECT_FALSE(prefs_.SetString(kInvalidKey, "value"));
+ EXPECT_FALSE(prefs_.Delete(kInvalidKey));
+
+ prefs_.RemoveObserver(kInvalidKey, &mock_obserser);
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/subprocess.cc b/common/subprocess.cc
new file mode 100644
index 0000000..f43aaac
--- /dev/null
+++ b/common/subprocess.cc
@@ -0,0 +1,271 @@
+//
+// 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/common/subprocess.h"
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/process.h>
+#include <brillo/secure_blob.h>
+
+using brillo::MessageLoop;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+bool SetupChild(const std::map<string, string>& env, uint32_t flags) {
+ // Setup the environment variables.
+ clearenv();
+ for (const auto& key_value : env) {
+ setenv(key_value.first.c_str(), key_value.second.c_str(), 0);
+ }
+
+ if ((flags & Subprocess::kRedirectStderrToStdout) != 0) {
+ if (HANDLE_EINTR(dup2(STDOUT_FILENO, STDERR_FILENO)) != STDERR_FILENO)
+ return false;
+ }
+
+ int fd = HANDLE_EINTR(open("/dev/null", O_RDONLY));
+ if (fd < 0)
+ return false;
+ if (HANDLE_EINTR(dup2(fd, STDIN_FILENO)) != STDIN_FILENO)
+ return false;
+ IGNORE_EINTR(close(fd));
+
+ return true;
+}
+
+// Helper function to launch a process with the given Subprocess::Flags.
+// This function only sets up and starts the process according to the |flags|.
+// The caller is responsible for watching the termination of the subprocess.
+// Return whether the process was successfully launched and fills in the |proc|
+// Process.
+bool LaunchProcess(const vector<string>& cmd,
+ uint32_t flags,
+ brillo::Process* proc) {
+ for (const string& arg : cmd)
+ proc->AddArg(arg);
+ proc->SetSearchPath((flags & Subprocess::kSearchPath) != 0);
+
+ // Create an environment for the child process with just the required PATHs.
+ std::map<string, string> env;
+ for (const char* key : {"LD_LIBRARY_PATH", "PATH"}) {
+ const char* value = getenv(key);
+ if (value)
+ env.emplace(key, value);
+ }
+
+ proc->RedirectUsingPipe(STDOUT_FILENO, false);
+ proc->SetPreExecCallback(base::Bind(&SetupChild, env, flags));
+
+ return proc->Start();
+}
+
+} // namespace
+
+void Subprocess::Init(
+ brillo::AsynchronousSignalHandlerInterface* async_signal_handler) {
+ if (subprocess_singleton_ == this)
+ return;
+ CHECK(subprocess_singleton_ == nullptr);
+ subprocess_singleton_ = this;
+
+ process_reaper_.Register(async_signal_handler);
+}
+
+Subprocess::~Subprocess() {
+ if (subprocess_singleton_ == this)
+ subprocess_singleton_ = nullptr;
+}
+
+void Subprocess::OnStdoutReady(SubprocessRecord* record) {
+ char buf[1024];
+ ssize_t rc = 0;
+ do {
+ rc = HANDLE_EINTR(read(record->stdout_fd, buf, arraysize(buf)));
+ if (rc < 0) {
+ // EAGAIN and EWOULDBLOCK are normal return values when there's no more
+ // input as we are in non-blocking mode.
+ if (errno != EWOULDBLOCK && errno != EAGAIN) {
+ PLOG(ERROR) << "Error reading fd " << record->stdout_fd;
+ MessageLoop::current()->CancelTask(record->stdout_task_id);
+ record->stdout_task_id = MessageLoop::kTaskIdNull;
+ }
+ } else if (rc == 0) {
+ // A value of 0 means that the child closed its end of the pipe and there
+ // is nothing else to read from stdout.
+ MessageLoop::current()->CancelTask(record->stdout_task_id);
+ record->stdout_task_id = MessageLoop::kTaskIdNull;
+ } else {
+ record->stdout.append(buf, rc);
+ }
+ } while (rc > 0);
+}
+
+void Subprocess::ChildExitedCallback(const siginfo_t& info) {
+ auto pid_record = subprocess_records_.find(info.si_pid);
+ if (pid_record == subprocess_records_.end())
+ return;
+ SubprocessRecord* record = pid_record->second.get();
+
+ // 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;
+
+ // Release and close all the pipes now.
+ record->proc.Release();
+ record->proc.Reset(0);
+
+ // Don't print any log if the subprocess exited with exit code 0.
+ if (info.si_code != CLD_EXITED) {
+ LOG(INFO) << "Subprocess terminated with si_code " << info.si_code;
+ } else if (info.si_status != 0) {
+ LOG(INFO) << "Subprocess exited with si_status: " << info.si_status;
+ }
+
+ if (!record->stdout.empty()) {
+ LOG(INFO) << "Subprocess output:\n" << record->stdout;
+ }
+ if (!record->callback.is_null()) {
+ record->callback.Run(info.si_status, record->stdout);
+ }
+ subprocess_records_.erase(pid_record);
+}
+
+pid_t Subprocess::Exec(const vector<string>& cmd,
+ const ExecCallback& callback) {
+ return ExecFlags(cmd, kRedirectStderrToStdout, callback);
+}
+
+pid_t Subprocess::ExecFlags(const vector<string>& cmd,
+ uint32_t flags,
+ const ExecCallback& callback) {
+ unique_ptr<SubprocessRecord> record(new SubprocessRecord(callback));
+
+ if (!LaunchProcess(cmd, flags, &record->proc)) {
+ LOG(ERROR) << "Failed to launch subprocess";
+ return 0;
+ }
+
+ pid_t pid = record->proc.pid();
+ CHECK(process_reaper_.WatchForChild(FROM_HERE, pid, base::Bind(
+ &Subprocess::ChildExitedCallback,
+ base::Unretained(this))));
+
+ record->stdout_fd = record->proc.GetPipe(STDOUT_FILENO);
+ // Capture the subprocess output. Make our end of the pipe non-blocking.
+ int fd_flags = fcntl(record->stdout_fd, F_GETFL, 0) | O_NONBLOCK;
+ if (HANDLE_EINTR(fcntl(record->stdout_fd, F_SETFL, fd_flags)) < 0) {
+ LOG(ERROR) << "Unable to set non-blocking I/O mode on fd "
+ << record->stdout_fd << ".";
+ }
+
+ record->stdout_task_id = MessageLoop::current()->WatchFileDescriptor(
+ FROM_HERE,
+ record->stdout_fd,
+ MessageLoop::WatchMode::kWatchRead,
+ true,
+ base::Bind(&Subprocess::OnStdoutReady, record.get()));
+
+ subprocess_records_[pid].reset(record.release());
+ return pid;
+}
+
+void Subprocess::KillExec(pid_t pid) {
+ auto pid_record = subprocess_records_.find(pid);
+ if (pid_record == subprocess_records_.end())
+ return;
+ pid_record->second->callback.Reset();
+ kill(pid, SIGTERM);
+}
+
+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);
+}
+
+bool Subprocess::SynchronousExecFlags(const vector<string>& cmd,
+ uint32_t flags,
+ int* return_code,
+ string* stdout) {
+ brillo::ProcessImpl proc;
+ if (!LaunchProcess(cmd, flags, &proc)) {
+ LOG(ERROR) << "Failed to launch subprocess";
+ return false;
+ }
+
+ if (stdout) {
+ stdout->clear();
+ }
+
+ int fd = proc.GetPipe(STDOUT_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)
+ stdout->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();
+ if (return_code)
+ *return_code = proc_return_code;
+ return proc_return_code != brillo::Process::kErrorExitStatus;
+}
+
+bool Subprocess::SubprocessInFlight() {
+ for (const auto& pid_record : subprocess_records_) {
+ if (!pid_record.second->callback.is_null())
+ return true;
+ }
+ return false;
+}
+
+Subprocess* Subprocess::subprocess_singleton_ = nullptr;
+
+} // namespace chromeos_update_engine
diff --git a/common/subprocess.h b/common/subprocess.h
new file mode 100644
index 0000000..6b952dc
--- /dev/null
+++ b/common/subprocess.h
@@ -0,0 +1,141 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_SUBPROCESS_H_
+#define UPDATE_ENGINE_COMMON_SUBPROCESS_H_
+
+#include <unistd.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/asynchronous_signal_handler_interface.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/process.h>
+#include <brillo/process_reaper.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+// 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
+// be saved and used to cancel the callback request and kill your process. If
+// you know you won't call KillExec(), you may safely lose the return value
+// from Exec().
+
+// To create the Subprocess singleton just instantiate it with and call Init().
+// You can't have two Subprocess instances initialized at the same time.
+
+namespace chromeos_update_engine {
+
+class Subprocess {
+ public:
+ enum Flags {
+ kSearchPath = 1 << 0,
+ kRedirectStderrToStdout = 1 << 1,
+ };
+
+ // Callback type used when an async process terminates. It receives the exit
+ // code and the stdout output (and stderr if redirected).
+ using ExecCallback = base::Callback<void(int, const std::string&)>;
+
+ Subprocess() = default;
+
+ // Destroy and unregister the Subprocess singleton.
+ ~Subprocess();
+
+ // Initialize and register the Subprocess singleton.
+ void Init(brillo::AsynchronousSignalHandlerInterface* async_signal_handler);
+
+ // Launches a process in the background and calls the passed |callback| when
+ // the process exits.
+ // Returns the process id of the new launched process or 0 in case of failure.
+ pid_t Exec(const std::vector<std::string>& cmd, const ExecCallback& callback);
+ pid_t ExecFlags(const std::vector<std::string>& cmd,
+ uint32_t flags,
+ const ExecCallback& callback);
+
+ // Kills the running process with SIGTERM and ignores the callback.
+ void KillExec(pid_t tag);
+
+ // 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.
+ static bool SynchronousExec(const std::vector<std::string>& cmd,
+ int* return_code,
+ std::string* stdout);
+ static bool SynchronousExecFlags(const std::vector<std::string>& cmd,
+ uint32_t flags,
+ int* return_code,
+ std::string* stdout);
+
+ // Gets the one instance.
+ static Subprocess& Get() {
+ return *subprocess_singleton_;
+ }
+
+ // Returns true iff there is at least one subprocess we're waiting on.
+ bool SubprocessInFlight();
+
+ private:
+ FRIEND_TEST(SubprocessTest, CancelTest);
+
+ struct SubprocessRecord {
+ explicit SubprocessRecord(const ExecCallback& callback)
+ : callback(callback) {}
+
+ // The callback supplied by the caller.
+ ExecCallback callback;
+
+ // The ProcessImpl instance managing the child process. Destroying this
+ // will close our end of the pipes we have open.
+ brillo::ProcessImpl proc;
+
+ // 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};
+ int stdout_fd{-1};
+ std::string stdout;
+ };
+
+ // Callback which runs whenever there is input available on the subprocess
+ // stdout pipe.
+ static void OnStdoutReady(SubprocessRecord* record);
+
+ // Callback for when any subprocess terminates. This calls the user
+ // requested callback.
+ void ChildExitedCallback(const siginfo_t& info);
+
+ // The global instance.
+ static Subprocess* subprocess_singleton_;
+
+ // A map from the asynchronous subprocess tag (see Exec) to the subprocess
+ // record structure for all active asynchronous subprocesses.
+ std::map<pid_t, std::unique_ptr<SubprocessRecord>> subprocess_records_;
+
+ // Used to watch for child processes.
+ brillo::ProcessReaper process_reaper_;
+
+ DISALLOW_COPY_AND_ASSIGN(Subprocess);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_SUBPROCESS_H_
diff --git a/common/subprocess_unittest.cc b/common/subprocess_unittest.cc
new file mode 100644
index 0000000..b37dc91
--- /dev/null
+++ b/common/subprocess_unittest.cc
@@ -0,0 +1,263 @@
+//
+// 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/common/subprocess.h"
+
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <poll.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <brillo/bind_lambda.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 <brillo/strings/string_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class SubprocessTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ async_signal_handler_.Init();
+ subprocess_.Init(&async_signal_handler_);
+ }
+
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop loop_{&base_loop_};
+ brillo::AsynchronousSignalHandler async_signal_handler_;
+ Subprocess subprocess_;
+};
+
+namespace {
+
+int local_server_port = 0;
+
+void ExpectedResults(int expected_return_code, const string& expected_output,
+ int return_code, const string& output) {
+ EXPECT_EQ(expected_return_code, return_code);
+ EXPECT_EQ(expected_output, output);
+ MessageLoop::current()->BreakLoop();
+}
+
+void ExpectedEnvVars(int return_code, const string& output) {
+ EXPECT_EQ(0, return_code);
+ const std::set<string> allowed_envs = {"LD_LIBRARY_PATH", "PATH"};
+ for (string key_value : brillo::string_utils::Split(output, "\n")) {
+ auto key_value_pair = brillo::string_utils::SplitAtFirst(
+ key_value, "=", true);
+ EXPECT_NE(allowed_envs.end(), allowed_envs.find(key_value_pair.first));
+ }
+ MessageLoop::current()->BreakLoop();
+}
+
+} // namespace
+
+TEST_F(SubprocessTest, IsASingleton) {
+ EXPECT_EQ(&subprocess_, &Subprocess::Get());
+}
+
+TEST_F(SubprocessTest, InactiveInstancesDontChangeTheSingleton) {
+ std::unique_ptr<Subprocess> another_subprocess(new Subprocess());
+ EXPECT_EQ(&subprocess_, &Subprocess::Get());
+ another_subprocess.reset();
+ EXPECT_EQ(&subprocess_, &Subprocess::Get());
+}
+
+TEST_F(SubprocessTest, SimpleTest) {
+ EXPECT_TRUE(subprocess_.Exec({"/bin/false"},
+ base::Bind(&ExpectedResults, 1, "")));
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, EchoTest) {
+ EXPECT_TRUE(subprocess_.Exec(
+ {"/bin/sh", "-c", "echo this is stdout; echo this is stderr >&2"},
+ base::Bind(&ExpectedResults, 0, "this is stdout\nthis is stderr\n")));
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, StderrNotIncludedInOutputTest) {
+ EXPECT_TRUE(subprocess_.ExecFlags(
+ {"/bin/sh", "-c", "echo on stdout; echo on stderr >&2"},
+ 0,
+ base::Bind(&ExpectedResults, 0, "on stdout\n")));
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, EnvVarsAreFiltered) {
+ EXPECT_TRUE(subprocess_.Exec({"/usr/bin/env"}, base::Bind(&ExpectedEnvVars)));
+ loop_.Run();
+}
+
+TEST_F(SubprocessTest, SynchronousTrueSearchsOnPath) {
+ int rc = -1;
+ EXPECT_TRUE(Subprocess::SynchronousExecFlags(
+ {"true"}, Subprocess::kSearchPath, &rc, nullptr));
+ EXPECT_EQ(0, rc);
+}
+
+TEST_F(SubprocessTest, SynchronousEchoTest) {
+ vector<string> cmd = {
+ "/bin/sh",
+ "-c",
+ "echo -n stdout-here; echo -n stderr-there > /dev/stderr"};
+ int rc = -1;
+ string stdout;
+ ASSERT_TRUE(Subprocess::SynchronousExec(cmd, &rc, &stdout));
+ EXPECT_EQ(0, rc);
+ EXPECT_EQ("stdout-herestderr-there", stdout);
+}
+
+TEST_F(SubprocessTest, SynchronousEchoNoOutputTest) {
+ int rc = -1;
+ ASSERT_TRUE(Subprocess::SynchronousExec(
+ {"/bin/sh", "-c", "echo test"},
+ &rc, nullptr));
+ EXPECT_EQ(0, rc);
+}
+
+namespace {
+void CallbackBad(int return_code, const string& output) {
+ ADD_FAILURE() << "should never be called.";
+}
+
+// TODO(garnold) this test method uses test_http_server as a representative for
+// interactive processes that can be spawned/terminated at will. This causes us
+// to go through hoops when spawning this process (e.g. obtaining the port
+// number it uses so we can control it with wget). It would have been much
+// preferred to use something else and thus simplify both test_http_server
+// (doesn't have to be able to communicate through a temp file) and the test
+// code below; for example, it sounds like a brain dead sleep loop with proper
+// signal handlers could be used instead.
+void StartAndCancelInRunLoop(bool* spawned) {
+ // Create a temp file for test_http_server to communicate its port number.
+ char temp_file_name[] = "/tmp/subprocess_unittest-test_http_server-XXXXXX";
+ int temp_fd = mkstemp(temp_file_name);
+ CHECK_GE(temp_fd, 0);
+ int temp_flags = fcntl(temp_fd, F_GETFL, 0) | O_NONBLOCK;
+ CHECK_EQ(fcntl(temp_fd, F_SETFL, temp_flags), 0);
+
+ vector<string> cmd;
+ cmd.push_back("./test_http_server");
+ cmd.push_back(temp_file_name);
+ uint32_t tag = Subprocess::Get().Exec(cmd, base::Bind(&CallbackBad));
+ EXPECT_NE(0, tag);
+ *spawned = true;
+ printf("test http server spawned\n");
+ // Wait for server to be up and running
+ TimeDelta total_wait_time;
+ const TimeDelta kSleepTime = TimeDelta::FromMilliseconds(100);
+ const TimeDelta kMaxWaitTime = TimeDelta::FromSeconds(3);
+ local_server_port = 0;
+ static const char* kServerListeningMsgPrefix = "listening on port ";
+ while (total_wait_time.InMicroseconds() < kMaxWaitTime.InMicroseconds()) {
+ char line[80];
+ int line_len = read(temp_fd, line, sizeof(line) - 1);
+ if (line_len > 0) {
+ line[line_len] = '\0';
+ CHECK_EQ(strstr(line, kServerListeningMsgPrefix), line);
+ const char* listening_port_str =
+ line + strlen(kServerListeningMsgPrefix);
+ char* end_ptr;
+ long raw_port = strtol(listening_port_str, // NOLINT(runtime/int)
+ &end_ptr, 10);
+ CHECK(!*end_ptr || *end_ptr == '\n');
+ local_server_port = static_cast<in_port_t>(raw_port);
+ break;
+ } else if (line_len < 0 && errno != EAGAIN) {
+ LOG(INFO) << "error reading from " << temp_file_name << ": "
+ << strerror(errno);
+ break;
+ }
+ usleep(kSleepTime.InMicroseconds());
+ total_wait_time += kSleepTime;
+ }
+ close(temp_fd);
+ remove(temp_file_name);
+ CHECK_GT(local_server_port, 0);
+ LOG(INFO) << "server listening on port " << local_server_port;
+ Subprocess::Get().KillExec(tag);
+}
+
+void ExitWhenDone(bool* spawned) {
+ if (*spawned && !Subprocess::Get().SubprocessInFlight()) {
+ // tear down the sub process
+ printf("tear down time\n");
+ int status = test_utils::System(
+ base::StringPrintf("wget -O /dev/null http://127.0.0.1:%d/quitquitquit",
+ local_server_port));
+ EXPECT_NE(-1, status) << "system() failed";
+ EXPECT_TRUE(WIFEXITED(status))
+ << "command failed to run or died abnormally";
+ MessageLoop::current()->BreakLoop();
+ } else {
+ // Re-run this callback again in 10 ms.
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ExitWhenDone, spawned),
+ TimeDelta::FromMilliseconds(10));
+ }
+}
+
+} // namespace
+
+TEST_F(SubprocessTest, CancelTest) {
+ bool spawned = false;
+ loop_.PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&StartAndCancelInRunLoop, &spawned),
+ TimeDelta::FromMilliseconds(100));
+ loop_.PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&ExitWhenDone, &spawned),
+ TimeDelta::FromMilliseconds(10));
+ loop_.Run();
+ // 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(10),
+ base::Bind([] {
+ return Subprocess::Get().subprocess_records_.empty();
+ }));
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/terminator.cc b/common/terminator.cc
new file mode 100644
index 0000000..62adafd
--- /dev/null
+++ b/common/terminator.cc
@@ -0,0 +1,56 @@
+//
+// 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/common/terminator.h"
+
+#include <cstdlib>
+
+namespace chromeos_update_engine {
+
+volatile sig_atomic_t Terminator::exit_status_ = 1; // default exit status
+volatile sig_atomic_t Terminator::exit_blocked_ = 0;
+volatile sig_atomic_t Terminator::exit_requested_ = 0;
+
+void Terminator::Init() {
+ exit_blocked_ = 0;
+ exit_requested_ = 0;
+ signal(SIGTERM, HandleSignal);
+}
+
+void Terminator::Init(int exit_status) {
+ exit_status_ = exit_status;
+ Init();
+}
+
+void Terminator::Exit() {
+ exit(exit_status_);
+}
+
+void Terminator::HandleSignal(int signum) {
+ if (exit_blocked_ == 0) {
+ Exit();
+ }
+ exit_requested_ = 1;
+}
+
+ScopedTerminatorExitUnblocker::~ScopedTerminatorExitUnblocker() {
+ Terminator::set_exit_blocked(false);
+ if (Terminator::exit_requested()) {
+ Terminator::Exit();
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/terminator.h b/common/terminator.h
new file mode 100644
index 0000000..20616f6
--- /dev/null
+++ b/common/terminator.h
@@ -0,0 +1,65 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_TERMINATOR_H_
+#define UPDATE_ENGINE_COMMON_TERMINATOR_H_
+
+#include <signal.h>
+
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+namespace chromeos_update_engine {
+
+// A class allowing graceful delayed exit.
+class Terminator {
+ public:
+ // Initializes the terminator and sets up signal handlers.
+ static void Init();
+ static void Init(int exit_status);
+
+ // Terminates the current process.
+ static void Exit();
+
+ // Set to true if the terminator should block termination requests in an
+ // attempt to block exiting.
+ static void set_exit_blocked(bool block) { exit_blocked_ = block ? 1 : 0; }
+ static bool exit_blocked() { return exit_blocked_ != 0; }
+
+ // Returns true if the system is trying to terminate the process, false
+ // otherwise. Returns true only if exit was blocked when the termination
+ // request arrived.
+ static bool exit_requested() { return exit_requested_ != 0; }
+
+ private:
+ FRIEND_TEST(TerminatorTest, HandleSignalTest);
+ FRIEND_TEST(TerminatorDeathTest, ScopedTerminatorExitUnblockerExitTest);
+
+ // The signal handler.
+ static void HandleSignal(int signum);
+
+ static volatile sig_atomic_t exit_status_;
+ static volatile sig_atomic_t exit_blocked_;
+ static volatile sig_atomic_t exit_requested_;
+};
+
+class ScopedTerminatorExitUnblocker {
+ public:
+ ~ScopedTerminatorExitUnblocker();
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_TERMINATOR_H_
diff --git a/common/terminator_unittest.cc b/common/terminator_unittest.cc
new file mode 100644
index 0000000..5e8302f
--- /dev/null
+++ b/common/terminator_unittest.cc
@@ -0,0 +1,85 @@
+//
+// 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/common/terminator.h"
+
+#include <gtest/gtest.h>
+#include <gtest/gtest-spi.h>
+
+using testing::ExitedWithCode;
+
+namespace chromeos_update_engine {
+
+class TerminatorTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ Terminator::Init();
+ ASSERT_FALSE(Terminator::exit_blocked());
+ ASSERT_FALSE(Terminator::exit_requested());
+ }
+ void TearDown() override {
+ // Makes sure subsequent non-Terminator tests don't get accidentally
+ // terminated.
+ Terminator::Init();
+ }
+};
+
+typedef TerminatorTest TerminatorDeathTest;
+
+namespace {
+void UnblockExitThroughUnblocker() {
+ ScopedTerminatorExitUnblocker unblocker = ScopedTerminatorExitUnblocker();
+}
+
+void RaiseSIGTERM() {
+ ASSERT_EXIT(raise(SIGTERM), ExitedWithCode(2), "");
+}
+} // namespace
+
+TEST_F(TerminatorTest, HandleSignalTest) {
+ Terminator::set_exit_blocked(true);
+ Terminator::HandleSignal(SIGTERM);
+ ASSERT_TRUE(Terminator::exit_requested());
+}
+
+TEST_F(TerminatorTest, ScopedTerminatorExitUnblockerTest) {
+ Terminator::set_exit_blocked(true);
+ ASSERT_TRUE(Terminator::exit_blocked());
+ ASSERT_FALSE(Terminator::exit_requested());
+ UnblockExitThroughUnblocker();
+ ASSERT_FALSE(Terminator::exit_blocked());
+ ASSERT_FALSE(Terminator::exit_requested());
+}
+
+TEST_F(TerminatorDeathTest, ExitTest) {
+ ASSERT_EXIT(Terminator::Exit(), ExitedWithCode(2), "");
+ Terminator::set_exit_blocked(true);
+ ASSERT_EXIT(Terminator::Exit(), ExitedWithCode(2), "");
+}
+
+TEST_F(TerminatorDeathTest, RaiseSignalTest) {
+ RaiseSIGTERM();
+ Terminator::set_exit_blocked(true);
+ EXPECT_FATAL_FAILURE(RaiseSIGTERM(), "");
+}
+
+TEST_F(TerminatorDeathTest, ScopedTerminatorExitUnblockerExitTest) {
+ Terminator::set_exit_blocked(true);
+ Terminator::exit_requested_ = 1;
+ ASSERT_EXIT(UnblockExitThroughUnblocker(), ExitedWithCode(2), "");
+}
+
+} // namespace chromeos_update_engine
diff --git a/common/test_utils.cc b/common/test_utils.cc
new file mode 100644
index 0000000..f89c448
--- /dev/null
+++ b/common/test_utils.cc
@@ -0,0 +1,268 @@
+//
+// 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/common/test_utils.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/file_writer.h"
+
+using base::StringPrintf;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void PrintTo(const Extent& extent, ::std::ostream* os) {
+ *os << "(" << extent.start_block() << ", " << extent.num_blocks() << ")";
+}
+
+namespace test_utils {
+
+const char* const kMountPathTemplate = "UpdateEngineTests_mnt-XXXXXX";
+
+const uint8_t kRandomString[] = {
+ 0xf2, 0xb7, 0x55, 0x92, 0xea, 0xa6, 0xc9, 0x57,
+ 0xe0, 0xf8, 0xeb, 0x34, 0x93, 0xd9, 0xc4, 0x8f,
+ 0xcb, 0x20, 0xfa, 0x37, 0x4b, 0x40, 0xcf, 0xdc,
+ 0xa5, 0x08, 0x70, 0x89, 0x79, 0x35, 0xe2, 0x3d,
+ 0x56, 0xa4, 0x75, 0x73, 0xa3, 0x6d, 0xd1, 0xd5,
+ 0x26, 0xbb, 0x9c, 0x60, 0xbd, 0x2f, 0x5a, 0xfa,
+ 0xb7, 0xd4, 0x3a, 0x50, 0xa7, 0x6b, 0x3e, 0xfd,
+ 0x61, 0x2b, 0x3a, 0x31, 0x30, 0x13, 0x33, 0x53,
+ 0xdb, 0xd0, 0x32, 0x71, 0x5c, 0x39, 0xed, 0xda,
+ 0xb4, 0x84, 0xca, 0xbc, 0xbd, 0x78, 0x1c, 0x0c,
+ 0xd8, 0x0b, 0x41, 0xe8, 0xe1, 0xe0, 0x41, 0xad,
+ 0x03, 0x12, 0xd3, 0x3d, 0xb8, 0x75, 0x9b, 0xe6,
+ 0xd9, 0x01, 0xd0, 0x87, 0xf4, 0x36, 0xfa, 0xa7,
+ 0x0a, 0xfa, 0xc5, 0x87, 0x65, 0xab, 0x9a, 0x7b,
+ 0xeb, 0x58, 0x23, 0xf0, 0xa8, 0x0a, 0xf2, 0x33,
+ 0x3a, 0xe2, 0xe3, 0x35, 0x74, 0x95, 0xdd, 0x3c,
+ 0x59, 0x5a, 0xd9, 0x52, 0x3a, 0x3c, 0xac, 0xe5,
+ 0x15, 0x87, 0x6d, 0x82, 0xbc, 0xf8, 0x7d, 0xbe,
+ 0xca, 0xd3, 0x2c, 0xd6, 0xec, 0x38, 0xeb, 0xe4,
+ 0x53, 0xb0, 0x4c, 0x3f, 0x39, 0x29, 0xf7, 0xa4,
+ 0x73, 0xa8, 0xcb, 0x32, 0x50, 0x05, 0x8c, 0x1c,
+ 0x1c, 0xca, 0xc9, 0x76, 0x0b, 0x8f, 0x6b, 0x57,
+ 0x1f, 0x24, 0x2b, 0xba, 0x82, 0xba, 0xed, 0x58,
+ 0xd8, 0xbf, 0xec, 0x06, 0x64, 0x52, 0x6a, 0x3f,
+ 0xe4, 0xad, 0xce, 0x84, 0xb4, 0x27, 0x55, 0x14,
+ 0xe3, 0x75, 0x59, 0x73, 0x71, 0x51, 0xea, 0xe8,
+ 0xcc, 0xda, 0x4f, 0x09, 0xaf, 0xa4, 0xbc, 0x0e,
+ 0xa6, 0x1f, 0xe2, 0x3a, 0xf8, 0x96, 0x7d, 0x30,
+ 0x23, 0xc5, 0x12, 0xb5, 0xd8, 0x73, 0x6b, 0x71,
+ 0xab, 0xf1, 0xd7, 0x43, 0x58, 0xa7, 0xc9, 0xf0,
+ 0xe4, 0x85, 0x1c, 0xd6, 0x92, 0x50, 0x2c, 0x98,
+ 0x36, 0xfe, 0x87, 0xaf, 0x43, 0x8f, 0x8f, 0xf5,
+ 0x88, 0x48, 0x18, 0x42, 0xcf, 0x42, 0xc1, 0xa8,
+ 0xe8, 0x05, 0x08, 0xa1, 0x45, 0x70, 0x5b, 0x8c,
+ 0x39, 0x28, 0xab, 0xe9, 0x6b, 0x51, 0xd2, 0xcb,
+ 0x30, 0x04, 0xea, 0x7d, 0x2f, 0x6e, 0x6c, 0x3b,
+ 0x5f, 0x82, 0xd9, 0x5b, 0x89, 0x37, 0x65, 0x65,
+ 0xbe, 0x9f, 0xa3, 0x5d,
+};
+
+bool IsXAttrSupported(const base::FilePath& dir_path) {
+ char *path = strdup(dir_path.Append("xattr_test_XXXXXX").value().c_str());
+
+ int fd = mkstemp(path);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error creating temporary file in " << dir_path.value();
+ free(path);
+ return false;
+ }
+
+ if (unlink(path) != 0) {
+ PLOG(ERROR) << "Error unlinking temporary file " << path;
+ close(fd);
+ free(path);
+ return false;
+ }
+
+ int xattr_res = fsetxattr(fd, "user.xattr-test", "value", strlen("value"), 0);
+ if (xattr_res != 0) {
+ if (errno == ENOTSUP) {
+ // Leave it to call-sites to warn about non-support.
+ } else {
+ PLOG(ERROR) << "Error setting xattr on " << path;
+ }
+ }
+ close(fd);
+ free(path);
+ return xattr_res == 0;
+}
+
+bool WriteFileVector(const string& path, const brillo::Blob& data) {
+ return utils::WriteFile(path.c_str(), data.data(), data.size());
+}
+
+bool WriteFileString(const string& path, const string& data) {
+ return utils::WriteFile(path.c_str(), data.data(), data.size());
+}
+
+// Binds provided |filename| to an unused loopback device, whose name is written
+// to the string pointed to by |lo_dev_name_p|. Returns true on success, false
+// otherwise (along with corresponding test failures), in which case the content
+// of |lo_dev_name_p| is unknown.
+bool BindToUnusedLoopDevice(const string& filename, string* lo_dev_name_p) {
+ CHECK(lo_dev_name_p);
+
+ // Bind to an unused loopback device, sanity check the device name.
+ lo_dev_name_p->clear();
+ if (!(utils::ReadPipe("losetup --show -f " + filename, lo_dev_name_p) &&
+ base::StartsWithASCII(*lo_dev_name_p, "/dev/loop", true))) {
+ ADD_FAILURE();
+ return false;
+ }
+
+ // Strip anything from the first newline char.
+ size_t newline_pos = lo_dev_name_p->find('\n');
+ if (newline_pos != string::npos)
+ lo_dev_name_p->erase(newline_pos);
+
+ return true;
+}
+
+bool ExpectVectorsEq(const brillo::Blob& expected,
+ const brillo::Blob& actual) {
+ EXPECT_EQ(expected.size(), actual.size());
+ if (expected.size() != actual.size())
+ return false;
+ bool is_all_eq = true;
+ for (unsigned int i = 0; i < expected.size(); i++) {
+ EXPECT_EQ(expected[i], actual[i]) << "offset: " << i;
+ is_all_eq = is_all_eq && (expected[i] == actual[i]);
+ }
+ return is_all_eq;
+}
+
+void FillWithData(brillo::Blob* buffer) {
+ size_t input_counter = 0;
+ for (uint8_t& b : *buffer) {
+ b = kRandomString[input_counter];
+ input_counter++;
+ input_counter %= sizeof(kRandomString);
+ }
+}
+
+void CreateEmptyExtImageAtPath(const string& path,
+ size_t size,
+ int block_size) {
+ EXPECT_EQ(0, System(StringPrintf("dd if=/dev/zero of=%s"
+ " seek=%" PRIuS " bs=1 count=1 status=none",
+ path.c_str(), size)));
+ EXPECT_EQ(0, System(StringPrintf("mkfs.ext3 -q -b %d -F %s",
+ block_size, path.c_str())));
+}
+
+void CreateExtImageAtPath(const string& path, vector<string>* out_paths) {
+ // create 10MiB sparse file, mounted at a unique location.
+ string mount_path;
+ CHECK(utils::MakeTempDirectory(kMountPathTemplate, &mount_path));
+ ScopedDirRemover mount_path_unlinker(mount_path);
+
+ EXPECT_EQ(0, System(StringPrintf("dd if=/dev/zero of=%s"
+ " seek=10485759 bs=1 count=1 status=none",
+ path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mkfs.ext3 -q -b 4096 -F %s",
+ path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mount -o loop %s %s", path.c_str(),
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("echo hi > %s/hi", mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("echo hello > %s/hello",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir", mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir/empty_dir",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mkdir %s/some_dir/mnt",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("echo T > %s/some_dir/test",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mkfifo %s/some_dir/fifo",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("mknod %s/cdev c 2 3", mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("ln -s /some/target %s/sym",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("ln %s/some_dir/test %s/testlink",
+ mount_path.c_str(), mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("echo T > %s/srchardlink0",
+ mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("ln %s/srchardlink0 %s/srchardlink1",
+ mount_path.c_str(), mount_path.c_str())));
+ EXPECT_EQ(0, System(StringPrintf("ln -s bogus %s/boguslink",
+ mount_path.c_str())));
+ EXPECT_TRUE(utils::UnmountFilesystem(mount_path.c_str()));
+
+ if (out_paths) {
+ out_paths->clear();
+ out_paths->push_back("");
+ out_paths->push_back("/hi");
+ out_paths->push_back("/boguslink");
+ out_paths->push_back("/hello");
+ out_paths->push_back("/some_dir");
+ out_paths->push_back("/some_dir/empty_dir");
+ out_paths->push_back("/some_dir/mnt");
+ out_paths->push_back("/some_dir/test");
+ out_paths->push_back("/some_dir/fifo");
+ out_paths->push_back("/cdev");
+ out_paths->push_back("/testlink");
+ out_paths->push_back("/sym");
+ out_paths->push_back("/srchardlink0");
+ out_paths->push_back("/srchardlink1");
+ out_paths->push_back("/lost+found");
+ }
+}
+
+ScopedLoopMounter::ScopedLoopMounter(const string& file_path,
+ string* mnt_path,
+ unsigned long flags) { // NOLINT - long
+ EXPECT_TRUE(utils::MakeTempDirectory("mnt.XXXXXX", mnt_path));
+ dir_remover_.reset(new ScopedDirRemover(*mnt_path));
+
+ string loop_dev;
+ loop_binder_.reset(new ScopedLoopbackDeviceBinder(file_path, &loop_dev));
+
+ EXPECT_TRUE(utils::MountFilesystem(loop_dev, *mnt_path, flags));
+ unmounter_.reset(new ScopedFilesystemUnmounter(*mnt_path));
+}
+
+base::FilePath GetBuildArtifactsPath() {
+ base::FilePath exe_path;
+ base::ReadSymbolicLink(base::FilePath("/proc/self/exe"), &exe_path);
+ return exe_path.DirName();
+}
+
+} // namespace test_utils
+} // namespace chromeos_update_engine
diff --git a/common/test_utils.h b/common/test_utils.h
new file mode 100644
index 0000000..616bdd3
--- /dev/null
+++ b/common/test_utils.h
@@ -0,0 +1,271 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_TEST_UTILS_H_
+#define UPDATE_ENGINE_COMMON_TEST_UTILS_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+// Streams used for gtest's PrintTo() functions.
+#include <iostream> // NOLINT(readability/streams)
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_metadata.pb.h"
+
+// These are some handy functions for unittests.
+
+namespace chromeos_update_engine {
+
+// PrintTo() functions are used by gtest to log these objects. These PrintTo()
+// functions must be defined in the same namespace as the first argument.
+void PrintTo(const Extent& extent, ::std::ostream* os);
+
+namespace test_utils {
+
+// 300 byte pseudo-random string. Not null terminated.
+// This does not gzip compress well.
+extern const uint8_t kRandomString[300];
+
+// Writes the data passed to path. The file at path will be overwritten if it
+// exists. Returns true on success, false otherwise.
+bool WriteFileVector(const std::string& path, const brillo::Blob& data);
+bool WriteFileString(const std::string& path, const std::string& data);
+
+bool BindToUnusedLoopDevice(const std::string &filename,
+ std::string* lo_dev_name_ptr);
+
+// Returns true iff a == b
+bool ExpectVectorsEq(const brillo::Blob& a, const brillo::Blob& b);
+
+inline int System(const std::string& cmd) {
+ return system(cmd.c_str());
+}
+
+inline int Symlink(const std::string& oldpath, const std::string& newpath) {
+ return symlink(oldpath.c_str(), newpath.c_str());
+}
+
+inline int Chmod(const std::string& path, mode_t mode) {
+ return chmod(path.c_str(), mode);
+}
+
+inline int Mkdir(const std::string& path, mode_t mode) {
+ return mkdir(path.c_str(), mode);
+}
+
+inline int Chdir(const std::string& path) {
+ return chdir(path.c_str());
+}
+
+// Checks if xattr is supported in the directory specified by
+// |dir_path| which must be writable. Returns true if the feature is
+// supported, false if not or if an error occurred.
+bool IsXAttrSupported(const base::FilePath& dir_path);
+
+void FillWithData(brillo::Blob* buffer);
+
+// Creates an empty ext image.
+void CreateEmptyExtImageAtPath(const std::string& path,
+ size_t size,
+ int block_size);
+
+// Creates an ext image with some files in it. The paths creates are
+// returned in out_paths.
+void CreateExtImageAtPath(const std::string& path,
+ std::vector<std::string>* out_paths);
+
+// Class to unmount FS when object goes out of scope
+class ScopedFilesystemUnmounter {
+ public:
+ explicit ScopedFilesystemUnmounter(const std::string& mountpoint)
+ : mountpoint_(mountpoint),
+ should_unmount_(true) {}
+ ~ScopedFilesystemUnmounter() {
+ if (should_unmount_) {
+ utils::UnmountFilesystem(mountpoint_);
+ }
+ }
+ void set_should_unmount(bool unmount) { should_unmount_ = unmount; }
+ private:
+ const std::string mountpoint_;
+ bool should_unmount_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedFilesystemUnmounter);
+};
+
+class ScopedLoopbackDeviceBinder {
+ public:
+ ScopedLoopbackDeviceBinder(const std::string& file, std::string* dev) {
+ is_bound_ = BindToUnusedLoopDevice(file, &dev_);
+ EXPECT_TRUE(is_bound_);
+
+ if (is_bound_ && dev)
+ *dev = dev_;
+ }
+
+ ~ScopedLoopbackDeviceBinder() {
+ if (!is_bound_)
+ return;
+
+ for (int retry = 0; retry < 5; retry++) {
+ std::vector<std::string> args;
+ args.push_back("/sbin/losetup");
+ args.push_back("-d");
+ args.push_back(dev_);
+ int return_code = 0;
+ EXPECT_TRUE(Subprocess::SynchronousExec(args, &return_code, nullptr));
+ if (return_code == 0) {
+ return;
+ }
+ sleep(1);
+ }
+ ADD_FAILURE();
+ }
+
+ const std::string &dev() {
+ EXPECT_TRUE(is_bound_);
+ return dev_;
+ }
+
+ bool is_bound() const { return is_bound_; }
+
+ private:
+ std::string dev_;
+ bool is_bound_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedLoopbackDeviceBinder);
+};
+
+class ScopedTempFile {
+ public:
+ ScopedTempFile() {
+ EXPECT_TRUE(utils::MakeTempFile("update_engine_test_temp_file.XXXXXX",
+ &path_,
+ nullptr));
+ unlinker_.reset(new ScopedPathUnlinker(path_));
+ }
+ const std::string& GetPath() { return path_; }
+ private:
+ std::string path_;
+ std::unique_ptr<ScopedPathUnlinker> unlinker_;
+};
+
+class ScopedLoopMounter {
+ public:
+ explicit ScopedLoopMounter(const std::string& file_path,
+ std::string* mnt_path,
+ unsigned long flags); // NOLINT(runtime/int)
+
+ private:
+ // These objects must be destructed in the following order:
+ // ScopedFilesystemUnmounter (the file system must be unmounted first)
+ // ScopedLoopbackDeviceBinder (then the loop device can be deleted)
+ // ScopedDirRemover (then the mount point can be deleted)
+ std::unique_ptr<ScopedDirRemover> dir_remover_;
+ std::unique_ptr<ScopedLoopbackDeviceBinder> loop_binder_;
+ std::unique_ptr<ScopedFilesystemUnmounter> unmounter_;
+};
+
+// Returns the path where the build artifacts are stored. This is the directory
+// where the unittest executable is being run from.
+base::FilePath GetBuildArtifactsPath();
+
+} // namespace test_utils
+
+// Useful actions for test. These need to be defined in the
+// chromeos_update_engine namespace.
+
+class NoneType;
+
+template<typename T>
+class ObjectFeederAction;
+
+template<typename T>
+class ActionTraits<ObjectFeederAction<T>> {
+ public:
+ typedef T OutputObjectType;
+ typedef NoneType InputObjectType;
+};
+
+// This is a simple Action class for testing. It feeds an object into
+// another action.
+template<typename T>
+class ObjectFeederAction : public Action<ObjectFeederAction<T>> {
+ public:
+ typedef NoneType InputObjectType;
+ typedef T OutputObjectType;
+ void PerformAction() {
+ LOG(INFO) << "feeder running!";
+ CHECK(this->processor_);
+ if (this->HasOutputPipe()) {
+ this->SetOutputObject(out_obj_);
+ }
+ this->processor_->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ static std::string StaticType() { return "ObjectFeederAction"; }
+ std::string Type() const { return StaticType(); }
+ void set_obj(const T& out_obj) {
+ out_obj_ = out_obj;
+ }
+ private:
+ T out_obj_;
+};
+
+template<typename T>
+class ObjectCollectorAction;
+
+template<typename T>
+class ActionTraits<ObjectCollectorAction<T>> {
+ public:
+ typedef NoneType OutputObjectType;
+ typedef T InputObjectType;
+};
+
+// This is a simple Action class for testing. It receives an object from
+// another action.
+template<typename T>
+class ObjectCollectorAction : public Action<ObjectCollectorAction<T>> {
+ public:
+ typedef T InputObjectType;
+ typedef NoneType OutputObjectType;
+ void PerformAction() {
+ LOG(INFO) << "collector running!";
+ ASSERT_TRUE(this->processor_);
+ if (this->HasInputObject()) {
+ object_ = this->GetInputObject();
+ }
+ this->processor_->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ static std::string StaticType() { return "ObjectCollectorAction"; }
+ std::string Type() const { return StaticType(); }
+ const T& object() const { return object_; }
+ private:
+ T object_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_COMMON_TEST_UTILS_H_
diff --git a/common/utils.cc b/common/utils.cc
new file mode 100644
index 0000000..f1a357b
--- /dev/null
+++ b/common/utils.cc
@@ -0,0 +1,1302 @@
+//
+// 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/common/utils.h"
+
+#include <stdint.h>
+
+#include <dirent.h>
+#include <elf.h>
+#include <endian.h>
+#include <errno.h>
+#include <ext2fs/ext2fs.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_file.h>
+#include <base/format_macros.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/rand_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/data_encoding.h>
+#include <brillo/message_loops/message_loop.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"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/payload_consumer/file_writer.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::min;
+using std::pair;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The following constants control how UnmountFilesystem should retry if
+// umount() fails with an errno EBUSY, i.e. retry 5 times over the course of
+// one second.
+const int kUnmountMaxNumOfRetries = 5;
+const int kUnmountRetryIntervalInMicroseconds = 200 * 1000; // 200 ms
+
+// Number of bytes to read from a file to attempt to detect its contents. Used
+// in GetFileFormat.
+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::StartsWithASCII(disk_name, "/dev/ubi", true) ||
+ base::StartsWithASCII(disk_name, "/dev/mtd", true);
+}
+
+// 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
+// temporary path in |template_path| and returns true.
+bool GetTempName(const string& path, base::FilePath* template_path) {
+ if (path[0] == '/' || base::StartsWithASCII(path, "./", true) ||
+ base::StartsWithASCII(path, "../", true)) {
+ *template_path = base::FilePath(path);
+ return true;
+ }
+
+ base::FilePath temp_dir;
+#ifdef __ANDROID__
+ temp_dir = base::FilePath(constants::kNonVolatileDirectory).Append("tmp");
+ if (!base::PathExists(temp_dir))
+ TEST_AND_RETURN_FALSE(base::CreateDirectory(temp_dir));
+#else
+ TEST_AND_RETURN_FALSE(base::GetTempDir(&temp_dir));
+#endif // __ANDROID__
+ *template_path = temp_dir.Append(path);
+ return true;
+}
+
+} // namespace
+
+namespace utils {
+
+// Cgroup container is created in update-engine's upstart script located at
+// /etc/init/update-engine.conf.
+static const char kCGroupDir[] = "/sys/fs/cgroup/cpu/update-engine";
+
+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, int data_len) {
+ DirectFileWriter writer;
+ TEST_AND_RETURN_FALSE_ERRNO(0 == writer.Open(path,
+ O_WRONLY | O_CREAT | O_TRUNC,
+ 0600));
+ ScopedFileWriterCloser closer(&writer);
+ TEST_AND_RETURN_FALSE_ERRNO(writer.Write(data, data_len));
+ return true;
+}
+
+bool WriteAll(int fd, const void* buf, size_t count) {
+ const char* c_buf = static_cast<const char*>(buf);
+ ssize_t bytes_written = 0;
+ while (bytes_written < static_cast<ssize_t>(count)) {
+ ssize_t rc = write(fd, c_buf + bytes_written, count - bytes_written);
+ TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+ bytes_written += rc;
+ }
+ return true;
+}
+
+bool PWriteAll(int fd, const void* buf, size_t count, off_t offset) {
+ const char* c_buf = static_cast<const char*>(buf);
+ size_t bytes_written = 0;
+ int num_attempts = 0;
+ while (bytes_written < count) {
+ num_attempts++;
+ ssize_t rc = pwrite(fd, c_buf + bytes_written, count - bytes_written,
+ offset + bytes_written);
+ // TODO(garnold) for debugging failure in chromium-os:31077; to be removed.
+ if (rc < 0) {
+ PLOG(ERROR) << "pwrite error; num_attempts=" << num_attempts
+ << " bytes_written=" << bytes_written
+ << " count=" << count << " offset=" << offset;
+ }
+ TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+ bytes_written += rc;
+ }
+ return true;
+}
+
+bool WriteAll(FileDescriptorPtr fd, const void* buf, size_t count) {
+ const char* c_buf = static_cast<const char*>(buf);
+ ssize_t bytes_written = 0;
+ while (bytes_written < static_cast<ssize_t>(count)) {
+ ssize_t rc = fd->Write(c_buf + bytes_written, count - bytes_written);
+ TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+ bytes_written += rc;
+ }
+ return true;
+}
+
+bool PWriteAll(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);
+}
+
+bool PReadAll(int fd, void* buf, size_t count, off_t offset,
+ ssize_t* out_bytes_read) {
+ char* c_buf = static_cast<char*>(buf);
+ ssize_t bytes_read = 0;
+ while (bytes_read < static_cast<ssize_t>(count)) {
+ ssize_t rc = pread(fd, c_buf + bytes_read, count - bytes_read,
+ offset + bytes_read);
+ TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+ if (rc == 0) {
+ break;
+ }
+ bytes_read += rc;
+ }
+ *out_bytes_read = bytes_read;
+ return true;
+}
+
+bool PReadAll(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);
+ ssize_t bytes_read = 0;
+ while (bytes_read < static_cast<ssize_t>(count)) {
+ ssize_t rc = fd->Read(c_buf + bytes_read, count - bytes_read);
+ TEST_AND_RETURN_FALSE_ERRNO(rc >= 0);
+ if (rc == 0) {
+ break;
+ }
+ bytes_read += rc;
+ }
+ *out_bytes_read = bytes_read;
+ return true;
+}
+
+// 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, size_t nbytes,
+ brillo::Blob* vec_p) {
+ CHECK(buf);
+ CHECK(vec_p);
+ vec_p->insert(vec_p->end(), buf, buf + nbytes);
+}
+static void AppendBytes(const uint8_t* buf, size_t nbytes,
+ string* str_p) {
+ CHECK(buf);
+ CHECK(str_p);
+ str_p->append(buf, buf + nbytes);
+}
+
+// Reads from an open file |fp|, appending the read content to the container
+// pointer to by |out_p|. Returns true upon successful reading all of the
+// file's content, false otherwise. If |size| is not -1, reads up to |size|
+// bytes.
+template <class T>
+static bool Read(FILE* fp, off_t size, T* out_p) {
+ CHECK(fp);
+ CHECK(size == -1 || size >= 0);
+ uint8_t buf[1024];
+ while (size == -1 || size > 0) {
+ off_t bytes_to_read = sizeof(buf);
+ if (size > 0 && bytes_to_read > size) {
+ bytes_to_read = size;
+ }
+ size_t nbytes = fread(buf, 1, bytes_to_read, fp);
+ if (!nbytes) {
+ break;
+ }
+ AppendBytes(buf, nbytes, out_p);
+ if (size != -1) {
+ CHECK(size >= static_cast<off_t>(nbytes));
+ size -= nbytes;
+ }
+ }
+ if (ferror(fp)) {
+ return false;
+ }
+ return size == 0 || feof(fp);
+}
+
+// Opens a file |path| for reading and appends its the contents to a container
+// |out_p|. Starts reading the file from |offset|. If |offset| is beyond the end
+// of the file, returns success. If |size| is not -1, reads up to |size| bytes.
+template <class T>
+static bool ReadFileChunkAndAppend(const string& path,
+ off_t offset,
+ off_t size,
+ T* out_p) {
+ CHECK_GE(offset, 0);
+ CHECK(size == -1 || size >= 0);
+ base::ScopedFILE fp(fopen(path.c_str(), "r"));
+ if (!fp.get())
+ return false;
+ if (offset) {
+ // Return success without appending any data if a chunk beyond the end of
+ // the file is requested.
+ if (offset >= FileSize(path)) {
+ return true;
+ }
+ TEST_AND_RETURN_FALSE_ERRNO(fseek(fp.get(), offset, SEEK_SET) == 0);
+ }
+ return Read(fp.get(), size, out_p);
+}
+
+// TODO(deymo): This is only used in unittest, but requires the private
+// Read<string>() defined here. Expose Read<string>() or move to base/ version.
+bool ReadPipe(const string& cmd, string* out_p) {
+ FILE* fp = popen(cmd.c_str(), "r");
+ if (!fp)
+ return false;
+ bool success = Read(fp, -1, out_p);
+ return (success && pclose(fp) >= 0);
+}
+
+bool ReadFile(const string& path, brillo::Blob* out_p) {
+ return ReadFileChunkAndAppend(path, 0, -1, out_p);
+}
+
+bool ReadFile(const string& path, string* out_p) {
+ return ReadFileChunkAndAppend(path, 0, -1, out_p);
+}
+
+bool ReadFileChunk(const string& path, off_t offset, off_t size,
+ brillo::Blob* out_p) {
+ return ReadFileChunkAndAppend(path, offset, size, out_p);
+}
+
+off_t BlockDevSize(int fd) {
+ uint64_t dev_size;
+ int rc = ioctl(fd, BLKGETSIZE64, &dev_size);
+ if (rc == -1) {
+ dev_size = -1;
+ PLOG(ERROR) << "Error running ioctl(BLKGETSIZE64) on " << fd;
+ }
+ return dev_size;
+}
+
+off_t FileSize(int fd) {
+ struct stat stbuf;
+ int rc = fstat(fd, &stbuf);
+ CHECK_EQ(rc, 0);
+ if (rc < 0) {
+ PLOG(ERROR) << "Error stat-ing " << fd;
+ return rc;
+ }
+ if (S_ISREG(stbuf.st_mode))
+ return stbuf.st_size;
+ if (S_ISBLK(stbuf.st_mode))
+ return BlockDevSize(fd);
+ LOG(ERROR) << "Couldn't determine the type of " << fd;
+ return -1;
+}
+
+off_t FileSize(const string& path) {
+ int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error opening " << path;
+ return fd;
+ }
+ off_t size = FileSize(fd);
+ if (size == -1)
+ PLOG(ERROR) << "Error getting file size of " << path;
+ close(fd);
+ return size;
+}
+
+void HexDumpArray(const uint8_t* const arr, const size_t length) {
+ LOG(INFO) << "Logging array of length: " << length;
+ const unsigned int bytes_per_line = 16;
+ for (uint32_t i = 0; i < length; i += bytes_per_line) {
+ const unsigned int bytes_remaining = length - i;
+ const unsigned int bytes_per_this_line = min(bytes_per_line,
+ bytes_remaining);
+ char header[100];
+ int r = snprintf(header, sizeof(header), "0x%08x : ", i);
+ TEST_AND_RETURN(r == 13);
+ string line = header;
+ for (unsigned int j = 0; j < bytes_per_this_line; j++) {
+ char buf[20];
+ uint8_t c = arr[i + j];
+ r = snprintf(buf, sizeof(buf), "%02x ", static_cast<unsigned int>(c));
+ TEST_AND_RETURN(r == 3);
+ line += buf;
+ }
+ LOG(INFO) << line;
+ }
+}
+
+bool SplitPartitionName(const string& partition_name,
+ string* out_disk_name,
+ int* out_partition_num) {
+ if (!base::StartsWithASCII(partition_name, "/dev/", true)) {
+ LOG(ERROR) << "Invalid partition device name: " << partition_name;
+ return false;
+ }
+
+ size_t last_nondigit_pos = partition_name.find_last_not_of("0123456789");
+ if (last_nondigit_pos == string::npos ||
+ (last_nondigit_pos + 1) == partition_name.size()) {
+ LOG(ERROR) << "Unable to parse partition device name: " << 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
+ size_t disk_name_len = last_nondigit_pos;
+ if (partition_name[last_nondigit_pos] != 'p' ||
+ last_nondigit_pos == 0 ||
+ !isdigit(partition_name[last_nondigit_pos - 1])) {
+ disk_name_len++;
+ }
+ *out_disk_name = partition_name.substr(0, disk_name_len);
+ }
+
+ if (out_partition_num) {
+ string partition_str = partition_name.substr(last_nondigit_pos + 1,
+ partition_name_len);
+ *out_partition_num = atoi(partition_str.c_str());
+ }
+ return true;
+}
+
+string MakePartitionName(const string& disk_name, int partition_num) {
+ if (partition_num < 1) {
+ LOG(ERROR) << "Invalid partition number: " << partition_num;
+ return string();
+ }
+
+ if (!base::StartsWithASCII(disk_name, "/dev/", true)) {
+ LOG(ERROR) << "Invalid disk name: " << disk_name;
+ 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.
+ // Add "p" to separate the disk name from partition number,
+ // e.g. "/dev/loop0p2"
+ partition_name += 'p';
+ }
+
+ partition_name += std::to_string(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';
+ return strerror_r(err, buf, sizeof(buf));
+}
+
+bool FileExists(const char* path) {
+ struct stat stbuf;
+ return 0 == lstat(path, &stbuf);
+}
+
+bool IsSymlink(const char* path) {
+ struct stat stbuf;
+ 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 MakeTempFile(const string& base_filename_template,
+ string* filename,
+ int* fd) {
+ base::FilePath filename_template;
+ TEST_AND_RETURN_FALSE(
+ GetTempName(base_filename_template, &filename_template));
+ DCHECK(filename || fd);
+ vector<char> buf(filename_template.value().size() + 1);
+ memcpy(buf.data(), filename_template.value().data(),
+ filename_template.value().size());
+ buf[filename_template.value().size()] = '\0';
+
+ int mkstemp_fd = mkstemp(buf.data());
+ TEST_AND_RETURN_FALSE_ERRNO(mkstemp_fd >= 0);
+ if (filename) {
+ *filename = buf.data();
+ }
+ if (fd) {
+ *fd = mkstemp_fd;
+ } else {
+ close(mkstemp_fd);
+ }
+ return true;
+}
+
+bool MakeTempDirectory(const string& base_dirname_template,
+ string* dirname) {
+ base::FilePath dirname_template;
+ TEST_AND_RETURN_FALSE(GetTempName(base_dirname_template, &dirname_template));
+ DCHECK(dirname);
+ vector<char> buf(dirname_template.value().size() + 1);
+ memcpy(buf.data(), dirname_template.value().data(),
+ dirname_template.value().size());
+ buf[dirname_template.value().size()] = '\0';
+
+ char* return_code = mkdtemp(buf.data());
+ TEST_AND_RETURN_FALSE_ERRNO(return_code != nullptr);
+ *dirname = buf.data();
+ return true;
+}
+
+bool MountFilesystem(const string& device,
+ const string& mountpoint,
+ unsigned long mountflags) { // NOLINT(runtime/int)
+ // TODO(sosa): Remove "ext3" once crbug.com/208022 is resolved.
+ const vector<const char*> fstypes{"ext2", "ext3", "ext4", "squashfs"};
+ for (const char* fstype : fstypes) {
+ int rc = mount(device.c_str(), mountpoint.c_str(), fstype, mountflags,
+ nullptr);
+ if (rc == 0)
+ return true;
+
+ PLOG(WARNING) << "Unable to mount destination device " << device
+ << " on " << mountpoint << " as " << fstype;
+ }
+ LOG(ERROR) << "Unable to mount " << device << " with any supported type";
+ return false;
+}
+
+bool UnmountFilesystem(const string& mountpoint) {
+ for (int num_retries = 0; ; ++num_retries) {
+ if (umount(mountpoint.c_str()) == 0)
+ break;
+
+ TEST_AND_RETURN_FALSE_ERRNO(errno == EBUSY &&
+ num_retries < kUnmountMaxNumOfRetries);
+ usleep(kUnmountRetryIntervalInMicroseconds);
+ }
+ return true;
+}
+
+bool GetFilesystemSize(const string& device,
+ int* out_block_count,
+ int* out_block_size) {
+ int fd = HANDLE_EINTR(open(device.c_str(), O_RDONLY));
+ TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+ ScopedFdCloser fd_closer(&fd);
+ return GetFilesystemSizeFromFD(fd, out_block_count, out_block_size);
+}
+
+bool GetFilesystemSizeFromFD(int fd,
+ int* out_block_count,
+ int* out_block_size) {
+ TEST_AND_RETURN_FALSE(fd >= 0);
+
+ // Determine the filesystem size by directly reading the block count and
+ // block size information from the superblock. Supported FS are ext3 and
+ // squashfs.
+
+ // Read from the fd only once and detect in memory. The first 2 KiB is enough
+ // to read the ext2 superblock (located at offset 1024) and the squashfs
+ // superblock (located at offset 0).
+ const ssize_t kBufferSize = 2048;
+
+ uint8_t buffer[kBufferSize];
+ if (HANDLE_EINTR(pread(fd, buffer, kBufferSize, 0)) != kBufferSize) {
+ PLOG(ERROR) << "Unable to read the file system header:";
+ return false;
+ }
+
+ if (GetSquashfs4Size(buffer, kBufferSize, out_block_count, out_block_size))
+ return true;
+ if (GetExt3Size(buffer, kBufferSize, out_block_count, out_block_size))
+ return true;
+
+ LOG(ERROR) << "Unable to determine file system type.";
+ return false;
+}
+
+bool GetExt3Size(const uint8_t* buffer, size_t buffer_size,
+ int* out_block_count,
+ int* out_block_size) {
+ // See include/linux/ext2_fs.h for more details on the structure. We obtain
+ // ext2 constants from ext2fs/ext2fs.h header but we don't link with the
+ // library.
+ if (buffer_size < SUPERBLOCK_OFFSET + SUPERBLOCK_SIZE)
+ return false;
+
+ const uint8_t* superblock = buffer + SUPERBLOCK_OFFSET;
+
+ // ext3_fs.h: ext3_super_block.s_blocks_count
+ uint32_t block_count =
+ *reinterpret_cast<const uint32_t*>(superblock + 1 * sizeof(int32_t));
+
+ // ext3_fs.h: ext3_super_block.s_log_block_size
+ uint32_t log_block_size =
+ *reinterpret_cast<const uint32_t*>(superblock + 6 * sizeof(int32_t));
+
+ // ext3_fs.h: ext3_super_block.s_magic
+ uint16_t magic =
+ *reinterpret_cast<const uint16_t*>(superblock + 14 * sizeof(int32_t));
+
+ block_count = le32toh(block_count);
+ log_block_size = le32toh(log_block_size) + EXT2_MIN_BLOCK_LOG_SIZE;
+ magic = le16toh(magic);
+
+ // Sanity check the parameters.
+ TEST_AND_RETURN_FALSE(magic == EXT2_SUPER_MAGIC);
+ 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);
+
+ if (out_block_count)
+ *out_block_count = block_count;
+ if (out_block_size)
+ *out_block_size = 1 << log_block_size;
+ return true;
+}
+
+bool GetSquashfs4Size(const uint8_t* buffer, size_t buffer_size,
+ int* out_block_count,
+ int* out_block_size) {
+ // See fs/squashfs/squashfs_fs.h for format details. We only support
+ // Squashfs 4.x little endian.
+
+ // sizeof(struct squashfs_super_block)
+ const size_t kSquashfsSuperBlockSize = 96;
+ if (buffer_size < kSquashfsSuperBlockSize)
+ return false;
+
+ // Check magic, squashfs_fs.h: SQUASHFS_MAGIC
+ if (memcmp(buffer, "hsqs", 4) != 0)
+ return false; // Only little endian is supported.
+
+ // squashfs_fs.h: struct squashfs_super_block.s_major
+ uint16_t s_major = *reinterpret_cast<const uint16_t*>(
+ buffer + 5 * sizeof(uint32_t) + 4 * sizeof(uint16_t));
+
+ if (s_major != 4) {
+ LOG(ERROR) << "Found unsupported squashfs major version " << s_major;
+ return false;
+ }
+
+ // squashfs_fs.h: struct squashfs_super_block.bytes_used
+ uint64_t bytes_used = *reinterpret_cast<const int64_t*>(
+ buffer + 5 * sizeof(uint32_t) + 6 * sizeof(uint16_t) + sizeof(uint64_t));
+
+ const int block_size = 4096;
+
+ // The squashfs' bytes_used doesn't need to be aligned with the block boundary
+ // so we round up to the nearest blocksize.
+ if (out_block_count)
+ *out_block_count = (bytes_used + block_size - 1) / block_size;
+ if (out_block_size)
+ *out_block_size = block_size;
+ return true;
+}
+
+bool IsExtFilesystem(const string& device) {
+ brillo::Blob header;
+ // The first 2 KiB is enough to read the ext2 superblock (located at offset
+ // 1024).
+ if (!ReadFileChunk(device, 0, 2048, &header))
+ return false;
+ return GetExt3Size(header.data(), header.size(), nullptr, nullptr);
+}
+
+bool IsSquashfsFilesystem(const string& device) {
+ brillo::Blob header;
+ // The first 96 is enough to read the squashfs superblock.
+ const ssize_t kSquashfsSuperBlockSize = 96;
+ if (!ReadFileChunk(device, 0, kSquashfsSuperBlockSize, &header))
+ return false;
+ return GetSquashfs4Size(header.data(), header.size(), nullptr, nullptr);
+}
+
+// Tries to parse the header of an ELF file to obtain a human-readable
+// description of it on the |output| string.
+static bool GetFileFormatELF(const uint8_t* buffer, size_t size,
+ string* output) {
+ // 0x00: EI_MAG - ELF magic header, 4 bytes.
+ if (size < SELFMAG || memcmp(buffer, ELFMAG, SELFMAG) != 0)
+ return false;
+ *output = "ELF";
+
+ // 0x04: EI_CLASS, 1 byte.
+ if (size < EI_CLASS + 1)
+ return true;
+ switch (buffer[EI_CLASS]) {
+ case ELFCLASS32:
+ *output += " 32-bit";
+ break;
+ case ELFCLASS64:
+ *output += " 64-bit";
+ break;
+ default:
+ *output += " ?-bit";
+ }
+
+ // 0x05: EI_DATA, endianness, 1 byte.
+ if (size < EI_DATA + 1)
+ return true;
+ uint8_t ei_data = buffer[EI_DATA];
+ switch (ei_data) {
+ case ELFDATA2LSB:
+ *output += " little-endian";
+ break;
+ case ELFDATA2MSB:
+ *output += " big-endian";
+ break;
+ default:
+ *output += " ?-endian";
+ // Don't parse anything after the 0x10 offset if endianness is unknown.
+ return true;
+ }
+
+ const Elf32_Ehdr* hdr = reinterpret_cast<const Elf32_Ehdr*>(buffer);
+ // 0x12: e_machine, 2 byte endianness based on ei_data. The position (0x12)
+ // and size is the same for both 32 and 64 bits.
+ if (size < offsetof(Elf32_Ehdr, e_machine) + sizeof(hdr->e_machine))
+ return true;
+ uint16_t e_machine;
+ // Fix endianess regardless of the host endianess.
+ if (ei_data == ELFDATA2LSB)
+ e_machine = le16toh(hdr->e_machine);
+ else
+ e_machine = be16toh(hdr->e_machine);
+
+ switch (e_machine) {
+ case EM_386:
+ *output += " x86";
+ break;
+ case EM_MIPS:
+ *output += " mips";
+ break;
+ case EM_ARM:
+ *output += " arm";
+ break;
+ case EM_X86_64:
+ *output += " x86-64";
+ break;
+ default:
+ *output += " unknown-arch";
+ }
+ return true;
+}
+
+string GetFileFormat(const string& path) {
+ brillo::Blob buffer;
+ if (!ReadFileChunkAndAppend(path, 0, kGetFileFormatMaxHeaderSize, &buffer))
+ return "File not found.";
+
+ string result;
+ if (GetFileFormatELF(buffer.data(), buffer.size(), &result))
+ return result;
+
+ return "data";
+}
+
+namespace {
+// Do the actual trigger. We do it as a main-loop callback to (try to) get a
+// consistent stack trace.
+void TriggerCrashReporterUpload() {
+ pid_t pid = fork();
+ CHECK_GE(pid, 0) << "fork failed"; // fork() failed. Something is very wrong.
+ if (pid == 0) {
+ // We are the child. Crash.
+ abort(); // never returns
+ }
+ // We are the parent. Wait for child to terminate.
+ pid_t result = waitpid(pid, nullptr, 0);
+ LOG_IF(ERROR, result < 0) << "waitpid() failed";
+}
+} // namespace
+
+void ScheduleCrashReporterUpload() {
+ brillo::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&TriggerCrashReporterUpload));
+}
+
+bool SetCpuShares(CpuShares shares) {
+ string string_shares = base::IntToString(static_cast<int>(shares));
+ string cpu_shares_file = string(utils::kCGroupDir) + "/cpu.shares";
+ LOG(INFO) << "Setting cgroup cpu shares to " << string_shares;
+ if (utils::WriteFile(cpu_shares_file.c_str(), string_shares.c_str(),
+ string_shares.size())) {
+ return true;
+ } else {
+ LOG(ERROR) << "Failed to change cgroup cpu shares to "<< string_shares
+ << " using " << cpu_shares_file;
+ return false;
+ }
+}
+
+int FuzzInt(int value, unsigned int range) {
+ int min = value - range / 2;
+ int max = value + range - range / 2;
+ return base::RandInt(min, max);
+}
+
+string FormatSecs(unsigned secs) {
+ return FormatTimeDelta(TimeDelta::FromSeconds(secs));
+}
+
+string FormatTimeDelta(TimeDelta delta) {
+ string str;
+
+ // Handle negative durations by prefixing with a minus.
+ if (delta.ToInternalValue() < 0) {
+ delta *= -1;
+ str = "-";
+ }
+
+ // Canonicalize into days, hours, minutes, seconds and microseconds.
+ unsigned days = delta.InDays();
+ delta -= TimeDelta::FromDays(days);
+ unsigned hours = delta.InHours();
+ delta -= TimeDelta::FromHours(hours);
+ unsigned mins = delta.InMinutes();
+ delta -= TimeDelta::FromMinutes(mins);
+ unsigned secs = delta.InSeconds();
+ delta -= TimeDelta::FromSeconds(secs);
+ unsigned usecs = delta.InMicroseconds();
+
+ if (days)
+ base::StringAppendF(&str, "%ud", days);
+ if (days || hours)
+ base::StringAppendF(&str, "%uh", hours);
+ if (days || hours || mins)
+ base::StringAppendF(&str, "%um", mins);
+ base::StringAppendF(&str, "%u", secs);
+ if (usecs) {
+ int width = 6;
+ while ((usecs / 10) * 10 == usecs) {
+ usecs /= 10;
+ width--;
+ }
+ base::StringAppendF(&str, ".%0*u", width, usecs);
+ }
+ base::StringAppendF(&str, "s");
+ return str;
+}
+
+string ToString(const Time utc_time) {
+ Time::Exploded exp_time;
+ utc_time.UTCExplode(&exp_time);
+ return base::StringPrintf("%d/%d/%d %d:%02d:%02d GMT",
+ exp_time.month,
+ exp_time.day_of_month,
+ exp_time.year,
+ exp_time.hour,
+ exp_time.minute,
+ exp_time.second);
+}
+
+string ToString(bool b) {
+ return (b ? "true" : "false");
+}
+
+string ToString(DownloadSource source) {
+ switch (source) {
+ case kDownloadSourceHttpsServer: return "HttpsServer";
+ case kDownloadSourceHttpServer: return "HttpServer";
+ case kDownloadSourceHttpPeer: return "HttpPeer";
+ case kNumDownloadSources: return "Unknown";
+ // Don't add a default case to let the compiler warn about newly added
+ // download sources which should be added here.
+ }
+
+ return "Unknown";
+}
+
+string ToString(PayloadType payload_type) {
+ switch (payload_type) {
+ case kPayloadTypeDelta: return "Delta";
+ case kPayloadTypeFull: return "Full";
+ case kPayloadTypeForcedFull: return "ForcedFull";
+ case kNumPayloadTypes: return "Unknown";
+ // Don't add a default case to let the compiler warn about newly added
+ // payload types which should be added here.
+ }
+
+ return "Unknown";
+}
+
+ErrorCode GetBaseErrorCode(ErrorCode code) {
+ // Ignore the higher order bits in the code by applying the mask as
+ // we want the enumerations to be in the small contiguous range
+ // with values less than ErrorCode::kUmaReportedMax.
+ ErrorCode base_code = static_cast<ErrorCode>(
+ static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+ // Make additional adjustments required for UMA and error classification.
+ // TODO(jaysri): Move this logic to UeErrorCode.cc when we fix
+ // chromium-os:34369.
+ if (base_code >= ErrorCode::kOmahaRequestHTTPResponseBase) {
+ // Since we want to keep the enums to a small value, aggregate all HTTP
+ // errors into this one bucket for UMA and error classification purposes.
+ LOG(INFO) << "Converting error code " << base_code
+ << " to ErrorCode::kOmahaErrorInHTTPResponse";
+ base_code = ErrorCode::kOmahaErrorInHTTPResponse;
+ }
+
+ return base_code;
+}
+
+string CodeToString(ErrorCode code) {
+ // If the given code has both parts (i.e. the error code part and the flags
+ // part) then strip off the flags part since the switch statement below
+ // has case statements only for the base error code or a single flag but
+ // doesn't support any combinations of those.
+ if ((static_cast<int>(code) & static_cast<int>(ErrorCode::kSpecialFlags)) &&
+ (static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags)))
+ code = static_cast<ErrorCode>(
+ static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+ switch (code) {
+ case ErrorCode::kSuccess: return "ErrorCode::kSuccess";
+ case ErrorCode::kError: return "ErrorCode::kError";
+ case ErrorCode::kOmahaRequestError: return "ErrorCode::kOmahaRequestError";
+ case ErrorCode::kOmahaResponseHandlerError:
+ return "ErrorCode::kOmahaResponseHandlerError";
+ case ErrorCode::kFilesystemCopierError:
+ return "ErrorCode::kFilesystemCopierError";
+ case ErrorCode::kPostinstallRunnerError:
+ return "ErrorCode::kPostinstallRunnerError";
+ case ErrorCode::kPayloadMismatchedType:
+ return "ErrorCode::kPayloadMismatchedType";
+ case ErrorCode::kInstallDeviceOpenError:
+ return "ErrorCode::kInstallDeviceOpenError";
+ case ErrorCode::kKernelDeviceOpenError:
+ return "ErrorCode::kKernelDeviceOpenError";
+ case ErrorCode::kDownloadTransferError:
+ return "ErrorCode::kDownloadTransferError";
+ case ErrorCode::kPayloadHashMismatchError:
+ return "ErrorCode::kPayloadHashMismatchError";
+ case ErrorCode::kPayloadSizeMismatchError:
+ return "ErrorCode::kPayloadSizeMismatchError";
+ case ErrorCode::kDownloadPayloadVerificationError:
+ return "ErrorCode::kDownloadPayloadVerificationError";
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ return "ErrorCode::kDownloadNewPartitionInfoError";
+ case ErrorCode::kDownloadWriteError:
+ return "ErrorCode::kDownloadWriteError";
+ case ErrorCode::kNewRootfsVerificationError:
+ return "ErrorCode::kNewRootfsVerificationError";
+ case ErrorCode::kNewKernelVerificationError:
+ return "ErrorCode::kNewKernelVerificationError";
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ return "ErrorCode::kSignedDeltaPayloadExpectedError";
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ return "ErrorCode::kDownloadPayloadPubKeyVerificationError";
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ return "ErrorCode::kPostinstallBootedFromFirmwareB";
+ case ErrorCode::kDownloadStateInitializationError:
+ return "ErrorCode::kDownloadStateInitializationError";
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ return "ErrorCode::kDownloadInvalidMetadataMagicString";
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ return "ErrorCode::kDownloadSignatureMissingInManifest";
+ case ErrorCode::kDownloadManifestParseError:
+ return "ErrorCode::kDownloadManifestParseError";
+ case ErrorCode::kDownloadMetadataSignatureError:
+ return "ErrorCode::kDownloadMetadataSignatureError";
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ return "ErrorCode::kDownloadMetadataSignatureVerificationError";
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ return "ErrorCode::kDownloadMetadataSignatureMismatch";
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ return "ErrorCode::kDownloadOperationHashVerificationError";
+ case ErrorCode::kDownloadOperationExecutionError:
+ return "ErrorCode::kDownloadOperationExecutionError";
+ case ErrorCode::kDownloadOperationHashMismatch:
+ return "ErrorCode::kDownloadOperationHashMismatch";
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ return "ErrorCode::kOmahaRequestEmptyResponseError";
+ case ErrorCode::kOmahaRequestXMLParseError:
+ return "ErrorCode::kOmahaRequestXMLParseError";
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ return "ErrorCode::kDownloadInvalidMetadataSize";
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ return "ErrorCode::kDownloadInvalidMetadataSignature";
+ case ErrorCode::kOmahaResponseInvalid:
+ return "ErrorCode::kOmahaResponseInvalid";
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ return "ErrorCode::kOmahaUpdateIgnoredPerPolicy";
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ return "ErrorCode::kOmahaUpdateDeferredPerPolicy";
+ case ErrorCode::kOmahaErrorInHTTPResponse:
+ return "ErrorCode::kOmahaErrorInHTTPResponse";
+ case ErrorCode::kDownloadOperationHashMissingError:
+ return "ErrorCode::kDownloadOperationHashMissingError";
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ return "ErrorCode::kDownloadMetadataSignatureMissingError";
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ return "ErrorCode::kOmahaUpdateDeferredForBackoff";
+ case ErrorCode::kPostinstallPowerwashError:
+ return "ErrorCode::kPostinstallPowerwashError";
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ return "ErrorCode::kUpdateCanceledByChannelChange";
+ case ErrorCode::kUmaReportedMax:
+ return "ErrorCode::kUmaReportedMax";
+ case ErrorCode::kOmahaRequestHTTPResponseBase:
+ return "ErrorCode::kOmahaRequestHTTPResponseBase";
+ case ErrorCode::kResumedFlag:
+ return "Resumed";
+ case ErrorCode::kDevModeFlag:
+ return "DevMode";
+ case ErrorCode::kTestImageFlag:
+ return "TestImage";
+ case ErrorCode::kTestOmahaUrlFlag:
+ return "TestOmahaUrl";
+ case ErrorCode::kSpecialFlags:
+ return "ErrorCode::kSpecialFlags";
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ return "ErrorCode::kPostinstallFirmwareRONotUpdatable";
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ return "ErrorCode::kUnsupportedMajorPayloadVersion";
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ return "ErrorCode::kUnsupportedMinorPayloadVersion";
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ return "ErrorCode::kOmahaRequestXMLHasEntityDecl";
+ case ErrorCode::kFilesystemVerifierError:
+ return "ErrorCode::kFilesystemVerifierError";
+ // 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));
+}
+
+bool CreatePowerwashMarkerFile(const char* file_path) {
+ const char* marker_file = file_path ? file_path : kPowerwashMarkerFile;
+ bool result = utils::WriteFile(marker_file,
+ kPowerwashCommand,
+ strlen(kPowerwashCommand));
+ if (result) {
+ LOG(INFO) << "Created " << marker_file << " to powerwash on next reboot";
+ } else {
+ PLOG(ERROR) << "Error in creating powerwash marker file: " << marker_file;
+ }
+
+ return result;
+}
+
+bool DeletePowerwashMarkerFile(const char* file_path) {
+ const char* marker_file = file_path ? file_path : kPowerwashMarkerFile;
+ const base::FilePath kPowerwashMarkerPath(marker_file);
+ bool result = base::DeleteFile(kPowerwashMarkerPath, false);
+
+ if (result)
+ LOG(INFO) << "Successfully deleted the powerwash marker file : "
+ << marker_file;
+ else
+ PLOG(ERROR) << "Could not delete the powerwash marker file : "
+ << marker_file;
+
+ return result;
+}
+
+Time TimeFromStructTimespec(struct timespec *ts) {
+ int64_t us = static_cast<int64_t>(ts->tv_sec) * Time::kMicrosecondsPerSecond +
+ static_cast<int64_t>(ts->tv_nsec) / Time::kNanosecondsPerMicrosecond;
+ return Time::UnixEpoch() + TimeDelta::FromMicroseconds(us);
+}
+
+string StringVectorToString(const vector<string> &vec_str) {
+ string str = "[";
+ for (vector<string>::const_iterator i = vec_str.begin();
+ i != vec_str.end(); ++i) {
+ if (i != vec_str.begin())
+ str += ", ";
+ str += '"';
+ str += *i;
+ str += '"';
+ }
+ str += "]";
+ return str;
+}
+
+string CalculateP2PFileId(const string& payload_hash, size_t payload_size) {
+ string encoded_hash = brillo::data_encoding::Base64Encode(payload_hash);
+ return base::StringPrintf("cros_update_size_%" PRIuS "_hash_%s",
+ payload_size,
+ encoded_hash.c_str());
+}
+
+bool DecodeAndStoreBase64String(const string& base64_encoded,
+ base::FilePath *out_path) {
+ brillo::Blob contents;
+
+ out_path->clear();
+
+ if (base64_encoded.size() == 0) {
+ LOG(ERROR) << "Can't decode empty string.";
+ return false;
+ }
+
+ if (!brillo::data_encoding::Base64Decode(base64_encoded, &contents) ||
+ contents.size() == 0) {
+ LOG(ERROR) << "Error decoding base64.";
+ return false;
+ }
+
+ FILE *file = base::CreateAndOpenTemporaryFile(out_path);
+ if (file == nullptr) {
+ LOG(ERROR) << "Error creating temporary file.";
+ return false;
+ }
+
+ if (fwrite(contents.data(), 1, contents.size(), file) != contents.size()) {
+ PLOG(ERROR) << "Error writing to temporary file.";
+ if (fclose(file) != 0)
+ PLOG(ERROR) << "Error closing temporary file.";
+ if (unlink(out_path->value().c_str()) != 0)
+ PLOG(ERROR) << "Error unlinking temporary file.";
+ out_path->clear();
+ return false;
+ }
+
+ if (fclose(file) != 0) {
+ PLOG(ERROR) << "Error closing temporary file.";
+ out_path->clear();
+ return false;
+ }
+
+ return true;
+}
+
+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;
+ const int64_t kNumSecondsPerWeek = 7*24*3600;
+ const int64_t kNumDaysPerWeek = 7;
+
+ time_t omaha_time = unix_time - kOmahaEpoch;
+
+ if (omaha_time < 0)
+ return false;
+
+ // Note, as per the comment in utils.h we are deliberately not
+ // handling DST correctly.
+
+ int64_t num_weeks_since_omaha_epoch = omaha_time / kNumSecondsPerWeek;
+ *out_num_days = num_weeks_since_omaha_epoch * kNumDaysPerWeek;
+
+ return true;
+}
+
+bool GetMinorVersion(const brillo::KeyValueStore& store,
+ uint32_t* minor_version) {
+ string result;
+ if (store.GetString("PAYLOAD_MINOR_VERSION", &result)) {
+ if (!base::StringToUint(result, minor_version)) {
+ LOG(ERROR) << "StringToUint failed when parsing delta minor version.";
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool ReadExtents(const string& path, const vector<Extent>& extents,
+ brillo::Blob* out_data, ssize_t out_data_size,
+ size_t block_size) {
+ brillo::Blob data(out_data_size);
+ ssize_t bytes_read = 0;
+ int fd = open(path.c_str(), O_RDONLY);
+ TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+ ScopedFdCloser fd_closer(&fd);
+
+ for (const Extent& extent : extents) {
+ ssize_t bytes_read_this_iteration = 0;
+ ssize_t bytes = extent.num_blocks() * block_size;
+ TEST_AND_RETURN_FALSE(bytes_read + bytes <= out_data_size);
+ TEST_AND_RETURN_FALSE(utils::PReadAll(fd,
+ &data[bytes_read],
+ bytes,
+ extent.start_block() * block_size,
+ &bytes_read_this_iteration));
+ TEST_AND_RETURN_FALSE(bytes_read_this_iteration == bytes);
+ bytes_read += bytes_read_this_iteration;
+ }
+ TEST_AND_RETURN_FALSE(out_data_size == bytes_read);
+ *out_data = data;
+ return true;
+}
+
+bool GetBootId(string* boot_id) {
+ TEST_AND_RETURN_FALSE(
+ base::ReadFileToString(base::FilePath(kBootIdPath), boot_id));
+ base::TrimWhitespaceASCII(*boot_id, base::TRIM_TRAILING, boot_id);
+ return true;
+}
+
+} // namespace utils
+
+} // namespace chromeos_update_engine
diff --git a/common/utils.h b/common/utils.h
new file mode 100644
index 0000000..0440dd8
--- /dev/null
+++ b/common/utils.h
@@ -0,0 +1,510 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_UTILS_H_
+#define UPDATE_ENGINE_COMMON_UTILS_H_
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/time/time.h>
+#include <brillo/key_value_store.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+namespace utils {
+
+// Converts a struct timespec representing a number of seconds since
+// the Unix epoch to a base::Time. Sub-microsecond time is rounded
+// down.
+base::Time TimeFromStructTimespec(struct timespec *ts);
+
+// Formats |vec_str| as a string of the form ["<elem1>", "<elem2>"].
+// Does no escaping, only use this for presentation in error messages.
+std::string StringVectorToString(const std::vector<std::string> &vec_str);
+
+// Calculates the p2p file id from payload hash and size
+std::string CalculateP2PFileId(const std::string& 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, int data_len);
+
+// Calls write() or pwrite() repeatedly until all count bytes at buf are
+// written to fd or an error occurs. Returns true on success.
+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(FileDescriptorPtr fd, const void* buf, size_t count);
+bool PWriteAll(FileDescriptorPtr fd,
+ const void* buf,
+ size_t count,
+ off_t offset);
+
+// Calls pread() repeatedly until count bytes are read, or EOF is reached.
+// Returns number of bytes read in *bytes_read. Returns true on success.
+bool PReadAll(int fd, void* buf, size_t count, off_t offset,
+ ssize_t* out_bytes_read);
+
+bool PReadAll(FileDescriptorPtr fd, void* buf, size_t count, off_t offset,
+ ssize_t* out_bytes_read);
+
+// Opens |path| for reading and appends its entire content to the container
+// pointed to by |out_p|. Returns true upon successfully reading all of the
+// file's content, false otherwise, in which case the state of the output
+// container is unknown. ReadFileChunk starts reading the file from |offset|; if
+// |size| is not -1, only up to |size| bytes are read in.
+bool ReadFile(const std::string& path, brillo::Blob* out_p);
+bool ReadFile(const std::string& path, std::string* out_p);
+bool ReadFileChunk(const std::string& path, off_t offset, off_t size,
+ brillo::Blob* out_p);
+
+// Invokes |cmd| in a pipe and appends its stdout to the container pointed to by
+// |out_p|. Returns true upon successfully reading all of the output, false
+// otherwise, in which case the state of the output container is unknown.
+bool ReadPipe(const std::string& cmd, std::string* out_p);
+
+// Returns the size of the block device at the file descriptor fd. If an error
+// occurs, -1 is returned.
+off_t BlockDevSize(int fd);
+
+// Returns the size of the file at path, or the file desciptor fd. If the file
+// is actually a block device, this function will automatically call
+// BlockDevSize. If the file doesn't exist or some error occurrs, -1 is
+// returned.
+off_t FileSize(const std::string& path);
+off_t FileSize(int fd);
+
+std::string ErrnoNumberAsString(int err);
+
+// Returns true if the file exists for sure. Returns false if it doesn't exist,
+// or an error occurs.
+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);
+
+// If |base_filename_template| is neither absolute (starts with "/") nor
+// explicitly relative to the current working directory (starts with "./" or
+// "../"), then it is prepended the system's temporary directory. On success,
+// stores the name of the new temporary file in |filename|. If |fd| is
+// non-null, the file descriptor returned by mkstemp is written to it and
+// kept open; otherwise, it is closed. The template must end with "XXXXXX".
+// Returns true on success.
+bool MakeTempFile(const std::string& base_filename_template,
+ std::string* filename,
+ int* fd);
+
+// If |base_dirname_template| is neither absolute (starts with "/") nor
+// explicitly relative to the current working directory (starts with "./" or
+// "../"), then it is prepended the system's temporary directory. On success,
+// stores the name of the new temporary directory in |dirname|. The template
+// must end with "XXXXXX". Returns true on success.
+bool MakeTempDirectory(const std::string& base_dirname_template,
+ std::string* dirname);
+
+// Splits the partition device name into the block device name and partition
+// number. For example, "/dev/sda3" will be split into {"/dev/sda", 3} and
+// "/dev/mmcblk0p2" into {"/dev/mmcblk0", 2}
+// Returns false when malformed device name is passed in.
+// If both output parameters are omitted (null), can be used
+// just to test the validity of the device name. Note that the function
+// simply checks if the device name looks like a valid device, no other
+// checks are performed (i.e. it doesn't check if the device actually exists).
+bool SplitPartitionName(const std::string& partition_name,
+ std::string* out_disk_name,
+ int* out_partition_num);
+
+// Builds a partition device name from the block device name and partition
+// number. For example:
+// {"/dev/sda", 1} => "/dev/sda1"
+// {"/dev/mmcblk2", 12} => "/dev/mmcblk2p12"
+// 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);
+
+// Synchronously mount or unmount a filesystem. Return true on success.
+// When mounting, it will attempt to mount the the device as "ext3", "ext2" and
+// "squashfs", with the passed |flags| options.
+bool MountFilesystem(const std::string& device, const std::string& mountpoint,
+ unsigned long flags); // NOLINT(runtime/int)
+bool UnmountFilesystem(const std::string& mountpoint);
+
+// Returns the block count and the block byte size of the file system on
+// |device| (which may be a real device or a path to a filesystem image) or on
+// an opened file descriptor |fd|. The actual file-system size is |block_count|
+// * |block_size| bytes. Returns true on success, false otherwise.
+bool GetFilesystemSize(const std::string& device,
+ int* out_block_count,
+ int* out_block_size);
+bool GetFilesystemSizeFromFD(int fd,
+ int* out_block_count,
+ int* out_block_size);
+
+// Determines the block count and block size of the ext3 fs. At least 2048 bytes
+// are required to parse the first superblock. Returns whether the buffer
+// contains a valid ext3 filesystem and the values were parsed.
+bool GetExt3Size(const uint8_t* buffer, size_t buffer_size,
+ int* out_block_count,
+ int* out_block_size);
+
+// Determines the block count and block size of the squashfs v4 fs. At least 96
+// bytes are required to parse the header of the filesystem. Since squashfs
+// doesn't define a physical block size, a value of 4096 is used for the block
+// size, which is the default padding when creating the filesystem.
+// Returns whether the buffer contains a valid squashfs v4 header and the size
+// was parsed. Only little endian squashfs is supported.
+bool GetSquashfs4Size(const uint8_t* buffer, size_t buffer_size,
+ int* out_block_count,
+ int* out_block_size);
+
+// Returns whether the filesystem is an ext[234] filesystem. In case of failure,
+// such as if the file |device| doesn't exists or can't be read, it returns
+// false.
+bool IsExtFilesystem(const std::string& device);
+
+// Returns whether the filesystem is a squashfs filesystem. In case of failure,
+// such as if the file |device| doesn't exists or can't be read, it returns
+// false.
+bool IsSquashfsFilesystem(const std::string& device);
+
+// Returns a human-readable string with the file format based on magic constants
+// on the header of the file.
+std::string GetFileFormat(const std::string& path);
+
+// Returns the string representation of the given UTC time.
+// such as "11/14/2011 14:05:30 GMT".
+std::string ToString(const base::Time utc_time);
+
+// Returns true or false depending on the value of b.
+std::string ToString(bool b);
+
+// Returns a string representation of the given enum.
+std::string ToString(DownloadSource source);
+
+// Returns a string representation of the given enum.
+std::string ToString(PayloadType payload_type);
+
+// Schedules a Main Loop callback to trigger the crash reporter to perform an
+// upload as if this process had crashed.
+void ScheduleCrashReporterUpload();
+
+// Fuzzes an integer |value| randomly in the range:
+// [value - range / 2, value + range - range / 2]
+int FuzzInt(int value, unsigned int range);
+
+// Log a string in hex to LOG(INFO). Useful for debugging.
+void HexDumpArray(const uint8_t* const arr, const size_t length);
+inline void HexDumpString(const std::string& str) {
+ HexDumpArray(reinterpret_cast<const uint8_t*>(str.data()), str.size());
+}
+inline void HexDumpVector(const brillo::Blob& vect) {
+ HexDumpArray(vect.data(), vect.size());
+}
+
+template<typename KeyType, typename ValueType>
+bool MapContainsKey(const std::map<KeyType, ValueType>& m, const KeyType& k) {
+ return m.find(k) != m.end();
+}
+template<typename KeyType>
+bool SetContainsKey(const std::set<KeyType>& s, const KeyType& k) {
+ return s.find(k) != s.end();
+}
+
+template<typename T>
+bool VectorContainsValue(const std::vector<T>& vect, const T& value) {
+ return std::find(vect.begin(), vect.end(), value) != vect.end();
+}
+
+template<typename T>
+bool VectorIndexOf(const std::vector<T>& vect, const T& value,
+ typename std::vector<T>::size_type* out_index) {
+ typename std::vector<T>::const_iterator it = std::find(vect.begin(),
+ vect.end(),
+ value);
+ if (it == vect.end()) {
+ return false;
+ } else {
+ *out_index = it - vect.begin();
+ return true;
+ }
+}
+
+// Cgroups cpu shares constants. 1024 is the default shares a standard process
+// gets and 2 is the minimum value. We set High as a value that gives the
+// update-engine 2x the cpu share of a standard process.
+enum CpuShares {
+ kCpuSharesHigh = 2048,
+ kCpuSharesNormal = 1024,
+ kCpuSharesLow = 2,
+};
+
+// Sets the current process shares to |shares|. Returns true on
+// success, false otherwise.
+bool SetCpuShares(CpuShares shares);
+
+// Converts seconds into human readable notation including days, hours, minutes
+// and seconds. For example, 185 will yield 3m5s, 4300 will yield 1h11m40s, and
+// 360000 will yield 4d4h0m0s. Zero padding not applied. Seconds are always
+// shown in the result.
+std::string FormatSecs(unsigned secs);
+
+// Converts a TimeDelta into human readable notation including days, hours,
+// minutes, seconds and fractions of a second down to microsecond granularity,
+// as necessary; for example, an output of 5d2h0m15.053s means that the input
+// time was precise to the milliseconds only. Zero padding not applied, except
+// for fractions. Seconds are always shown, but fractions thereof are only shown
+// when applicable. If |delta| is negative, the output will have a leading '-'
+// followed by the absolute duration.
+std::string FormatTimeDelta(base::TimeDelta delta);
+
+// This method transforms the given error code to be suitable for UMA and
+// for error classification purposes by removing the higher order bits and
+// aggregating error codes beyond the enum range, etc. This method is
+// idempotent, i.e. if called with a value previously returned by this method,
+// it'll return the same value again.
+ErrorCode GetBaseErrorCode(ErrorCode code);
+
+// Returns a string representation of the ErrorCodes (either the base
+// error codes or the bit flags) for logging purposes.
+std::string CodeToString(ErrorCode code);
+
+// Creates the powerwash marker file with the appropriate commands in it. Uses
+// |file_path| as the path to the marker file if non-null, otherwise uses the
+// global default. Returns true if successfully created. False otherwise.
+bool CreatePowerwashMarkerFile(const char* file_path);
+
+// Deletes the marker file used to trigger Powerwash using clobber-state. Uses
+// |file_path| as the path to the marker file if non-null, otherwise uses the
+// global default. Returns true if successfully deleted. False otherwise.
+bool DeletePowerwashMarkerFile(const char* file_path);
+
+// Decodes the data in |base64_encoded| and stores it in a temporary
+// file. Returns false if the given data is empty, not well-formed
+// base64 or if an error occurred. If true is returned, the decoded
+// data is stored in the file returned in |out_path|. The file should
+// be deleted when no longer needed.
+bool DecodeAndStoreBase64String(const std::string& base64_encoded,
+ base::FilePath *out_path);
+
+// Converts |time| to an Omaha InstallDate which is defined as "the
+// number of PST8PDT calendar weeks since Jan 1st 2007 0:00 PST, times
+// seven" with PST8PDT defined as "Pacific Time" (e.g. UTC-07:00 if
+// daylight savings is observed and UTC-08:00 otherwise.)
+//
+// If the passed in |time| variable is before Monday January 1st 2007
+// 0:00 PST, False is returned and the value returned in
+// |out_num_days| is undefined. Otherwise the number of PST8PDT
+// calendar weeks since that date times seven is returned in
+// |out_num_days| and the function returns True.
+//
+// (NOTE: This function does not currently take daylight savings time
+// into account so the result may up to one hour off. This is because
+// the glibc date and timezone routines depend on the TZ environment
+// variable and changing environment variables is not thread-safe.
+bool ConvertToOmahaInstallDate(base::Time time, int *out_num_days);
+
+// Look for the minor version value in the passed |store| and set
+// |minor_version| to that value. Return whether the value was found and valid.
+bool GetMinorVersion(const brillo::KeyValueStore& store,
+ uint32_t* minor_version);
+
+// This function reads the specified data in |extents| into |out_data|. The
+// extents are read from the file at |path|. |out_data_size| is the size of
+// |out_data|. Returns false if the number of bytes to read given in
+// |extents| does not equal |out_data_size|.
+bool ReadExtents(const std::string& path, const std::vector<Extent>& extents,
+ brillo::Blob* out_data, ssize_t out_data_size,
+ size_t block_size);
+
+// Read the current boot identifier and store it in |boot_id|. This identifier
+// is constants during the same boot of the kernel and is regenerated after
+// reboot. Returns whether it succeeded getting the boot_id.
+bool GetBootId(std::string* boot_id);
+
+} // namespace utils
+
+
+// Utility class to close a file descriptor
+class ScopedFdCloser {
+ public:
+ explicit ScopedFdCloser(int* fd) : fd_(fd) {}
+ ~ScopedFdCloser() {
+ if (should_close_ && fd_ && (*fd_ >= 0) && !IGNORE_EINTR(close(*fd_)))
+ *fd_ = -1;
+ }
+ void set_should_close(bool should_close) { should_close_ = should_close; }
+ private:
+ int* fd_;
+ bool should_close_ = true;
+ DISALLOW_COPY_AND_ASSIGN(ScopedFdCloser);
+};
+
+// Utility class to delete a file when it goes out of scope.
+class ScopedPathUnlinker {
+ public:
+ explicit ScopedPathUnlinker(const std::string& path)
+ : path_(path),
+ should_remove_(true) {}
+ ~ScopedPathUnlinker() {
+ if (should_remove_ && unlink(path_.c_str()) < 0) {
+ PLOG(ERROR) << "Unable to unlink path " << path_;
+ }
+ }
+ void set_should_remove(bool should_remove) { should_remove_ = should_remove; }
+
+ private:
+ const std::string path_;
+ bool should_remove_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedPathUnlinker);
+};
+
+// Utility class to delete an empty directory when it goes out of scope.
+class ScopedDirRemover {
+ public:
+ explicit ScopedDirRemover(const std::string& path)
+ : path_(path),
+ should_remove_(true) {}
+ ~ScopedDirRemover() {
+ if (should_remove_ && (rmdir(path_.c_str()) < 0)) {
+ PLOG(ERROR) << "Unable to remove dir " << path_;
+ }
+ }
+ void set_should_remove(bool should_remove) { should_remove_ = should_remove; }
+
+ protected:
+ const std::string path_;
+
+ private:
+ bool should_remove_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedDirRemover);
+};
+
+// A little object to call ActionComplete on the ActionProcessor when
+// it's destructed.
+class ScopedActionCompleter {
+ public:
+ explicit ScopedActionCompleter(ActionProcessor* processor,
+ AbstractAction* action)
+ : processor_(processor),
+ action_(action),
+ code_(ErrorCode::kError),
+ should_complete_(true) {}
+ ~ScopedActionCompleter() {
+ if (should_complete_)
+ processor_->ActionComplete(action_, code_);
+ }
+ void set_code(ErrorCode code) { code_ = code; }
+ void set_should_complete(bool should_complete) {
+ should_complete_ = should_complete;
+ }
+ ErrorCode get_code() const { return code_; }
+
+ private:
+ ActionProcessor* processor_;
+ AbstractAction* action_;
+ ErrorCode code_;
+ bool should_complete_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedActionCompleter);
+};
+
+} // namespace chromeos_update_engine
+
+#define TEST_AND_RETURN_FALSE_ERRNO(_x) \
+ do { \
+ bool _success = static_cast<bool>(_x); \
+ if (!_success) { \
+ std::string _msg = \
+ chromeos_update_engine::utils::ErrnoNumberAsString(errno); \
+ LOG(ERROR) << #_x " failed: " << _msg; \
+ return false; \
+ } \
+ } while (0)
+
+#define TEST_AND_RETURN_FALSE(_x) \
+ do { \
+ bool _success = static_cast<bool>(_x); \
+ if (!_success) { \
+ LOG(ERROR) << #_x " failed."; \
+ return false; \
+ } \
+ } while (0)
+
+#define TEST_AND_RETURN_ERRNO(_x) \
+ do { \
+ bool _success = static_cast<bool>(_x); \
+ if (!_success) { \
+ std::string _msg = \
+ chromeos_update_engine::utils::ErrnoNumberAsString(errno); \
+ LOG(ERROR) << #_x " failed: " << _msg; \
+ return; \
+ } \
+ } while (0)
+
+#define TEST_AND_RETURN(_x) \
+ do { \
+ bool _success = static_cast<bool>(_x); \
+ if (!_success) { \
+ LOG(ERROR) << #_x " failed."; \
+ return; \
+ } \
+ } while (0)
+
+#define TEST_AND_RETURN_FALSE_ERRCODE(_x) \
+ do { \
+ errcode_t _error = (_x); \
+ if (_error) { \
+ errno = _error; \
+ LOG(ERROR) << #_x " failed: " << _error; \
+ return false; \
+ } \
+ } while (0)
+
+#endif // UPDATE_ENGINE_COMMON_UTILS_H_
diff --git a/common/utils_unittest.cc b/common/utils_unittest.cc
new file mode 100644
index 0000000..02f919e
--- /dev/null
+++ b/common/utils_unittest.cc
@@ -0,0 +1,569 @@
+//
+// 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/common/utils.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+
+using brillo::FakeMessageLoop;
+using std::map;
+using std::string;
+using std::vector;
+
+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, ReadFileFailure) {
+ brillo::Blob empty;
+ EXPECT_FALSE(utils::ReadFile("/this/doesn't/exist", &empty));
+}
+
+TEST(UtilsTest, ReadFileChunk) {
+ base::FilePath file;
+ EXPECT_TRUE(base::CreateTemporaryFile(&file));
+ ScopedPathUnlinker unlinker(file.value());
+ brillo::Blob data;
+ const size_t kSize = 1024 * 1024;
+ for (size_t i = 0; i < kSize; i++) {
+ data.push_back(i % 255);
+ }
+ EXPECT_TRUE(utils::WriteFile(file.value().c_str(), data.data(), data.size()));
+ brillo::Blob in_data;
+ EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), kSize, 10, &in_data));
+ EXPECT_TRUE(in_data.empty());
+ EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), 0, -1, &in_data));
+ EXPECT_TRUE(data == in_data);
+ in_data.clear();
+ EXPECT_TRUE(utils::ReadFileChunk(file.value().c_str(), 10, 20, &in_data));
+ EXPECT_TRUE(brillo::Blob(data.begin() + 10, data.begin() + 10 + 20) ==
+ in_data);
+}
+
+TEST(UtilsTest, ErrnoNumberAsStringTest) {
+ EXPECT_EQ("No such file or directory", utils::ErrnoNumberAsString(ENOENT));
+}
+
+TEST(UtilsTest, IsSymlinkTest) {
+ string temp_dir;
+ EXPECT_TRUE(utils::MakeTempDirectory("symlink-test.XXXXXX", &temp_dir));
+ string temp_file = temp_dir + "/temp-file";
+ EXPECT_TRUE(utils::WriteFile(temp_file.c_str(), "", 0));
+ string temp_symlink = temp_dir + "/temp-symlink";
+ EXPECT_EQ(0, symlink(temp_file.c_str(), temp_symlink.c_str()));
+ EXPECT_FALSE(utils::IsSymlink(temp_dir.c_str()));
+ EXPECT_FALSE(utils::IsSymlink(temp_file.c_str()));
+ EXPECT_TRUE(utils::IsSymlink(temp_symlink.c_str()));
+ EXPECT_FALSE(utils::IsSymlink("/non/existent/path"));
+ EXPECT_TRUE(base::DeleteFile(base::FilePath(temp_dir), true));
+}
+
+TEST(UtilsTest, SplitPartitionNameTest) {
+ string disk;
+ int part_num;
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/sda3", &disk, &part_num));
+ EXPECT_EQ("/dev/sda", disk);
+ EXPECT_EQ(3, part_num);
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/sdp1234", &disk, &part_num));
+ EXPECT_EQ("/dev/sdp", disk);
+ EXPECT_EQ(1234, part_num);
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/mmcblk0p3", &disk, &part_num));
+ 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);
+
+ EXPECT_TRUE(utils::SplitPartitionName("/dev/loop28p11", &disk, &part_num));
+ 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));
+ EXPECT_FALSE(utils::SplitPartitionName("/", &disk, &part_num));
+ EXPECT_FALSE(utils::SplitPartitionName("", &disk, &part_num));
+}
+
+TEST(UtilsTest, MakePartitionNameTest) {
+ EXPECT_EQ("/dev/sda4", utils::MakePartitionName("/dev/sda", 4));
+ EXPECT_EQ("/dev/sda123", utils::MakePartitionName("/dev/sda", 123));
+ EXPECT_EQ("/dev/mmcblk2", utils::MakePartitionName("/dev/mmcblk", 2));
+ 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"));
+}
+
+namespace {
+// Compares cpu shares and returns an integer that is less
+// than, equal to or greater than 0 if |shares_lhs| is,
+// respectively, lower than, same as or higher than |shares_rhs|.
+int CompareCpuShares(utils::CpuShares shares_lhs,
+ utils::CpuShares shares_rhs) {
+ return static_cast<int>(shares_lhs) - static_cast<int>(shares_rhs);
+}
+} // namespace
+
+// Tests the CPU shares enum is in the order we expect it.
+TEST(UtilsTest, CompareCpuSharesTest) {
+ EXPECT_LT(CompareCpuShares(utils::kCpuSharesLow,
+ utils::kCpuSharesNormal), 0);
+ EXPECT_GT(CompareCpuShares(utils::kCpuSharesNormal,
+ utils::kCpuSharesLow), 0);
+ EXPECT_EQ(CompareCpuShares(utils::kCpuSharesNormal,
+ utils::kCpuSharesNormal), 0);
+ EXPECT_GT(CompareCpuShares(utils::kCpuSharesHigh,
+ utils::kCpuSharesNormal), 0);
+}
+
+TEST(UtilsTest, FuzzIntTest) {
+ static const unsigned int kRanges[] = { 0, 1, 2, 20 };
+ for (unsigned int range : kRanges) {
+ const int kValue = 50;
+ for (int tries = 0; tries < 100; ++tries) {
+ int value = utils::FuzzInt(kValue, range);
+ EXPECT_GE(value, kValue - range / 2);
+ EXPECT_LE(value, kValue + range - range / 2);
+ }
+ }
+}
+
+TEST(UtilsTest, RunAsRootGetFilesystemSizeTest) {
+ string img;
+ EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
+ ScopedPathUnlinker img_unlinker(img);
+ test_utils::CreateExtImageAtPath(img, nullptr);
+ // Extend the "partition" holding the file system from 10MiB to 20MiB.
+ EXPECT_EQ(0, test_utils::System(base::StringPrintf(
+ "dd if=/dev/zero of=%s seek=20971519 bs=1 count=1 status=none",
+ img.c_str())));
+ EXPECT_EQ(20 * 1024 * 1024, utils::FileSize(img));
+ int block_count = 0;
+ int block_size = 0;
+ EXPECT_TRUE(utils::GetFilesystemSize(img, &block_count, &block_size));
+ EXPECT_EQ(4096, block_size);
+ EXPECT_EQ(10 * 1024 * 1024 / 4096, block_count);
+}
+
+// Squashfs example filesystem, generated with:
+// echo hola>hola
+// mksquashfs hola hola.sqfs -noappend -nopad
+// hexdump hola.sqfs -e '16/1 "%02x, " "\n"'
+const uint8_t kSquashfsFile[] = {
+ 0x68, 0x73, 0x71, 0x73, 0x02, 0x00, 0x00, 0x00, // magic, inodes
+ 0x3e, 0x49, 0x61, 0x54, 0x00, 0x00, 0x02, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x11, 0x00,
+ 0xc0, 0x00, 0x02, 0x00, 0x04, 0x00, 0x00, 0x00, // flags, noids, major, minor
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // root_inode
+ 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // bytes_used
+ 0xe7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x93, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x68, 0x6f, 0x6c, 0x61, 0x0a, 0x2c, 0x00, 0x78,
+ 0xda, 0x63, 0x62, 0x58, 0xc2, 0xc8, 0xc0, 0xc0,
+ 0xc8, 0xd0, 0x6b, 0x91, 0x18, 0x02, 0x64, 0xa0,
+ 0x00, 0x56, 0x06, 0x90, 0xcc, 0x7f, 0xb0, 0xbc,
+ 0x9d, 0x67, 0x62, 0x08, 0x13, 0x54, 0x1c, 0x44,
+ 0x4b, 0x03, 0x31, 0x33, 0x10, 0x03, 0x00, 0xb5,
+ 0x87, 0x04, 0x89, 0x16, 0x00, 0x78, 0xda, 0x63,
+ 0x60, 0x80, 0x00, 0x46, 0x28, 0xcd, 0xc4, 0xc0,
+ 0xcc, 0x90, 0x91, 0x9f, 0x93, 0x08, 0x00, 0x04,
+ 0x70, 0x01, 0xab, 0x10, 0x80, 0x60, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0xab, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x78,
+ 0xda, 0x63, 0x60, 0x80, 0x00, 0x05, 0x28, 0x0d,
+ 0x00, 0x01, 0x10, 0x00, 0x21, 0xc5, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x80, 0x99,
+ 0xcd, 0x02, 0x00, 0x88, 0x13, 0x00, 0x00, 0xdd,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+TEST(UtilsTest, GetSquashfs4Size) {
+ uint8_t buffer[sizeof(kSquashfsFile)];
+ memcpy(buffer, kSquashfsFile, sizeof(kSquashfsFile));
+
+ int block_count = -1;
+ int block_size = -1;
+ // Not enough bytes passed.
+ EXPECT_FALSE(utils::GetSquashfs4Size(buffer, 10, nullptr, nullptr));
+
+ // The whole file system is passed, which is enough for parsing.
+ EXPECT_TRUE(utils::GetSquashfs4Size(buffer, sizeof(kSquashfsFile),
+ &block_count, &block_size));
+ EXPECT_EQ(4096, block_size);
+ EXPECT_EQ(1, block_count);
+
+ // Modify the major version to 5.
+ uint16_t* s_major = reinterpret_cast<uint16_t*>(buffer + 0x1c);
+ *s_major = 5;
+ EXPECT_FALSE(utils::GetSquashfs4Size(buffer, 10, nullptr, nullptr));
+ memcpy(buffer, kSquashfsFile, sizeof(kSquashfsFile));
+
+ // Modify the bytes_used to have 6 blocks.
+ int64_t* bytes_used = reinterpret_cast<int64_t*>(buffer + 0x28);
+ *bytes_used = 4096 * 5 + 1; // 6 "blocks".
+ EXPECT_TRUE(utils::GetSquashfs4Size(buffer, sizeof(kSquashfsFile),
+ &block_count, &block_size));
+ EXPECT_EQ(4096, block_size);
+ EXPECT_EQ(6, block_count);
+}
+
+namespace {
+void GetFileFormatTester(const string& expected,
+ const vector<uint8_t>& contents) {
+ test_utils::ScopedTempFile file;
+ ASSERT_TRUE(utils::WriteFile(file.GetPath().c_str(),
+ reinterpret_cast<const char*>(contents.data()),
+ contents.size()));
+ EXPECT_EQ(expected, utils::GetFileFormat(file.GetPath()));
+}
+} // namespace
+
+TEST(UtilsTest, GetFileFormatTest) {
+ EXPECT_EQ("File not found.", utils::GetFileFormat("/path/to/nowhere"));
+ GetFileFormatTester("data", vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8});
+ GetFileFormatTester("ELF", vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46});
+
+ // Real tests from cros_installer on different boards.
+ // ELF 32-bit LSB executable, Intel 80386
+ GetFileFormatTester(
+ "ELF 32-bit little-endian x86",
+ vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x90, 0x83, 0x04, 0x08, 0x34, 0x00, 0x00, 0x00});
+
+ // ELF 32-bit LSB executable, MIPS
+ GetFileFormatTester(
+ "ELF 32-bit little-endian mips",
+ vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0x00, 0x08, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xc0, 0x12, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00});
+
+ // ELF 32-bit LSB executable, ARM
+ GetFileFormatTester(
+ "ELF 32-bit little-endian arm",
+ vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x01, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x28, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x85, 0x8b, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00});
+
+ // ELF 64-bit LSB executable, x86-64
+ GetFileFormatTester(
+ "ELF 64-bit little-endian x86-64",
+ vector<uint8_t>{0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0xb0, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00});
+}
+
+TEST(UtilsTest, ScheduleCrashReporterUploadTest) {
+ // Not much to test. At least this tests for memory leaks, crashes,
+ // log errors.
+ FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+ utils::ScheduleCrashReporterUpload();
+ // Test that we scheduled one callback from the crash reporter.
+ EXPECT_EQ(1, brillo::MessageLoopRunMaxIterations(&loop, 100));
+ EXPECT_FALSE(loop.PendingTasks());
+}
+
+TEST(UtilsTest, FormatTimeDeltaTest) {
+ // utils::FormatTimeDelta() is not locale-aware (it's only used for logging
+ // which is not localized) so we only need to test the C locale
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromMilliseconds(100)),
+ "0.1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(0)),
+ "0s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(1)),
+ "1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(59)),
+ "59s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(60)),
+ "1m0s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(61)),
+ "1m1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(90)),
+ "1m30s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(1205)),
+ "20m5s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3600)),
+ "1h0m0s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3601)),
+ "1h0m1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(3661)),
+ "1h1m1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(7261)),
+ "2h1m1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(86400)),
+ "1d0h0m0s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(86401)),
+ "1d0h0m1s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(200000)),
+ "2d7h33m20s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(200000) +
+ base::TimeDelta::FromMilliseconds(1)),
+ "2d7h33m20.001s");
+ EXPECT_EQ(utils::FormatTimeDelta(base::TimeDelta::FromSeconds(-1)),
+ "-1s");
+}
+
+TEST(UtilsTest, TimeFromStructTimespecTest) {
+ struct timespec ts;
+
+ // Unix epoch (Thursday 00:00:00 UTC on Jan 1, 1970)
+ ts = (struct timespec) {.tv_sec = 0, .tv_nsec = 0};
+ EXPECT_EQ(base::Time::UnixEpoch(), utils::TimeFromStructTimespec(&ts));
+
+ // 42 ms after the Unix billennium (Sunday 01:46:40 UTC on September 9, 2001)
+ ts = (struct timespec) {.tv_sec = 1000 * 1000 * 1000,
+ .tv_nsec = 42 * 1000 * 1000};
+ base::Time::Exploded exploded = (base::Time::Exploded) {
+ .year = 2001, .month = 9, .day_of_week = 0, .day_of_month = 9,
+ .hour = 1, .minute = 46, .second = 40, .millisecond = 42};
+ EXPECT_EQ(base::Time::FromUTCExploded(exploded),
+ utils::TimeFromStructTimespec(&ts));
+}
+
+TEST(UtilsTest, DecodeAndStoreBase64String) {
+ base::FilePath path;
+
+ // Ensure we return false on empty strings or invalid base64.
+ EXPECT_FALSE(utils::DecodeAndStoreBase64String("", &path));
+ EXPECT_FALSE(utils::DecodeAndStoreBase64String("not valid base64", &path));
+
+ // Pass known base64 and check that it matches. This string was generated
+ // the following way:
+ //
+ // $ echo "Update Engine" | base64
+ // VXBkYXRlIEVuZ2luZQo=
+ EXPECT_TRUE(utils::DecodeAndStoreBase64String("VXBkYXRlIEVuZ2luZQo=",
+ &path));
+ ScopedPathUnlinker unlinker(path.value());
+ string expected_contents = "Update Engine\n";
+ string contents;
+ EXPECT_TRUE(utils::ReadFile(path.value(), &contents));
+ EXPECT_EQ(contents, expected_contents);
+ EXPECT_EQ(utils::FileSize(path.value()), expected_contents.size());
+}
+
+TEST(UtilsTest, ConvertToOmahaInstallDate) {
+ // The Omaha Epoch starts at Jan 1, 2007 0:00 PST which is a
+ // Monday. In Unix time, this point in time is easily obtained via
+ // the date(1) command like this:
+ //
+ // $ date +"%s" --date="Jan 1, 2007 0:00 PST"
+ const time_t omaha_epoch = 1167638400;
+ int value;
+
+ // Points in time *on and after* the Omaha epoch should not fail.
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch), &value));
+ EXPECT_GE(value, 0);
+
+ // Anything before the Omaha epoch should fail. We test it for two points.
+ EXPECT_FALSE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch - 1), &value));
+ EXPECT_FALSE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch - 100*24*3600), &value));
+
+ // Check that we jump from 0 to 7 exactly on the one-week mark, e.g.
+ // on Jan 8, 2007 0:00 PST.
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 7*24*3600 - 1), &value));
+ EXPECT_EQ(value, 0);
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 7*24*3600), &value));
+ EXPECT_EQ(value, 7);
+
+ // Check a couple of more values.
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 10*24*3600), &value));
+ EXPECT_EQ(value, 7);
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 20*24*3600), &value));
+ EXPECT_EQ(value, 14);
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 26*24*3600), &value));
+ EXPECT_EQ(value, 21);
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(omaha_epoch + 29*24*3600), &value));
+ EXPECT_EQ(value, 28);
+
+ // The date Jun 4, 2007 0:00 PDT is a Monday and is hence a point
+ // where the Omaha InstallDate jumps 7 days. Its unix time is
+ // 1180940400. Notably, this is a point in time where Daylight
+ // Savings Time (DST) was is in effect (e.g. it's PDT, not PST).
+ //
+ // Note that as utils::ConvertToOmahaInstallDate() _deliberately_
+ // ignores DST (as it's hard to implement in a thread-safe way using
+ // glibc, see comments in utils.h) we have to fudge by the DST
+ // offset which is one hour. Conveniently, if the function were
+ // someday modified to be DST aware, this test would have to be
+ // modified as well.
+ const time_t dst_time = 1180940400; // Jun 4, 2007 0:00 PDT.
+ const time_t fudge = 3600;
+ int value1, value2;
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(dst_time + fudge - 1), &value1));
+ EXPECT_TRUE(utils::ConvertToOmahaInstallDate(
+ base::Time::FromTimeT(dst_time + fudge), &value2));
+ EXPECT_EQ(value1, value2 - 7);
+}
+
+TEST(UtilsTest, GetMinorVersion) {
+ // Test GetMinorVersion by verifying that it parses the conf file and returns
+ // the correct value.
+ uint32_t minor_version;
+
+ brillo::KeyValueStore store;
+ EXPECT_FALSE(utils::GetMinorVersion(store, &minor_version));
+
+ EXPECT_TRUE(store.LoadFromString("PAYLOAD_MINOR_VERSION=one-two-three\n"));
+ EXPECT_FALSE(utils::GetMinorVersion(store, &minor_version));
+
+ EXPECT_TRUE(store.LoadFromString("PAYLOAD_MINOR_VERSION=123\n"));
+ EXPECT_TRUE(utils::GetMinorVersion(store, &minor_version));
+ EXPECT_EQ(minor_version, 123);
+}
+
+static bool BoolMacroTestHelper() {
+ int i = 1;
+ unsigned int ui = 1;
+ bool b = 1;
+ std::unique_ptr<char> cptr(new char);
+
+ TEST_AND_RETURN_FALSE(i);
+ TEST_AND_RETURN_FALSE(ui);
+ TEST_AND_RETURN_FALSE(b);
+ TEST_AND_RETURN_FALSE(cptr);
+
+ TEST_AND_RETURN_FALSE_ERRNO(i);
+ TEST_AND_RETURN_FALSE_ERRNO(ui);
+ TEST_AND_RETURN_FALSE_ERRNO(b);
+ TEST_AND_RETURN_FALSE_ERRNO(cptr);
+
+ return true;
+}
+
+static void VoidMacroTestHelper(bool* ret) {
+ int i = 1;
+ unsigned int ui = 1;
+ bool b = 1;
+ std::unique_ptr<char> cptr(new char);
+
+ *ret = false;
+
+ TEST_AND_RETURN(i);
+ TEST_AND_RETURN(ui);
+ TEST_AND_RETURN(b);
+ TEST_AND_RETURN(cptr);
+
+ TEST_AND_RETURN_ERRNO(i);
+ TEST_AND_RETURN_ERRNO(ui);
+ TEST_AND_RETURN_ERRNO(b);
+ TEST_AND_RETURN_ERRNO(cptr);
+
+ *ret = true;
+}
+
+TEST(UtilsTest, TestMacros) {
+ bool void_test = false;
+ VoidMacroTestHelper(&void_test);
+ EXPECT_TRUE(void_test);
+
+ EXPECT_TRUE(BoolMacroTestHelper());
+}
+
+} // namespace chromeos_update_engine
diff --git a/connection_manager.cc b/connection_manager.cc
new file mode 100644
index 0000000..778cba5
--- /dev/null
+++ b/connection_manager.cc
@@ -0,0 +1,245 @@
+//
+// 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/connection_manager.h"
+
+#include <set>
+#include <string>
+
+#include <base/stl_util.h>
+#include <base/strings/string_util.h>
+#include <policy/device_policy.h>
+#include <shill/dbus-constants.h>
+#include <shill/dbus-proxies.h>
+
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/system_state.h"
+
+using org::chromium::flimflam::ManagerProxyInterface;
+using org::chromium::flimflam::ServiceProxyInterface;
+using std::set;
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+NetworkConnectionType ParseConnectionType(const string& type_str) {
+ if (type_str == shill::kTypeEthernet) {
+ return NetworkConnectionType::kEthernet;
+ } else if (type_str == shill::kTypeWifi) {
+ return NetworkConnectionType::kWifi;
+ } else if (type_str == shill::kTypeWimax) {
+ return NetworkConnectionType::kWimax;
+ } else if (type_str == shill::kTypeBluetooth) {
+ return NetworkConnectionType::kBluetooth;
+ } else if (type_str == shill::kTypeCellular) {
+ return NetworkConnectionType::kCellular;
+ }
+ return NetworkConnectionType::kUnknown;
+}
+
+NetworkTethering ParseTethering(const string& tethering_str) {
+ if (tethering_str == shill::kTetheringNotDetectedState) {
+ return NetworkTethering::kNotDetected;
+ } else if (tethering_str == shill::kTetheringSuspectedState) {
+ return NetworkTethering::kSuspected;
+ } else if (tethering_str == shill::kTetheringConfirmedState) {
+ return NetworkTethering::kConfirmed;
+ }
+ LOG(WARNING) << "Unknown Tethering value: " << tethering_str;
+ return NetworkTethering::kUnknown;
+}
+
+} // namespace
+
+ConnectionManager::ConnectionManager(ShillProxyInterface* shill_proxy,
+ SystemState* system_state)
+ : shill_proxy_(shill_proxy), system_state_(system_state) {}
+
+bool ConnectionManager::IsUpdateAllowedOver(NetworkConnectionType type,
+ NetworkTethering tethering) const {
+ switch (type) {
+ case NetworkConnectionType::kBluetooth:
+ return false;
+
+ case NetworkConnectionType::kCellular: {
+ set<string> allowed_types;
+ const policy::DevicePolicy* device_policy =
+ system_state_->device_policy();
+
+ // A device_policy is loaded in a lazy way right before an update check,
+ // so the device_policy should be already loaded at this point. If it's
+ // not, return a safe value for this setting.
+ if (!device_policy) {
+ LOG(INFO) << "Disabling updates over cellular networks as there's no "
+ "device policy loaded yet.";
+ return false;
+ }
+
+ if (device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+ // The update setting is enforced by the device policy.
+
+ if (!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;
+ } else {
+ // There's no update setting in the device policy, using the local user
+ // setting.
+ PrefsInterface* prefs = system_state_->prefs();
+
+ if (!prefs || !prefs->Exists(kPrefsUpdateOverCellularPermission)) {
+ LOG(INFO) << "Disabling updates over cellular connection as there's "
+ "no device policy setting nor user preference present.";
+ return false;
+ }
+
+ bool stored_value;
+ if (!prefs->GetBoolean(kPrefsUpdateOverCellularPermission,
+ &stored_value)) {
+ return false;
+ }
+
+ if (!stored_value) {
+ LOG(INFO) << "Disabling updates over cellular connection per user "
+ "setting.";
+ return false;
+ }
+ LOG(INFO) << "Allowing updates over cellular per user setting.";
+ return true;
+ }
+ }
+
+ default:
+ if (tethering == NetworkTethering::kConfirmed) {
+ // Treat this connection as if it is a cellular connection.
+ LOG(INFO) << "Current connection is confirmed tethered, using Cellular "
+ "setting.";
+ return IsUpdateAllowedOver(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown);
+ }
+ return true;
+ }
+}
+
+// static
+const char* ConnectionManager::StringForConnectionType(
+ NetworkConnectionType type) {
+ switch (type) {
+ case NetworkConnectionType::kEthernet:
+ return shill::kTypeEthernet;
+ case NetworkConnectionType::kWifi:
+ return shill::kTypeWifi;
+ case NetworkConnectionType::kWimax:
+ return shill::kTypeWimax;
+ case NetworkConnectionType::kBluetooth:
+ return shill::kTypeBluetooth;
+ case NetworkConnectionType::kCellular:
+ return shill::kTypeCellular;
+ case NetworkConnectionType::kUnknown:
+ return "Unknown";
+ }
+ return "Unknown";
+}
+
+bool ConnectionManager::GetConnectionProperties(
+ NetworkConnectionType* out_type,
+ NetworkTethering* out_tethering) {
+ dbus::ObjectPath default_service_path;
+ TEST_AND_RETURN_FALSE(GetDefaultServicePath(&default_service_path));
+ if (!default_service_path.IsValid())
+ return false;
+ // Shill uses the "/" service path to indicate that it is not connected.
+ if (default_service_path.value() == "/")
+ return false;
+ TEST_AND_RETURN_FALSE(
+ GetServicePathProperties(default_service_path, out_type, out_tethering));
+ return true;
+}
+
+bool ConnectionManager::GetDefaultServicePath(dbus::ObjectPath* out_path) {
+ brillo::VariantDictionary properties;
+ brillo::ErrorPtr error;
+ ManagerProxyInterface* manager_proxy = shill_proxy_->GetManagerProxy();
+ if (!manager_proxy)
+ return false;
+ TEST_AND_RETURN_FALSE(manager_proxy->GetProperties(&properties, &error));
+
+ const auto& prop_default_service =
+ properties.find(shill::kDefaultServiceProperty);
+ if (prop_default_service == properties.end())
+ return false;
+
+ *out_path = prop_default_service->second.TryGet<dbus::ObjectPath>();
+ return out_path->IsValid();
+}
+
+bool ConnectionManager::GetServicePathProperties(
+ const dbus::ObjectPath& path,
+ NetworkConnectionType* out_type,
+ NetworkTethering* out_tethering) {
+ // We create and dispose the ServiceProxyInterface on every request.
+ std::unique_ptr<ServiceProxyInterface> service =
+ shill_proxy_->GetServiceForPath(path);
+
+ brillo::VariantDictionary properties;
+ brillo::ErrorPtr error;
+ TEST_AND_RETURN_FALSE(service->GetProperties(&properties, &error));
+
+ // Populate the out_tethering.
+ const auto& prop_tethering = properties.find(shill::kTetheringProperty);
+ if (prop_tethering == properties.end()) {
+ // Set to Unknown if not present.
+ *out_tethering = NetworkTethering::kUnknown;
+ } else {
+ // If the property doesn't contain a string value, the empty string will
+ // become kUnknown.
+ *out_tethering = ParseTethering(prop_tethering->second.TryGet<string>());
+ }
+
+ // Populate the out_type property.
+ const auto& prop_type = properties.find(shill::kTypeProperty);
+ if (prop_type == properties.end()) {
+ // Set to Unknown if not present.
+ *out_type = NetworkConnectionType::kUnknown;
+ return false;
+ }
+
+ string type_str = prop_type->second.TryGet<string>();
+ if (type_str == shill::kTypeVPN) {
+ const auto& prop_physical =
+ properties.find(shill::kPhysicalTechnologyProperty);
+ if (prop_physical == properties.end()) {
+ LOG(ERROR) << "No PhysicalTechnology property found for a VPN"
+ " connection (service: "
+ << path.value() << "). Returning default kUnknown value.";
+ *out_type = NetworkConnectionType::kUnknown;
+ } else {
+ *out_type = ParseConnectionType(prop_physical->second.TryGet<string>());
+ }
+ } else {
+ *out_type = ParseConnectionType(type_str);
+ }
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/connection_manager.h b/connection_manager.h
new file mode 100644
index 0000000..2057f3b
--- /dev/null
+++ b/connection_manager.h
@@ -0,0 +1,73 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_CONNECTION_MANAGER_H_
+#define UPDATE_ENGINE_CONNECTION_MANAGER_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <dbus/object_path.h>
+
+#include "update_engine/connection_manager_interface.h"
+#include "update_engine/shill_proxy_interface.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// This class implements the concrete class that talks with the connection
+// manager (shill) over DBus.
+// TODO(deymo): Remove this class and use ShillProvider from the UpdateManager.
+class ConnectionManager : public ConnectionManagerInterface {
+ public:
+ // Returns the string representation corresponding to the given
+ // connection type.
+ static const char* StringForConnectionType(NetworkConnectionType type);
+
+ // Constructs a new ConnectionManager object initialized with the
+ // given system state.
+ ConnectionManager(ShillProxyInterface* shill_proxy,
+ SystemState* system_state);
+ ~ConnectionManager() override = default;
+
+ // ConnectionManagerInterface overrides.
+ bool GetConnectionProperties(NetworkConnectionType* out_type,
+ NetworkTethering* out_tethering) override;
+ bool IsUpdateAllowedOver(NetworkConnectionType type,
+ NetworkTethering tethering) const override;
+
+ private:
+ // Returns (via out_path) the default network path, or empty string if
+ // there's no network up. Returns true on success.
+ bool GetDefaultServicePath(dbus::ObjectPath* out_path);
+
+ bool GetServicePathProperties(const dbus::ObjectPath& path,
+ NetworkConnectionType* out_type,
+ NetworkTethering* out_tethering);
+
+ // The mockable interface to access the shill DBus proxies.
+ 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_
diff --git a/connection_manager_interface.h b/connection_manager_interface.h
new file mode 100644
index 0000000..cb60a3c
--- /dev/null
+++ b/connection_manager_interface.h
@@ -0,0 +1,68 @@
+//
+// 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_CONNECTION_MANAGER_INTERFACE_H_
+#define UPDATE_ENGINE_CONNECTION_MANAGER_INTERFACE_H_
+
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+enum class NetworkConnectionType {
+ kEthernet,
+ kWifi,
+ kWimax,
+ kBluetooth,
+ kCellular,
+ kUnknown
+};
+
+enum class NetworkTethering {
+ kNotDetected,
+ kSuspected,
+ kConfirmed,
+ kUnknown
+};
+
+// This class exposes a generic interface to the connection manager
+// (e.g FlimFlam, Shill, etc.) to consolidate all connection-related
+// logic in update_engine.
+class ConnectionManagerInterface {
+ public:
+ virtual ~ConnectionManagerInterface() = default;
+
+ // Populates |out_type| with the type of the network connection
+ // that we are currently connected and |out_tethering| with the estimate of
+ // whether that network is being tethered.
+ virtual bool GetConnectionProperties(NetworkConnectionType* out_type,
+ NetworkTethering* out_tethering) = 0;
+
+ // Returns true if we're allowed to update the system when we're
+ // connected to the internet through the given network connection type and the
+ // given tethering state.
+ virtual bool IsUpdateAllowedOver(NetworkConnectionType type,
+ NetworkTethering tethering) const = 0;
+
+ protected:
+ ConnectionManagerInterface() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConnectionManagerInterface);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CONNECTION_MANAGER_INTERFACE_H_
diff --git a/connection_manager_unittest.cc b/connection_manager_unittest.cc
new file mode 100644
index 0000000..612929b
--- /dev/null
+++ b/connection_manager_unittest.cc
@@ -0,0 +1,409 @@
+//
+// 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/connection_manager.h"
+
+#include <set>
+#include <string>
+
+#include <base/logging.h>
+#include <brillo/any.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/variant_dictionary.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <shill/dbus-constants.h>
+#include <shill/dbus-proxies.h>
+#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"
+
+using org::chromium::flimflam::ManagerProxyMock;
+using org::chromium::flimflam::ServiceProxyMock;
+using std::set;
+using std::string;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+class ConnectionManagerTest : public ::testing::Test {
+ public:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ fake_system_state_.set_connection_manager(&cmut_);
+ }
+
+ void TearDown() override { EXPECT_FALSE(loop_.PendingTasks()); }
+
+ protected:
+ // Sets the default_service object path in the response from the
+ // ManagerProxyMock instance.
+ void SetManagerReply(const char* default_service, bool reply_succeeds);
+
+ // Sets the |service_type|, |physical_technology| and |service_tethering|
+ // properties in the mocked service |service_path|. If any of the three
+ // const char* is a nullptr, the corresponding property will not be included
+ // in the response.
+ void SetServiceReply(const string& service_path,
+ const char* service_type,
+ const char* physical_technology,
+ const char* service_tethering);
+
+ void TestWithServiceType(
+ const char* service_type,
+ const char* physical_technology,
+ NetworkConnectionType expected_type);
+ void TestWithServiceTethering(
+ const char* service_tethering,
+ NetworkTethering 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_};
+};
+
+void ConnectionManagerTest::SetManagerReply(const char* default_service,
+ bool reply_succeeds) {
+ ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_.GetManagerProxy();
+ if (!reply_succeeds) {
+ EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _))
+ .WillOnce(Return(false));
+ return;
+ }
+
+ // Create a dictionary of properties and optionally include the default
+ // service.
+ brillo::VariantDictionary reply_dict;
+ reply_dict["SomeOtherProperty"] = 0xC0FFEE;
+
+ if (default_service) {
+ reply_dict[shill::kDefaultServiceProperty] =
+ dbus::ObjectPath(default_service);
+ }
+ EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true)));
+}
+
+void ConnectionManagerTest::SetServiceReply(const string& service_path,
+ const char* service_type,
+ const char* physical_technology,
+ const char* service_tethering) {
+ brillo::VariantDictionary reply_dict;
+ reply_dict["SomeOtherProperty"] = 0xC0FFEE;
+
+ if (service_type)
+ reply_dict[shill::kTypeProperty] = string(service_type);
+
+ if (physical_technology) {
+ reply_dict[shill::kPhysicalTechnologyProperty] =
+ string(physical_technology);
+ }
+
+ if (service_tethering)
+ reply_dict[shill::kTetheringProperty] = string(service_tethering);
+
+ std::unique_ptr<ServiceProxyMock> service_proxy_mock(new ServiceProxyMock());
+
+ // Plumb return value into mock object.
+ EXPECT_CALL(*service_proxy_mock.get(), GetProperties(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true)));
+
+ fake_shill_proxy_.SetServiceForPath(dbus::ObjectPath(service_path),
+ std::move(service_proxy_mock));
+}
+
+void ConnectionManagerTest::TestWithServiceType(
+ const char* service_type,
+ const char* physical_technology,
+ NetworkConnectionType expected_type) {
+ SetManagerReply("/service/guest/network", true);
+ SetServiceReply("/service/guest/network",
+ service_type,
+ physical_technology,
+ shill::kTetheringNotDetectedState);
+
+ NetworkConnectionType type;
+ NetworkTethering tethering;
+ EXPECT_TRUE(cmut_.GetConnectionProperties(&type, &tethering));
+ EXPECT_EQ(expected_type, type);
+ testing::Mock::VerifyAndClearExpectations(
+ fake_shill_proxy_.GetManagerProxy());
+}
+
+void ConnectionManagerTest::TestWithServiceTethering(
+ const char* service_tethering,
+ NetworkTethering expected_tethering) {
+ SetManagerReply("/service/guest/network", true);
+ SetServiceReply(
+ "/service/guest/network", shill::kTypeWifi, nullptr, service_tethering);
+
+ NetworkConnectionType type;
+ NetworkTethering tethering;
+ EXPECT_TRUE(cmut_.GetConnectionProperties(&type, &tethering));
+ EXPECT_EQ(expected_tethering, tethering);
+ testing::Mock::VerifyAndClearExpectations(
+ fake_shill_proxy_.GetManagerProxy());
+}
+
+TEST_F(ConnectionManagerTest, SimpleTest) {
+ TestWithServiceType(shill::kTypeEthernet, nullptr,
+ NetworkConnectionType::kEthernet);
+ TestWithServiceType(shill::kTypeWifi, nullptr,
+ NetworkConnectionType::kWifi);
+ TestWithServiceType(shill::kTypeWimax, nullptr,
+ NetworkConnectionType::kWimax);
+ TestWithServiceType(shill::kTypeBluetooth, nullptr,
+ NetworkConnectionType::kBluetooth);
+ TestWithServiceType(shill::kTypeCellular, nullptr,
+ NetworkConnectionType::kCellular);
+}
+
+TEST_F(ConnectionManagerTest, PhysicalTechnologyTest) {
+ TestWithServiceType(shill::kTypeVPN, nullptr,
+ NetworkConnectionType::kUnknown);
+ TestWithServiceType(shill::kTypeVPN, shill::kTypeVPN,
+ NetworkConnectionType::kUnknown);
+ TestWithServiceType(shill::kTypeVPN, shill::kTypeWifi,
+ NetworkConnectionType::kWifi);
+ TestWithServiceType(shill::kTypeVPN, shill::kTypeWimax,
+ NetworkConnectionType::kWimax);
+}
+
+TEST_F(ConnectionManagerTest, TetheringTest) {
+ TestWithServiceTethering(shill::kTetheringConfirmedState,
+ NetworkTethering::kConfirmed);
+ TestWithServiceTethering(shill::kTetheringNotDetectedState,
+ NetworkTethering::kNotDetected);
+ TestWithServiceTethering(shill::kTetheringSuspectedState,
+ NetworkTethering::kSuspected);
+ TestWithServiceTethering("I'm not a valid property value =)",
+ NetworkTethering::kUnknown);
+}
+
+TEST_F(ConnectionManagerTest, UnknownTest) {
+ TestWithServiceType("foo", nullptr, NetworkConnectionType::kUnknown);
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverEthernetTest) {
+ // Updates over Ethernet are allowed even if there's no policy.
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kEthernet,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverWifiTest) {
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kWifi,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOverWimaxTest) {
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kWimax,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverBluetoothTest) {
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kBluetooth,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOnlyOver3GPerPolicyTest) {
+ policy::MockDevicePolicy allow_3g_policy;
+
+ fake_system_state_.set_device_policy(&allow_3g_policy);
+
+ // This test tests cellular (3G) being the only connection type being allowed.
+ set<string> allowed_set;
+ allowed_set.insert(
+ cmut_.StringForConnectionType(NetworkConnectionType::kCellular));
+
+ EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(1)
+ .WillOnce(DoAll(SetArgPointee<0>(allowed_set), Return(true)));
+
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, AllowUpdatesOver3GAndOtherTypesPerPolicyTest) {
+ policy::MockDevicePolicy allow_3g_policy;
+
+ fake_system_state_.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).
+ set<string> allowed_set;
+ allowed_set.insert(
+ cmut_.StringForConnectionType(NetworkConnectionType::kCellular));
+ allowed_set.insert(
+ cmut_.StringForConnectionType(NetworkConnectionType::kBluetooth));
+
+ EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(3)
+ .WillRepeatedly(DoAll(SetArgPointee<0>(allowed_set), Return(true)));
+
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kEthernet,
+ NetworkTethering::kUnknown));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kEthernet,
+ NetworkTethering::kNotDetected));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kWifi,
+ NetworkTethering::kUnknown));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kWimax,
+ NetworkTethering::kUnknown));
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kBluetooth,
+ NetworkTethering::kUnknown));
+
+ // Tethered networks are treated in the same way as Cellular networks and
+ // thus allowed.
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kEthernet,
+ NetworkTethering::kConfirmed));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kWifi,
+ NetworkTethering::kConfirmed));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverCellularByDefaultTest) {
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOverTetheredNetworkByDefaultTest) {
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kWifi,
+ NetworkTethering::kConfirmed));
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kEthernet,
+ NetworkTethering::kConfirmed));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kWifi,
+ NetworkTethering::kSuspected));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOver3GPerPolicyTest) {
+ policy::MockDevicePolicy block_3g_policy;
+
+ fake_system_state_.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(
+ cmut_.StringForConnectionType(NetworkConnectionType::kEthernet));
+ allowed_set.insert(
+ cmut_.StringForConnectionType(NetworkConnectionType::kWifi));
+ allowed_set.insert(
+ cmut_.StringForConnectionType(NetworkConnectionType::kWimax));
+
+ EXPECT_CALL(block_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(1)
+ .WillOnce(DoAll(SetArgPointee<0>(allowed_set), Return(true)));
+
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, BlockUpdatesOver3GIfErrorInPolicyFetchTest) {
+ policy::MockDevicePolicy allow_3g_policy;
+
+ fake_system_state_.set_device_policy(&allow_3g_policy);
+
+ set<string> allowed_set;
+ allowed_set.insert(
+ cmut_.StringForConnectionType(NetworkConnectionType::kCellular));
+
+ // Return false for GetAllowedConnectionTypesForUpdate and see
+ // that updates are still blocked for 3G despite the value being in
+ // the string set above.
+ EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(1)
+ .WillOnce(DoAll(SetArgPointee<0>(allowed_set), Return(false)));
+
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, UseUserPrefForUpdatesOverCellularIfNoPolicyTest) {
+ policy::MockDevicePolicy no_policy;
+ testing::NiceMock<MockPrefs>* prefs = fake_system_state_.mock_prefs();
+
+ fake_system_state_.set_device_policy(&no_policy);
+
+ // No setting enforced by the device policy, user prefs should be used.
+ EXPECT_CALL(no_policy, GetAllowedConnectionTypesForUpdate(_))
+ .Times(3)
+ .WillRepeatedly(Return(false));
+
+ // No user pref: block.
+ EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+ .Times(1)
+ .WillOnce(Return(false));
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown));
+
+ // Allow per user pref.
+ EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+ .Times(1)
+ .WillOnce(Return(true));
+ EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
+ .Times(1)
+ .WillOnce(DoAll(SetArgPointee<1>(true), Return(true)));
+ EXPECT_TRUE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown));
+
+ // Block per user pref.
+ EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+ .Times(1)
+ .WillOnce(Return(true));
+ EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
+ .Times(1)
+ .WillOnce(DoAll(SetArgPointee<1>(false), Return(true)));
+ EXPECT_FALSE(cmut_.IsUpdateAllowedOver(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown));
+}
+
+TEST_F(ConnectionManagerTest, StringForConnectionTypeTest) {
+ EXPECT_STREQ(shill::kTypeEthernet,
+ cmut_.StringForConnectionType(NetworkConnectionType::kEthernet));
+ EXPECT_STREQ(shill::kTypeWifi,
+ cmut_.StringForConnectionType(NetworkConnectionType::kWifi));
+ EXPECT_STREQ(shill::kTypeWimax,
+ cmut_.StringForConnectionType(NetworkConnectionType::kWimax));
+ EXPECT_STREQ(shill::kTypeBluetooth,
+ cmut_.StringForConnectionType(
+ NetworkConnectionType::kBluetooth));
+ EXPECT_STREQ(shill::kTypeCellular,
+ cmut_.StringForConnectionType(NetworkConnectionType::kCellular));
+ EXPECT_STREQ("Unknown",
+ cmut_.StringForConnectionType(NetworkConnectionType::kUnknown));
+ EXPECT_STREQ("Unknown",
+ cmut_.StringForConnectionType(
+ static_cast<NetworkConnectionType>(999999)));
+}
+
+TEST_F(ConnectionManagerTest, MalformedServiceList) {
+ SetManagerReply("/service/guest/network", false);
+
+ NetworkConnectionType type;
+ NetworkTethering tethering;
+ EXPECT_FALSE(cmut_.GetConnectionProperties(&type, &tethering));
+}
+
+} // namespace chromeos_update_engine
diff --git a/daemon.cc b/daemon.cc
new file mode 100644
index 0000000..0b13c18
--- /dev/null
+++ b/daemon.cc
@@ -0,0 +1,153 @@
+//
+// 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>
+#include <base/time/time.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/clock.h"
+#include "update_engine/update_attempter.h"
+
+using brillo::MessageLoop;
+
+namespace {
+const int kDBusSystemMaxWaitSeconds = 2 * 60;
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace {
+// Wait for passed |bus| DBus to be connected by attempting to connect it up to
+// |timeout| time. Returns whether the Connect() eventually succeeded.
+bool WaitForDBusSystem(dbus::Bus* bus, base::TimeDelta timeout) {
+ Clock clock;
+ base::Time deadline = clock.GetMonotonicTime() + timeout;
+
+ while (clock.GetMonotonicTime() < deadline) {
+ if (bus->Connect())
+ return true;
+ LOG(WARNING) << "Failed to get system bus, waiting.";
+ // Wait 1 second.
+ sleep(1);
+ }
+ LOG(ERROR) << "Failed to get system bus after " << timeout.InSeconds()
+ << " seconds.";
+ return false;
+}
+} // namespace
+
+UpdateEngineDaemon::~UpdateEngineDaemon() {
+ UpdateAttempter* update_attempter = real_system_state_->update_attempter();
+ // Prevent any DBus communication from UpdateAttempter when shutting down the
+ // daemon.
+ if (update_attempter)
+ update_attempter->set_dbus_adaptor(nullptr);
+}
+
+int UpdateEngineDaemon::OnInit() {
+ // Register the |subprocess_| singleton with this Daemon as the signal
+ // handler.
+ subprocess_.Init(this);
+
+ // We use Daemon::OnInit() and not DBusDaemon::OnInit() to gracefully wait for
+ // the D-Bus connection for up two minutes to avoid re-spawning the daemon
+ // too fast causing thrashing if dbus-daemon is not running.
+ int exit_code = Daemon::OnInit();
+ if (exit_code != EX_OK)
+ return exit_code;
+
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SYSTEM;
+ bus_ = new dbus::Bus(options);
+
+ // Wait for DBus to be ready and exit if it doesn't become available after
+ // the timeout.
+ if (!WaitForDBusSystem(
+ bus_.get(),
+ base::TimeDelta::FromSeconds(kDBusSystemMaxWaitSeconds))) {
+ // TODO(deymo): Make it possible to run update_engine even if dbus-daemon
+ // is not running or constantly crashing.
+ LOG(ERROR) << "Failed to initialize DBus, aborting.";
+ return 1;
+ }
+
+ CHECK(bus_->SetUpAsyncOperations());
+
+ // Initialize update engine global state but continue if something fails.
+ real_system_state_.reset(new RealSystemState(bus_));
+ LOG_IF(ERROR, !real_system_state_->Initialize())
+ << "Failed to initialize system state.";
+ UpdateAttempter* update_attempter = real_system_state_->update_attempter();
+ CHECK(update_attempter);
+
+ // Create the DBus service.
+ dbus_adaptor_.reset(new UpdateEngineAdaptor(real_system_state_.get(), bus_));
+ update_attempter->set_dbus_adaptor(dbus_adaptor_.get());
+
+ dbus_adaptor_->RegisterAsync(base::Bind(&UpdateEngineDaemon::OnDBusRegistered,
+ base::Unretained(this)));
+ LOG(INFO) << "Waiting for DBus object to be registered.";
+ return EX_OK;
+}
+
+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;
+ }
+
+ // Initiate update checks.
+ UpdateAttempter* update_attempter = real_system_state_->update_attempter();
+ update_attempter->ScheduleUpdates();
+
+ // Update boot flags after 45 seconds.
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UpdateAttempter::UpdateBootFlags,
+ base::Unretained(update_attempter)),
+ 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)));
+
+ // Run the UpdateEngineStarted() method on |update_attempter|.
+ MessageLoop::current()->PostTask(FROM_HERE, base::Bind(
+ &UpdateAttempter::UpdateEngineStarted,
+ base::Unretained(update_attempter)));
+
+ LOG(INFO) << "Finished initialization. Now running the loop.";
+}
+
+} // namespace chromeos_update_engine
diff --git a/daemon.h b/daemon.h
new file mode 100644
index 0000000..66841f6
--- /dev/null
+++ b/daemon.h
@@ -0,0 +1,60 @@
+//
+// 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_DAEMON_H_
+#define UPDATE_ENGINE_DAEMON_H_
+
+#include <memory>
+#include <string>
+
+#include <brillo/daemons/dbus_daemon.h>
+
+#include "update_engine/common/subprocess.h"
+#include "update_engine/dbus_service.h"
+#include "update_engine/real_system_state.h"
+
+namespace chromeos_update_engine {
+
+class UpdateEngineDaemon : public brillo::DBusDaemon {
+ public:
+ UpdateEngineDaemon() = default;
+ ~UpdateEngineDaemon();
+
+ protected:
+ int OnInit() override;
+
+ private:
+ // 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);
+
+ // 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_;
+
+ std::unique_ptr<UpdateEngineAdaptor> dbus_adaptor_;
+
+ // The RealSystemState uses the previous classes so it should be defined last.
+ std::unique_ptr<RealSystemState> real_system_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateEngineDaemon);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_DAEMON_H_
diff --git a/dbus_bindings/dbus-service-config.json b/dbus_bindings/dbus-service-config.json
new file mode 100644
index 0000000..fdae3ba
--- /dev/null
+++ b/dbus_bindings/dbus-service-config.json
@@ -0,0 +1,3 @@
+{
+ "service_name": "org.chromium.UpdateEngine"
+}
diff --git a/dbus_bindings/org.chromium.LibCrosService.dbus-xml b/dbus_bindings/org.chromium.LibCrosService.dbus-xml
new file mode 100644
index 0000000..2ea8313
--- /dev/null
+++ b/dbus_bindings/org.chromium.LibCrosService.dbus-xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/org/chromium/LibCrosService"
+ xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+ <interface name="org.chromium.LibCrosServiceInterface">
+ <method name="ResolveNetworkProxy">
+ <arg name="source_url" type="s" direction="in" />
+ <arg name="signal_interface" type="s" direction="in" />
+ <arg name="signal_name" type="s" direction="in" />
+ <annotation name="org.chromium.DBus.Method.Kind" value="simple" />
+ </method>
+ </interface>
+ <interface name="org.chromium.UpdateEngineLibcrosProxyResolvedInterface">
+ <signal name="ProxyResolved">
+ <arg name="source_url" type="s" direction="out" />
+ <arg name="proxy_info" type="s" direction="out" />
+ <arg name="error_message" type="s" direction="out" />
+ </signal>
+ </interface>
+</node>
diff --git a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
new file mode 100644
index 0000000..bc4ec36
--- /dev/null
+++ b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<node name="/org/chromium/UpdateEngine">
+ <interface name="org.chromium.UpdateEngineInterface">
+ <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+ value="update_engine_service" />
+ <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+ value="update_engine_client" />
+ <method name="AttemptUpdate">
+ <arg type="s" name="app_version" direction="in" />
+ <arg type="s" name="omaha_url" direction="in" />
+ </method>
+ <!-- TODO(zeuthen,chromium:286399): Rename to AttemptUpdate and
+ update Chrome and other users of the AttemptUpdate() method
+ in lockstep.
+ -->
+ <method name="AttemptUpdateWithFlags">
+ <arg type="s" name="app_version" direction="in" />
+ <arg type="s" name="omaha_url" direction="in" />
+ <!-- See AttemptUpdateFlags enum in update_engine/dbus-constants.h. -->
+ <arg type="i" name="flags" direction="in" />
+ </method>
+ <method name="AttemptRollback">
+ <arg type="b" name="powerwash" direction="in" />
+ </method>
+ <method name="CanRollback">
+ <arg type="b" name="can_rollback" direction="out" />
+ </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>
+ <method name="RebootIfNeeded">
+ </method>
+ <method name="SetChannel">
+ <arg type="s" name="target_channel" direction="in" />
+ <arg type="b" name="is_powerwash_allowed" direction="in" />
+ </method>
+ <method name="GetChannel">
+ <arg type="b" name="get_current_channel" direction="in" />
+ <arg type="s" name="channel" direction="out" />
+ </method>
+ <method name="SetP2PUpdatePermission">
+ <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+ value="update_engine_service_set_p2p_update_permission" />
+ <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+ value="update_engine_client_set_p2p_update_permission" />
+ <arg type="b" name="enabled" direction="in" />
+ </method>
+ <method name="GetP2PUpdatePermission">
+ <annotation name="org.freedesktop.DBus.GLib.CSymbol"
+ value="update_engine_service_get_p2p_update_permission" />
+ <annotation name="org.freedesktop.DBus.GLib.ClientCSymbol"
+ value="update_engine_client_get_p2p_update_permission" />
+ <arg type="b" name="enabled" direction="out" />
+ </method>
+ <method name="SetUpdateOverCellularPermission">
+ <arg type="b" name="allowed" direction="in" />
+ </method>
+ <method name="GetUpdateOverCellularPermission">
+ <arg type="b" name="allowed" direction="out" />
+ </method>
+ <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>
+ <method name="GetPrevVersion">
+ <arg type="s" name="prev_version" direction="out" />
+ </method>
+ <method name="GetRollbackPartition">
+ <arg type="s" name="rollback_partition_name" direction="out" />
+ </method>
+ </interface>
+</node>
diff --git a/dbus_service.cc b/dbus_service.cc
new file mode 100644
index 0000000..6ed31d4
--- /dev/null
+++ b/dbus_service.cc
@@ -0,0 +1,342 @@
+//
+// 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/dbus_service.h"
+
+#include <set>
+#include <string>
+
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/strings/string_utils.h>
+#include <policy/device_policy.h>
+#include <update_engine/dbus-constants.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/utils.h"
+#include "update_engine/connection_manager_interface.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/update_attempter.h"
+
+using base::StringPrintf;
+using brillo::ErrorPtr;
+using brillo::string_utils::ToString;
+using std::set;
+using std::string;
+using update_engine::AttemptUpdateFlags;
+using update_engine::kAttemptUpdateFlagNonInteractive;
+
+namespace {
+// Log and set the error on the passed ErrorPtr.
+void LogAndSetError(ErrorPtr *error,
+ const tracked_objects::Location& location,
+ const string& reason) {
+ brillo::Error::AddTo(
+ error, location,
+ brillo::errors::dbus::kDomain,
+ update_engine::kUpdateEngineServiceErrorFailed, reason);
+ LOG(ERROR) << "Sending DBus Failure: " << location.ToString() << ": "
+ << reason;
+}
+} // namespace
+
+namespace chromeos_update_engine {
+
+UpdateEngineService::UpdateEngineService(SystemState* system_state)
+ : system_state_(system_state) {}
+
+// org::chromium::UpdateEngineInterfaceInterface methods implementation.
+
+bool UpdateEngineService::AttemptUpdate(ErrorPtr* error,
+ const string& in_app_version,
+ const string& in_omaha_url) {
+ return AttemptUpdateWithFlags(
+ error, in_app_version, in_omaha_url, 0 /* no flags */);
+}
+
+bool UpdateEngineService::AttemptUpdateWithFlags(ErrorPtr* /* error */,
+ const string& in_app_version,
+ const string& in_omaha_url,
+ int32_t in_flags_as_int) {
+ AttemptUpdateFlags flags = static_cast<AttemptUpdateFlags>(in_flags_as_int);
+ bool interactive = !(flags & kAttemptUpdateFlagNonInteractive);
+
+ LOG(INFO) << "Attempt update: app_version=\"" << in_app_version << "\" "
+ << "omaha_url=\"" << in_omaha_url << "\" "
+ << "flags=0x" << std::hex << flags << " "
+ << "interactive=" << (interactive? "yes" : "no");
+ system_state_->update_attempter()->CheckForUpdate(
+ in_app_version, in_omaha_url, interactive);
+ return true;
+}
+
+bool UpdateEngineService::AttemptRollback(ErrorPtr* error,
+ bool in_powerwash) {
+ LOG(INFO) << "Attempting rollback to non-active partitions.";
+
+ if (!system_state_->update_attempter()->Rollback(in_powerwash)) {
+ // TODO(dgarrett): Give a more specific error code/reason.
+ LogAndSetError(error, FROM_HERE, "Rollback attempt failed.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::CanRollback(ErrorPtr* /* error */,
+ bool* out_can_rollback) {
+ bool can_rollback = system_state_->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()) {
+ // TODO(dgarrett): Give a more specific error code/reason.
+ LogAndSetError(error, FROM_HERE, "ResetStatus failed.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::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) {
+ if (!system_state_->update_attempter()->GetStatus(out_last_checked_time,
+ out_progress,
+ out_current_operation,
+ out_new_version,
+ out_new_size)) {
+ LogAndSetError(error, FROM_HERE, "GetStatus failed.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::RebootIfNeeded(ErrorPtr* error) {
+ if (!system_state_->update_attempter()->RebootIfNeeded()) {
+ // TODO(dgarrett): Give a more specific error code/reason.
+ LogAndSetError(error, FROM_HERE, "Reboot not needed, or attempt failed.");
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::SetChannel(ErrorPtr* error,
+ const string& in_target_channel,
+ bool in_is_powerwash_allowed) {
+ 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();
+ }
+ }
+
+ bool delegated = false;
+ if (device_policy &&
+ device_policy->GetReleaseChannelDelegated(&delegated) && !delegated) {
+ LogAndSetError(
+ error, FROM_HERE,
+ "Cannot set target channel explicitly when channel "
+ "policy/settings is not delegated");
+ return false;
+ }
+
+ LOG(INFO) << "Setting destination channel to: " << in_target_channel;
+ string error_message;
+ if (!system_state_->request_params()->SetTargetChannel(
+ in_target_channel, in_is_powerwash_allowed, &error_message)) {
+ LogAndSetError(error, FROM_HERE, error_message);
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::GetChannel(ErrorPtr* /* error */,
+ bool in_get_current_channel,
+ string* out_channel) {
+ OmahaRequestParams* rp = system_state_->request_params();
+ *out_channel = (in_get_current_channel ?
+ rp->current_channel() : rp->target_channel());
+ return true;
+}
+
+bool UpdateEngineService::SetP2PUpdatePermission(ErrorPtr* error,
+ bool in_enabled) {
+ PrefsInterface* prefs = system_state_->prefs();
+
+ if (!prefs->SetBoolean(kPrefsP2PEnabled, in_enabled)) {
+ LogAndSetError(
+ error, FROM_HERE,
+ StringPrintf("Error setting the update via p2p permission to %s.",
+ ToString(in_enabled).c_str()));
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::GetP2PUpdatePermission(ErrorPtr* error,
+ bool* out_enabled) {
+ PrefsInterface* prefs = system_state_->prefs();
+
+ bool p2p_pref = false; // Default if no setting is present.
+ if (prefs->Exists(kPrefsP2PEnabled) &&
+ !prefs->GetBoolean(kPrefsP2PEnabled, &p2p_pref)) {
+ LogAndSetError(error, FROM_HERE, "Error getting the P2PEnabled setting.");
+ return false;
+ }
+
+ *out_enabled = p2p_pref;
+ return true;
+}
+
+bool UpdateEngineService::SetUpdateOverCellularPermission(ErrorPtr* error,
+ bool in_allowed) {
+ 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();
+ }
+ }
+
+ // Check if this setting is allowed by the device policy.
+ if (device_policy &&
+ device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+ LogAndSetError(error, FROM_HERE,
+ "Ignoring the update over cellular setting since there's "
+ "a device policy enforcing this setting.");
+ return false;
+ }
+
+ // 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->SetBoolean(kPrefsUpdateOverCellularPermission, in_allowed)) {
+ LogAndSetError(error, FROM_HERE,
+ string("Error setting the update over cellular to ") +
+ (in_allowed ? "true" : "false"));
+ return false;
+ }
+ return true;
+}
+
+bool UpdateEngineService::GetUpdateOverCellularPermission(ErrorPtr* /* error */,
+ bool* out_allowed) {
+ ConnectionManagerInterface* cm = system_state_->connection_manager();
+
+ // The device_policy is loaded in a lazy way before an update check and is
+ // used to determine if an update is allowed over cellular. Load the device
+ // policy now from the libbrillo cache if it wasn't already loaded.
+ if (!system_state_->device_policy()) {
+ UpdateAttempter* update_attempter = system_state_->update_attempter();
+ if (update_attempter)
+ update_attempter->RefreshDevicePolicy();
+ }
+
+ // Return the current setting based on the same logic used while checking for
+ // updates. A log message could be printed as the result of this test.
+ LOG(INFO) << "Checking if updates over cellular networks are allowed:";
+ *out_allowed = cm->IsUpdateAllowedOver(
+ chromeos_update_engine::NetworkConnectionType::kCellular,
+ chromeos_update_engine::NetworkTethering::kUnknown);
+ return true;
+}
+
+bool UpdateEngineService::GetDurationSinceUpdate(ErrorPtr* error,
+ int64_t* out_usec_wallclock) {
+ base::Time time;
+ if (!system_state_->update_attempter()->GetBootTimeAtUpdate(&time)) {
+ LogAndSetError(error, FROM_HERE, "No pending update.");
+ return false;
+ }
+
+ ClockInterface* clock = system_state_->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();
+ return true;
+}
+
+bool UpdateEngineService::GetRollbackPartition(
+ ErrorPtr* /* error */,
+ string* out_rollback_partition_name) {
+ BootControlInterface::Slot rollback_slot =
+ system_state_->update_attempter()->GetRollbackSlot();
+
+ if (rollback_slot == BootControlInterface::kInvalidSlot) {
+ out_rollback_partition_name->clear();
+ return true;
+ }
+
+ string name;
+ if (!system_state_->boot_control()->GetPartitionDevice(
+ "KERNEL", rollback_slot, &name)) {
+ LOG(ERROR) << "Invalid rollback device";
+ return false;
+ }
+
+ LOG(INFO) << "Getting rollback partition name. Result: " << name;
+ *out_rollback_partition_name = name;
+ return true;
+}
+
+UpdateEngineAdaptor::UpdateEngineAdaptor(SystemState* system_state,
+ const scoped_refptr<dbus::Bus>& bus)
+ : org::chromium::UpdateEngineInterfaceAdaptor(&dbus_service_),
+ bus_(bus),
+ dbus_service_(system_state),
+ dbus_object_(nullptr,
+ bus,
+ dbus::ObjectPath(update_engine::kUpdateEngineServicePath)) {}
+
+void UpdateEngineAdaptor::RegisterAsync(
+ const base::Callback<void(bool)>& completion_callback) {
+ RegisterWithDBusObject(&dbus_object_);
+ dbus_object_.RegisterAsync(completion_callback);
+}
+
+bool UpdateEngineAdaptor::RequestOwnership() {
+ return bus_->RequestOwnershipAndBlock(update_engine::kUpdateEngineServiceName,
+ dbus::Bus::REQUIRE_PRIMARY);
+}
+
+} // namespace chromeos_update_engine
diff --git a/dbus_service.h b/dbus_service.h
new file mode 100644
index 0000000..88bd90a
--- /dev/null
+++ b/dbus_service.h
@@ -0,0 +1,160 @@
+//
+// 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_DBUS_SERVICE_H_
+#define UPDATE_ENGINE_DBUS_SERVICE_H_
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/memory/ref_counted.h>
+#include <brillo/errors/error.h>
+
+#include "update_engine/update_attempter.h"
+
+#include "dbus_bindings/org.chromium.UpdateEngineInterface.h"
+
+namespace chromeos_update_engine {
+
+class UpdateEngineService
+ : public org::chromium::UpdateEngineInterfaceInterface {
+ public:
+ explicit UpdateEngineService(SystemState* system_state);
+ virtual ~UpdateEngineService() = default;
+
+ // Implementation of org::chromium::UpdateEngineInterfaceInterface.
+ bool AttemptUpdate(brillo::ErrorPtr* error,
+ const std::string& in_app_version,
+ const std::string& in_omaha_url) override;
+
+ bool AttemptUpdateWithFlags(brillo::ErrorPtr* error,
+ const std::string& in_app_version,
+ const std::string& in_omaha_url,
+ int32_t in_flags_as_int) override;
+
+ bool AttemptRollback(brillo::ErrorPtr* error, bool in_powerwash) override;
+
+ // Checks if the system rollback is available by verifying if the secondary
+ // system partition is valid and bootable.
+ bool CanRollback(brillo::ErrorPtr* error, bool* out_can_rollback) override;
+
+ // Resets the status of the update_engine to idle, ignoring any applied
+ // 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;
+
+ // Reboots the device if an update is applied and a reboot is required.
+ bool RebootIfNeeded(brillo::ErrorPtr* error) override;
+
+ // Changes the current channel of the device to the target channel. If the
+ // target channel is a less stable channel than the current channel, then the
+ // channel change happens immediately (at the next update check). If the
+ // target channel is a more stable channel, then if is_powerwash_allowed is
+ // set to true, then also the change happens immediately but with a powerwash
+ // if required. Otherwise, the change takes effect eventually (when the
+ // version on the target channel goes above the version number of what the
+ // device currently has).
+ bool SetChannel(brillo::ErrorPtr* error,
+ const std::string& in_target_channel,
+ bool in_is_powerwash_allowed) override;
+
+ // If get_current_channel is set to true, populates |channel| with the name of
+ // the channel that the device is currently on. Otherwise, it populates it
+ // with the name of the channel the device is supposed to be (in case of a
+ // pending channel change).
+ bool GetChannel(brillo::ErrorPtr* error,
+ bool in_get_current_channel,
+ std::string* out_channel) override;
+
+ // Enables or disables the sharing and consuming updates over P2P feature
+ // according to the |enabled| argument passed.
+ bool SetP2PUpdatePermission(brillo::ErrorPtr* error,
+ bool in_enabled) override;
+
+ // Returns the current value for the P2P enabled setting. This involves both
+ // sharing and consuming updates over P2P.
+ bool GetP2PUpdatePermission(brillo::ErrorPtr* error,
+ bool* out_enabled) override;
+
+ // If there's no device policy installed, sets the update over cellular
+ // networks permission to the |allowed| value. Otherwise, this method returns
+ // with an error since this setting is overridden by the applied policy.
+ bool SetUpdateOverCellularPermission(brillo::ErrorPtr* error,
+ bool in_allowed) override;
+
+ // Returns the current value of the update over cellular network setting,
+ // either forced by the device policy if the device is enrolled or the current
+ // user preference otherwise.
+ bool GetUpdateOverCellularPermission(brillo::ErrorPtr* error,
+ bool* out_allowed) override;
+
+ // Returns the duration since the last successful update, as the
+ // duration on the wallclock. Returns an error if the device has not
+ // updated.
+ bool GetDurationSinceUpdate(brillo::ErrorPtr* error,
+ int64_t* out_usec_wallclock) override;
+
+ // Returns the version string of OS that was used before the last reboot
+ // into an updated version. This is available only when rebooting into an
+ // update from previous version, otherwise an empty string is returned.
+ bool GetPrevVersion(brillo::ErrorPtr* error,
+ std::string* out_prev_version) override;
+
+ // Returns the name of kernel partition that can be rolled back into.
+ bool GetRollbackPartition(brillo::ErrorPtr* error,
+ std::string* out_rollback_partition_name) override;
+
+ private:
+ SystemState* system_state_;
+};
+
+// The UpdateEngineAdaptor class runs the UpdateEngineInterface in the fixed
+// object path, without an ObjectManager notifying the interfaces, since it is
+// all static and clients don't expect it to be implemented.
+class UpdateEngineAdaptor : public org::chromium::UpdateEngineInterfaceAdaptor {
+ public:
+ UpdateEngineAdaptor(SystemState* system_state,
+ const scoped_refptr<dbus::Bus>& bus);
+ ~UpdateEngineAdaptor() = default;
+
+ // Register the DBus object with the update engine service asynchronously.
+ // Calls |copmletion_callback| when done passing a boolean indicating if the
+ // registration succeeded.
+ void RegisterAsync(const base::Callback<void(bool)>& completion_callback);
+
+ // Takes ownership of the well-known DBus name and returns whether it
+ // succeeded.
+ bool RequestOwnership();
+
+ private:
+ scoped_refptr<dbus::Bus> bus_;
+ UpdateEngineService dbus_service_;
+ brillo::dbus_utils::DBusObject dbus_object_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_DBUS_SERVICE_H_
diff --git a/dbus_service_unittest.cc b/dbus_service_unittest.cc
new file mode 100644
index 0000000..db63b2d
--- /dev/null
+++ b/dbus_service_unittest.cc
@@ -0,0 +1,143 @@
+//
+// 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/dbus_service.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+#include <brillo/errors/error.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_device_policy.h>
+#include <update_engine/dbus-constants.h>
+
+#include "update_engine/fake_system_state.h"
+
+using brillo::errors::dbus::kDomain;
+using std::string;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::_;
+using update_engine::kUpdateEngineServiceErrorFailed;
+
+namespace chromeos_update_engine {
+
+class UpdateEngineServiceTest : public ::testing::Test {
+ protected:
+ UpdateEngineServiceTest()
+ : mock_update_attempter_(fake_system_state_.mock_update_attempter()),
+ dbus_service_(&fake_system_state_) {}
+
+ void SetUp() override {
+ fake_system_state_.set_device_policy(nullptr);
+ }
+
+ // 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_;
+ UpdateEngineService dbus_service_;
+};
+
+TEST_F(UpdateEngineServiceTest, AttemptUpdate) {
+ // Simple test to ensure that the default is an interactive check.
+ EXPECT_CALL(*mock_update_attempter_,
+ CheckForUpdate("app_ver", "url", true /* interactive */));
+ EXPECT_TRUE(dbus_service_.AttemptUpdate(&error_, "app_ver", "url"));
+ EXPECT_EQ(nullptr, error_);
+}
+
+TEST_F(UpdateEngineServiceTest, AttemptUpdateWithFlags) {
+ EXPECT_CALL(*mock_update_attempter_, CheckForUpdate(
+ "app_ver", "url", false /* interactive */));
+ // The update is non-interactive when we pass the non-interactive flag.
+ EXPECT_TRUE(dbus_service_.AttemptUpdateWithFlags(
+ &error_, "app_ver", "url",
+ update_engine::kAttemptUpdateFlagNonInteractive));
+ EXPECT_EQ(nullptr, error_);
+}
+
+// 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(),
+ SetTargetChannel("stable-channel", true, _))
+ .WillOnce(Return(true));
+ EXPECT_TRUE(dbus_service_.SetChannel(&error_, "stable-channel", true));
+ ASSERT_EQ(nullptr, error_);
+}
+
+// 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);
+ EXPECT_CALL(mock_device_policy, GetReleaseChannelDelegated(_))
+ .WillOnce(DoAll(SetArgumentPointee<0>(true), Return(true)));
+ EXPECT_CALL(*fake_system_state_.mock_request_params(),
+ SetTargetChannel("beta-channel", true, _))
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(dbus_service_.SetChannel(&error_, "beta-channel", true));
+ ASSERT_EQ(nullptr, error_);
+}
+
+// When passing an invalid value (SetTargetChannel fails) an error should be
+// raised.
+TEST_F(UpdateEngineServiceTest, SetChannelWithInvalidChannel) {
+ EXPECT_CALL(*mock_update_attempter_, RefreshDevicePolicy());
+ EXPECT_CALL(*fake_system_state_.mock_request_params(),
+ SetTargetChannel("foo-channel", true, _)).WillOnce(Return(false));
+
+ EXPECT_FALSE(dbus_service_.SetChannel(&error_, "foo-channel", true));
+ ASSERT_NE(nullptr, error_);
+ EXPECT_TRUE(error_->HasError(kDomain, kUpdateEngineServiceErrorFailed));
+}
+
+TEST_F(UpdateEngineServiceTest, GetChannel) {
+ fake_system_state_.mock_request_params()->set_current_channel("current");
+ fake_system_state_.mock_request_params()->set_target_channel("target");
+ string channel;
+ EXPECT_TRUE(dbus_service_.GetChannel(
+ &error_, true /* get_current_channel */, &channel));
+ EXPECT_EQ(nullptr, error_);
+ EXPECT_EQ("current", channel);
+
+ EXPECT_TRUE(dbus_service_.GetChannel(
+ &error_, false /* get_current_channel */, &channel));
+ EXPECT_EQ(nullptr, error_);
+ EXPECT_EQ("target", channel);
+}
+
+TEST_F(UpdateEngineServiceTest, ResetStatusSucceeds) {
+ EXPECT_CALL(*mock_update_attempter_, ResetStatus()).WillOnce(Return(true));
+ EXPECT_TRUE(dbus_service_.ResetStatus(&error_));
+ EXPECT_EQ(nullptr, error_);
+}
+
+TEST_F(UpdateEngineServiceTest, ResetStatusFails) {
+ EXPECT_CALL(*mock_update_attempter_, ResetStatus()).WillOnce(Return(false));
+ EXPECT_FALSE(dbus_service_.ResetStatus(&error_));
+ ASSERT_NE(nullptr, error_);
+ EXPECT_TRUE(error_->HasError(kDomain, kUpdateEngineServiceErrorFailed));
+}
+
+} // namespace chromeos_update_engine
diff --git a/dbus_test_utils.h b/dbus_test_utils.h
new file mode 100644
index 0000000..b3748ce
--- /dev/null
+++ b/dbus_test_utils.h
@@ -0,0 +1,89 @@
+//
+// 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_DBUS_TEST_UTILS_H_
+#define UPDATE_ENGINE_DBUS_TEST_UTILS_H_
+
+#include <set>
+#include <string>
+
+#include <base/bind.h>
+#include <brillo/message_loops/message_loop.h>
+#include <gmock/gmock.h>
+
+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))); \
+ } while (false)
+
+template <typename T>
+class MockSignalHandler {
+ public:
+ MockSignalHandler() = default;
+ ~MockSignalHandler() {
+ if (callback_connected_task_ != brillo::MessageLoop::kTaskIdNull)
+ brillo::MessageLoop::current()->CancelTask(callback_connected_task_);
+ }
+
+ // Returns whether the signal handler is registered.
+ bool IsHandlerRegistered() const { return signal_callback_ != nullptr; }
+
+ const base::Callback<T>& signal_callback() { return *signal_callback_.get(); }
+
+ void GrabCallbacks(
+ const base::Callback<T>& signal_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));
+ // Notify from the main loop that the callback was connected.
+ callback_connected_task_ = brillo::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockSignalHandler<T>::OnCallbackConnected,
+ base::Unretained(this)));
+ }
+
+ private:
+ void OnCallbackConnected() {
+ callback_connected_task_ = brillo::MessageLoop::kTaskIdNull;
+ on_connected_callback_->Run("", "", true);
+ }
+
+ brillo::MessageLoop::TaskId callback_connected_task_{
+ brillo::MessageLoop::kTaskIdNull};
+
+ std::unique_ptr<base::Callback<T>> signal_callback_;
+ std::unique_ptr<dbus::ObjectProxy::OnConnectedCallback>
+ on_connected_callback_;
+};
+
+// Defines the action that will call MockSignalHandler<T>::GrabCallbacks for the
+// right type.
+ACTION_P(GrabCallbacks, mock_signal_handler) {
+ mock_signal_handler->GrabCallbacks(arg0, arg1);
+}
+
+} // namespace dbus_test_utils
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_DBUS_TEST_UTILS_H_
diff --git a/fake_file_writer.h b/fake_file_writer.h
new file mode 100644
index 0000000..43b71c7
--- /dev/null
+++ b/fake_file_writer.h
@@ -0,0 +1,76 @@
+//
+// 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_FAKE_FILE_WRITER_H_
+#define UPDATE_ENGINE_FAKE_FILE_WRITER_H_
+
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/file_writer.h"
+
+// FakeFileWriter is an implementation of FileWriter. It will succeed
+// calls to Open(), Close(), but not do any work. All calls to Write()
+// will append the passed data to an internal vector.
+
+namespace chromeos_update_engine {
+
+class FakeFileWriter : public FileWriter {
+ public:
+ FakeFileWriter() : was_opened_(false), was_closed_(false) {}
+
+ virtual int Open(const char* path, int flags, mode_t mode) {
+ CHECK(!was_opened_);
+ CHECK(!was_closed_);
+ was_opened_ = true;
+ return 0;
+ }
+
+ virtual ssize_t Write(const void* bytes, size_t count) {
+ CHECK(was_opened_);
+ CHECK(!was_closed_);
+ const char* char_bytes = reinterpret_cast<const char*>(bytes);
+ bytes_.insert(bytes_.end(), char_bytes, char_bytes + count);
+ return count;
+ }
+
+ virtual int Close() {
+ CHECK(was_opened_);
+ CHECK(!was_closed_);
+ was_closed_ = true;
+ return 0;
+ }
+
+ const brillo::Blob& bytes() {
+ return bytes_;
+ }
+
+ private:
+ // The internal store of all bytes that have been written
+ brillo::Blob bytes_;
+
+ // These are just to ensure FileWriter methods are called properly.
+ bool was_opened_;
+ bool was_closed_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeFileWriter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_FILE_WRITER_H_
diff --git a/fake_p2p_manager.h b/fake_p2p_manager.h
new file mode 100644
index 0000000..a8cf4ea
--- /dev/null
+++ b/fake_p2p_manager.h
@@ -0,0 +1,130 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_FAKE_P2P_MANAGER_H_
+#define UPDATE_ENGINE_FAKE_P2P_MANAGER_H_
+
+#include <string>
+
+#include "update_engine/p2p_manager.h"
+
+namespace chromeos_update_engine {
+
+// A fake implementation of P2PManager.
+class FakeP2PManager : public P2PManager {
+ public:
+ FakeP2PManager() :
+ is_p2p_enabled_(false),
+ ensure_p2p_running_result_(false),
+ ensure_p2p_not_running_result_(false),
+ perform_housekeeping_result_(false),
+ count_shared_files_result_(0) {}
+
+ // P2PManager overrides.
+ void SetDevicePolicy(const policy::DevicePolicy* device_policy) override {}
+
+ bool IsP2PEnabled() override {
+ return is_p2p_enabled_;
+ }
+
+ bool EnsureP2PRunning() override {
+ return ensure_p2p_running_result_;
+ }
+
+ bool EnsureP2PNotRunning() override {
+ return ensure_p2p_not_running_result_;
+ }
+
+ bool PerformHousekeeping() override {
+ return perform_housekeeping_result_;
+ }
+
+ void LookupUrlForFile(const std::string& file_id,
+ size_t minimum_size,
+ base::TimeDelta max_time_to_wait,
+ LookupCallback callback) override {
+ callback.Run(lookup_url_for_file_result_);
+ }
+
+ bool FileShare(const std::string& file_id,
+ size_t expected_size) override {
+ return false;
+ }
+
+ base::FilePath FileGetPath(const std::string& file_id) override {
+ return base::FilePath();
+ }
+
+ ssize_t FileGetSize(const std::string& file_id) override {
+ return -1;
+ }
+
+ ssize_t FileGetExpectedSize(const std::string& file_id) override {
+ return -1;
+ }
+
+ bool FileGetVisible(const std::string& file_id,
+ bool *out_result) override {
+ return false;
+ }
+
+ bool FileMakeVisible(const std::string& file_id) override {
+ return false;
+ }
+
+ int CountSharedFiles() override {
+ return count_shared_files_result_;
+ }
+
+ // Methods for controlling what the fake returns and how it acts.
+ void SetP2PEnabled(bool is_p2p_enabled) {
+ is_p2p_enabled_ = is_p2p_enabled;
+ }
+
+ void SetEnsureP2PRunningResult(bool ensure_p2p_running_result) {
+ ensure_p2p_running_result_ = ensure_p2p_running_result;
+ }
+
+ void SetEnsureP2PNotRunningResult(bool ensure_p2p_not_running_result) {
+ ensure_p2p_not_running_result_ = ensure_p2p_not_running_result;
+ }
+
+ void SetPerformHousekeepingResult(bool perform_housekeeping_result) {
+ perform_housekeeping_result_ = perform_housekeeping_result;
+ }
+
+ void SetCountSharedFilesResult(int count_shared_files_result) {
+ count_shared_files_result_ = count_shared_files_result;
+ }
+
+ void SetLookupUrlForFileResult(const std::string& url) {
+ lookup_url_for_file_result_ = url;
+ }
+
+ private:
+ bool is_p2p_enabled_;
+ bool ensure_p2p_running_result_;
+ bool ensure_p2p_not_running_result_;
+ bool perform_housekeeping_result_;
+ int count_shared_files_result_;
+ std::string lookup_url_for_file_result_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeP2PManager);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_P2P_MANAGER_H_
diff --git a/fake_p2p_manager_configuration.h b/fake_p2p_manager_configuration.h
new file mode 100644
index 0000000..3d3afe4
--- /dev/null
+++ b/fake_p2p_manager_configuration.h
@@ -0,0 +1,115 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_
+#define UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/p2p_manager.h"
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+
+namespace chromeos_update_engine {
+
+// Configuration for P2PManager for use in unit tests. Instead of
+// /var/cache/p2p, a temporary directory is used.
+class FakeP2PManagerConfiguration : public P2PManager::Configuration {
+ public:
+ FakeP2PManagerConfiguration() {
+ EXPECT_TRUE(utils::MakeTempDirectory("p2p-tc.XXXXXX", &p2p_dir_));
+ }
+
+ ~FakeP2PManagerConfiguration() {
+ if (p2p_dir_.size() > 0 &&
+ !base::DeleteFile(base::FilePath(p2p_dir_), true)) {
+ PLOG(ERROR) << "Unable to unlink files and directory in " << p2p_dir_;
+ }
+ }
+
+ // P2PManager::Configuration override
+ base::FilePath GetP2PDir() override {
+ return base::FilePath(p2p_dir_);
+ }
+
+ // P2PManager::Configuration override
+ std::vector<std::string> GetInitctlArgs(bool is_start) override {
+ return is_start ? initctl_start_args_ : initctl_stop_args_;
+ }
+
+ // P2PManager::Configuration override
+ std::vector<std::string> GetP2PClientArgs(const std::string &file_id,
+ size_t minimum_size) override {
+ std::vector<std::string> formatted_command = p2p_client_cmd_format_;
+ // Replace {variable} on the passed string.
+ std::string str_minimum_size = std::to_string(minimum_size);
+ for (std::string& arg : formatted_command) {
+ ReplaceSubstringsAfterOffset(&arg, 0, "{file_id}", file_id);
+ ReplaceSubstringsAfterOffset(&arg, 0, "{minsize}", str_minimum_size);
+ }
+ return formatted_command;
+ }
+
+ // Use |command_line| instead of "initctl start p2p" when attempting
+ // to start the p2p service.
+ void SetInitctlStartCommand(const std::vector<std::string>& command) {
+ initctl_start_args_ = command;
+ }
+
+ // Use |command_line| instead of "initctl stop p2p" when attempting
+ // to stop the p2p service.
+ void SetInitctlStopCommand(const std::vector<std::string>& command) {
+ initctl_stop_args_ = command;
+ }
+
+ // Use |command_format| instead of "p2p-client --get-url={file_id}
+ // --minimum-size={minsize}" when attempting to look up a file using
+ // p2p-client(1).
+ //
+ // The passed |command_format| argument can have "{file_id}" and "{minsize}"
+ // as substrings of any of its elements, that will be replaced by the
+ // corresponding values passed to GetP2PClientArgs().
+ void SetP2PClientCommand(const std::vector<std::string>& command_format) {
+ p2p_client_cmd_format_ = command_format;
+ }
+
+ private:
+ // The temporary directory used for p2p.
+ std::string p2p_dir_;
+
+ // Argument vector for starting p2p.
+ std::vector<std::string> initctl_start_args_{"initctl", "start", "p2p"};
+
+ // Argument vector for stopping p2p.
+ std::vector<std::string> initctl_stop_args_{"initctl", "stop", "p2p"};
+
+ // A string for generating the p2p-client command. See the
+ // SetP2PClientCommandLine() for details.
+ std::vector<std::string> p2p_client_cmd_format_{
+ "p2p-client", "--get-url={file_id}", "--minimum-size={minsize}"};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeP2PManagerConfiguration);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_P2P_MANAGER_CONFIGURATION_H_
diff --git a/fake_shill_proxy.cc b/fake_shill_proxy.cc
new file mode 100644
index 0000000..17698cd
--- /dev/null
+++ b/fake_shill_proxy.cc
@@ -0,0 +1,47 @@
+//
+// 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/fake_shill_proxy.h"
+
+using org::chromium::flimflam::ManagerProxyMock;
+using org::chromium::flimflam::ServiceProxyInterface;
+
+namespace chromeos_update_engine {
+
+FakeShillProxy::FakeShillProxy()
+ : manager_proxy_mock_(new ManagerProxyMock()) {}
+
+ManagerProxyMock* FakeShillProxy::GetManagerProxy() {
+ return manager_proxy_mock_.get();
+}
+
+std::unique_ptr<ServiceProxyInterface> FakeShillProxy::GetServiceForPath(
+ const dbus::ObjectPath& path) {
+ auto it = service_proxy_mocks_.find(path.value());
+ CHECK(it != service_proxy_mocks_.end()) << "No ServiceProxyMock set for "
+ << path.value();
+ std::unique_ptr<ServiceProxyInterface> result = std::move(it->second);
+ service_proxy_mocks_.erase(it);
+ return result;
+}
+
+void FakeShillProxy::SetServiceForPath(
+ const dbus::ObjectPath& path,
+ std::unique_ptr<ServiceProxyInterface> service_proxy) {
+ service_proxy_mocks_[path.value()] = std::move(service_proxy);
+}
+
+} // namespace chromeos_update_engine
diff --git a/fake_shill_proxy.h b/fake_shill_proxy.h
new file mode 100644
index 0000000..ae17eaa
--- /dev/null
+++ b/fake_shill_proxy.h
@@ -0,0 +1,66 @@
+//
+// 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_FAKE_SHILL_PROXY_H_
+#define UPDATE_ENGINE_FAKE_SHILL_PROXY_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <shill/dbus-proxies.h>
+#include <shill/dbus-proxy-mocks.h>
+
+#include "update_engine/shill_proxy_interface.h"
+
+namespace chromeos_update_engine {
+
+// This class implements the connection to shill using real DBus calls.
+class FakeShillProxy : public ShillProxyInterface {
+ public:
+ FakeShillProxy();
+ ~FakeShillProxy() override = default;
+
+ // ShillProxyInterface overrides.
+
+ // GetManagerProxy returns the subclass ManagerProxyMock so tests can easily
+ // use it. Mocks for the return value of GetServiceForPath() can be provided
+ // with SetServiceForPath().
+ org::chromium::flimflam::ManagerProxyMock* GetManagerProxy() override;
+ std::unique_ptr<org::chromium::flimflam::ServiceProxyInterface>
+ GetServiceForPath(const dbus::ObjectPath& path) override;
+
+ // Sets the service_proxy that will be returned by GetServiceForPath().
+ void SetServiceForPath(
+ const dbus::ObjectPath& path,
+ std::unique_ptr<org::chromium::flimflam::ServiceProxyInterface>
+ service_proxy);
+
+ private:
+ std::unique_ptr<org::chromium::flimflam::ManagerProxyMock>
+ manager_proxy_mock_;
+
+ std::map<std::string,
+ std::unique_ptr<org::chromium::flimflam::ServiceProxyInterface>>
+ service_proxy_mocks_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeShillProxy);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_SHILL_PROXY_H_
diff --git a/fake_system_state.cc b/fake_system_state.cc
new file mode 100644
index 0000000..49ba058
--- /dev/null
+++ b/fake_system_state.cc
@@ -0,0 +1,43 @@
+//
+// 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/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, nullptr, nullptr),
+ mock_request_params_(this),
+ fake_update_manager_(&fake_clock_),
+ clock_(&fake_clock_),
+ connection_manager_(&mock_connection_manager_),
+ hardware_(&fake_hardware_),
+ metrics_lib_(&mock_metrics_lib_),
+ prefs_(&mock_prefs_),
+ powerwash_safe_prefs_(&mock_powerwash_safe_prefs_),
+ payload_state_(&mock_payload_state_),
+ update_attempter_(&mock_update_attempter_),
+ request_params_(&mock_request_params_),
+ p2p_manager_(&mock_p2p_manager_),
+ update_manager_(&fake_update_manager_),
+ device_policy_(nullptr),
+ fake_system_rebooted_(false) {
+ mock_payload_state_.Initialize(this);
+}
+
+} // namespace chromeos_update_engine
diff --git a/fake_system_state.h b/fake_system_state.h
new file mode 100644
index 0000000..75ef315
--- /dev/null
+++ b/fake_system_state.h
@@ -0,0 +1,274 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_
+
+#include <base/logging.h>
+#include <gmock/gmock.h>
+#include <policy/mock_device_policy.h>
+#include <power_manager/dbus-proxies.h>
+#include <power_manager/dbus-proxy-mocks.h>
+
+#include "metrics/metrics_library_mock.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/mock_prefs.h"
+#include "update_engine/mock_connection_manager.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_update_attempter.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/fake_update_manager.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.
+class FakeSystemState : public SystemState {
+ public:
+ FakeSystemState();
+
+ // Base class overrides. All getters return the current implementation of
+ // various members, either the default (fake/mock) or the one set to override
+ // it by client code.
+
+ BootControlInterface* boot_control() override { return boot_control_; }
+
+ inline ClockInterface* clock() override { return clock_; }
+
+ inline void set_device_policy(
+ const policy::DevicePolicy* device_policy) override {
+ device_policy_ = device_policy;
+ }
+
+ inline const policy::DevicePolicy* device_policy() override {
+ return device_policy_;
+ }
+
+ inline ConnectionManagerInterface* connection_manager() override {
+ return connection_manager_;
+ }
+
+ inline HardwareInterface* hardware() override { return hardware_; }
+
+ inline MetricsLibraryInterface* metrics_lib() override {
+ return metrics_lib_;
+ }
+
+ inline PrefsInterface* prefs() override { return prefs_; }
+
+ inline PrefsInterface* powerwash_safe_prefs() override {
+ return powerwash_safe_prefs_;
+ }
+
+ inline PayloadStateInterface* payload_state() override {
+ return payload_state_;
+ }
+
+ inline UpdateAttempter* update_attempter() override {
+ return update_attempter_;
+ }
+
+ inline OmahaRequestParams* request_params() override {
+ return request_params_;
+ }
+
+ inline P2PManager* p2p_manager() override { return p2p_manager_; }
+
+ inline chromeos_update_manager::UpdateManager* update_manager() override {
+ return update_manager_;
+ }
+
+ inline org::chromium::PowerManagerProxyInterface* power_manager_proxy()
+ override {
+ return power_manager_proxy_;
+ }
+
+ inline bool system_rebooted() override { return fake_system_rebooted_; }
+
+ // Setters for the various members, can be used for overriding the default
+ // implementations. For convenience, setting to a null pointer will restore
+ // the default implementation.
+
+ void set_boot_control(BootControlInterface* boot_control) {
+ boot_control_ = boot_control ? boot_control : &fake_boot_control_;
+ }
+
+ inline void set_clock(ClockInterface* clock) {
+ clock_ = clock ? clock : &fake_clock_;
+ }
+
+ inline void set_connection_manager(
+ ConnectionManagerInterface* connection_manager) {
+ connection_manager_ = (connection_manager ? connection_manager :
+ &mock_connection_manager_);
+ }
+
+ inline void set_hardware(HardwareInterface* hardware) {
+ hardware_ = hardware ? hardware : &fake_hardware_;
+ }
+
+ inline void set_metrics_lib(MetricsLibraryInterface* metrics_lib) {
+ metrics_lib_ = metrics_lib ? metrics_lib : &mock_metrics_lib_;
+ }
+
+ inline void set_prefs(PrefsInterface* prefs) {
+ prefs_ = prefs ? prefs : &mock_prefs_;
+ }
+
+ inline void set_powerwash_safe_prefs(PrefsInterface* powerwash_safe_prefs) {
+ powerwash_safe_prefs_ = (powerwash_safe_prefs ? powerwash_safe_prefs :
+ &mock_powerwash_safe_prefs_);
+ }
+
+ inline void set_payload_state(PayloadStateInterface *payload_state) {
+ payload_state_ = payload_state ? payload_state : &mock_payload_state_;
+ }
+
+ inline void set_update_attempter(UpdateAttempter* update_attempter) {
+ update_attempter_ = (update_attempter ? update_attempter :
+ &mock_update_attempter_);
+ }
+
+ inline void set_request_params(OmahaRequestParams* request_params) {
+ request_params_ = (request_params ? request_params :
+ &mock_request_params_);
+ }
+
+ inline void set_p2p_manager(P2PManager *p2p_manager) {
+ p2p_manager_ = p2p_manager ? p2p_manager : &mock_p2p_manager_;
+ }
+
+ inline void set_update_manager(
+ chromeos_update_manager::UpdateManager *update_manager) {
+ update_manager_ = update_manager ? update_manager : &fake_update_manager_;
+ }
+
+ inline void set_system_rebooted(bool system_rebooted) {
+ fake_system_rebooted_ = system_rebooted;
+ }
+
+ // Getters for the built-in default implementations. These return the actual
+ // concrete type of each implementation. For additional safety, they will fail
+ // whenever the requested default was overridden by a different
+ // implementation.
+
+ inline FakeBootControl* fake_boot_control() {
+ CHECK(boot_control_ == &fake_boot_control_);
+ return &fake_boot_control_;
+ }
+
+ inline FakeClock* fake_clock() {
+ CHECK(clock_ == &fake_clock_);
+ return &fake_clock_;
+ }
+
+ inline testing::NiceMock<MockConnectionManager>* mock_connection_manager() {
+ CHECK(connection_manager_ == &mock_connection_manager_);
+ return &mock_connection_manager_;
+ }
+
+ inline FakeHardware* fake_hardware() {
+ CHECK(hardware_ == &fake_hardware_);
+ return &fake_hardware_;
+ }
+
+ inline testing::NiceMock<MetricsLibraryMock>* mock_metrics_lib() {
+ CHECK(metrics_lib_ == &mock_metrics_lib_);
+ return &mock_metrics_lib_;
+ }
+
+ inline testing::NiceMock<MockPrefs> *mock_prefs() {
+ CHECK(prefs_ == &mock_prefs_);
+ return &mock_prefs_;
+ }
+
+ inline testing::NiceMock<MockPrefs> *mock_powerwash_safe_prefs() {
+ CHECK(powerwash_safe_prefs_ == &mock_powerwash_safe_prefs_);
+ return &mock_powerwash_safe_prefs_;
+ }
+
+ inline testing::NiceMock<MockPayloadState>* mock_payload_state() {
+ CHECK(payload_state_ == &mock_payload_state_);
+ return &mock_payload_state_;
+ }
+
+ inline testing::NiceMock<MockUpdateAttempter>* mock_update_attempter() {
+ CHECK(update_attempter_ == &mock_update_attempter_);
+ return &mock_update_attempter_;
+ }
+
+ inline testing::NiceMock<MockOmahaRequestParams>* mock_request_params() {
+ CHECK(request_params_ == &mock_request_params_);
+ return &mock_request_params_;
+ }
+
+ inline testing::NiceMock<MockP2PManager>* mock_p2p_manager() {
+ CHECK(p2p_manager_ == &mock_p2p_manager_);
+ return &mock_p2p_manager_;
+ }
+
+ inline chromeos_update_manager::FakeUpdateManager* fake_update_manager() {
+ CHECK(update_manager_ == &fake_update_manager_);
+ return &fake_update_manager_;
+ }
+
+ private:
+ // Default mock/fake implementations (owned).
+ FakeBootControl fake_boot_control_;
+ FakeClock fake_clock_;
+ testing::NiceMock<MockConnectionManager> mock_connection_manager_;
+ FakeHardware fake_hardware_;
+ testing::NiceMock<MetricsLibraryMock> mock_metrics_lib_;
+ testing::NiceMock<MockPrefs> mock_prefs_;
+ testing::NiceMock<MockPrefs> mock_powerwash_safe_prefs_;
+ testing::NiceMock<MockPayloadState> mock_payload_state_;
+ 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_;
+ org::chromium::PowerManagerProxyMock mock_power_manager_;
+
+ // Pointers to objects that client code can override. They are initialized to
+ // the default implementations above.
+ BootControlInterface* boot_control_{&fake_boot_control_};
+ ClockInterface* clock_;
+ ConnectionManagerInterface* connection_manager_;
+ HardwareInterface* hardware_;
+ MetricsLibraryInterface* metrics_lib_;
+ PrefsInterface* prefs_;
+ PrefsInterface* powerwash_safe_prefs_;
+ PayloadStateInterface* payload_state_;
+ UpdateAttempter* update_attempter_;
+ OmahaRequestParams* request_params_;
+ P2PManager* p2p_manager_;
+ chromeos_update_manager::UpdateManager* update_manager_;
+ org::chromium::PowerManagerProxyInterface* power_manager_proxy_{
+ &mock_power_manager_};
+
+ // Other object pointers (not preinitialized).
+ const policy::DevicePolicy* device_policy_;
+
+ // Other data members.
+ bool fake_system_rebooted_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_FAKE_SYSTEM_STATE_H_
diff --git a/generate_pc_file.sh b/generate_pc_file.sh
new file mode 100755
index 0000000..ab101f4
--- /dev/null
+++ b/generate_pc_file.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+#
+# 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.
+#
+
+set -e
+
+OUT=$1
+shift
+PC_IN=$1
+shift
+INCLUDE_DIR=$1
+shift
+
+sed \
+ -e "s|@INCLUDE_DIR@|${INCLUDE_DIR}|g" \
+ "${PC_IN}.pc.in" > "${OUT}/${PC_IN}.pc"
diff --git a/hardware_android.cc b/hardware_android.cc
new file mode 100644
index 0000000..a20fe6f
--- /dev/null
+++ b/hardware_android.cc
@@ -0,0 +1,113 @@
+//
+// 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/hardware_android.h"
+
+#include <base/files/file_util.h>
+#include <brillo/make_unique_ptr.h>
+#include <cutils/properties.h>
+
+#include "update_engine/common/hardware.h"
+#include "update_engine/common/platform_constants.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace hardware {
+
+// Factory defined in hardware.h.
+std::unique_ptr<HardwareInterface> CreateHardware() {
+ return brillo::make_unique_ptr(new HardwareAndroid());
+}
+
+} // namespace hardware
+
+// In Android there are normally three kinds of builds: eng, userdebug and user.
+// These builds target respectively a developer build, a debuggable version of
+// the final product and the pristine final product the end user will run.
+// Apart from the ro.build.type property name, they differ in the following
+// properties that characterize the builds:
+// * eng builds: ro.secure=0 and ro.debuggable=1
+// * userdebug builds: ro.secure=1 and ro.debuggable=1
+// * user builds: ro.secure=1 and ro.debuggable=0
+//
+// See IsOfficialBuild() and IsNormalMode() for the meaning of these options in
+// Android.
+
+bool HardwareAndroid::IsOfficialBuild() const {
+ // We run an official build iff ro.secure == 1, because we expect the build to
+ // behave like the end user product and check for updates. Note that while
+ // developers are able to build "official builds" by just running "make user",
+ // that will only result in a more restrictive environment. The important part
+ // is that we don't produce and push "non-official" builds to the end user.
+ //
+ // In case of a non-bool value, we take the most restrictive option and
+ // assume we are in an official-build.
+ return property_get_bool("ro.secure", 1) != 0;
+}
+
+bool HardwareAndroid::IsNormalBootMode() const {
+ // We are running in "dev-mode" iff ro.debuggable == 1. In dev-mode the
+ // update_engine will allow extra developers options, such as providing a
+ // different update URL. In case of error, we assume the build is in
+ // normal-mode.
+ return property_get_bool("ro.debuggable", 0) != 1;
+}
+
+bool HardwareAndroid::IsOOBEComplete(base::Time* out_time_of_oobe) const {
+ LOG(WARNING) << "STUB: Assuming OOBE is complete.";
+ if (out_time_of_oobe)
+ *out_time_of_oobe = base::Time();
+ return true;
+}
+
+string HardwareAndroid::GetHardwareClass() const {
+ LOG(WARNING) << "STUB: GetHardwareClass().";
+ return "ANDROID";
+}
+
+string HardwareAndroid::GetFirmwareVersion() const {
+ LOG(WARNING) << "STUB: GetFirmwareVersion().";
+ return "0";
+}
+
+string HardwareAndroid::GetECVersion() const {
+ LOG(WARNING) << "STUB: GetECVersion().";
+ return "0";
+}
+
+int HardwareAndroid::GetPowerwashCount() const {
+ LOG(WARNING) << "STUB: Assuming no factory reset was performed.";
+ return 0;
+}
+
+bool HardwareAndroid::GetNonVolatileDirectory(base::FilePath* path) const {
+ base::FilePath local_path(constants::kNonVolatileDirectory);
+ if (!base::PathExists(local_path)) {
+ LOG(ERROR) << "Non-volatile directory not found: " << local_path.value();
+ return false;
+ }
+ *path = local_path;
+ return true;
+}
+
+bool HardwareAndroid::GetPowerwashSafeDirectory(base::FilePath* path) const {
+ // On Android, we don't have a directory persisted across powerwash.
+ return false;
+}
+
+} // namespace chromeos_update_engine
diff --git a/hardware_android.h b/hardware_android.h
new file mode 100644
index 0000000..1b03661
--- /dev/null
+++ b/hardware_android.h
@@ -0,0 +1,53 @@
+//
+// 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_HARDWARE_ANDROID_H_
+#define UPDATE_ENGINE_HARDWARE_ANDROID_H_
+
+#include <string>
+
+#include <base/macros.h>
+#include <base/time/time.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 {
+ public:
+ HardwareAndroid() = default;
+ ~HardwareAndroid() override = default;
+
+ // HardwareInterface methods.
+ bool IsOfficialBuild() const override;
+ bool IsNormalBootMode() 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;
+ int GetPowerwashCount() const override;
+ bool GetNonVolatileDirectory(base::FilePath* path) const override;
+ bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HardwareAndroid);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_HARDWARE_ANDROID_H_
diff --git a/hardware_chromeos.cc b/hardware_chromeos.cc
new file mode 100644
index 0000000..ccb3978
--- /dev/null
+++ b/hardware_chromeos.cc
@@ -0,0 +1,153 @@
+//
+// 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.
+//
+
+#include "update_engine/hardware_chromeos.h"
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <brillo/make_unique_ptr.h>
+#include <vboot/crossystem.h>
+
+extern "C" {
+#include "vboot/vboot_host.h"
+}
+
+#include "update_engine/common/hardware.h"
+#include "update_engine/common/hwid_override.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+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.
+const char kPowerwashSafeDirectory[] =
+ "/mnt/stateful_partition/unencrypted/preserve";
+
+// The powerwash_count marker file contains the number of times the device was
+// powerwashed. This value is incremented by the clobber-state script when
+// a powerwash is performed.
+const char kPowerwashCountMarker[] = "powerwash_count";
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace hardware {
+
+// Factory defined in hardware.h.
+std::unique_ptr<HardwareInterface> CreateHardware() {
+ return brillo::make_unique_ptr(new HardwareChromeOS());
+}
+
+} // namespace hardware
+
+bool HardwareChromeOS::IsOfficialBuild() const {
+ return VbGetSystemPropertyInt("debug_build") == 0;
+}
+
+bool HardwareChromeOS::IsNormalBootMode() const {
+ bool dev_mode = VbGetSystemPropertyInt("devsw_boot") != 0;
+ return !dev_mode;
+}
+
+bool HardwareChromeOS::IsOOBEComplete(base::Time* out_time_of_oobe) const {
+ struct stat statbuf;
+ if (stat(kOOBECompletedMarker, &statbuf) != 0) {
+ if (errno != ENOENT) {
+ PLOG(ERROR) << "Error getting information about "
+ << kOOBECompletedMarker;
+ }
+ return false;
+ }
+
+ if (out_time_of_oobe != nullptr)
+ *out_time_of_oobe = base::Time::FromTimeT(statbuf.st_mtime);
+ return true;
+}
+
+static string ReadValueFromCrosSystem(const string& key) {
+ char value_buffer[VB_MAX_STRING_PROPERTY];
+
+ const char* rv = VbGetSystemPropertyString(key.c_str(), value_buffer,
+ sizeof(value_buffer));
+ if (rv != nullptr) {
+ string return_value(value_buffer);
+ base::TrimWhitespaceASCII(return_value, base::TRIM_ALL, &return_value);
+ return return_value;
+ }
+
+ LOG(ERROR) << "Unable to read crossystem key " << key;
+ return "";
+}
+
+string HardwareChromeOS::GetHardwareClass() const {
+ if (USE_HWID_OVERRIDE) {
+ return HwidOverride::Read(base::FilePath("/"));
+ }
+ 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);
+}
+
+int HardwareChromeOS::GetPowerwashCount() const {
+ int powerwash_count;
+ base::FilePath marker_path = base::FilePath(kPowerwashSafeDirectory).Append(
+ kPowerwashCountMarker);
+ string contents;
+ if (!utils::ReadFile(marker_path.value(), &contents))
+ return -1;
+ base::TrimWhitespaceASCII(contents, base::TRIM_TRAILING, &contents);
+ if (!base::StringToInt(contents, &powerwash_count))
+ return -1;
+ return powerwash_count;
+}
+
+bool HardwareChromeOS::GetNonVolatileDirectory(base::FilePath* path) const {
+ *path = base::FilePath(constants::kNonVolatileDirectory);
+ return true;
+}
+
+bool HardwareChromeOS::GetPowerwashSafeDirectory(base::FilePath* path) const {
+ *path = base::FilePath(kPowerwashSafeDirectory);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/hardware_chromeos.h b/hardware_chromeos.h
new file mode 100644
index 0000000..80888ab
--- /dev/null
+++ b/hardware_chromeos.h
@@ -0,0 +1,54 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_HARDWARE_CHROMEOS_H_
+#define UPDATE_ENGINE_HARDWARE_CHROMEOS_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/hardware_interface.h"
+
+namespace chromeos_update_engine {
+
+// Implements the real interface with Chrome OS verified boot and recovery
+// process.
+class HardwareChromeOS final : public HardwareInterface {
+ public:
+ HardwareChromeOS() = default;
+ ~HardwareChromeOS() override = default;
+
+ // HardwareInterface methods.
+ bool IsOfficialBuild() const override;
+ bool IsNormalBootMode() 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;
+ int GetPowerwashCount() const override;
+ bool GetNonVolatileDirectory(base::FilePath* path) const override;
+ bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HardwareChromeOS);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_HARDWARE_CHROMEOS_H_
diff --git a/image_properties.h b/image_properties.h
new file mode 100644
index 0000000..6026c2e
--- /dev/null
+++ b/image_properties.h
@@ -0,0 +1,84 @@
+//
+// 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.
+//
+
+// This module abstracts the properties tied to the current running image. These
+// 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_
+
+#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 product version of this image.
+ std::string version;
+
+ // The board name this image was built for.
+ std::string board;
+
+ // The release channel this image was obtained from.
+ std::string current_channel;
+
+ // The Omaha URL this image should get updates from.
+ std::string omaha_url;
+};
+
+// The mutable image properties are read-write image properties, initialized
+// with values from the image but can be modified by storing them in the
+// stateful partition.
+struct MutableImageProperties {
+ // The release channel we are tracking.
+ std::string target_channel;
+
+ // Whether powerwash is allowed when downloading an update for the selected
+ // target_channel.
+ bool is_powerwash_allowed{false};
+};
+
+// 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);
+
+// Loads the mutable image properties from the stateful partition if found or the
+// system image otherwise.
+MutableImageProperties LoadMutableImageProperties(SystemState* system_state);
+
+// Stores the mutable image properties in the stateful partition. Returns
+// whether the operation succeeded.
+bool StoreMutableImageProperties(SystemState* system_state,
+ const MutableImageProperties& properties);
+
+// Sets the root_prefix used to load files from during unittests to
+// |test_root_prefix|. Passing a nullptr value resets it to the default.
+namespace test {
+void SetImagePropertiesRootPrefix(const char* test_root_prefix);
+} // namespace test
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_IMAGE_PROPERTIES_H_
diff --git a/image_properties_android.cc b/image_properties_android.cc
new file mode 100644
index 0000000..00822da
--- /dev/null
+++ b/image_properties_android.cc
@@ -0,0 +1,111 @@
+//
+// 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 <string>
+
+#include <base/logging.h>
+#include <brillo/osrelease_reader.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/system_state.h"
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Build time properties name used in Brillo.
+const char kProductId[] = "product_id";
+const char kProductVersion[] = "product_version";
+
+// Prefs used to store the target channel and powerwash settings.
+const char kPrefsImgPropChannelName[] = "img-prop-channel-name";
+const char kPrefsImgPropPowerwashAllowed[] = "img-prop-powerwash-allowed";
+
+std::string GetStringWithDefault(const brillo::OsReleaseReader& osrelease,
+ const std::string& key,
+ const std::string& default_value) {
+ std::string result;
+ if (osrelease.GetString(key, &result))
+ return result;
+ LOG(INFO) << "Cannot load ImageProperty " << key << ", using default value "
+ << default_value;
+ return default_value;
+}
+
+} // namespace
+
+namespace test {
+void SetImagePropertiesRootPrefix(const char* /* test_root_prefix */) {}
+} // namespace test
+
+ImageProperties LoadImageProperties(SystemState* system_state) {
+ ImageProperties result;
+
+ brillo::OsReleaseReader osrelease;
+ osrelease.Load();
+ result.product_id = GetStringWithDefault(
+ osrelease, kProductId, "developer-boards:brillo-starter-board");
+ result.canary_product_id = result.product_id;
+ result.version = GetStringWithDefault(osrelease, kProductVersion, "0.0.0.0");
+
+ result.board = "brillo";
+
+ // Brillo images don't have a channel assigned. We stored the name of the
+ // channel where we got the image from in prefs at the time of the update, so
+ // we use that as the current channel if available. During provisioning, there
+ // is no value assigned, so we default to the "stable-channel".
+ std::string current_channel_key =
+ kPrefsChannelOnSlotPrefix +
+ std::to_string(system_state->boot_control()->GetCurrentSlot());
+ std::string current_channel;
+ if (!system_state->prefs()->Exists(current_channel_key) ||
+ !system_state->prefs()->GetString(current_channel_key, ¤t_channel))
+ current_channel = "stable-channel";
+ result.current_channel = current_channel;
+
+ // Brillo only supports the official omaha URL.
+ result.omaha_url = constants::kOmahaDefaultProductionURL;
+
+ return result;
+}
+
+MutableImageProperties LoadMutableImageProperties(SystemState* system_state) {
+ MutableImageProperties result;
+ PrefsInterface* const prefs = system_state->prefs();
+ if (!prefs->GetString(kPrefsImgPropChannelName, &result.target_channel))
+ result.target_channel.clear();
+ if (!prefs->GetBoolean(kPrefsImgPropPowerwashAllowed,
+ &result.is_powerwash_allowed)) {
+ result.is_powerwash_allowed = false;
+ }
+ return result;
+}
+
+bool StoreMutableImageProperties(SystemState* system_state,
+ const MutableImageProperties& properties) {
+ PrefsInterface* const prefs = system_state->prefs();
+ return (
+ prefs->SetString(kPrefsImgPropChannelName, properties.target_channel) &&
+ prefs->SetBoolean(kPrefsImgPropPowerwashAllowed,
+ properties.is_powerwash_allowed));
+}
+
+} // namespace chromeos_update_engine
diff --git a/image_properties_chromeos.cc b/image_properties_chromeos.cc
new file mode 100644
index 0000000..501e662
--- /dev/null
+++ b/image_properties_chromeos.cc
@@ -0,0 +1,150 @@
+//
+// 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 <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <brillo/key_value_store.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/system_state.h"
+
+namespace {
+
+const char kLsbRelease[] = "/etc/lsb-release";
+
+const char kLsbReleaseAppIdKey[] = "CHROMEOS_RELEASE_APPID";
+const char kLsbReleaseAutoUpdateServerKey[] = "CHROMEOS_AUSERVER";
+const char kLsbReleaseBoardAppIdKey[] = "CHROMEOS_BOARD_APPID";
+const char kLsbReleaseBoardKey[] = "CHROMEOS_RELEASE_BOARD";
+const char kLsbReleaseCanaryAppIdKey[] = "CHROMEOS_CANARY_APPID";
+const char kLsbReleaseIsPowerwashAllowedKey[] = "CHROMEOS_IS_POWERWASH_ALLOWED";
+const char kLsbReleaseUpdateChannelKey[] = "CHROMEOS_RELEASE_TRACK";
+const char kLsbReleaseVersionKey[] = "CHROMEOS_RELEASE_VERSION";
+
+const char kDefaultAppId[] = "{87efface-864d-49a5-9bb3-4b050a7c227a}";
+
+// A prefix added to the path, used for testing.
+const char* root_prefix = nullptr;
+
+std::string GetStringWithDefault(const brillo::KeyValueStore& store,
+ const std::string& key,
+ const std::string& default_value) {
+ std::string result;
+ if (store.GetString(key, &result))
+ return result;
+ LOG(INFO) << "Cannot load ImageProperty " << key << ", using default value "
+ << default_value;
+ return default_value;
+}
+
+enum class LsbReleaseSource {
+ kSystem,
+ kStateful,
+};
+
+// Loads the lsb-release properties into the key-value |store| reading the file
+// from either the system image or the stateful partition as specified by
+// |source|. The loaded values are added to the store, possibly overriding
+// existing values.
+void LoadLsbRelease(LsbReleaseSource source, brillo::KeyValueStore* store) {
+ std::string path;
+ if (root_prefix)
+ path = root_prefix;
+ if (source == LsbReleaseSource::kStateful)
+ path += chromeos_update_engine::kStatefulPartition;
+ store->Load(base::FilePath(path + kLsbRelease));
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+namespace test {
+void SetImagePropertiesRootPrefix(const char* test_root_prefix) {
+ root_prefix = test_root_prefix;
+}
+} // namespace test
+
+ImageProperties LoadImageProperties(SystemState* system_state) {
+ ImageProperties result;
+
+ brillo::KeyValueStore lsb_release;
+ LoadLsbRelease(LsbReleaseSource::kSystem, &lsb_release);
+ result.current_channel = GetStringWithDefault(
+ lsb_release, kLsbReleaseUpdateChannelKey, "stable-channel");
+
+ // 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();
+ if (!hardware->IsOfficialBuild() || !hardware->IsNormalBootMode())
+ LoadLsbRelease(LsbReleaseSource::kStateful, &lsb_release);
+
+ // The release_app_id is used as the default appid, but can be override by
+ // the board appid in the general case or the canary appid for the canary
+ // channel only.
+ std::string release_app_id =
+ GetStringWithDefault(lsb_release, kLsbReleaseAppIdKey, kDefaultAppId);
+
+ result.product_id = GetStringWithDefault(
+ lsb_release, kLsbReleaseBoardAppIdKey, release_app_id);
+ result.canary_product_id = GetStringWithDefault(
+ lsb_release, kLsbReleaseCanaryAppIdKey, release_app_id);
+ result.board = GetStringWithDefault(lsb_release, kLsbReleaseBoardKey, "");
+ result.version = GetStringWithDefault(lsb_release, kLsbReleaseVersionKey, "");
+ result.omaha_url =
+ GetStringWithDefault(lsb_release, kLsbReleaseAutoUpdateServerKey,
+ constants::kOmahaDefaultProductionURL);
+
+ return result;
+}
+
+MutableImageProperties LoadMutableImageProperties(SystemState* system_state) {
+ MutableImageProperties result;
+ brillo::KeyValueStore lsb_release;
+ LoadLsbRelease(LsbReleaseSource::kSystem, &lsb_release);
+ LoadLsbRelease(LsbReleaseSource::kStateful, &lsb_release);
+ result.target_channel = GetStringWithDefault(
+ lsb_release, kLsbReleaseUpdateChannelKey, "stable-channel");
+ if (!lsb_release.GetBoolean(kLsbReleaseIsPowerwashAllowedKey,
+ &result.is_powerwash_allowed))
+ result.is_powerwash_allowed = false;
+ return result;
+}
+
+bool StoreMutableImageProperties(SystemState* system_state,
+ const MutableImageProperties& properties) {
+ brillo::KeyValueStore lsb_release;
+ LoadLsbRelease(LsbReleaseSource::kStateful, &lsb_release);
+ lsb_release.SetString(kLsbReleaseUpdateChannelKey, properties.target_channel);
+ lsb_release.SetBoolean(kLsbReleaseIsPowerwashAllowedKey,
+ properties.is_powerwash_allowed);
+
+ std::string root_prefix_str = root_prefix ? root_prefix : "";
+ base::FilePath path(root_prefix_str + kStatefulPartition + kLsbRelease);
+ if (!base::DirectoryExists(path.DirName()))
+ base::CreateDirectory(path.DirName());
+ return lsb_release.Save(path);
+}
+
+} // namespace chromeos_update_engine
diff --git a/include/debugd/dbus-constants.h b/include/debugd/dbus-constants.h
new file mode 100644
index 0000000..3427a99
--- /dev/null
+++ b/include/debugd/dbus-constants.h
@@ -0,0 +1,56 @@
+// Copyright 2015 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.
+
+#ifndef SYSTEM_API_DBUS_DEBUGD_DBUS_CONSTANTS_H_
+#define SYSTEM_API_DBUS_DEBUGD_DBUS_CONSTANTS_H_
+
+namespace debugd {
+const char kDebugdInterface[] = "org.chromium.debugd";
+const char kDebugdServicePath[] = "/org/chromium/debugd";
+const char kDebugdServiceName[] = "org.chromium.debugd";
+
+// Methods.
+const char kDumpDebugLogs[] = "DumpDebugLogs";
+const char kGetDebugLogs[] = "GetDebugLogs";
+const char kGetInterfaces[] = "GetInterfaces";
+const char kGetModemStatus[] = "GetModemStatus";
+const char kGetNetworkStatus[] = "GetNetworkStatus";
+const char kGetPerfOutput[] = "GetPerfOutput";
+const char kGetRandomPerfOutput[] = "GetRandomPerfOutput";
+const char kGetRichPerfData[] = "GetRichPerfData";
+const char kGetRoutes[] = "GetRoutes";
+const char kGetWiMaxStatus[] = "GetWiMaxStatus";
+const char kSetDebugMode[] = "SetDebugMode";
+const char kSystraceStart[] = "SystraceStart";
+const char kSystraceStop[] = "SystraceStop";
+const char kSystraceStatus[] = "SystraceStatus";
+const char kGetLog[] = "GetLog";
+const char kGetAllLogs[] = "GetAllLogs";
+const char kGetUserLogFiles[] = "GetUserLogFiles";
+const char kGetFeedbackLogs[] = "GetFeedbackLogs";
+const char kTestICMP[] = "TestICMP";
+const char kTestICMPWithOptions[] = "TestICMPWithOptions";
+const char kLogKernelTaskStates[] = "LogKernelTaskStates";
+const char kUploadCrashes[] = "UploadCrashes";
+const char kRemoveRootfsVerification[] = "RemoveRootfsVerification";
+const char kEnableChromeRemoteDebugging[] = "EnableChromeRemoteDebugging";
+const char kEnableBootFromUsb[] = "EnableBootFromUsb";
+const char kConfigureSshServer[] = "ConfigureSshServer";
+const char kSetUserPassword[] = "SetUserPassword";
+const char kEnableChromeDevFeatures[] = "EnableChromeDevFeatures";
+const char kQueryDevFeatures[] = "QueryDevFeatures";
+
+// Values.
+enum DevFeatureFlag {
+ DEV_FEATURES_DISABLED = 1 << 0,
+ DEV_FEATURE_ROOTFS_VERIFICATION_REMOVED = 1 << 1,
+ DEV_FEATURE_BOOT_FROM_USB_ENABLED = 1 << 2,
+ DEV_FEATURE_SSH_SERVER_CONFIGURED = 1 << 3,
+ DEV_FEATURE_DEV_MODE_ROOT_PASSWORD_SET = 1 << 4,
+ DEV_FEATURE_SYSTEM_ROOT_PASSWORD_SET = 1 << 5,
+ DEV_FEATURE_CHROME_REMOTE_DEBUGGING_ENABLED = 1 << 6,
+};
+} // namespace debugd
+
+#endif // SYSTEM_API_DBUS_DEBUGD_DBUS_CONSTANTS_H_
diff --git a/include/debugd/dbus-proxies.h b/include/debugd/dbus-proxies.h
new file mode 100644
index 0000000..a528480
--- /dev/null
+++ b/include/debugd/dbus-proxies.h
@@ -0,0 +1,2334 @@
+// Automatic generation of D-Bus interfaces:
+// - org.chromium.debugd
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXIES_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXIES_H
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <brillo/any.h>
+#include <brillo/dbus/dbus_method_invoker.h>
+#include <brillo/dbus/dbus_property.h>
+#include <brillo/dbus/dbus_signal_handler.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+namespace org {
+namespace chromium {
+
+// Abstract interface proxy for org::chromium::debugd.
+class debugdProxyInterface {
+ public:
+ virtual ~debugdProxyInterface() = default;
+
+ // Starts pinging the specified hostname with the specified options, with
+ // output directed to the given output file descriptor. The returned opaque
+ // string functions as a handle for this particular ping. Multiple pings
+ // can be running at once.
+ virtual bool PingStart(
+ const dbus::FileDescriptor& in_outfd,
+ const std::string& in_destination,
+ const brillo::VariantDictionary& in_options,
+ std::string* out_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Starts pinging the specified hostname with the specified options, with
+ // output directed to the given output file descriptor. The returned opaque
+ // string functions as a handle for this particular ping. Multiple pings
+ // can be running at once.
+ virtual void PingStartAsync(
+ const dbus::FileDescriptor& in_outfd,
+ const std::string& in_destination,
+ const brillo::VariantDictionary& in_options,
+ const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stops a running ping.
+ virtual bool PingStop(
+ const std::string& in_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stops a running ping.
+ virtual void PingStopAsync(
+ const std::string& in_handle,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Start system/kernel tracing. If tracing is already enabled it is
+ // stopped first and any collected events are discarded. The kernel
+ // must have been configured to support tracing.
+ virtual bool SystraceStart(
+ const std::string& in_categories,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Start system/kernel tracing. If tracing is already enabled it is
+ // stopped first and any collected events are discarded. The kernel
+ // must have been configured to support tracing.
+ virtual void SystraceStartAsync(
+ const std::string& in_categories,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stop system/kernel tracing and write the collected event data.
+ virtual bool SystraceStop(
+ const dbus::FileDescriptor& in_outfd,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stop system/kernel tracing and write the collected event data.
+ virtual void SystraceStopAsync(
+ const dbus::FileDescriptor& in_outfd,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Return current status for system/kernel tracing including whether it
+ // is enabled, the tracing clock, and the set of events enabled.
+ virtual bool SystraceStatus(
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Return current status for system/kernel tracing including whether it
+ // is enabled, the tracing clock, and the set of events enabled.
+ virtual void SystraceStatusAsync(
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool TracePathStart(
+ const dbus::FileDescriptor& in_outfd,
+ const std::string& in_destination,
+ const brillo::VariantDictionary& in_options,
+ std::string* out_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void TracePathStartAsync(
+ const dbus::FileDescriptor& in_outfd,
+ const std::string& in_destination,
+ const brillo::VariantDictionary& in_options,
+ const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stops a running tracepath.
+ virtual bool TracePathStop(
+ const std::string& in_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stops a running tracepath.
+ virtual void TracePathStopAsync(
+ const std::string& in_handle,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns the routing table.
+ virtual bool GetRoutes(
+ const brillo::VariantDictionary& in_options,
+ std::vector<std::string>* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns the routing table.
+ virtual void GetRoutesAsync(
+ const brillo::VariantDictionary& in_options,
+ const base::Callback<void(const std::vector<std::string>& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns modem information as a JSON string. See the design document for
+ // a rationale.
+ virtual bool GetModemStatus(
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns modem information as a JSON string. See the design document for
+ // a rationale.
+ virtual void GetModemStatusAsync(
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Runs the specified command through the modem serial interface and
+ // returns the output.
+ virtual bool RunModemCommand(
+ const std::string& in_command,
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Runs the specified command through the modem serial interface and
+ // returns the output.
+ virtual void RunModemCommandAsync(
+ const std::string& in_command,
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns network information as a JSON string. See the design document
+ // for a rationale.
+ virtual bool GetNetworkStatus(
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns network information as a JSON string. See the design document
+ // for a rationale.
+ virtual void GetNetworkStatusAsync(
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns WiMAX information as a JSON string. See the design document for
+ // a rationale.
+ virtual bool GetWiMaxStatus(
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns WiMAX information as a JSON string. See the design document for
+ // a rationale.
+ virtual void GetWiMaxStatusAsync(
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Runs system-wide perf profiling. The profile parameters are selected by
+ // perf_args.
+ virtual bool GetPerfOutput(
+ uint32_t in_duration_sec,
+ const std::vector<std::string>& in_perf_args,
+ int32_t* out_status,
+ std::vector<uint8_t>* out_perf_data,
+ std::vector<uint8_t>* out_perf_stat,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Runs system-wide perf profiling. The profile parameters are selected by
+ // perf_args.
+ virtual void GetPerfOutputAsync(
+ uint32_t in_duration_sec,
+ const std::vector<std::string>& in_perf_args,
+ const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Runs system-wide perf profiling. It can can profile events other than
+ // cycles (example: iTLB-misses), and can collect branch profiles. It can
+ // also return raw counter values. The exact profile or counters to be
+ // collected is chosen at random and depends on what CPU is used by the
+ // system (certain CPUs do not support certain profiling modes).
+ virtual bool GetRandomPerfOutput(
+ uint32_t in_duration_sec,
+ int32_t* out_status,
+ std::vector<uint8_t>* out_perf_data,
+ std::vector<uint8_t>* out_perf_stat,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Runs system-wide perf profiling. It can can profile events other than
+ // cycles (example: iTLB-misses), and can collect branch profiles. It can
+ // also return raw counter values. The exact profile or counters to be
+ // collected is chosen at random and depends on what CPU is used by the
+ // system (certain CPUs do not support certain profiling modes).
+ virtual void GetRandomPerfOutputAsync(
+ uint32_t in_duration_sec,
+ const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns perf event data. Does systemwide profiling. It can profile
+ // events other than cycles (example: iTLB-misses), and can collect branch
+ // profiles. The exact profile to be collected is chosen at random
+ // and depends on what CPU is used by the system (certain CPUs do not
+ // support certain profiling modes).
+ virtual bool GetRichPerfData(
+ uint32_t in_duration_sec,
+ std::vector<uint8_t>* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns perf event data. Does systemwide profiling. It can profile
+ // events other than cycles (example: iTLB-misses), and can collect branch
+ // profiles. The exact profile to be collected is chosen at random
+ // and depends on what CPU is used by the system (certain CPUs do not
+ // support certain profiling modes).
+ virtual void GetRichPerfDataAsync(
+ uint32_t in_duration_sec,
+ const base::Callback<void(const std::vector<uint8_t>& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // DEPRECATED: Use DumpDebugLogs instead.
+ // Packages up system logs into a .tar.gz and returns it over the supplied
+ // file descriptor.
+ virtual bool GetDebugLogs(
+ const dbus::FileDescriptor& in_outfd,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // DEPRECATED: Use DumpDebugLogs instead.
+ // Packages up system logs into a .tar.gz and returns it over the supplied
+ // file descriptor.
+ virtual void GetDebugLogsAsync(
+ const dbus::FileDescriptor& in_outfd,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Packages up system logs into a .tar(.gz) and returns it over the
+ // supplied file descriptor.
+ virtual bool DumpDebugLogs(
+ bool in_is_compressed,
+ const dbus::FileDescriptor& in_outfd,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Packages up system logs into a .tar(.gz) and returns it over the
+ // supplied file descriptor.
+ virtual void DumpDebugLogsAsync(
+ bool in_is_compressed,
+ const dbus::FileDescriptor& in_outfd,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Enables or disables debug mode for a specified subsystem.
+ virtual bool SetDebugMode(
+ const std::string& in_subsystem,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Enables or disables debug mode for a specified subsystem.
+ virtual void SetDebugModeAsync(
+ const std::string& in_subsystem,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Fetches the contents of a single system log, identified by name. See
+ // /src/log_tool.cc for a list of valid names.
+ virtual bool GetLog(
+ const std::string& in_log,
+ std::string* out_contents,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Fetches the contents of a single system log, identified by name. See
+ // /src/log_tool.cc for a list of valid names.
+ virtual void GetLogAsync(
+ const std::string& in_log,
+ const base::Callback<void(const std::string& /*contents*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns all the system logs.
+ virtual bool GetAllLogs(
+ std::map<std::string, std::string>* out_logs,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns all the system logs.
+ virtual void GetAllLogsAsync(
+ const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns system logs for feedback reports.
+ virtual bool GetFeedbackLogs(
+ std::map<std::string, std::string>* out_logs,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns system logs for feedback reports.
+ virtual void GetFeedbackLogsAsync(
+ const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns list of User log file names that Chrome itself must collect.
+ // These logfiles are relative to the user's profile path and must be
+ // collected separately for each user.
+ virtual bool GetUserLogFiles(
+ std::map<std::string, std::string>* out_user_log_files,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns list of User log file names that Chrome itself must collect.
+ // These logfiles are relative to the user's profile path and must be
+ // collected separately for each user.
+ virtual void GetUserLogFilesAsync(
+ const base::Callback<void(const std::map<std::string, std::string>& /*user_log_files*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Example method. See /doc/hacking.md.
+ virtual bool GetExample(
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Example method. See /doc/hacking.md.
+ virtual void GetExampleAsync(
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns information about network interfaces as a JSON string.
+ virtual bool GetInterfaces(
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Returns information about network interfaces as a JSON string.
+ virtual void GetInterfacesAsync(
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Tests ICMP connectivity to a specified host.
+ virtual bool TestICMP(
+ const std::string& in_host,
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Tests ICMP connectivity to a specified host.
+ virtual void TestICMPAsync(
+ const std::string& in_host,
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Tests ICMP connectivity to a specified host (with options).
+ virtual bool TestICMPWithOptions(
+ const std::string& in_host,
+ const std::map<std::string, std::string>& in_options,
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Tests ICMP connectivity to a specified host (with options).
+ virtual void TestICMPWithOptionsAsync(
+ const std::string& in_host,
+ const std::map<std::string, std::string>& in_options,
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Runs BatteryFirmware utility.
+ virtual bool BatteryFirmware(
+ const std::string& in_option,
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Runs BatteryFirmware utility.
+ virtual void BatteryFirmwareAsync(
+ const std::string& in_option,
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Runs Smartctl utility.
+ virtual bool Smartctl(
+ const std::string& in_option,
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Runs Smartctl utility.
+ virtual void SmartctlAsync(
+ const std::string& in_option,
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Starts running memtester.
+ virtual bool MemtesterStart(
+ const dbus::FileDescriptor& in_outfd,
+ uint32_t in_memory,
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Starts running memtester.
+ virtual void MemtesterStartAsync(
+ const dbus::FileDescriptor& in_outfd,
+ uint32_t in_memory,
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stops running memtester.
+ virtual bool MemtesterStop(
+ const std::string& in_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stops running memtester.
+ virtual void MemtesterStopAsync(
+ const std::string& in_handle,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Starts running badblocks test.
+ virtual bool BadblocksStart(
+ const dbus::FileDescriptor& in_outfd,
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Starts running badblocks test.
+ virtual void BadblocksStartAsync(
+ const dbus::FileDescriptor& in_outfd,
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stops running badblocks.
+ virtual bool BadblocksStop(
+ const std::string& in_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stops running badblocks.
+ virtual void BadblocksStopAsync(
+ const std::string& in_handle,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Starts a packet capture with the specified options, with diagnostic
+ // status directed to the "statfd" file descriptor and packet capture
+ // data sent to the "outfd" file descriptor. The returned opaque string
+ // functions as a handle for this particular packet capture. Multiple
+ // captures can be running at once. Captures can be initiated on
+ // Ethernet-like devices or WiFi devices in "client mode" (showing only
+ // Ethernet frames) by specifying the "device" parameter (see below).
+ // By specifying a channel, the script will find or create a "monitor
+ // mode" interface if one is available and produce an "over the air"
+ // packet capture. The name of the output packet capture file is sent
+ // to the output file descriptor.
+ virtual bool PacketCaptureStart(
+ const dbus::FileDescriptor& in_statfd,
+ const dbus::FileDescriptor& in_outfd,
+ const brillo::VariantDictionary& in_options,
+ std::string* out_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Starts a packet capture with the specified options, with diagnostic
+ // status directed to the "statfd" file descriptor and packet capture
+ // data sent to the "outfd" file descriptor. The returned opaque string
+ // functions as a handle for this particular packet capture. Multiple
+ // captures can be running at once. Captures can be initiated on
+ // Ethernet-like devices or WiFi devices in "client mode" (showing only
+ // Ethernet frames) by specifying the "device" parameter (see below).
+ // By specifying a channel, the script will find or create a "monitor
+ // mode" interface if one is available and produce an "over the air"
+ // packet capture. The name of the output packet capture file is sent
+ // to the output file descriptor.
+ virtual void PacketCaptureStartAsync(
+ const dbus::FileDescriptor& in_statfd,
+ const dbus::FileDescriptor& in_outfd,
+ const brillo::VariantDictionary& in_options,
+ const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stops a running packet capture.
+ virtual bool PacketCaptureStop(
+ const std::string& in_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Stops a running packet capture.
+ virtual void PacketCaptureStopAsync(
+ const std::string& in_handle,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Triggers show-task-states(T) SysRq.
+ // See https://www.kernel.org/doc/Documentation/sysrq.txt.
+ virtual bool LogKernelTaskStates(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Triggers show-task-states(T) SysRq.
+ // See https://www.kernel.org/doc/Documentation/sysrq.txt.
+ virtual void LogKernelTaskStatesAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Triggers uploading of system crashes (the crash_sender program).
+ virtual bool UploadCrashes(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Triggers uploading of system crashes (the crash_sender program).
+ virtual void UploadCrashesAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Removes rootfs verification. Requires a system reboot before it will
+ // take effect. Restricted to pre-owner dev mode.
+ virtual bool RemoveRootfsVerification(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Removes rootfs verification. Requires a system reboot before it will
+ // take effect. Restricted to pre-owner dev mode.
+ virtual void RemoveRootfsVerificationAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Enables OS booting from a USB image. Restricted to pre-owner dev mode.
+ virtual bool EnableBootFromUsb(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Enables OS booting from a USB image. Restricted to pre-owner dev mode.
+ virtual void EnableBootFromUsbAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Sets up sshd to provide an SSH server immediately and on future reboots.
+ // Also installs the test SSH keys to allow access by cros tools. Requires
+ // that rootfs verification has been removed. Restricted to pre-owner dev
+ // mode.
+ virtual bool ConfigureSshServer(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Sets up sshd to provide an SSH server immediately and on future reboots.
+ // Also installs the test SSH keys to allow access by cros tools. Requires
+ // that rootfs verification has been removed. Restricted to pre-owner dev
+ // mode.
+ virtual void ConfigureSshServerAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Sets both the system and dev mode password for the indicated account.
+ // Restricted to pre-owner dev mode.
+ virtual bool SetUserPassword(
+ const std::string& in_username,
+ const std::string& in_password,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Sets both the system and dev mode password for the indicated account.
+ // Restricted to pre-owner dev mode.
+ virtual void SetUserPasswordAsync(
+ const std::string& in_username,
+ const std::string& in_password,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Sets up Chrome for remote debugging. It will take effect after a reboot
+ // and using port 9222.
+ // Requires that rootfs verification has been removed. Restricted to
+ // pre-owner dev mode.
+ virtual bool EnableChromeRemoteDebugging(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Sets up Chrome for remote debugging. It will take effect after a reboot
+ // and using port 9222.
+ // Requires that rootfs verification has been removed. Restricted to
+ // pre-owner dev mode.
+ virtual void EnableChromeRemoteDebuggingAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Convenience function to enable a predefined set of tools from the Chrome
+ // UI. Equivalent to calling these functions in order:
+ // 1. EnableBootFromUsb()
+ // 2. ConfigureSshServer()
+ // 3. SetUserPassword("root", root_password)
+ // Requires that rootfs verification has been removed. If any sub-function
+ // fails, this function will exit with an error without attempting any
+ // further configuration or rollback. Restricted to pre-owner dev mode.
+ virtual bool EnableChromeDevFeatures(
+ const std::string& in_root_password,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Convenience function to enable a predefined set of tools from the Chrome
+ // UI. Equivalent to calling these functions in order:
+ // 1. EnableBootFromUsb()
+ // 2. ConfigureSshServer()
+ // 3. SetUserPassword("root", root_password)
+ // Requires that rootfs verification has been removed. If any sub-function
+ // fails, this function will exit with an error without attempting any
+ // further configuration or rollback. Restricted to pre-owner dev mode.
+ virtual void EnableChromeDevFeaturesAsync(
+ const std::string& in_root_password,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Queries which dev features have been enabled. Each dev feature will be
+ // indicated by a bit flag in the return value. Flags are defined in the
+ // DevFeatureFlag enumeration. If the dev tools are unavailable (system is
+ // not in dev mode/pre-login state), the DEV_FEATURES_DISABLED flag will be
+ // set and the rest of the bits will always be set to 0.
+ virtual bool QueryDevFeatures(
+ int32_t* out_features,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Queries which dev features have been enabled. Each dev feature will be
+ // indicated by a bit flag in the return value. Flags are defined in the
+ // DevFeatureFlag enumeration. If the dev tools are unavailable (system is
+ // not in dev mode/pre-login state), the DEV_FEATURES_DISABLED flag will be
+ // set and the rest of the bits will always be set to 0.
+ virtual void QueryDevFeaturesAsync(
+ const base::Callback<void(int32_t /*features*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Allow uploading of device coredump files.
+ virtual bool EnableDevCoredumpUpload(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Allow uploading of device coredump files.
+ virtual void EnableDevCoredumpUploadAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Disallow uploading of device coredump files.
+ virtual bool DisableDevCoredumpUpload(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // Disallow uploading of device coredump files.
+ virtual void DisableDevCoredumpUploadAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+};
+
+} // namespace chromium
+} // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::debugd.
+class debugdProxy final : public debugdProxyInterface {
+ public:
+ debugdProxy(const scoped_refptr<dbus::Bus>& bus) :
+ bus_{bus},
+ dbus_object_proxy_{
+ bus_->GetObjectProxy(service_name_, object_path_)} {
+ }
+
+ ~debugdProxy() override {
+ bus_->RemoveObjectProxy(
+ service_name_, object_path_, base::Bind(&base::DoNothing));
+ }
+
+ void ReleaseObjectProxy(const base::Closure& callback) {
+ bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+ }
+
+ const dbus::ObjectPath& GetObjectPath() const {
+ return object_path_;
+ }
+
+ dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+ // Starts pinging the specified hostname with the specified options, with
+ // output directed to the given output file descriptor. The returned opaque
+ // string functions as a handle for this particular ping. Multiple pings
+ // can be running at once.
+ bool PingStart(
+ const dbus::FileDescriptor& in_outfd,
+ const std::string& in_destination,
+ const brillo::VariantDictionary& in_options,
+ std::string* out_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "PingStart",
+ error,
+ in_outfd,
+ in_destination,
+ in_options);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_handle);
+ }
+
+ // Starts pinging the specified hostname with the specified options, with
+ // output directed to the given output file descriptor. The returned opaque
+ // string functions as a handle for this particular ping. Multiple pings
+ // can be running at once.
+ void PingStartAsync(
+ const dbus::FileDescriptor& in_outfd,
+ const std::string& in_destination,
+ const brillo::VariantDictionary& in_options,
+ const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "PingStart",
+ success_callback,
+ error_callback,
+ in_outfd,
+ in_destination,
+ in_options);
+ }
+
+ // Stops a running ping.
+ bool PingStop(
+ const std::string& in_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "PingStop",
+ error,
+ in_handle);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Stops a running ping.
+ void PingStopAsync(
+ const std::string& in_handle,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "PingStop",
+ success_callback,
+ error_callback,
+ in_handle);
+ }
+
+ // Start system/kernel tracing. If tracing is already enabled it is
+ // stopped first and any collected events are discarded. The kernel
+ // must have been configured to support tracing.
+ bool SystraceStart(
+ const std::string& in_categories,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "SystraceStart",
+ error,
+ in_categories);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Start system/kernel tracing. If tracing is already enabled it is
+ // stopped first and any collected events are discarded. The kernel
+ // must have been configured to support tracing.
+ void SystraceStartAsync(
+ const std::string& in_categories,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "SystraceStart",
+ success_callback,
+ error_callback,
+ in_categories);
+ }
+
+ // Stop system/kernel tracing and write the collected event data.
+ bool SystraceStop(
+ const dbus::FileDescriptor& in_outfd,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "SystraceStop",
+ error,
+ in_outfd);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Stop system/kernel tracing and write the collected event data.
+ void SystraceStopAsync(
+ const dbus::FileDescriptor& in_outfd,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "SystraceStop",
+ success_callback,
+ error_callback,
+ in_outfd);
+ }
+
+ // Return current status for system/kernel tracing including whether it
+ // is enabled, the tracing clock, and the set of events enabled.
+ bool SystraceStatus(
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "SystraceStatus",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_status);
+ }
+
+ // Return current status for system/kernel tracing including whether it
+ // is enabled, the tracing clock, and the set of events enabled.
+ void SystraceStatusAsync(
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "SystraceStatus",
+ success_callback,
+ error_callback);
+ }
+
+ bool TracePathStart(
+ const dbus::FileDescriptor& in_outfd,
+ const std::string& in_destination,
+ const brillo::VariantDictionary& in_options,
+ std::string* out_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "TracePathStart",
+ error,
+ in_outfd,
+ in_destination,
+ in_options);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_handle);
+ }
+
+ void TracePathStartAsync(
+ const dbus::FileDescriptor& in_outfd,
+ const std::string& in_destination,
+ const brillo::VariantDictionary& in_options,
+ const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "TracePathStart",
+ success_callback,
+ error_callback,
+ in_outfd,
+ in_destination,
+ in_options);
+ }
+
+ // Stops a running tracepath.
+ bool TracePathStop(
+ const std::string& in_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "TracePathStop",
+ error,
+ in_handle);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Stops a running tracepath.
+ void TracePathStopAsync(
+ const std::string& in_handle,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "TracePathStop",
+ success_callback,
+ error_callback,
+ in_handle);
+ }
+
+ // Returns the routing table.
+ bool GetRoutes(
+ const brillo::VariantDictionary& in_options,
+ std::vector<std::string>* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetRoutes",
+ error,
+ in_options);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_result);
+ }
+
+ // Returns the routing table.
+ void GetRoutesAsync(
+ const brillo::VariantDictionary& in_options,
+ const base::Callback<void(const std::vector<std::string>& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetRoutes",
+ success_callback,
+ error_callback,
+ in_options);
+ }
+
+ // Returns modem information as a JSON string. See the design document for
+ // a rationale.
+ bool GetModemStatus(
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetModemStatus",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_status);
+ }
+
+ // Returns modem information as a JSON string. See the design document for
+ // a rationale.
+ void GetModemStatusAsync(
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetModemStatus",
+ success_callback,
+ error_callback);
+ }
+
+ // Runs the specified command through the modem serial interface and
+ // returns the output.
+ bool RunModemCommand(
+ const std::string& in_command,
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "RunModemCommand",
+ error,
+ in_command);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_status);
+ }
+
+ // Runs the specified command through the modem serial interface and
+ // returns the output.
+ void RunModemCommandAsync(
+ const std::string& in_command,
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "RunModemCommand",
+ success_callback,
+ error_callback,
+ in_command);
+ }
+
+ // Returns network information as a JSON string. See the design document
+ // for a rationale.
+ bool GetNetworkStatus(
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetNetworkStatus",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_status);
+ }
+
+ // Returns network information as a JSON string. See the design document
+ // for a rationale.
+ void GetNetworkStatusAsync(
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetNetworkStatus",
+ success_callback,
+ error_callback);
+ }
+
+ // Returns WiMAX information as a JSON string. See the design document for
+ // a rationale.
+ bool GetWiMaxStatus(
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetWiMaxStatus",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_status);
+ }
+
+ // Returns WiMAX information as a JSON string. See the design document for
+ // a rationale.
+ void GetWiMaxStatusAsync(
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetWiMaxStatus",
+ success_callback,
+ error_callback);
+ }
+
+ // Runs system-wide perf profiling. The profile parameters are selected by
+ // perf_args.
+ bool GetPerfOutput(
+ uint32_t in_duration_sec,
+ const std::vector<std::string>& in_perf_args,
+ int32_t* out_status,
+ std::vector<uint8_t>* out_perf_data,
+ std::vector<uint8_t>* out_perf_stat,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetPerfOutput",
+ error,
+ in_duration_sec,
+ in_perf_args);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_status, out_perf_data, out_perf_stat);
+ }
+
+ // Runs system-wide perf profiling. The profile parameters are selected by
+ // perf_args.
+ void GetPerfOutputAsync(
+ uint32_t in_duration_sec,
+ const std::vector<std::string>& in_perf_args,
+ const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetPerfOutput",
+ success_callback,
+ error_callback,
+ in_duration_sec,
+ in_perf_args);
+ }
+
+ // Runs system-wide perf profiling. It can can profile events other than
+ // cycles (example: iTLB-misses), and can collect branch profiles. It can
+ // also return raw counter values. The exact profile or counters to be
+ // collected is chosen at random and depends on what CPU is used by the
+ // system (certain CPUs do not support certain profiling modes).
+ bool GetRandomPerfOutput(
+ uint32_t in_duration_sec,
+ int32_t* out_status,
+ std::vector<uint8_t>* out_perf_data,
+ std::vector<uint8_t>* out_perf_stat,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetRandomPerfOutput",
+ error,
+ in_duration_sec);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_status, out_perf_data, out_perf_stat);
+ }
+
+ // Runs system-wide perf profiling. It can can profile events other than
+ // cycles (example: iTLB-misses), and can collect branch profiles. It can
+ // also return raw counter values. The exact profile or counters to be
+ // collected is chosen at random and depends on what CPU is used by the
+ // system (certain CPUs do not support certain profiling modes).
+ void GetRandomPerfOutputAsync(
+ uint32_t in_duration_sec,
+ const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetRandomPerfOutput",
+ success_callback,
+ error_callback,
+ in_duration_sec);
+ }
+
+ // Returns perf event data. Does systemwide profiling. It can profile
+ // events other than cycles (example: iTLB-misses), and can collect branch
+ // profiles. The exact profile to be collected is chosen at random
+ // and depends on what CPU is used by the system (certain CPUs do not
+ // support certain profiling modes).
+ bool GetRichPerfData(
+ uint32_t in_duration_sec,
+ std::vector<uint8_t>* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetRichPerfData",
+ error,
+ in_duration_sec);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_status);
+ }
+
+ // Returns perf event data. Does systemwide profiling. It can profile
+ // events other than cycles (example: iTLB-misses), and can collect branch
+ // profiles. The exact profile to be collected is chosen at random
+ // and depends on what CPU is used by the system (certain CPUs do not
+ // support certain profiling modes).
+ void GetRichPerfDataAsync(
+ uint32_t in_duration_sec,
+ const base::Callback<void(const std::vector<uint8_t>& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetRichPerfData",
+ success_callback,
+ error_callback,
+ in_duration_sec);
+ }
+
+ // DEPRECATED: Use DumpDebugLogs instead.
+ // Packages up system logs into a .tar.gz and returns it over the supplied
+ // file descriptor.
+ bool GetDebugLogs(
+ const dbus::FileDescriptor& in_outfd,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetDebugLogs",
+ error,
+ in_outfd);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // DEPRECATED: Use DumpDebugLogs instead.
+ // Packages up system logs into a .tar.gz and returns it over the supplied
+ // file descriptor.
+ void GetDebugLogsAsync(
+ const dbus::FileDescriptor& in_outfd,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetDebugLogs",
+ success_callback,
+ error_callback,
+ in_outfd);
+ }
+
+ // Packages up system logs into a .tar(.gz) and returns it over the
+ // supplied file descriptor.
+ bool DumpDebugLogs(
+ bool in_is_compressed,
+ const dbus::FileDescriptor& in_outfd,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "DumpDebugLogs",
+ error,
+ in_is_compressed,
+ in_outfd);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Packages up system logs into a .tar(.gz) and returns it over the
+ // supplied file descriptor.
+ void DumpDebugLogsAsync(
+ bool in_is_compressed,
+ const dbus::FileDescriptor& in_outfd,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "DumpDebugLogs",
+ success_callback,
+ error_callback,
+ in_is_compressed,
+ in_outfd);
+ }
+
+ // Enables or disables debug mode for a specified subsystem.
+ bool SetDebugMode(
+ const std::string& in_subsystem,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "SetDebugMode",
+ error,
+ in_subsystem);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Enables or disables debug mode for a specified subsystem.
+ void SetDebugModeAsync(
+ const std::string& in_subsystem,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "SetDebugMode",
+ success_callback,
+ error_callback,
+ in_subsystem);
+ }
+
+ // Fetches the contents of a single system log, identified by name. See
+ // /src/log_tool.cc for a list of valid names.
+ bool GetLog(
+ const std::string& in_log,
+ std::string* out_contents,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetLog",
+ error,
+ in_log);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_contents);
+ }
+
+ // Fetches the contents of a single system log, identified by name. See
+ // /src/log_tool.cc for a list of valid names.
+ void GetLogAsync(
+ const std::string& in_log,
+ const base::Callback<void(const std::string& /*contents*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetLog",
+ success_callback,
+ error_callback,
+ in_log);
+ }
+
+ // Returns all the system logs.
+ bool GetAllLogs(
+ std::map<std::string, std::string>* out_logs,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetAllLogs",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_logs);
+ }
+
+ // Returns all the system logs.
+ void GetAllLogsAsync(
+ const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetAllLogs",
+ success_callback,
+ error_callback);
+ }
+
+ // Returns system logs for feedback reports.
+ bool GetFeedbackLogs(
+ std::map<std::string, std::string>* out_logs,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetFeedbackLogs",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_logs);
+ }
+
+ // Returns system logs for feedback reports.
+ void GetFeedbackLogsAsync(
+ const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetFeedbackLogs",
+ success_callback,
+ error_callback);
+ }
+
+ // Returns list of User log file names that Chrome itself must collect.
+ // These logfiles are relative to the user's profile path and must be
+ // collected separately for each user.
+ bool GetUserLogFiles(
+ std::map<std::string, std::string>* out_user_log_files,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetUserLogFiles",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_user_log_files);
+ }
+
+ // Returns list of User log file names that Chrome itself must collect.
+ // These logfiles are relative to the user's profile path and must be
+ // collected separately for each user.
+ void GetUserLogFilesAsync(
+ const base::Callback<void(const std::map<std::string, std::string>& /*user_log_files*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetUserLogFiles",
+ success_callback,
+ error_callback);
+ }
+
+ // Example method. See /doc/hacking.md.
+ bool GetExample(
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetExample",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_result);
+ }
+
+ // Example method. See /doc/hacking.md.
+ void GetExampleAsync(
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetExample",
+ success_callback,
+ error_callback);
+ }
+
+ // Returns information about network interfaces as a JSON string.
+ bool GetInterfaces(
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetInterfaces",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_result);
+ }
+
+ // Returns information about network interfaces as a JSON string.
+ void GetInterfacesAsync(
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "GetInterfaces",
+ success_callback,
+ error_callback);
+ }
+
+ // Tests ICMP connectivity to a specified host.
+ bool TestICMP(
+ const std::string& in_host,
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "TestICMP",
+ error,
+ in_host);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_result);
+ }
+
+ // Tests ICMP connectivity to a specified host.
+ void TestICMPAsync(
+ const std::string& in_host,
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "TestICMP",
+ success_callback,
+ error_callback,
+ in_host);
+ }
+
+ // Tests ICMP connectivity to a specified host (with options).
+ bool TestICMPWithOptions(
+ const std::string& in_host,
+ const std::map<std::string, std::string>& in_options,
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "TestICMPWithOptions",
+ error,
+ in_host,
+ in_options);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_result);
+ }
+
+ // Tests ICMP connectivity to a specified host (with options).
+ void TestICMPWithOptionsAsync(
+ const std::string& in_host,
+ const std::map<std::string, std::string>& in_options,
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "TestICMPWithOptions",
+ success_callback,
+ error_callback,
+ in_host,
+ in_options);
+ }
+
+ // Runs BatteryFirmware utility.
+ bool BatteryFirmware(
+ const std::string& in_option,
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "BatteryFirmware",
+ error,
+ in_option);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_result);
+ }
+
+ // Runs BatteryFirmware utility.
+ void BatteryFirmwareAsync(
+ const std::string& in_option,
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "BatteryFirmware",
+ success_callback,
+ error_callback,
+ in_option);
+ }
+
+ // Runs Smartctl utility.
+ bool Smartctl(
+ const std::string& in_option,
+ std::string* out_result,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "Smartctl",
+ error,
+ in_option);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_result);
+ }
+
+ // Runs Smartctl utility.
+ void SmartctlAsync(
+ const std::string& in_option,
+ const base::Callback<void(const std::string& /*result*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "Smartctl",
+ success_callback,
+ error_callback,
+ in_option);
+ }
+
+ // Starts running memtester.
+ bool MemtesterStart(
+ const dbus::FileDescriptor& in_outfd,
+ uint32_t in_memory,
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "MemtesterStart",
+ error,
+ in_outfd,
+ in_memory);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_status);
+ }
+
+ // Starts running memtester.
+ void MemtesterStartAsync(
+ const dbus::FileDescriptor& in_outfd,
+ uint32_t in_memory,
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "MemtesterStart",
+ success_callback,
+ error_callback,
+ in_outfd,
+ in_memory);
+ }
+
+ // Stops running memtester.
+ bool MemtesterStop(
+ const std::string& in_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "MemtesterStop",
+ error,
+ in_handle);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Stops running memtester.
+ void MemtesterStopAsync(
+ const std::string& in_handle,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "MemtesterStop",
+ success_callback,
+ error_callback,
+ in_handle);
+ }
+
+ // Starts running badblocks test.
+ bool BadblocksStart(
+ const dbus::FileDescriptor& in_outfd,
+ std::string* out_status,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "BadblocksStart",
+ error,
+ in_outfd);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_status);
+ }
+
+ // Starts running badblocks test.
+ void BadblocksStartAsync(
+ const dbus::FileDescriptor& in_outfd,
+ const base::Callback<void(const std::string& /*status*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "BadblocksStart",
+ success_callback,
+ error_callback,
+ in_outfd);
+ }
+
+ // Stops running badblocks.
+ bool BadblocksStop(
+ const std::string& in_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "BadblocksStop",
+ error,
+ in_handle);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Stops running badblocks.
+ void BadblocksStopAsync(
+ const std::string& in_handle,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "BadblocksStop",
+ success_callback,
+ error_callback,
+ in_handle);
+ }
+
+ // Starts a packet capture with the specified options, with diagnostic
+ // status directed to the "statfd" file descriptor and packet capture
+ // data sent to the "outfd" file descriptor. The returned opaque string
+ // functions as a handle for this particular packet capture. Multiple
+ // captures can be running at once. Captures can be initiated on
+ // Ethernet-like devices or WiFi devices in "client mode" (showing only
+ // Ethernet frames) by specifying the "device" parameter (see below).
+ // By specifying a channel, the script will find or create a "monitor
+ // mode" interface if one is available and produce an "over the air"
+ // packet capture. The name of the output packet capture file is sent
+ // to the output file descriptor.
+ bool PacketCaptureStart(
+ const dbus::FileDescriptor& in_statfd,
+ const dbus::FileDescriptor& in_outfd,
+ const brillo::VariantDictionary& in_options,
+ std::string* out_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "PacketCaptureStart",
+ error,
+ in_statfd,
+ in_outfd,
+ in_options);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_handle);
+ }
+
+ // Starts a packet capture with the specified options, with diagnostic
+ // status directed to the "statfd" file descriptor and packet capture
+ // data sent to the "outfd" file descriptor. The returned opaque string
+ // functions as a handle for this particular packet capture. Multiple
+ // captures can be running at once. Captures can be initiated on
+ // Ethernet-like devices or WiFi devices in "client mode" (showing only
+ // Ethernet frames) by specifying the "device" parameter (see below).
+ // By specifying a channel, the script will find or create a "monitor
+ // mode" interface if one is available and produce an "over the air"
+ // packet capture. The name of the output packet capture file is sent
+ // to the output file descriptor.
+ void PacketCaptureStartAsync(
+ const dbus::FileDescriptor& in_statfd,
+ const dbus::FileDescriptor& in_outfd,
+ const brillo::VariantDictionary& in_options,
+ const base::Callback<void(const std::string& /*handle*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "PacketCaptureStart",
+ success_callback,
+ error_callback,
+ in_statfd,
+ in_outfd,
+ in_options);
+ }
+
+ // Stops a running packet capture.
+ bool PacketCaptureStop(
+ const std::string& in_handle,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "PacketCaptureStop",
+ error,
+ in_handle);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Stops a running packet capture.
+ void PacketCaptureStopAsync(
+ const std::string& in_handle,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "PacketCaptureStop",
+ success_callback,
+ error_callback,
+ in_handle);
+ }
+
+ // Triggers show-task-states(T) SysRq.
+ // See https://www.kernel.org/doc/Documentation/sysrq.txt.
+ bool LogKernelTaskStates(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "LogKernelTaskStates",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Triggers show-task-states(T) SysRq.
+ // See https://www.kernel.org/doc/Documentation/sysrq.txt.
+ void LogKernelTaskStatesAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "LogKernelTaskStates",
+ success_callback,
+ error_callback);
+ }
+
+ // Triggers uploading of system crashes (the crash_sender program).
+ bool UploadCrashes(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "UploadCrashes",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Triggers uploading of system crashes (the crash_sender program).
+ void UploadCrashesAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "UploadCrashes",
+ success_callback,
+ error_callback);
+ }
+
+ // Removes rootfs verification. Requires a system reboot before it will
+ // take effect. Restricted to pre-owner dev mode.
+ bool RemoveRootfsVerification(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "RemoveRootfsVerification",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Removes rootfs verification. Requires a system reboot before it will
+ // take effect. Restricted to pre-owner dev mode.
+ void RemoveRootfsVerificationAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "RemoveRootfsVerification",
+ success_callback,
+ error_callback);
+ }
+
+ // Enables OS booting from a USB image. Restricted to pre-owner dev mode.
+ bool EnableBootFromUsb(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "EnableBootFromUsb",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Enables OS booting from a USB image. Restricted to pre-owner dev mode.
+ void EnableBootFromUsbAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "EnableBootFromUsb",
+ success_callback,
+ error_callback);
+ }
+
+ // Sets up sshd to provide an SSH server immediately and on future reboots.
+ // Also installs the test SSH keys to allow access by cros tools. Requires
+ // that rootfs verification has been removed. Restricted to pre-owner dev
+ // mode.
+ bool ConfigureSshServer(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "ConfigureSshServer",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Sets up sshd to provide an SSH server immediately and on future reboots.
+ // Also installs the test SSH keys to allow access by cros tools. Requires
+ // that rootfs verification has been removed. Restricted to pre-owner dev
+ // mode.
+ void ConfigureSshServerAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "ConfigureSshServer",
+ success_callback,
+ error_callback);
+ }
+
+ // Sets both the system and dev mode password for the indicated account.
+ // Restricted to pre-owner dev mode.
+ bool SetUserPassword(
+ const std::string& in_username,
+ const std::string& in_password,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "SetUserPassword",
+ error,
+ in_username,
+ in_password);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Sets both the system and dev mode password for the indicated account.
+ // Restricted to pre-owner dev mode.
+ void SetUserPasswordAsync(
+ const std::string& in_username,
+ const std::string& in_password,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "SetUserPassword",
+ success_callback,
+ error_callback,
+ in_username,
+ in_password);
+ }
+
+ // Sets up Chrome for remote debugging. It will take effect after a reboot
+ // and using port 9222.
+ // Requires that rootfs verification has been removed. Restricted to
+ // pre-owner dev mode.
+ bool EnableChromeRemoteDebugging(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "EnableChromeRemoteDebugging",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Sets up Chrome for remote debugging. It will take effect after a reboot
+ // and using port 9222.
+ // Requires that rootfs verification has been removed. Restricted to
+ // pre-owner dev mode.
+ void EnableChromeRemoteDebuggingAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "EnableChromeRemoteDebugging",
+ success_callback,
+ error_callback);
+ }
+
+ // Convenience function to enable a predefined set of tools from the Chrome
+ // UI. Equivalent to calling these functions in order:
+ // 1. EnableBootFromUsb()
+ // 2. ConfigureSshServer()
+ // 3. SetUserPassword("root", root_password)
+ // Requires that rootfs verification has been removed. If any sub-function
+ // fails, this function will exit with an error without attempting any
+ // further configuration or rollback. Restricted to pre-owner dev mode.
+ bool EnableChromeDevFeatures(
+ const std::string& in_root_password,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "EnableChromeDevFeatures",
+ error,
+ in_root_password);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Convenience function to enable a predefined set of tools from the Chrome
+ // UI. Equivalent to calling these functions in order:
+ // 1. EnableBootFromUsb()
+ // 2. ConfigureSshServer()
+ // 3. SetUserPassword("root", root_password)
+ // Requires that rootfs verification has been removed. If any sub-function
+ // fails, this function will exit with an error without attempting any
+ // further configuration or rollback. Restricted to pre-owner dev mode.
+ void EnableChromeDevFeaturesAsync(
+ const std::string& in_root_password,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "EnableChromeDevFeatures",
+ success_callback,
+ error_callback,
+ in_root_password);
+ }
+
+ // Queries which dev features have been enabled. Each dev feature will be
+ // indicated by a bit flag in the return value. Flags are defined in the
+ // DevFeatureFlag enumeration. If the dev tools are unavailable (system is
+ // not in dev mode/pre-login state), the DEV_FEATURES_DISABLED flag will be
+ // set and the rest of the bits will always be set to 0.
+ bool QueryDevFeatures(
+ int32_t* out_features,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "QueryDevFeatures",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_features);
+ }
+
+ // Queries which dev features have been enabled. Each dev feature will be
+ // indicated by a bit flag in the return value. Flags are defined in the
+ // DevFeatureFlag enumeration. If the dev tools are unavailable (system is
+ // not in dev mode/pre-login state), the DEV_FEATURES_DISABLED flag will be
+ // set and the rest of the bits will always be set to 0.
+ void QueryDevFeaturesAsync(
+ const base::Callback<void(int32_t /*features*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "QueryDevFeatures",
+ success_callback,
+ error_callback);
+ }
+
+ // Allow uploading of device coredump files.
+ bool EnableDevCoredumpUpload(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "EnableDevCoredumpUpload",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Allow uploading of device coredump files.
+ void EnableDevCoredumpUploadAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "EnableDevCoredumpUpload",
+ success_callback,
+ error_callback);
+ }
+
+ // Disallow uploading of device coredump files.
+ bool DisableDevCoredumpUpload(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "DisableDevCoredumpUpload",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // Disallow uploading of device coredump files.
+ void DisableDevCoredumpUploadAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.debugd",
+ "DisableDevCoredumpUpload",
+ success_callback,
+ error_callback);
+ }
+
+ private:
+ scoped_refptr<dbus::Bus> bus_;
+ const std::string service_name_{"org.chromium.debugd"};
+ const dbus::ObjectPath object_path_{"/org/chromium/debugd"};
+ dbus::ObjectProxy* dbus_object_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(debugdProxy);
+};
+
+} // namespace chromium
+} // namespace org
+
+#endif // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXIES_H
diff --git a/include/debugd/dbus-proxy-mocks.h b/include/debugd/dbus-proxy-mocks.h
new file mode 100644
index 0000000..042a9fd
--- /dev/null
+++ b/include/debugd/dbus-proxy-mocks.h
@@ -0,0 +1,453 @@
+// Automatic generation of D-Bus interface mock proxies for:
+// - org.chromium.debugd
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXY_MOCKS_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXY_MOCKS_H
+#include <string>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/any.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <gmock/gmock.h>
+
+#include "debugd/dbus-proxies.h"
+
+namespace org {
+namespace chromium {
+
+// Mock object for debugdProxyInterface.
+class debugdProxyMock : public debugdProxyInterface {
+ public:
+ debugdProxyMock() = default;
+
+ MOCK_METHOD6(PingStart,
+ bool(const dbus::FileDescriptor& /*in_outfd*/,
+ const std::string& /*in_destination*/,
+ const brillo::VariantDictionary& /*in_options*/,
+ std::string* /*out_handle*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD6(PingStartAsync,
+ void(const dbus::FileDescriptor& /*in_outfd*/,
+ const std::string& /*in_destination*/,
+ const brillo::VariantDictionary& /*in_options*/,
+ const base::Callback<void(const std::string& /*handle*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(PingStop,
+ bool(const std::string& /*in_handle*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(PingStopAsync,
+ void(const std::string& /*in_handle*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(SystraceStart,
+ bool(const std::string& /*in_categories*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(SystraceStartAsync,
+ void(const std::string& /*in_categories*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(SystraceStop,
+ bool(const dbus::FileDescriptor& /*in_outfd*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(SystraceStopAsync,
+ void(const dbus::FileDescriptor& /*in_outfd*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(SystraceStatus,
+ bool(std::string* /*out_status*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(SystraceStatusAsync,
+ void(const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD6(TracePathStart,
+ bool(const dbus::FileDescriptor& /*in_outfd*/,
+ const std::string& /*in_destination*/,
+ const brillo::VariantDictionary& /*in_options*/,
+ std::string* /*out_handle*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD6(TracePathStartAsync,
+ void(const dbus::FileDescriptor& /*in_outfd*/,
+ const std::string& /*in_destination*/,
+ const brillo::VariantDictionary& /*in_options*/,
+ const base::Callback<void(const std::string& /*handle*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(TracePathStop,
+ bool(const std::string& /*in_handle*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(TracePathStopAsync,
+ void(const std::string& /*in_handle*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(GetRoutes,
+ bool(const brillo::VariantDictionary& /*in_options*/,
+ std::vector<std::string>* /*out_result*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(GetRoutesAsync,
+ void(const brillo::VariantDictionary& /*in_options*/,
+ const base::Callback<void(const std::vector<std::string>& /*result*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetModemStatus,
+ bool(std::string* /*out_status*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetModemStatusAsync,
+ void(const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RunModemCommand,
+ bool(const std::string& /*in_command*/,
+ std::string* /*out_status*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RunModemCommandAsync,
+ void(const std::string& /*in_command*/,
+ const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetNetworkStatus,
+ bool(std::string* /*out_status*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetNetworkStatusAsync,
+ void(const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetWiMaxStatus,
+ bool(std::string* /*out_status*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetWiMaxStatusAsync,
+ void(const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD7(GetPerfOutput,
+ bool(uint32_t /*in_duration_sec*/,
+ const std::vector<std::string>& /*in_perf_args*/,
+ int32_t* /*out_status*/,
+ std::vector<uint8_t>* /*out_perf_data*/,
+ std::vector<uint8_t>* /*out_perf_stat*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(GetPerfOutputAsync,
+ void(uint32_t /*in_duration_sec*/,
+ const std::vector<std::string>& /*in_perf_args*/,
+ const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD6(GetRandomPerfOutput,
+ bool(uint32_t /*in_duration_sec*/,
+ int32_t* /*out_status*/,
+ std::vector<uint8_t>* /*out_perf_data*/,
+ std::vector<uint8_t>* /*out_perf_stat*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(GetRandomPerfOutputAsync,
+ void(uint32_t /*in_duration_sec*/,
+ const base::Callback<void(int32_t /*status*/, const std::vector<uint8_t>& /*perf_data*/, const std::vector<uint8_t>& /*perf_stat*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(GetRichPerfData,
+ bool(uint32_t /*in_duration_sec*/,
+ std::vector<uint8_t>* /*out_status*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(GetRichPerfDataAsync,
+ void(uint32_t /*in_duration_sec*/,
+ const base::Callback<void(const std::vector<uint8_t>& /*status*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetDebugLogs,
+ bool(const dbus::FileDescriptor& /*in_outfd*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(GetDebugLogsAsync,
+ void(const dbus::FileDescriptor& /*in_outfd*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(DumpDebugLogs,
+ bool(bool /*in_is_compressed*/,
+ const dbus::FileDescriptor& /*in_outfd*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(DumpDebugLogsAsync,
+ void(bool /*in_is_compressed*/,
+ const dbus::FileDescriptor& /*in_outfd*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(SetDebugMode,
+ bool(const std::string& /*in_subsystem*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(SetDebugModeAsync,
+ void(const std::string& /*in_subsystem*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(GetLog,
+ bool(const std::string& /*in_log*/,
+ std::string* /*out_contents*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(GetLogAsync,
+ void(const std::string& /*in_log*/,
+ const base::Callback<void(const std::string& /*contents*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetAllLogs,
+ bool(std::map<std::string, std::string>* /*out_logs*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetAllLogsAsync,
+ void(const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetFeedbackLogs,
+ bool(std::map<std::string, std::string>* /*out_logs*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetFeedbackLogsAsync,
+ void(const base::Callback<void(const std::map<std::string, std::string>& /*logs*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetUserLogFiles,
+ bool(std::map<std::string, std::string>* /*out_user_log_files*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetUserLogFilesAsync,
+ void(const base::Callback<void(const std::map<std::string, std::string>& /*user_log_files*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetExample,
+ bool(std::string* /*out_result*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetExampleAsync,
+ void(const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetInterfaces,
+ bool(std::string* /*out_result*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetInterfacesAsync,
+ void(const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(TestICMP,
+ bool(const std::string& /*in_host*/,
+ std::string* /*out_result*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(TestICMPAsync,
+ void(const std::string& /*in_host*/,
+ const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(TestICMPWithOptions,
+ bool(const std::string& /*in_host*/,
+ const std::map<std::string, std::string>& /*in_options*/,
+ std::string* /*out_result*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(TestICMPWithOptionsAsync,
+ void(const std::string& /*in_host*/,
+ const std::map<std::string, std::string>& /*in_options*/,
+ const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(BatteryFirmware,
+ bool(const std::string& /*in_option*/,
+ std::string* /*out_result*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(BatteryFirmwareAsync,
+ void(const std::string& /*in_option*/,
+ const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(Smartctl,
+ bool(const std::string& /*in_option*/,
+ std::string* /*out_result*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(SmartctlAsync,
+ void(const std::string& /*in_option*/,
+ const base::Callback<void(const std::string& /*result*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(MemtesterStart,
+ bool(const dbus::FileDescriptor& /*in_outfd*/,
+ uint32_t /*in_memory*/,
+ std::string* /*out_status*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(MemtesterStartAsync,
+ void(const dbus::FileDescriptor& /*in_outfd*/,
+ uint32_t /*in_memory*/,
+ const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(MemtesterStop,
+ bool(const std::string& /*in_handle*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(MemtesterStopAsync,
+ void(const std::string& /*in_handle*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(BadblocksStart,
+ bool(const dbus::FileDescriptor& /*in_outfd*/,
+ std::string* /*out_status*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(BadblocksStartAsync,
+ void(const dbus::FileDescriptor& /*in_outfd*/,
+ const base::Callback<void(const std::string& /*status*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(BadblocksStop,
+ bool(const std::string& /*in_handle*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(BadblocksStopAsync,
+ void(const std::string& /*in_handle*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD6(PacketCaptureStart,
+ bool(const dbus::FileDescriptor& /*in_statfd*/,
+ const dbus::FileDescriptor& /*in_outfd*/,
+ const brillo::VariantDictionary& /*in_options*/,
+ std::string* /*out_handle*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD6(PacketCaptureStartAsync,
+ void(const dbus::FileDescriptor& /*in_statfd*/,
+ const dbus::FileDescriptor& /*in_outfd*/,
+ const brillo::VariantDictionary& /*in_options*/,
+ const base::Callback<void(const std::string& /*handle*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(PacketCaptureStop,
+ bool(const std::string& /*in_handle*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(PacketCaptureStopAsync,
+ void(const std::string& /*in_handle*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(LogKernelTaskStates,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(LogKernelTaskStatesAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(UploadCrashes,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(UploadCrashesAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(RemoveRootfsVerification,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RemoveRootfsVerificationAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(EnableBootFromUsb,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(EnableBootFromUsbAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(ConfigureSshServer,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(ConfigureSshServerAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(SetUserPassword,
+ bool(const std::string& /*in_username*/,
+ const std::string& /*in_password*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(SetUserPasswordAsync,
+ void(const std::string& /*in_username*/,
+ const std::string& /*in_password*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(EnableChromeRemoteDebugging,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(EnableChromeRemoteDebuggingAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(EnableChromeDevFeatures,
+ bool(const std::string& /*in_root_password*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(EnableChromeDevFeaturesAsync,
+ void(const std::string& /*in_root_password*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(QueryDevFeatures,
+ bool(int32_t* /*out_features*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(QueryDevFeaturesAsync,
+ void(const base::Callback<void(int32_t /*features*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(EnableDevCoredumpUpload,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(EnableDevCoredumpUploadAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(DisableDevCoredumpUpload,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(DisableDevCoredumpUploadAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(debugdProxyMock);
+};
+} // namespace chromium
+} // namespace org
+
+#endif // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_DEBUGD_CLIENT_OUT_DEFAULT_GEN_INCLUDE_DEBUGD_DBUS_PROXY_MOCKS_H
diff --git a/include/power_manager/dbus-constants.h b/include/power_manager/dbus-constants.h
new file mode 100644
index 0000000..fa42cb1
--- /dev/null
+++ b/include/power_manager/dbus-constants.h
@@ -0,0 +1,66 @@
+// Copyright 2015 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.
+
+#ifndef SYSTEM_API_DBUS_POWER_MANAGER_DBUS_CONSTANTS_H_
+#define SYSTEM_API_DBUS_POWER_MANAGER_DBUS_CONSTANTS_H_
+
+namespace power_manager {
+// powerd
+const char kPowerManagerInterface[] = "org.chromium.PowerManager";
+const char kPowerManagerServicePath[] = "/org/chromium/PowerManager";
+const char kPowerManagerServiceName[] = "org.chromium.PowerManager";
+// Methods exposed by powerd.
+const char kDecreaseScreenBrightnessMethod[] = "DecreaseScreenBrightness";
+const char kIncreaseScreenBrightnessMethod[] = "IncreaseScreenBrightness";
+const char kGetScreenBrightnessPercentMethod[] = "GetScreenBrightnessPercent";
+const char kSetScreenBrightnessPercentMethod[] = "SetScreenBrightnessPercent";
+const char kDecreaseKeyboardBrightnessMethod[] = "DecreaseKeyboardBrightness";
+const char kIncreaseKeyboardBrightnessMethod[] = "IncreaseKeyboardBrightness";
+const char kRequestRestartMethod[] = "RequestRestart";
+const char kRequestShutdownMethod[] = "RequestShutdown";
+const char kRequestSuspendMethod[] = "RequestSuspend";
+const char kGetPowerSupplyPropertiesMethod[] = "GetPowerSupplyProperties";
+const char kHandleUserActivityMethod[] = "HandleUserActivity";
+const char kHandleVideoActivityMethod[] = "HandleVideoActivity";
+const char kSetIsProjectingMethod[] = "SetIsProjecting";
+const char kSetPolicyMethod[] = "SetPolicy";
+const char kSetPowerSourceMethod[] = "SetPowerSource";
+const char kRegisterSuspendDelayMethod[] = "RegisterSuspendDelay";
+const char kUnregisterSuspendDelayMethod[] = "UnregisterSuspendDelay";
+const char kHandleSuspendReadinessMethod[] = "HandleSuspendReadiness";
+const char kRegisterDarkSuspendDelayMethod[] = "RegisterDarkSuspendDelay";
+const char kUnregisterDarkSuspendDelayMethod[] = "UnregisterDarkSuspendDelay";
+const char kHandleDarkSuspendReadinessMethod[] = "HandleDarkSuspendReadiness";
+const char kHandlePowerButtonAcknowledgmentMethod[] =
+ "HandlePowerButtonAcknowledgment";
+const char kRecordDarkResumeWakeReasonMethod[] = "RecordDarkResumeWakeReason";
+// Signals emitted by powerd.
+const char kBrightnessChangedSignal[] = "BrightnessChanged";
+const char kKeyboardBrightnessChangedSignal[] = "KeyboardBrightnessChanged";
+const char kPeripheralBatteryStatusSignal[] = "PeripheralBatteryStatus";
+const char kPowerSupplyPollSignal[] = "PowerSupplyPoll";
+const char kSuspendImminentSignal[] = "SuspendImminent";
+const char kDarkSuspendImminentSignal[] = "DarkSuspendImminent";
+const char kSuspendDoneSignal[] = "SuspendDone";
+const char kInputEventSignal[] = "InputEvent";
+const char kIdleActionImminentSignal[] = "IdleActionImminent";
+const char kIdleActionDeferredSignal[] = "IdleActionDeferred";
+// Values
+const int kBrightnessTransitionGradual = 1;
+const int kBrightnessTransitionInstant = 2;
+enum UserActivityType {
+ USER_ACTIVITY_OTHER = 0,
+ USER_ACTIVITY_BRIGHTNESS_UP_KEY_PRESS = 1,
+ USER_ACTIVITY_BRIGHTNESS_DOWN_KEY_PRESS = 2,
+ USER_ACTIVITY_VOLUME_UP_KEY_PRESS = 3,
+ USER_ACTIVITY_VOLUME_DOWN_KEY_PRESS = 4,
+ USER_ACTIVITY_VOLUME_MUTE_KEY_PRESS = 5,
+};
+enum RequestRestartReason {
+ REQUEST_RESTART_FOR_USER = 0,
+ REQUEST_RESTART_FOR_UPDATE = 1,
+};
+} // namespace power_manager
+
+#endif // SYSTEM_API_DBUS_POWER_MANAGER_DBUS_CONSTANTS_H_
diff --git a/include/power_manager/dbus-proxies.h b/include/power_manager/dbus-proxies.h
new file mode 100644
index 0000000..e66848d
--- /dev/null
+++ b/include/power_manager/dbus-proxies.h
@@ -0,0 +1,1280 @@
+// Automatic generation of D-Bus interfaces:
+// - org.chromium.PowerManager
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXIES_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXIES_H
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <brillo/any.h>
+#include <brillo/dbus/dbus_method_invoker.h>
+#include <brillo/dbus/dbus_property.h>
+#include <brillo/dbus/dbus_signal_handler.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+namespace org {
+namespace chromium {
+
+// Abstract interface proxy for org::chromium::PowerManager.
+class PowerManagerProxyInterface {
+ public:
+ virtual ~PowerManagerProxyInterface() = default;
+
+ virtual bool RequestShutdown(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void RequestShutdownAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |reason| arg is a power_manager::RequestRestartReason value.
+ virtual bool RequestRestart(
+ int32_t in_reason,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |reason| arg is a power_manager::RequestRestartReason value.
+ virtual void RequestRestartAsync(
+ int32_t in_reason,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |external_wakeup_count| arg is optional, and it will call two
+ // different methods in the backend. This can't be expressed in the DBus
+ // Introspection XML file.
+ virtual bool RequestSuspend(
+ uint64_t in_external_wakeup_count,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |external_wakeup_count| arg is optional, and it will call two
+ // different methods in the backend. This can't be expressed in the DBus
+ // Introspection XML file.
+ virtual void RequestSuspendAsync(
+ uint64_t in_external_wakeup_count,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool DecreaseScreenBrightness(
+ bool in_allow_off,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void DecreaseScreenBrightnessAsync(
+ bool in_allow_off,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool IncreaseScreenBrightness(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void IncreaseScreenBrightnessAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool GetScreenBrightnessPercent(
+ double* out_percent,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void GetScreenBrightnessPercentAsync(
+ const base::Callback<void(double /*percent*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |style| arg must be one of the values:
+ // power_manager::kBrightnessTransitionGradual or
+ // power_manager::kBrightnessTransitionInstant.
+ virtual bool SetScreenBrightnessPercent(
+ double in_percent,
+ int32_t in_style,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |style| arg must be one of the values:
+ // power_manager::kBrightnessTransitionGradual or
+ // power_manager::kBrightnessTransitionInstant.
+ virtual void SetScreenBrightnessPercentAsync(
+ double in_percent,
+ int32_t in_style,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool DecreaseKeyboardBrightness(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void DecreaseKeyboardBrightnessAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool IncreaseKeyboardBrightness(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void IncreaseKeyboardBrightnessAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::PowerSupplyProperties protobuf.
+ virtual bool GetPowerSupplyProperties(
+ std::vector<uint8_t>* out_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::PowerSupplyProperties protobuf.
+ virtual void GetPowerSupplyPropertiesAsync(
+ const base::Callback<void(const std::vector<uint8_t>& /*serialized_proto*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool HandleVideoActivity(
+ bool in_fullscreen,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void HandleVideoActivityAsync(
+ bool in_fullscreen,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |type| arg is a power_manager::UserActivityType.
+ virtual bool HandleUserActivity(
+ int32_t in_type,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |type| arg is a power_manager::UserActivityType.
+ virtual void HandleUserActivityAsync(
+ int32_t in_type,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool SetIsProjecting(
+ bool in_is_projecting,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void SetIsProjectingAsync(
+ bool in_is_projecting,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::PowerManagementPolicy protobuf.
+ virtual bool SetPolicy(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::PowerManagementPolicy protobuf.
+ virtual void SetPolicyAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool SetPowerSource(
+ const std::string& in_id,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void SetPowerSourceAsync(
+ const std::string& in_id,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |timestamp_internal| arg is represented as the return value of
+ // base::TimeTicks::ToInternalValue().
+ virtual bool HandlePowerButtonAcknowledgment(
+ int64_t in_timestamp_internal,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |timestamp_internal| arg is represented as the return value of
+ // base::TimeTicks::ToInternalValue().
+ virtual void HandlePowerButtonAcknowledgmentAsync(
+ int64_t in_timestamp_internal,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_request_proto| arg is a serialized
+ // power_manager::RegisterSuspendDelayRequest protobuf.
+ // The |serialized_reply_proto| arg is a serialized
+ // RegisterSuspendDelayReply protobuf.
+ virtual bool RegisterSuspendDelay(
+ const std::vector<uint8_t>& in_serialized_request_proto,
+ std::vector<uint8_t>* out_serialized_reply_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_request_proto| arg is a serialized
+ // power_manager::RegisterSuspendDelayRequest protobuf.
+ // The |serialized_reply_proto| arg is a serialized
+ // RegisterSuspendDelayReply protobuf.
+ virtual void RegisterSuspendDelayAsync(
+ const std::vector<uint8_t>& in_serialized_request_proto,
+ const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::UnregisterSuspendDelayRequest protobuf.
+ virtual bool UnregisterSuspendDelay(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::UnregisterSuspendDelayRequest protobuf.
+ virtual void UnregisterSuspendDelayAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::SuspendReadinessInfo protobuf.
+ virtual bool HandleSuspendReadiness(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::SuspendReadinessInfo protobuf.
+ virtual void HandleSuspendReadinessAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_request_proto| arg is a serialized
+ // power_manager::RegisterSuspendDelayRequest protobuf.
+ // The |serialized_reply_proto| arg is a serialized
+ // RegisterSuspendDelayReply protobuf.
+ virtual bool RegisterDarkSuspendDelay(
+ const std::vector<uint8_t>& in_serialized_request_proto,
+ std::vector<uint8_t>* out_serialized_reply_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_request_proto| arg is a serialized
+ // power_manager::RegisterSuspendDelayRequest protobuf.
+ // The |serialized_reply_proto| arg is a serialized
+ // RegisterSuspendDelayReply protobuf.
+ virtual void RegisterDarkSuspendDelayAsync(
+ const std::vector<uint8_t>& in_serialized_request_proto,
+ const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::UnregisterSuspendDelayRequest protobuf.
+ virtual bool UnregisterDarkSuspendDelay(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::UnregisterSuspendDelayRequest protobuf.
+ virtual void UnregisterDarkSuspendDelayAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::SuspendReadinessInfo protobuf.
+ virtual bool HandleDarkSuspendReadiness(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::SuspendReadinessInfo protobuf.
+ virtual void HandleDarkSuspendReadinessAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::DarkResumeWakeReason protobuf.
+ virtual bool RecordDarkResumeWakeReason(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::DarkResumeWakeReason protobuf.
+ virtual void RecordDarkResumeWakeReasonAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void RegisterBrightnessChangedSignalHandler(
+ const base::Callback<void(int32_t,
+ bool)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterKeyboardBrightnessChangedSignalHandler(
+ const base::Callback<void(int32_t,
+ bool)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterPeripheralBatteryStatusSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterPowerSupplyPollSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterSuspendImminentSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterSuspendDoneSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterDarkSuspendImminentSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterInputEventSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterIdleActionImminentSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterIdleActionDeferredSignalHandler(
+ const base::Closure& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+};
+
+} // namespace chromium
+} // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::PowerManager.
+class PowerManagerProxy final : public PowerManagerProxyInterface {
+ public:
+ PowerManagerProxy(const scoped_refptr<dbus::Bus>& bus) :
+ bus_{bus},
+ dbus_object_proxy_{
+ bus_->GetObjectProxy(service_name_, object_path_)} {
+ }
+
+ ~PowerManagerProxy() override {
+ bus_->RemoveObjectProxy(
+ service_name_, object_path_, base::Bind(&base::DoNothing));
+ }
+
+ void RegisterBrightnessChangedSignalHandler(
+ const base::Callback<void(int32_t,
+ bool)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "BrightnessChanged",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterKeyboardBrightnessChangedSignalHandler(
+ const base::Callback<void(int32_t,
+ bool)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "KeyboardBrightnessChanged",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterPeripheralBatteryStatusSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "PeripheralBatteryStatus",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterPowerSupplyPollSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "PowerSupplyPoll",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterSuspendImminentSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "SuspendImminent",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterSuspendDoneSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "SuspendDone",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterDarkSuspendImminentSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "DarkSuspendImminent",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterInputEventSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "InputEvent",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterIdleActionImminentSignalHandler(
+ const base::Callback<void(const std::vector<uint8_t>&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "IdleActionImminent",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterIdleActionDeferredSignalHandler(
+ const base::Closure& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "IdleActionDeferred",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void ReleaseObjectProxy(const base::Closure& callback) {
+ bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+ }
+
+ const dbus::ObjectPath& GetObjectPath() const {
+ return object_path_;
+ }
+
+ dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+ bool RequestShutdown(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RequestShutdown",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void RequestShutdownAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RequestShutdown",
+ success_callback,
+ error_callback);
+ }
+
+ // The |reason| arg is a power_manager::RequestRestartReason value.
+ bool RequestRestart(
+ int32_t in_reason,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RequestRestart",
+ error,
+ in_reason);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |reason| arg is a power_manager::RequestRestartReason value.
+ void RequestRestartAsync(
+ int32_t in_reason,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RequestRestart",
+ success_callback,
+ error_callback,
+ in_reason);
+ }
+
+ // The |external_wakeup_count| arg is optional, and it will call two
+ // different methods in the backend. This can't be expressed in the DBus
+ // Introspection XML file.
+ bool RequestSuspend(
+ uint64_t in_external_wakeup_count,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RequestSuspend",
+ error,
+ in_external_wakeup_count);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |external_wakeup_count| arg is optional, and it will call two
+ // different methods in the backend. This can't be expressed in the DBus
+ // Introspection XML file.
+ void RequestSuspendAsync(
+ uint64_t in_external_wakeup_count,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RequestSuspend",
+ success_callback,
+ error_callback,
+ in_external_wakeup_count);
+ }
+
+ bool DecreaseScreenBrightness(
+ bool in_allow_off,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "DecreaseScreenBrightness",
+ error,
+ in_allow_off);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void DecreaseScreenBrightnessAsync(
+ bool in_allow_off,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "DecreaseScreenBrightness",
+ success_callback,
+ error_callback,
+ in_allow_off);
+ }
+
+ bool IncreaseScreenBrightness(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "IncreaseScreenBrightness",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void IncreaseScreenBrightnessAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "IncreaseScreenBrightness",
+ success_callback,
+ error_callback);
+ }
+
+ bool GetScreenBrightnessPercent(
+ double* out_percent,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "GetScreenBrightnessPercent",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_percent);
+ }
+
+ void GetScreenBrightnessPercentAsync(
+ const base::Callback<void(double /*percent*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "GetScreenBrightnessPercent",
+ success_callback,
+ error_callback);
+ }
+
+ // The |style| arg must be one of the values:
+ // power_manager::kBrightnessTransitionGradual or
+ // power_manager::kBrightnessTransitionInstant.
+ bool SetScreenBrightnessPercent(
+ double in_percent,
+ int32_t in_style,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "SetScreenBrightnessPercent",
+ error,
+ in_percent,
+ in_style);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |style| arg must be one of the values:
+ // power_manager::kBrightnessTransitionGradual or
+ // power_manager::kBrightnessTransitionInstant.
+ void SetScreenBrightnessPercentAsync(
+ double in_percent,
+ int32_t in_style,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "SetScreenBrightnessPercent",
+ success_callback,
+ error_callback,
+ in_percent,
+ in_style);
+ }
+
+ bool DecreaseKeyboardBrightness(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "DecreaseKeyboardBrightness",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void DecreaseKeyboardBrightnessAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "DecreaseKeyboardBrightness",
+ success_callback,
+ error_callback);
+ }
+
+ bool IncreaseKeyboardBrightness(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "IncreaseKeyboardBrightness",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void IncreaseKeyboardBrightnessAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "IncreaseKeyboardBrightness",
+ success_callback,
+ error_callback);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::PowerSupplyProperties protobuf.
+ bool GetPowerSupplyProperties(
+ std::vector<uint8_t>* out_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "GetPowerSupplyProperties",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_serialized_proto);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::PowerSupplyProperties protobuf.
+ void GetPowerSupplyPropertiesAsync(
+ const base::Callback<void(const std::vector<uint8_t>& /*serialized_proto*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "GetPowerSupplyProperties",
+ success_callback,
+ error_callback);
+ }
+
+ bool HandleVideoActivity(
+ bool in_fullscreen,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "HandleVideoActivity",
+ error,
+ in_fullscreen);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void HandleVideoActivityAsync(
+ bool in_fullscreen,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "HandleVideoActivity",
+ success_callback,
+ error_callback,
+ in_fullscreen);
+ }
+
+ // The |type| arg is a power_manager::UserActivityType.
+ bool HandleUserActivity(
+ int32_t in_type,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "HandleUserActivity",
+ error,
+ in_type);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |type| arg is a power_manager::UserActivityType.
+ void HandleUserActivityAsync(
+ int32_t in_type,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "HandleUserActivity",
+ success_callback,
+ error_callback,
+ in_type);
+ }
+
+ bool SetIsProjecting(
+ bool in_is_projecting,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "SetIsProjecting",
+ error,
+ in_is_projecting);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void SetIsProjectingAsync(
+ bool in_is_projecting,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "SetIsProjecting",
+ success_callback,
+ error_callback,
+ in_is_projecting);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::PowerManagementPolicy protobuf.
+ bool SetPolicy(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "SetPolicy",
+ error,
+ in_serialized_proto);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::PowerManagementPolicy protobuf.
+ void SetPolicyAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "SetPolicy",
+ success_callback,
+ error_callback,
+ in_serialized_proto);
+ }
+
+ bool SetPowerSource(
+ const std::string& in_id,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "SetPowerSource",
+ error,
+ in_id);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void SetPowerSourceAsync(
+ const std::string& in_id,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "SetPowerSource",
+ success_callback,
+ error_callback,
+ in_id);
+ }
+
+ // The |timestamp_internal| arg is represented as the return value of
+ // base::TimeTicks::ToInternalValue().
+ bool HandlePowerButtonAcknowledgment(
+ int64_t in_timestamp_internal,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "HandlePowerButtonAcknowledgment",
+ error,
+ in_timestamp_internal);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |timestamp_internal| arg is represented as the return value of
+ // base::TimeTicks::ToInternalValue().
+ void HandlePowerButtonAcknowledgmentAsync(
+ int64_t in_timestamp_internal,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "HandlePowerButtonAcknowledgment",
+ success_callback,
+ error_callback,
+ in_timestamp_internal);
+ }
+
+ // The |serialized_request_proto| arg is a serialized
+ // power_manager::RegisterSuspendDelayRequest protobuf.
+ // The |serialized_reply_proto| arg is a serialized
+ // RegisterSuspendDelayReply protobuf.
+ bool RegisterSuspendDelay(
+ const std::vector<uint8_t>& in_serialized_request_proto,
+ std::vector<uint8_t>* out_serialized_reply_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RegisterSuspendDelay",
+ error,
+ in_serialized_request_proto);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_serialized_reply_proto);
+ }
+
+ // The |serialized_request_proto| arg is a serialized
+ // power_manager::RegisterSuspendDelayRequest protobuf.
+ // The |serialized_reply_proto| arg is a serialized
+ // RegisterSuspendDelayReply protobuf.
+ void RegisterSuspendDelayAsync(
+ const std::vector<uint8_t>& in_serialized_request_proto,
+ const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RegisterSuspendDelay",
+ success_callback,
+ error_callback,
+ in_serialized_request_proto);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::UnregisterSuspendDelayRequest protobuf.
+ bool UnregisterSuspendDelay(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "UnregisterSuspendDelay",
+ error,
+ in_serialized_proto);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::UnregisterSuspendDelayRequest protobuf.
+ void UnregisterSuspendDelayAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "UnregisterSuspendDelay",
+ success_callback,
+ error_callback,
+ in_serialized_proto);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::SuspendReadinessInfo protobuf.
+ bool HandleSuspendReadiness(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "HandleSuspendReadiness",
+ error,
+ in_serialized_proto);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::SuspendReadinessInfo protobuf.
+ void HandleSuspendReadinessAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "HandleSuspendReadiness",
+ success_callback,
+ error_callback,
+ in_serialized_proto);
+ }
+
+ // The |serialized_request_proto| arg is a serialized
+ // power_manager::RegisterSuspendDelayRequest protobuf.
+ // The |serialized_reply_proto| arg is a serialized
+ // RegisterSuspendDelayReply protobuf.
+ bool RegisterDarkSuspendDelay(
+ const std::vector<uint8_t>& in_serialized_request_proto,
+ std::vector<uint8_t>* out_serialized_reply_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RegisterDarkSuspendDelay",
+ error,
+ in_serialized_request_proto);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_serialized_reply_proto);
+ }
+
+ // The |serialized_request_proto| arg is a serialized
+ // power_manager::RegisterSuspendDelayRequest protobuf.
+ // The |serialized_reply_proto| arg is a serialized
+ // RegisterSuspendDelayReply protobuf.
+ void RegisterDarkSuspendDelayAsync(
+ const std::vector<uint8_t>& in_serialized_request_proto,
+ const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RegisterDarkSuspendDelay",
+ success_callback,
+ error_callback,
+ in_serialized_request_proto);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::UnregisterSuspendDelayRequest protobuf.
+ bool UnregisterDarkSuspendDelay(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "UnregisterDarkSuspendDelay",
+ error,
+ in_serialized_proto);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::UnregisterSuspendDelayRequest protobuf.
+ void UnregisterDarkSuspendDelayAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "UnregisterDarkSuspendDelay",
+ success_callback,
+ error_callback,
+ in_serialized_proto);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::SuspendReadinessInfo protobuf.
+ bool HandleDarkSuspendReadiness(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "HandleDarkSuspendReadiness",
+ error,
+ in_serialized_proto);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::SuspendReadinessInfo protobuf.
+ void HandleDarkSuspendReadinessAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "HandleDarkSuspendReadiness",
+ success_callback,
+ error_callback,
+ in_serialized_proto);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::DarkResumeWakeReason protobuf.
+ bool RecordDarkResumeWakeReason(
+ const std::vector<uint8_t>& in_serialized_proto,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RecordDarkResumeWakeReason",
+ error,
+ in_serialized_proto);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ // The |serialized_proto| arg is a serialized
+ // power_manager::DarkResumeWakeReason protobuf.
+ void RecordDarkResumeWakeReasonAsync(
+ const std::vector<uint8_t>& in_serialized_proto,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.PowerManager",
+ "RecordDarkResumeWakeReason",
+ success_callback,
+ error_callback,
+ in_serialized_proto);
+ }
+
+ private:
+ scoped_refptr<dbus::Bus> bus_;
+ const std::string service_name_{"org.chromium.PowerManager"};
+ const dbus::ObjectPath object_path_{"/org/chromium/PowerManager"};
+ dbus::ObjectProxy* dbus_object_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(PowerManagerProxy);
+};
+
+} // namespace chromium
+} // namespace org
+
+#endif // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXIES_H
diff --git a/include/power_manager/dbus-proxy-mocks.h b/include/power_manager/dbus-proxy-mocks.h
new file mode 100644
index 0000000..d4e3dd0
--- /dev/null
+++ b/include/power_manager/dbus-proxy-mocks.h
@@ -0,0 +1,266 @@
+// Automatic generation of D-Bus interface mock proxies for:
+// - org.chromium.PowerManager
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXY_MOCKS_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXY_MOCKS_H
+#include <string>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/any.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <gmock/gmock.h>
+
+#include "power_manager/dbus-proxies.h"
+
+namespace org {
+namespace chromium {
+
+// Mock object for PowerManagerProxyInterface.
+class PowerManagerProxyMock : public PowerManagerProxyInterface {
+ public:
+ PowerManagerProxyMock() = default;
+
+ MOCK_METHOD2(RequestShutdown,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RequestShutdownAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RequestRestart,
+ bool(int32_t /*in_reason*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RequestRestartAsync,
+ void(int32_t /*in_reason*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RequestSuspend,
+ bool(uint64_t /*in_external_wakeup_count*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RequestSuspendAsync,
+ void(uint64_t /*in_external_wakeup_count*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(DecreaseScreenBrightness,
+ bool(bool /*in_allow_off*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(DecreaseScreenBrightnessAsync,
+ void(bool /*in_allow_off*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(IncreaseScreenBrightness,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(IncreaseScreenBrightnessAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetScreenBrightnessPercent,
+ bool(double* /*out_percent*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetScreenBrightnessPercentAsync,
+ void(const base::Callback<void(double /*percent*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(SetScreenBrightnessPercent,
+ bool(double /*in_percent*/,
+ int32_t /*in_style*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(SetScreenBrightnessPercentAsync,
+ void(double /*in_percent*/,
+ int32_t /*in_style*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(DecreaseKeyboardBrightness,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(DecreaseKeyboardBrightnessAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(IncreaseKeyboardBrightness,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(IncreaseKeyboardBrightnessAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetPowerSupplyProperties,
+ bool(std::vector<uint8_t>* /*out_serialized_proto*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetPowerSupplyPropertiesAsync,
+ void(const base::Callback<void(const std::vector<uint8_t>& /*serialized_proto*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(HandleVideoActivity,
+ bool(bool /*in_fullscreen*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(HandleVideoActivityAsync,
+ void(bool /*in_fullscreen*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(HandleUserActivity,
+ bool(int32_t /*in_type*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(HandleUserActivityAsync,
+ void(int32_t /*in_type*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(SetIsProjecting,
+ bool(bool /*in_is_projecting*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(SetIsProjectingAsync,
+ void(bool /*in_is_projecting*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(SetPolicy,
+ bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(SetPolicyAsync,
+ void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(SetPowerSource,
+ bool(const std::string& /*in_id*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(SetPowerSourceAsync,
+ void(const std::string& /*in_id*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(HandlePowerButtonAcknowledgment,
+ bool(int64_t /*in_timestamp_internal*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(HandlePowerButtonAcknowledgmentAsync,
+ void(int64_t /*in_timestamp_internal*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RegisterSuspendDelay,
+ bool(const std::vector<uint8_t>& /*in_serialized_request_proto*/,
+ std::vector<uint8_t>* /*out_serialized_reply_proto*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RegisterSuspendDelayAsync,
+ void(const std::vector<uint8_t>& /*in_serialized_request_proto*/,
+ const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(UnregisterSuspendDelay,
+ bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(UnregisterSuspendDelayAsync,
+ void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(HandleSuspendReadiness,
+ bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(HandleSuspendReadinessAsync,
+ void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RegisterDarkSuspendDelay,
+ bool(const std::vector<uint8_t>& /*in_serialized_request_proto*/,
+ std::vector<uint8_t>* /*out_serialized_reply_proto*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RegisterDarkSuspendDelayAsync,
+ void(const std::vector<uint8_t>& /*in_serialized_request_proto*/,
+ const base::Callback<void(const std::vector<uint8_t>& /*serialized_reply_proto*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(UnregisterDarkSuspendDelay,
+ bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(UnregisterDarkSuspendDelayAsync,
+ void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(HandleDarkSuspendReadiness,
+ bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(HandleDarkSuspendReadinessAsync,
+ void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RecordDarkResumeWakeReason,
+ bool(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RecordDarkResumeWakeReasonAsync,
+ void(const std::vector<uint8_t>& /*in_serialized_proto*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(RegisterBrightnessChangedSignalHandler,
+ void(const base::Callback<void(int32_t,
+ bool)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterKeyboardBrightnessChangedSignalHandler,
+ void(const base::Callback<void(int32_t,
+ bool)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterPeripheralBatteryStatusSignalHandler,
+ void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterPowerSupplyPollSignalHandler,
+ void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterSuspendImminentSignalHandler,
+ void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterSuspendDoneSignalHandler,
+ void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterDarkSuspendImminentSignalHandler,
+ void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterInputEventSignalHandler,
+ void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterIdleActionImminentSignalHandler,
+ void(const base::Callback<void(const std::vector<uint8_t>&)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterIdleActionDeferredSignalHandler,
+ void(const base::Closure& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PowerManagerProxyMock);
+};
+} // namespace chromium
+} // namespace org
+
+#endif // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_TMP_PORTAGE_CHROMEOS_BASE_POWER_MANAGER_9999_WORK_BUILD_OUT_DEFAULT_GEN_INCLUDE_POWER_MANAGER_DBUS_PROXY_MOCKS_H
diff --git a/include/session_manager/dbus-proxies.h b/include/session_manager/dbus-proxies.h
new file mode 100644
index 0000000..2ca0128
--- /dev/null
+++ b/include/session_manager/dbus-proxies.h
@@ -0,0 +1,1065 @@
+// Automatic generation of D-Bus interfaces:
+// - org.chromium.SessionManagerInterface
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXIES_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXIES_H
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <base/memory/ref_counted.h>
+#include <brillo/any.h>
+#include <brillo/dbus/dbus_method_invoker.h>
+#include <brillo/dbus/dbus_property.h>
+#include <brillo/dbus/dbus_signal_handler.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <dbus/bus.h>
+#include <dbus/message.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <dbus/object_proxy.h>
+
+namespace org {
+namespace chromium {
+
+// Abstract interface proxy for org::chromium::SessionManagerInterface.
+class SessionManagerInterfaceProxyInterface {
+ public:
+ virtual ~SessionManagerInterfaceProxyInterface() = default;
+
+ virtual bool EmitLoginPromptVisible(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void EmitLoginPromptVisibleAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool EnableChromeTesting(
+ bool in_force_relaunch,
+ const std::vector<std::string>& in_extra_arguments,
+ std::string* out_filepath,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void EnableChromeTestingAsync(
+ bool in_force_relaunch,
+ const std::vector<std::string>& in_extra_arguments,
+ const base::Callback<void(const std::string& /*filepath*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool StartSession(
+ const std::string& in_email_address,
+ const std::string& in_unique_identifier,
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void StartSessionAsync(
+ const std::string& in_email_address,
+ const std::string& in_unique_identifier,
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool StopSession(
+ const std::string& in_unique_identifier,
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void StopSessionAsync(
+ const std::string& in_unique_identifier,
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool StorePolicy(
+ const std::vector<uint8_t>& in_policy_blob,
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void StorePolicyAsync(
+ const std::vector<uint8_t>& in_policy_blob,
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool RetrievePolicy(
+ std::vector<uint8_t>* out_policy_blob,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void RetrievePolicyAsync(
+ const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool StorePolicyForUser(
+ const std::string& in_user_email,
+ const std::vector<uint8_t>& in_policy_blob,
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void StorePolicyForUserAsync(
+ const std::string& in_user_email,
+ const std::vector<uint8_t>& in_policy_blob,
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool RetrievePolicyForUser(
+ const std::string& in_user_email,
+ std::vector<uint8_t>* out_policy_blob,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void RetrievePolicyForUserAsync(
+ const std::string& in_user_email,
+ const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool StoreDeviceLocalAccountPolicy(
+ const std::string& in_account_id,
+ const std::vector<uint8_t>& in_policy_blob,
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void StoreDeviceLocalAccountPolicyAsync(
+ const std::string& in_account_id,
+ const std::vector<uint8_t>& in_policy_blob,
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool RetrieveDeviceLocalAccountPolicy(
+ const std::string& in_account_id,
+ std::vector<uint8_t>* out_policy_blob,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void RetrieveDeviceLocalAccountPolicyAsync(
+ const std::string& in_account_id,
+ const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool RetrieveSessionState(
+ std::string* out_state,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void RetrieveSessionStateAsync(
+ const base::Callback<void(const std::string& /*state*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool RetrieveActiveSessions(
+ std::map<std::string, std::string>* out_sessions,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void RetrieveActiveSessionsAsync(
+ const base::Callback<void(const std::map<std::string, std::string>& /*sessions*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool HandleSupervisedUserCreationStarting(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void HandleSupervisedUserCreationStartingAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool HandleSupervisedUserCreationFinished(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void HandleSupervisedUserCreationFinishedAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool LockScreen(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void LockScreenAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool HandleLockScreenShown(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void HandleLockScreenShownAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool HandleLockScreenDismissed(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void HandleLockScreenDismissedAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool RestartJob(
+ const dbus::FileDescriptor& in_cred_fd,
+ const std::vector<std::string>& in_argv,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void RestartJobAsync(
+ const dbus::FileDescriptor& in_cred_fd,
+ const std::vector<std::string>& in_argv,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool StartDeviceWipe(
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void StartDeviceWipeAsync(
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool SetFlagsForUser(
+ const std::string& in_user_email,
+ const std::vector<std::string>& in_flags,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void SetFlagsForUserAsync(
+ const std::string& in_user_email,
+ const std::vector<std::string>& in_flags,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool GetServerBackedStateKeys(
+ std::vector<std::vector<uint8_t>>* out_state_keys,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void GetServerBackedStateKeysAsync(
+ const base::Callback<void(const std::vector<std::vector<uint8_t>>& /*state_keys*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual bool InitMachineInfo(
+ const std::string& in_data,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void InitMachineInfoAsync(
+ const std::string& in_data,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0;
+
+ virtual void RegisterLoginPromptVisibleSignalHandler(
+ const base::Closure& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterSessionStateChangedSignalHandler(
+ const base::Callback<void(const std::string&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterSetOwnerKeyCompleteSignalHandler(
+ const base::Callback<void(const std::string&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterPropertyChangeCompleteSignalHandler(
+ const base::Callback<void(const std::string&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterScreenIsLockedSignalHandler(
+ const base::Closure& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+
+ virtual void RegisterScreenIsUnlockedSignalHandler(
+ const base::Closure& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0;
+};
+
+} // namespace chromium
+} // namespace org
+
+namespace org {
+namespace chromium {
+
+// Interface proxy for org::chromium::SessionManagerInterface.
+class SessionManagerInterfaceProxy final : public SessionManagerInterfaceProxyInterface {
+ public:
+ SessionManagerInterfaceProxy(const scoped_refptr<dbus::Bus>& bus) :
+ bus_{bus},
+ dbus_object_proxy_{
+ bus_->GetObjectProxy(service_name_, object_path_)} {
+ }
+
+ ~SessionManagerInterfaceProxy() override {
+ bus_->RemoveObjectProxy(
+ service_name_, object_path_, base::Bind(&base::DoNothing));
+ }
+
+ void RegisterLoginPromptVisibleSignalHandler(
+ const base::Closure& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "LoginPromptVisible",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterSessionStateChangedSignalHandler(
+ const base::Callback<void(const std::string&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "SessionStateChanged",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterSetOwnerKeyCompleteSignalHandler(
+ const base::Callback<void(const std::string&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "SetOwnerKeyComplete",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterPropertyChangeCompleteSignalHandler(
+ const base::Callback<void(const std::string&)>& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "PropertyChangeComplete",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterScreenIsLockedSignalHandler(
+ const base::Closure& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "ScreenIsLocked",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void RegisterScreenIsUnlockedSignalHandler(
+ const base::Closure& signal_callback,
+ dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override {
+ brillo::dbus_utils::ConnectToSignal(
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "ScreenIsUnlocked",
+ signal_callback,
+ on_connected_callback);
+ }
+
+ void ReleaseObjectProxy(const base::Closure& callback) {
+ bus_->RemoveObjectProxy(service_name_, object_path_, callback);
+ }
+
+ const dbus::ObjectPath& GetObjectPath() const {
+ return object_path_;
+ }
+
+ dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; }
+
+ bool EmitLoginPromptVisible(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "EmitLoginPromptVisible",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void EmitLoginPromptVisibleAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "EmitLoginPromptVisible",
+ success_callback,
+ error_callback);
+ }
+
+ bool EnableChromeTesting(
+ bool in_force_relaunch,
+ const std::vector<std::string>& in_extra_arguments,
+ std::string* out_filepath,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "EnableChromeTesting",
+ error,
+ in_force_relaunch,
+ in_extra_arguments);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_filepath);
+ }
+
+ void EnableChromeTestingAsync(
+ bool in_force_relaunch,
+ const std::vector<std::string>& in_extra_arguments,
+ const base::Callback<void(const std::string& /*filepath*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "EnableChromeTesting",
+ success_callback,
+ error_callback,
+ in_force_relaunch,
+ in_extra_arguments);
+ }
+
+ bool StartSession(
+ const std::string& in_email_address,
+ const std::string& in_unique_identifier,
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StartSession",
+ error,
+ in_email_address,
+ in_unique_identifier);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_done);
+ }
+
+ void StartSessionAsync(
+ const std::string& in_email_address,
+ const std::string& in_unique_identifier,
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StartSession",
+ success_callback,
+ error_callback,
+ in_email_address,
+ in_unique_identifier);
+ }
+
+ bool StopSession(
+ const std::string& in_unique_identifier,
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StopSession",
+ error,
+ in_unique_identifier);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_done);
+ }
+
+ void StopSessionAsync(
+ const std::string& in_unique_identifier,
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StopSession",
+ success_callback,
+ error_callback,
+ in_unique_identifier);
+ }
+
+ bool StorePolicy(
+ const std::vector<uint8_t>& in_policy_blob,
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StorePolicy",
+ error,
+ in_policy_blob);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_done);
+ }
+
+ void StorePolicyAsync(
+ const std::vector<uint8_t>& in_policy_blob,
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StorePolicy",
+ success_callback,
+ error_callback,
+ in_policy_blob);
+ }
+
+ bool RetrievePolicy(
+ std::vector<uint8_t>* out_policy_blob,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RetrievePolicy",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_policy_blob);
+ }
+
+ void RetrievePolicyAsync(
+ const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RetrievePolicy",
+ success_callback,
+ error_callback);
+ }
+
+ bool StorePolicyForUser(
+ const std::string& in_user_email,
+ const std::vector<uint8_t>& in_policy_blob,
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StorePolicyForUser",
+ error,
+ in_user_email,
+ in_policy_blob);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_done);
+ }
+
+ void StorePolicyForUserAsync(
+ const std::string& in_user_email,
+ const std::vector<uint8_t>& in_policy_blob,
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StorePolicyForUser",
+ success_callback,
+ error_callback,
+ in_user_email,
+ in_policy_blob);
+ }
+
+ bool RetrievePolicyForUser(
+ const std::string& in_user_email,
+ std::vector<uint8_t>* out_policy_blob,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RetrievePolicyForUser",
+ error,
+ in_user_email);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_policy_blob);
+ }
+
+ void RetrievePolicyForUserAsync(
+ const std::string& in_user_email,
+ const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RetrievePolicyForUser",
+ success_callback,
+ error_callback,
+ in_user_email);
+ }
+
+ bool StoreDeviceLocalAccountPolicy(
+ const std::string& in_account_id,
+ const std::vector<uint8_t>& in_policy_blob,
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StoreDeviceLocalAccountPolicy",
+ error,
+ in_account_id,
+ in_policy_blob);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_done);
+ }
+
+ void StoreDeviceLocalAccountPolicyAsync(
+ const std::string& in_account_id,
+ const std::vector<uint8_t>& in_policy_blob,
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StoreDeviceLocalAccountPolicy",
+ success_callback,
+ error_callback,
+ in_account_id,
+ in_policy_blob);
+ }
+
+ bool RetrieveDeviceLocalAccountPolicy(
+ const std::string& in_account_id,
+ std::vector<uint8_t>* out_policy_blob,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RetrieveDeviceLocalAccountPolicy",
+ error,
+ in_account_id);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_policy_blob);
+ }
+
+ void RetrieveDeviceLocalAccountPolicyAsync(
+ const std::string& in_account_id,
+ const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RetrieveDeviceLocalAccountPolicy",
+ success_callback,
+ error_callback,
+ in_account_id);
+ }
+
+ bool RetrieveSessionState(
+ std::string* out_state,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RetrieveSessionState",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_state);
+ }
+
+ void RetrieveSessionStateAsync(
+ const base::Callback<void(const std::string& /*state*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RetrieveSessionState",
+ success_callback,
+ error_callback);
+ }
+
+ bool RetrieveActiveSessions(
+ std::map<std::string, std::string>* out_sessions,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RetrieveActiveSessions",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_sessions);
+ }
+
+ void RetrieveActiveSessionsAsync(
+ const base::Callback<void(const std::map<std::string, std::string>& /*sessions*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RetrieveActiveSessions",
+ success_callback,
+ error_callback);
+ }
+
+ bool HandleSupervisedUserCreationStarting(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "HandleSupervisedUserCreationStarting",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void HandleSupervisedUserCreationStartingAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "HandleSupervisedUserCreationStarting",
+ success_callback,
+ error_callback);
+ }
+
+ bool HandleSupervisedUserCreationFinished(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "HandleSupervisedUserCreationFinished",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void HandleSupervisedUserCreationFinishedAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "HandleSupervisedUserCreationFinished",
+ success_callback,
+ error_callback);
+ }
+
+ bool LockScreen(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "LockScreen",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void LockScreenAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "LockScreen",
+ success_callback,
+ error_callback);
+ }
+
+ bool HandleLockScreenShown(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "HandleLockScreenShown",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void HandleLockScreenShownAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "HandleLockScreenShown",
+ success_callback,
+ error_callback);
+ }
+
+ bool HandleLockScreenDismissed(
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "HandleLockScreenDismissed",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void HandleLockScreenDismissedAsync(
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "HandleLockScreenDismissed",
+ success_callback,
+ error_callback);
+ }
+
+ bool RestartJob(
+ const dbus::FileDescriptor& in_cred_fd,
+ const std::vector<std::string>& in_argv,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RestartJob",
+ error,
+ in_cred_fd,
+ in_argv);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void RestartJobAsync(
+ const dbus::FileDescriptor& in_cred_fd,
+ const std::vector<std::string>& in_argv,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "RestartJob",
+ success_callback,
+ error_callback,
+ in_cred_fd,
+ in_argv);
+ }
+
+ bool StartDeviceWipe(
+ bool* out_done,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StartDeviceWipe",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_done);
+ }
+
+ void StartDeviceWipeAsync(
+ const base::Callback<void(bool /*done*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "StartDeviceWipe",
+ success_callback,
+ error_callback);
+ }
+
+ bool SetFlagsForUser(
+ const std::string& in_user_email,
+ const std::vector<std::string>& in_flags,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "SetFlagsForUser",
+ error,
+ in_user_email,
+ in_flags);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void SetFlagsForUserAsync(
+ const std::string& in_user_email,
+ const std::vector<std::string>& in_flags,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "SetFlagsForUser",
+ success_callback,
+ error_callback,
+ in_user_email,
+ in_flags);
+ }
+
+ bool GetServerBackedStateKeys(
+ std::vector<std::vector<uint8_t>>* out_state_keys,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "GetServerBackedStateKeys",
+ error);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error, out_state_keys);
+ }
+
+ void GetServerBackedStateKeysAsync(
+ const base::Callback<void(const std::vector<std::vector<uint8_t>>& /*state_keys*/)>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "GetServerBackedStateKeys",
+ success_callback,
+ error_callback);
+ }
+
+ bool InitMachineInfo(
+ const std::string& in_data,
+ brillo::ErrorPtr* error,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ auto response = brillo::dbus_utils::CallMethodAndBlockWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "InitMachineInfo",
+ error,
+ in_data);
+ return response && brillo::dbus_utils::ExtractMethodCallResults(
+ response.get(), error);
+ }
+
+ void InitMachineInfoAsync(
+ const std::string& in_data,
+ const base::Callback<void()>& success_callback,
+ const base::Callback<void(brillo::Error*)>& error_callback,
+ int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override {
+ brillo::dbus_utils::CallMethodWithTimeout(
+ timeout_ms,
+ dbus_object_proxy_,
+ "org.chromium.SessionManagerInterface",
+ "InitMachineInfo",
+ success_callback,
+ error_callback,
+ in_data);
+ }
+
+ private:
+ scoped_refptr<dbus::Bus> bus_;
+ const std::string service_name_{"org.chromium.SessionManager"};
+ const dbus::ObjectPath object_path_{"/org/chromium/SessionManager"};
+ dbus::ObjectProxy* dbus_object_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(SessionManagerInterfaceProxy);
+};
+
+} // namespace chromium
+} // namespace org
+
+#endif // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXIES_H
diff --git a/include/session_manager/dbus-proxy-mocks.h b/include/session_manager/dbus-proxy-mocks.h
new file mode 100644
index 0000000..2b6ce4d
--- /dev/null
+++ b/include/session_manager/dbus-proxy-mocks.h
@@ -0,0 +1,252 @@
+// Automatic generation of D-Bus interface mock proxies for:
+// - org.chromium.SessionManagerInterface
+#ifndef ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXY_MOCKS_H
+#define ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXY_MOCKS_H
+#include <string>
+#include <vector>
+
+#include <base/callback_forward.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/any.h>
+#include <brillo/errors/error.h>
+#include <brillo/variant_dictionary.h>
+#include <gmock/gmock.h>
+
+#include "session_manager/dbus-proxies.h"
+
+namespace org {
+namespace chromium {
+
+// Mock object for SessionManagerInterfaceProxyInterface.
+class SessionManagerInterfaceProxyMock : public SessionManagerInterfaceProxyInterface {
+ public:
+ SessionManagerInterfaceProxyMock() = default;
+
+ MOCK_METHOD2(EmitLoginPromptVisible,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(EmitLoginPromptVisibleAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(EnableChromeTesting,
+ bool(bool /*in_force_relaunch*/,
+ const std::vector<std::string>& /*in_extra_arguments*/,
+ std::string* /*out_filepath*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(EnableChromeTestingAsync,
+ void(bool /*in_force_relaunch*/,
+ const std::vector<std::string>& /*in_extra_arguments*/,
+ const base::Callback<void(const std::string& /*filepath*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(StartSession,
+ bool(const std::string& /*in_email_address*/,
+ const std::string& /*in_unique_identifier*/,
+ bool* /*out_done*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(StartSessionAsync,
+ void(const std::string& /*in_email_address*/,
+ const std::string& /*in_unique_identifier*/,
+ const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(StopSession,
+ bool(const std::string& /*in_unique_identifier*/,
+ bool* /*out_done*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(StopSessionAsync,
+ void(const std::string& /*in_unique_identifier*/,
+ const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(StorePolicy,
+ bool(const std::vector<uint8_t>& /*in_policy_blob*/,
+ bool* /*out_done*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(StorePolicyAsync,
+ void(const std::vector<uint8_t>& /*in_policy_blob*/,
+ const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RetrievePolicy,
+ bool(std::vector<uint8_t>* /*out_policy_blob*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RetrievePolicyAsync,
+ void(const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(StorePolicyForUser,
+ bool(const std::string& /*in_user_email*/,
+ const std::vector<uint8_t>& /*in_policy_blob*/,
+ bool* /*out_done*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(StorePolicyForUserAsync,
+ void(const std::string& /*in_user_email*/,
+ const std::vector<uint8_t>& /*in_policy_blob*/,
+ const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RetrievePolicyForUser,
+ bool(const std::string& /*in_user_email*/,
+ std::vector<uint8_t>* /*out_policy_blob*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RetrievePolicyForUserAsync,
+ void(const std::string& /*in_user_email*/,
+ const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(StoreDeviceLocalAccountPolicy,
+ bool(const std::string& /*in_account_id*/,
+ const std::vector<uint8_t>& /*in_policy_blob*/,
+ bool* /*out_done*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(StoreDeviceLocalAccountPolicyAsync,
+ void(const std::string& /*in_account_id*/,
+ const std::vector<uint8_t>& /*in_policy_blob*/,
+ const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RetrieveDeviceLocalAccountPolicy,
+ bool(const std::string& /*in_account_id*/,
+ std::vector<uint8_t>* /*out_policy_blob*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RetrieveDeviceLocalAccountPolicyAsync,
+ void(const std::string& /*in_account_id*/,
+ const base::Callback<void(const std::vector<uint8_t>& /*policy_blob*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RetrieveSessionState,
+ bool(std::string* /*out_state*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RetrieveSessionStateAsync,
+ void(const base::Callback<void(const std::string& /*state*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RetrieveActiveSessions,
+ bool(std::map<std::string, std::string>* /*out_sessions*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(RetrieveActiveSessionsAsync,
+ void(const base::Callback<void(const std::map<std::string, std::string>& /*sessions*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(HandleSupervisedUserCreationStarting,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(HandleSupervisedUserCreationStartingAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(HandleSupervisedUserCreationFinished,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(HandleSupervisedUserCreationFinishedAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(LockScreen,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(LockScreenAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(HandleLockScreenShown,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(HandleLockScreenShownAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(HandleLockScreenDismissed,
+ bool(brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(HandleLockScreenDismissedAsync,
+ void(const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(RestartJob,
+ bool(const dbus::FileDescriptor& /*in_cred_fd*/,
+ const std::vector<std::string>& /*in_argv*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(RestartJobAsync,
+ void(const dbus::FileDescriptor& /*in_cred_fd*/,
+ const std::vector<std::string>& /*in_argv*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(StartDeviceWipe,
+ bool(bool* /*out_done*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(StartDeviceWipeAsync,
+ void(const base::Callback<void(bool /*done*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(SetFlagsForUser,
+ bool(const std::string& /*in_user_email*/,
+ const std::vector<std::string>& /*in_flags*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD5(SetFlagsForUserAsync,
+ void(const std::string& /*in_user_email*/,
+ const std::vector<std::string>& /*in_flags*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetServerBackedStateKeys,
+ bool(std::vector<std::vector<uint8_t>>* /*out_state_keys*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(GetServerBackedStateKeysAsync,
+ void(const base::Callback<void(const std::vector<std::vector<uint8_t>>& /*state_keys*/)>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD3(InitMachineInfo,
+ bool(const std::string& /*in_data*/,
+ brillo::ErrorPtr* /*error*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD4(InitMachineInfoAsync,
+ void(const std::string& /*in_data*/,
+ const base::Callback<void()>& /*success_callback*/,
+ const base::Callback<void(brillo::Error*)>& /*error_callback*/,
+ int /*timeout_ms*/));
+ MOCK_METHOD2(RegisterLoginPromptVisibleSignalHandler,
+ void(const base::Closure& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterSessionStateChangedSignalHandler,
+ void(const base::Callback<void(const std::string&)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterSetOwnerKeyCompleteSignalHandler,
+ void(const base::Callback<void(const std::string&)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterPropertyChangeCompleteSignalHandler,
+ void(const base::Callback<void(const std::string&)>& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterScreenIsLockedSignalHandler,
+ void(const base::Closure& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+ MOCK_METHOD2(RegisterScreenIsUnlockedSignalHandler,
+ void(const base::Closure& /*signal_callback*/,
+ dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SessionManagerInterfaceProxyMock);
+};
+} // namespace chromium
+} // namespace org
+
+#endif // ____CHROMEOS_DBUS_BINDING____________________BUILD_LINK_VAR_CACHE_PORTAGE_CHROMEOS_BASE_CHROMEOS_LOGIN_OUT_DEFAULT_GEN_INCLUDE_SESSION_MANAGER_DBUS_PROXY_MOCKS_H
diff --git a/include/update_engine/dbus-constants.h b/include/update_engine/dbus-constants.h
new file mode 100644
index 0000000..94e35f1
--- /dev/null
+++ b/include/update_engine/dbus-constants.h
@@ -0,0 +1,51 @@
+// Copyright 2015 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.
+
+#ifndef SYSTEM_API_DBUS_UPDATE_ENGINE_DBUS_CONSTANTS_H_
+#define SYSTEM_API_DBUS_UPDATE_ENGINE_DBUS_CONSTANTS_H_
+
+namespace update_engine {
+const char kUpdateEngineInterface[] = "org.chromium.UpdateEngineInterface";
+const char kUpdateEngineServicePath[] = "/org/chromium/UpdateEngine";
+const char kUpdateEngineServiceName[] = "org.chromium.UpdateEngine";
+
+// Generic UpdateEngine D-Bus error.
+static const char* const kUpdateEngineServiceErrorFailed =
+ "org.chromium.UpdateEngine.Error.Failed";
+
+// Methods.
+const char kAttemptUpdate[] = "AttemptUpdate";
+const char kGetStatus[] = "GetStatus";
+const char kRebootIfNeeded[] = "RebootIfNeeded";
+const char kSetChannel[] = "SetChannel";
+const char kGetChannel[] = "GetChannel";
+const char kAttemptRollback[] = "AttemptRollback";
+const char kCanRollback[] = "CanRollback";
+
+// Signals.
+const char kStatusUpdate[] = "StatusUpdate";
+
+// Flags used in the AttemptUpdateWithFlags() D-Bus method.
+typedef enum {
+ kAttemptUpdateFlagNonInteractive = (1<<0)
+} AttemptUpdateFlags;
+
+// Operations contained in StatusUpdate signals.
+const char kUpdateStatusIdle[] = "UPDATE_STATUS_IDLE";
+const char kUpdateStatusCheckingForUpdate[] =
+ "UPDATE_STATUS_CHECKING_FOR_UPDATE";
+const char kUpdateStatusUpdateAvailable[] = "UPDATE_STATUS_UPDATE_AVAILABLE";
+const char kUpdateStatusDownloading[] = "UPDATE_STATUS_DOWNLOADING";
+const char kUpdateStatusVerifying[] = "UPDATE_STATUS_VERIFYING";
+const char kUpdateStatusFinalizing[] = "UPDATE_STATUS_FINALIZING";
+const char kUpdateStatusUpdatedNeedReboot[] =
+ "UPDATE_STATUS_UPDATED_NEED_REBOOT";
+const char kUpdateStatusReportingErrorEvent[] =
+ "UPDATE_STATUS_REPORTING_ERROR_EVENT";
+const char kUpdateStatusAttemptingRollback[] =
+ "UPDATE_STATUS_ATTEMPTING_ROLLBACK";
+const char kUpdateStatusDisabled[] = "UPDATE_STATUS_DISABLED";
+} // namespace update_engine
+
+#endif // SYSTEM_API_DBUS_UPDATE_ENGINE_DBUS_CONSTANTS_H_
diff --git a/init/update-engine.conf b/init/update-engine.conf
new file mode 100644
index 0000000..4c05cf4
--- /dev/null
+++ b/init/update-engine.conf
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+description "System software update service"
+author "chromium-os-dev@chromium.org"
+
+# N.B. The chromeos-factoryinstall ebuild edits the 'start on' line so as
+# to disable update_engine in factory images. Do not change this without
+# also updating that reference.
+start on starting system-services
+stop on stopping system-services
+respawn
+
+expect fork
+
+# Runs the daemon at low/idle IO priority so that updates don't
+# impact system responsiveness.
+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"
+end script
diff --git a/libcros_proxy.cc b/libcros_proxy.cc
new file mode 100644
index 0000000..689ed39
--- /dev/null
+++ b/libcros_proxy.cc
@@ -0,0 +1,57 @@
+//
+// 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/libcros_proxy.h"
+
+using org::chromium::LibCrosServiceInterfaceProxy;
+using org::chromium::LibCrosServiceInterfaceProxyInterface;
+using org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxy;
+using org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface;
+
+namespace {
+const char kLibCrosServiceName[] = "org.chromium.LibCrosService";
+} // namespace
+
+namespace chromeos_update_engine {
+
+LibCrosProxy::LibCrosProxy(
+ std::unique_ptr<LibCrosServiceInterfaceProxyInterface>
+ service_interface_proxy,
+ std::unique_ptr<UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface>
+ ue_proxy_resolved_interface)
+ : service_interface_proxy_(std::move(service_interface_proxy)),
+ ue_proxy_resolved_interface_(std::move(ue_proxy_resolved_interface)) {
+}
+
+LibCrosProxy::LibCrosProxy(const scoped_refptr<dbus::Bus>& bus)
+ : service_interface_proxy_(
+ new LibCrosServiceInterfaceProxy(bus, kLibCrosServiceName)),
+ ue_proxy_resolved_interface_(
+ new UpdateEngineLibcrosProxyResolvedInterfaceProxy(
+ bus,
+ kLibCrosServiceName)) {
+}
+
+LibCrosServiceInterfaceProxyInterface* LibCrosProxy::service_interface_proxy() {
+ return service_interface_proxy_.get();
+}
+
+UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface*
+LibCrosProxy::ue_proxy_resolved_interface() {
+ return ue_proxy_resolved_interface_.get();
+}
+
+} // namespace chromeos_update_engine
diff --git a/libcros_proxy.h b/libcros_proxy.h
new file mode 100644
index 0000000..afb5d54
--- /dev/null
+++ b/libcros_proxy.h
@@ -0,0 +1,62 @@
+//
+// 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_LIBCROS_PROXY_H_
+#define UPDATE_ENGINE_LIBCROS_PROXY_H_
+
+#include <memory>
+
+#include <base/macros.h>
+#include <dbus/bus.h>
+
+#include "libcros/dbus-proxies.h"
+
+namespace chromeos_update_engine {
+
+// This class handles the DBus connection with chrome to resolve proxies. This
+// is a thin class to just hold the generated proxies (real or mocked ones).
+class LibCrosProxy final {
+ public:
+ explicit LibCrosProxy(const scoped_refptr<dbus::Bus>& bus);
+ LibCrosProxy(
+ std::unique_ptr<org::chromium::LibCrosServiceInterfaceProxyInterface>
+ service_interface_proxy,
+ std::unique_ptr<
+ org::chromium::
+ UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface>
+ ue_proxy_resolved_interface);
+
+ ~LibCrosProxy() = default;
+
+ // Getters for the two proxies.
+ org::chromium::LibCrosServiceInterfaceProxyInterface*
+ service_interface_proxy();
+ org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface*
+ ue_proxy_resolved_interface();
+
+ private:
+ std::unique_ptr<org::chromium::LibCrosServiceInterfaceProxyInterface>
+ service_interface_proxy_;
+ std::unique_ptr<
+ org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface>
+ ue_proxy_resolved_interface_;
+
+ DISALLOW_COPY_AND_ASSIGN(LibCrosProxy);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_LIBCROS_PROXY_H_
diff --git a/libupdate_engine-client-test.pc.in b/libupdate_engine-client-test.pc.in
new file mode 100644
index 0000000..92a4af3
--- /dev/null
+++ b/libupdate_engine-client-test.pc.in
@@ -0,0 +1,6 @@
+include_dir=@INCLUDE_DIR@
+
+Name: libupdate_engine-client-test
+Version: 1.0
+Description: update_engine client interface mock library
+Cflags: -I${include_dir}
diff --git a/libupdate_engine-client.pc.in b/libupdate_engine-client.pc.in
new file mode 100644
index 0000000..4c87e1d
--- /dev/null
+++ b/libupdate_engine-client.pc.in
@@ -0,0 +1,6 @@
+include_dir=@INCLUDE_DIR@
+
+Name: libupdate_engine-client
+Version: 1.0
+Description: update_engine client interface library
+Cflags: -I${include_dir}
diff --git a/local_coverage_rate b/local_coverage_rate
new file mode 100755
index 0000000..b189806
--- /dev/null
+++ b/local_coverage_rate
@@ -0,0 +1,101 @@
+#!/bin/bash
+
+#
+# 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.
+#
+
+# Calculates the test-coverage percentage for non-test files in the
+# update_engine directory. Requires a file 'app.info' to contain the
+# results of running the unittests while collecting coverage data.
+
+cat app.info | awk -F '[,:]' '
+
+BEGIN { OFS = ":"; }
+
+/^SF:/{ FILEN = $2; }
+
+/^end_of_record$/{ FILEN = ""; }
+
+/^DA:/{ print FILEN, $2, $3; }
+
+' | sort | awk -F : '
+BEGIN {
+ OFS = ":";
+ FILEN = "";
+ LINE = "";
+ HITS = 0;
+}
+{
+ NEWFILEN = $1;
+ NEWLINE = $2;
+ if ((NEWFILEN == FILEN) && (NEWLINE == LINE)) {
+ HITS += $3
+ } else {
+ if (FILEN != "") {
+ print FILEN, LINE, HITS;
+ }
+ FILEN = NEWFILEN;
+ LINE = NEWLINE;
+ HITS = $3;
+ }
+}
+' | grep '^.*\/trunk\/src\/platform\/update_engine\/' | \
+fgrep -v '_unittest.cc:' | \
+fgrep -v '/test_utils.' | \
+fgrep -v '/test_http_server.cc' | \
+fgrep -v '/testrunner.cc' | \
+fgrep -v '/mock' | \
+fgrep -v '.pb.cc' | \
+awk -F : '
+
+function printfile() {
+ if (FNAME != "")
+ printf "%-40s %4d / %4d: %5.1f%%\n", FNAME, FILE_GOOD_LINES,
+ (FILE_BAD_LINES + FILE_GOOD_LINES),
+ (FILE_GOOD_LINES * 100) / (FILE_BAD_LINES + FILE_GOOD_LINES);
+}
+
+BEGIN {
+ FNAME = "";
+ FILE_BAD_LINES = 0;
+ FILE_GOOD_LINES = 0;
+}
+{
+ // calc filename
+ ARR_SIZE = split($1, PARTS, "/");
+ NEWFNAME = PARTS[ARR_SIZE];
+ if (NEWFNAME != FNAME) {
+ printfile();
+ FILE_BAD_LINES = 0;
+ FILE_GOOD_LINES = 0;
+ FNAME = NEWFNAME;
+ }
+ if ($3 == "0") {
+ BAD_LINES += 1;
+ FILE_BAD_LINES += 1;
+ } else {
+ GOOD_LINES += 1;
+ FILE_GOOD_LINES += 1;
+ }
+}
+
+END {
+ printfile();
+ print "---\nSummary: tested " GOOD_LINES " / " (BAD_LINES + GOOD_LINES);
+ printf(
+ "Test coverage: %.1f%%\n",
+ ((GOOD_LINES * 100) / (BAD_LINES + GOOD_LINES)));
+}
+'
diff --git a/main.cc b/main.cc
new file mode 100644
index 0000000..8e88f23
--- /dev/null
+++ b/main.cc
@@ -0,0 +1,132 @@
+//
+// 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 <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <xz.h>
+
+#include <string>
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/flag_helper.h>
+#include <brillo/message_loops/base_message_loop.h>
+
+#include "update_engine/common/terminator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/daemon.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+namespace {
+
+void SetupLogSymlink(const string& symlink_path, const string& log_path) {
+ // TODO(petkov): To ensure a smooth transition between non-timestamped and
+ // timestamped logs, move an existing log to start the first timestamped
+ // one. This code can go away once all clients are switched to this version or
+ // we stop caring about the old-style logs.
+ if (utils::FileExists(symlink_path.c_str()) &&
+ !utils::IsSymlink(symlink_path.c_str())) {
+ base::ReplaceFile(base::FilePath(symlink_path),
+ base::FilePath(log_path),
+ nullptr);
+ }
+ base::DeleteFile(base::FilePath(symlink_path), true);
+ if (symlink(log_path.c_str(), symlink_path.c_str()) == -1) {
+ PLOG(ERROR) << "Unable to create symlink " << symlink_path
+ << " pointing at " << log_path;
+ }
+}
+
+string GetTimeAsString(time_t utime) {
+ struct tm tm;
+ CHECK_EQ(localtime_r(&utime, &tm), &tm);
+ char str[16];
+ CHECK_EQ(strftime(str, sizeof(str), "%Y%m%d-%H%M%S", &tm), 15u);
+ return str;
+}
+
+string SetupLogFile(const string& kLogsRoot) {
+ const string kLogSymlink = kLogsRoot + "/update_engine.log";
+ const string kLogsDir = kLogsRoot + "/update_engine";
+ const string kLogPath =
+ base::StringPrintf("%s/update_engine.%s",
+ kLogsDir.c_str(),
+ GetTimeAsString(::time(nullptr)).c_str());
+ mkdir(kLogsDir.c_str(), 0755);
+ SetupLogSymlink(kLogSymlink, kLogPath);
+ return kLogSymlink;
+}
+
+void SetupLogging(bool log_to_std_err) {
+ string log_file;
+ logging::LoggingSettings log_settings;
+ log_settings.lock_log = logging::DONT_LOCK_LOG_FILE;
+ log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+
+ if (log_to_std_err) {
+ // Log to stderr initially.
+ log_settings.log_file = nullptr;
+ log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
+ } else {
+ log_file = SetupLogFile("/var/log");
+ log_settings.log_file = log_file.c_str();
+ log_settings.logging_dest = logging::LOG_TO_FILE;
+ }
+
+ logging::InitLogging(log_settings);
+}
+
+} // namespace
+} // namespace chromeos_update_engine
+
+int main(int argc, char** argv) {
+ DEFINE_bool(logtostderr, false,
+ "Write logs to stderr instead of to a file in log_dir.");
+ DEFINE_bool(foreground, false,
+ "Don't daemon()ize; run in foreground.");
+
+ chromeos_update_engine::Terminator::Init();
+ brillo::FlagHelper::Init(argc, argv, "Chromium OS Update Engine");
+ chromeos_update_engine::SetupLogging(FLAGS_logtostderr);
+ if (!FLAGS_foreground)
+ PLOG_IF(FATAL, daemon(0, 0) == 1) << "daemon() failed";
+
+ LOG(INFO) << "Chrome OS Update Engine starting";
+
+ // xz-embedded requires to initialize its CRC-32 table once on startup.
+ xz_crc32_init();
+
+ // Ensure that all written files have safe permissions.
+ // This is a mask, so we _block_ all permissions for the group owner and other
+ // users but allow all permissions for the user owner. We allow execution
+ // for the owner so we can create directories.
+ // Done _after_ log file creation.
+ umask(S_IRWXG | S_IRWXO);
+
+ chromeos_update_engine::UpdateEngineDaemon update_engine_daemon;
+ int exit_code = update_engine_daemon.Run();
+
+ LOG(INFO) << "Chrome OS Update Engine terminating with exit code "
+ << exit_code;
+ return exit_code;
+}
diff --git a/metrics.cc b/metrics.cc
new file mode 100644
index 0000000..742ba7e
--- /dev/null
+++ b/metrics.cc
@@ -0,0 +1,526 @@
+//
+// 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/metrics.h"
+
+#include <string>
+
+#include <base/logging.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/utils.h"
+#include "update_engine/metrics_utils.h"
+#include "update_engine/system_state.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace metrics {
+
+// UpdateEngine.Daily.* metrics.
+const char kMetricDailyOSAgeDays[] = "UpdateEngine.Daily.OSAgeDays";
+
+// UpdateEngine.Check.* metrics.
+const char kMetricCheckDownloadErrorCode[] =
+ "UpdateEngine.Check.DownloadErrorCode";
+const char kMetricCheckReaction[] = "UpdateEngine.Check.Reaction";
+const char kMetricCheckResult[] = "UpdateEngine.Check.Result";
+const char kMetricCheckTimeSinceLastCheckMinutes[] =
+ "UpdateEngine.Check.TimeSinceLastCheckMinutes";
+const char kMetricCheckTimeSinceLastCheckUptimeMinutes[] =
+ "UpdateEngine.Check.TimeSinceLastCheckUptimeMinutes";
+
+// UpdateEngine.Attempt.* metrics.
+const char kMetricAttemptNumber[] = "UpdateEngine.Attempt.Number";
+const char kMetricAttemptPayloadType[] =
+ "UpdateEngine.Attempt.PayloadType";
+const char kMetricAttemptPayloadSizeMiB[] =
+ "UpdateEngine.Attempt.PayloadSizeMiB";
+const char kMetricAttemptConnectionType[] =
+ "UpdateEngine.Attempt.ConnectionType";
+const char kMetricAttemptDurationMinutes[] =
+ "UpdateEngine.Attempt.DurationMinutes";
+const char kMetricAttemptDurationUptimeMinutes[] =
+ "UpdateEngine.Attempt.DurationUptimeMinutes";
+const char kMetricAttemptTimeSinceLastAttemptMinutes[] =
+ "UpdateEngine.Attempt.TimeSinceLastAttemptMinutes";
+const char kMetricAttemptTimeSinceLastAttemptUptimeMinutes[] =
+ "UpdateEngine.Attempt.TimeSinceLastAttemptUptimeMinutes";
+const char kMetricAttemptPayloadBytesDownloadedMiB[] =
+ "UpdateEngine.Attempt.PayloadBytesDownloadedMiB";
+const char kMetricAttemptPayloadDownloadSpeedKBps[] =
+ "UpdateEngine.Attempt.PayloadDownloadSpeedKBps";
+const char kMetricAttemptDownloadSource[] =
+ "UpdateEngine.Attempt.DownloadSource";
+const char kMetricAttemptResult[] =
+ "UpdateEngine.Attempt.Result";
+const char kMetricAttemptInternalErrorCode[] =
+ "UpdateEngine.Attempt.InternalErrorCode";
+const char kMetricAttemptDownloadErrorCode[] =
+ "UpdateEngine.Attempt.DownloadErrorCode";
+
+// UpdateEngine.SuccessfulUpdate.* metrics.
+const char kMetricSuccessfulUpdateAttemptCount[] =
+ "UpdateEngine.SuccessfulUpdate.AttemptCount";
+const char kMetricSuccessfulUpdateBytesDownloadedMiB[] =
+ "UpdateEngine.SuccessfulUpdate.BytesDownloadedMiB";
+const char kMetricSuccessfulUpdateDownloadOverheadPercentage[] =
+ "UpdateEngine.SuccessfulUpdate.DownloadOverheadPercentage";
+const char kMetricSuccessfulUpdateDownloadSourcesUsed[] =
+ "UpdateEngine.SuccessfulUpdate.DownloadSourcesUsed";
+const char kMetricSuccessfulUpdatePayloadType[] =
+ "UpdateEngine.SuccessfulUpdate.PayloadType";
+const char kMetricSuccessfulUpdatePayloadSizeMiB[] =
+ "UpdateEngine.SuccessfulUpdate.PayloadSizeMiB";
+const char kMetricSuccessfulUpdateRebootCount[] =
+ "UpdateEngine.SuccessfulUpdate.RebootCount";
+const char kMetricSuccessfulUpdateTotalDurationMinutes[] =
+ "UpdateEngine.SuccessfulUpdate.TotalDurationMinutes";
+const char kMetricSuccessfulUpdateUpdatesAbandonedCount[] =
+ "UpdateEngine.SuccessfulUpdate.UpdatesAbandonedCount";
+const char kMetricSuccessfulUpdateUrlSwitchCount[] =
+ "UpdateEngine.SuccessfulUpdate.UrlSwitchCount";
+
+// UpdateEngine.Rollback.* metric.
+const char kMetricRollbackResult[] = "UpdateEngine.Rollback.Result";
+
+// UpdateEngine.CertificateCheck.* metrics.
+const char kMetricCertificateCheckUpdateCheck[] =
+ "UpdateEngine.CertificateCheck.UpdateCheck";
+const char kMetricCertificateCheckDownload[] =
+ "UpdateEngine.CertificateCheck.Download";
+
+// UpdateEngine.* metrics.
+const char kMetricFailedUpdateCount[] = "UpdateEngine.FailedUpdateCount";
+const char kMetricInstallDateProvisioningSource[] =
+ "UpdateEngine.InstallDateProvisioningSource";
+const char kMetricTimeToRebootMinutes[] =
+ "UpdateEngine.TimeToRebootMinutes";
+
+void ReportDailyMetrics(SystemState *system_state,
+ base::TimeDelta os_age) {
+ string metric = metrics::kMetricDailyOSAgeDays;
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(os_age)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ static_cast<int>(os_age.InDays()),
+ 0, // min: 0 days
+ 6*30, // max: 6 months (approx)
+ 50); // num_buckets
+}
+
+void ReportUpdateCheckMetrics(SystemState *system_state,
+ CheckResult result,
+ CheckReaction reaction,
+ DownloadErrorCode download_error_code) {
+ string metric;
+ int value;
+ int max_value;
+
+ if (result != metrics::CheckResult::kUnset) {
+ 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)";
+ system_state->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)";
+ system_state->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)";
+ system_state->metrics_lib()->SendSparseToUMA(metric, value);
+ }
+
+ base::TimeDelta time_since_last;
+ if (metrics_utils::WallclockDurationHelper(
+ system_state,
+ kPrefsMetricsCheckLastReportingTime,
+ &time_since_last)) {
+ metric = kMetricCheckTimeSinceLastCheckMinutes;
+ LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ time_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30*24*60, // max: 30 days
+ 50); // num_buckets
+ }
+
+ 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)) {
+ metric = kMetricCheckTimeSinceLastCheckUptimeMinutes;
+ LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ uptime_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30*24*60, // max: 30 days
+ 50); // num_buckets
+ }
+}
+
+void ReportAbnormallyTerminatedUpdateAttemptMetrics(
+ SystemState *system_state) {
+
+ string metric = metrics::kMetricAttemptResult;
+ AttemptResult attempt_result = AttemptResult::kAbnormalTermination;
+
+ LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric,
+ static_cast<int>(attempt_result),
+ static_cast<int>(AttemptResult::kNumConstants));
+}
+
+void ReportUpdateAttemptMetrics(
+ SystemState *system_state,
+ int attempt_number,
+ PayloadType payload_type,
+ base::TimeDelta duration,
+ base::TimeDelta duration_uptime,
+ int64_t payload_size,
+ int64_t payload_bytes_downloaded,
+ int64_t payload_download_speed_bps,
+ DownloadSource download_source,
+ AttemptResult attempt_result,
+ ErrorCode internal_error_code,
+ DownloadErrorCode payload_download_error_code,
+ ConnectionType connection_type) {
+ string metric;
+
+ metric = metrics::kMetricAttemptNumber;
+ LOG(INFO) << "Uploading " << attempt_number << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ attempt_number,
+ 0, // min: 0 attempts
+ 49, // max: 49 attempts
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadType;
+ LOG(INFO) << "Uploading " << utils::ToString(payload_type)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(metric,
+ payload_type,
+ kNumPayloadTypes);
+
+ metric = metrics::kMetricAttemptDurationMinutes;
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ duration.InMinutes(),
+ 0, // min: 0 min
+ 10*24*60, // max: 10 days
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptDurationUptimeMinutes;
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration_uptime)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ duration_uptime.InMinutes(),
+ 0, // min: 0 min
+ 10*24*60, // max: 10 days
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadSizeMiB;
+ int64_t payload_size_mib = payload_size / kNumBytesInOneMiB;
+ LOG(INFO) << "Uploading " << payload_size_mib << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ payload_size_mib,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadBytesDownloadedMiB;
+ int64_t payload_bytes_downloaded_mib =
+ payload_bytes_downloaded / kNumBytesInOneMiB;
+ LOG(INFO) << "Uploading " << payload_bytes_downloaded_mib
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ payload_bytes_downloaded_mib,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptPayloadDownloadSpeedKBps;
+ int64_t payload_download_speed_kbps = payload_download_speed_bps / 1000;
+ LOG(INFO) << "Uploading " << payload_download_speed_kbps
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ payload_download_speed_kbps,
+ 0, // min: 0 kB/s
+ 10*1000, // max: 10000 kB/s = 10 MB/s
+ 50); // num_buckets
+
+ metric = metrics::kMetricAttemptDownloadSource;
+ LOG(INFO) << "Uploading " << download_source
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(metric,
+ download_source,
+ kNumDownloadSources);
+
+ metric = metrics::kMetricAttemptResult;
+ LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric,
+ static_cast<int>(attempt_result),
+ static_cast<int>(AttemptResult::kNumConstants));
+
+ if (internal_error_code != ErrorCode::kSuccess) {
+ metric = metrics::kMetricAttemptInternalErrorCode;
+ LOG(INFO) << "Uploading " << internal_error_code
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric,
+ static_cast<int>(internal_error_code),
+ static_cast<int>(ErrorCode::kUmaReportedMax));
+ }
+
+ if (payload_download_error_code != DownloadErrorCode::kUnset) {
+ metric = metrics::kMetricAttemptDownloadErrorCode;
+ LOG(INFO) << "Uploading " << static_cast<int>(payload_download_error_code)
+ << " for metric " << metric << " (sparse)";
+ system_state->metrics_lib()->SendSparseToUMA(
+ metric,
+ static_cast<int>(payload_download_error_code));
+ }
+
+ base::TimeDelta time_since_last;
+ if (metrics_utils::WallclockDurationHelper(
+ system_state,
+ kPrefsMetricsAttemptLastReportingTime,
+ &time_since_last)) {
+ metric = kMetricAttemptTimeSinceLastAttemptMinutes;
+ LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ time_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30*24*60, // max: 30 days
+ 50); // num_buckets
+ }
+
+ 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)) {
+ metric = kMetricAttemptTimeSinceLastAttemptUptimeMinutes;
+ LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ uptime_since_last.InMinutes(),
+ 0, // min: 0 min
+ 30*24*60, // max: 30 days
+ 50); // num_buckets
+ }
+
+ metric = metrics::kMetricAttemptConnectionType;
+ LOG(INFO) << "Uploading " << static_cast<int>(connection_type)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric,
+ static_cast<int>(connection_type),
+ static_cast<int>(ConnectionType::kNumConstants));
+}
+
+
+void ReportSuccessfulUpdateMetrics(
+ SystemState *system_state,
+ int attempt_count,
+ int updates_abandoned_count,
+ PayloadType payload_type,
+ int64_t payload_size,
+ int64_t num_bytes_downloaded[kNumDownloadSources],
+ int download_overhead_percentage,
+ base::TimeDelta total_duration,
+ int reboot_count,
+ int url_switch_count) {
+ string metric;
+ int64_t mbs;
+
+ metric = kMetricSuccessfulUpdatePayloadSizeMiB;
+ mbs = payload_size / kNumBytesInOneMiB;
+ LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ mbs,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+
+ int64_t total_bytes = 0;
+ int download_sources_used = 0;
+ for (int i = 0; i < kNumDownloadSources + 1; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+
+ // Only consider this download source (and send byte counts) as
+ // having been used if we downloaded a non-trivial amount of bytes
+ // (e.g. at least 1 MiB) that contributed to the
+ // update. Otherwise we're going to end up with a lot of zero-byte
+ // events in the histogram.
+
+ metric = metrics::kMetricSuccessfulUpdateBytesDownloadedMiB;
+ if (i < kNumDownloadSources) {
+ metric += utils::ToString(source);
+ mbs = num_bytes_downloaded[i] / kNumBytesInOneMiB;
+ total_bytes += num_bytes_downloaded[i];
+ if (mbs > 0)
+ download_sources_used |= (1 << i);
+ } else {
+ mbs = total_bytes / kNumBytesInOneMiB;
+ }
+
+ if (mbs > 0) {
+ LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ mbs,
+ 0, // min: 0 MiB
+ 1024, // max: 1024 MiB = 1 GiB
+ 50); // num_buckets
+ }
+ }
+
+ metric = metrics::kMetricSuccessfulUpdateDownloadSourcesUsed;
+ LOG(INFO) << "Uploading 0x" << std::hex << download_sources_used
+ << " (bit flags) for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ download_sources_used,
+ 0, // min
+ (1 << kNumDownloadSources) - 1, // max
+ 1 << kNumDownloadSources); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage;
+ LOG(INFO) << "Uploading " << download_overhead_percentage
+ << "% for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ download_overhead_percentage,
+ 0, // min: 0% overhead
+ 1000, // max: 1000% overhead
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateUrlSwitchCount;
+ LOG(INFO) << "Uploading " << url_switch_count
+ << " (count) for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ url_switch_count,
+ 0, // min: 0 URL switches
+ 49, // max: 49 URL switches
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateTotalDurationMinutes;
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(total_duration)
+ << " for metric " << metric;
+ system_state->metrics_lib()->SendToUMA(
+ metric,
+ static_cast<int>(total_duration.InMinutes()),
+ 0, // min: 0 min
+ 365*24*60, // max: 365 days ~= 1 year
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdateRebootCount;
+ LOG(INFO) << "Uploading reboot count of " << reboot_count << " for metric "
+ << metric;
+ system_state->metrics_lib()->SendToUMA(metric,
+ reboot_count,
+ 0, // min: 0 reboots
+ 49, // max: 49 reboots
+ 50); // num_buckets
+
+ metric = metrics::kMetricSuccessfulUpdatePayloadType;
+ system_state->metrics_lib()->SendEnumToUMA(metric,
+ payload_type,
+ kNumPayloadTypes);
+ LOG(INFO) << "Uploading " << utils::ToString(payload_type)
+ << " for metric " << metric;
+
+ metric = metrics::kMetricSuccessfulUpdateAttemptCount;
+ system_state->metrics_lib()->SendToUMA(metric,
+ attempt_count,
+ 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;
+ system_state->metrics_lib()->SendToUMA(metric,
+ updates_abandoned_count,
+ 0, // min: 0 counts
+ 49, // max: 49 counts
+ 50); // num_buckets
+}
+
+void ReportRollbackMetrics(SystemState *system_state,
+ RollbackResult result) {
+ string metric;
+ int value;
+
+ metric = metrics::kMetricRollbackResult;
+ value = static_cast<int>(result);
+ LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric,
+ value,
+ static_cast<int>(metrics::RollbackResult::kNumConstants));
+}
+
+void ReportCertificateCheckMetrics(SystemState* system_state,
+ ServerToCheck server_to_check,
+ CertificateCheckResult result) {
+ string metric;
+ switch (server_to_check) {
+ case ServerToCheck::kUpdate:
+ metric = kMetricCertificateCheckUpdateCheck;
+ break;
+ case ServerToCheck::kDownload:
+ metric = kMetricCertificateCheckDownload;
+ break;
+ case ServerToCheck::kNone:
+ return;
+ }
+ LOG(INFO) << "Uploading " << static_cast<int>(result) << " for metric "
+ << metric;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric, static_cast<int>(result),
+ static_cast<int>(CertificateCheckResult::kNumConstants));
+}
+
+} // namespace metrics
+
+} // namespace chromeos_update_engine
diff --git a/metrics.h b/metrics.h
new file mode 100644
index 0000000..ec9d880
--- /dev/null
+++ b/metrics.h
@@ -0,0 +1,320 @@
+//
+// 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_METRICS_H_
+#define UPDATE_ENGINE_METRICS_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/common/certificate_checker.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/error_code.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+namespace metrics {
+
+// UpdateEngine.Daily.* metrics.
+extern const char kMetricDailyOSAgeDays[];
+
+// UpdateEngine.Check.* metrics.
+extern const char kMetricCheckDownloadErrorCode[];
+extern const char kMetricCheckReaction[];
+extern const char kMetricCheckResult[];
+extern const char kMetricCheckTimeSinceLastCheckMinutes[];
+extern const char kMetricCheckTimeSinceLastCheckUptimeMinutes[];
+
+// UpdateEngine.Attempt.* metrics.
+extern const char kMetricAttemptNumber[];
+extern const char kMetricAttemptPayloadType[];
+extern const char kMetricAttemptPayloadSizeMiB[];
+extern const char kMetricAttemptConnectionType[];
+extern const char kMetricAttemptDurationMinutes[];
+extern const char kMetricAttemptDurationUptimeMinutes[];
+extern const char kMetricAttemptTimeSinceLastAttemptSeconds[];
+extern const char kMetricAttemptTimeSinceLastAttemptUptimeSeconds[];
+extern const char kMetricAttemptPayloadBytesDownloaded[];
+extern const char kMetricAttemptPayloadDownloadSpeedKBps[];
+extern const char kMetricAttemptDownloadSource[];
+extern const char kMetricAttemptResult[];
+extern const char kMetricAttemptInternalErrorCode[];
+extern const char kMetricAttemptDownloadErrorCode[];
+
+// UpdateEngine.SuccessfulUpdate.* metrics.
+extern const char kMetricSuccessfulUpdateAttemptCount[];
+extern const char kMetricSuccessfulUpdateBytesDownloadedMiB[];
+extern const char kMetricSuccessfulUpdateDownloadOverheadPercentage[];
+extern const char kMetricSuccessfulUpdateDownloadSourcesUsed[];
+extern const char kMetricSuccessfulUpdatePayloadType[];
+extern const char kMetricSuccessfulUpdatePayloadSizeMiB[];
+extern const char kMetricSuccessfulUpdateTotalDurationMinutes[];
+extern const char kMetricSuccessfulUpdateRebootCount[];
+extern const char kMetricSuccessfulUpdateUpdatesAbandonedCount[];
+extern const char kMetricSuccessfulUpdateUrlSwitchCount[];
+
+// UpdateEngine.Rollback.* metric.
+extern const char kMetricRollbackResult[];
+
+// UpdateEngine.* metrics.
+extern const char kMetricFailedUpdateCount[];
+extern const char kMetricInstallDateProvisioningSource[];
+extern const char kMetricTimeToRebootMinutes[];
+
+// The possible outcomes when checking for updates.
+//
+// This is used in the UpdateEngine.Check.Result histogram.
+enum class CheckResult {
+ kUpdateAvailable, // Response indicates an update is available.
+ kNoUpdateAvailable, // Response indicates no updates are available.
+ kDownloadError, // Error downloading response from Omaha.
+ kParsingError, // Error parsing response.
+ kRebootPending, // No update check was performed a reboot is pending.
+
+ kNumConstants,
+ kUnset = -1
+};
+
+// Possible ways a device can react to a new update being available.
+//
+// This is used in the UpdateEngine.Check.Reaction histogram.
+enum class CheckReaction {
+ kUpdating, // Device proceeds to download and apply update.
+ kIgnored , // Device-policy dictates ignoring the update.
+ kDeferring, // Device-policy dictates waiting.
+ kBackingOff, // Previous errors dictates waiting.
+
+ kNumConstants,
+ kUnset = -1
+};
+
+// The possible ways that downloading from a HTTP or HTTPS server can fail.
+//
+// This is used in the UpdateEngine.Check.DownloadErrorCode and
+// UpdateEngine.Attempt.DownloadErrorCode histograms.
+enum class DownloadErrorCode {
+ // Errors that can happen in the field. See http://crbug.com/355745
+ // for how we plan to add more detail in the future.
+ kDownloadError = 0, // Error downloading data from server.
+
+ // IMPORTANT: When adding a new error code, add at the bottom of the
+ // above block and before the kInputMalformed field. This
+ // is to ensure that error codes are not reordered.
+
+ // This error code is used to convey that malformed input was given
+ // to the utils::GetDownloadErrorCode() function. This should never
+ // happen but if it does it's because of an internal update_engine
+ // error and we're interested in knowing this.
+ kInputMalformed = 100,
+
+ // Bucket for capturing HTTP status codes not in the 200-599
+ // range. This should never happen in practice but if it does we
+ // want to know.
+ kHttpStatusOther = 101,
+
+ // Above 200 and below 600, the value is the HTTP status code.
+ kHttpStatus200 = 200,
+
+ kNumConstants = 600,
+
+ kUnset = -1
+};
+
+// Possible ways an update attempt can end.
+//
+// This is used in the UpdateEngine.Attempt.Result histogram.
+enum class AttemptResult {
+ kUpdateSucceeded, // The update succeeded.
+ kInternalError, // An internal error occurred.
+ kPayloadDownloadError, // Failure while downloading payload.
+ kMetadataMalformed, // Metadata was malformed.
+ kOperationMalformed, // An operation was malformed.
+ kOperationExecutionError, // An operation failed to execute.
+ kMetadataVerificationFailed, // Metadata verification failed.
+ kPayloadVerificationFailed, // Payload verification failed.
+ kVerificationFailed, // Root or Kernel partition verification failed.
+ kPostInstallFailed, // The postinstall step failed.
+ kAbnormalTermination, // The attempt ended abnormally.
+
+ kNumConstants,
+
+ kUnset = -1
+};
+
+// Possible ways the device is connected to the Internet.
+//
+// This is used in the UpdateEngine.Attempt.ConnectionType histogram.
+enum class ConnectionType {
+ kUnknown, // Unknown.
+ kEthernet, // Ethernet.
+ kWifi, // Wireless.
+ kWimax, // WiMax.
+ kBluetooth, // Bluetooth.
+ kCellular, // Cellular.
+ kTetheredEthernet, // Tethered (Ethernet).
+ kTetheredWifi, // Tethered (Wifi).
+
+ kNumConstants,
+ kUnset = -1
+};
+
+// Possible ways a rollback can end.
+//
+// This is used in the UpdateEngine.Rollback histogram.
+enum class RollbackResult {
+ kFailed,
+ kSuccess,
+
+ kNumConstants
+};
+
+// Helper function to report metrics related to rollback. The
+// following metrics are reported:
+//
+// |kMetricRollbackResult|
+void ReportRollbackMetrics(SystemState *system_state,
+ RollbackResult result);
+
+// Helper function to report metrics reported once a day. The
+// following metrics are reported:
+//
+// |kMetricDailyOSAgeDays|
+void ReportDailyMetrics(SystemState *system_state,
+ base::TimeDelta os_age);
+
+// Helper function to report metrics after completing an update check
+// with the ChromeOS update server ("Omaha"). The following metrics
+// are reported:
+//
+// |kMetricCheckResult|
+// |kMetricCheckReaction|
+// |kMetricCheckDownloadErrorCode|
+// |kMetricCheckTimeSinceLastCheckMinutes|
+// |kMetricCheckTimeSinceLastCheckUptimeMinutes|
+//
+// The |kMetricCheckResult| metric will only be reported if |result|
+// is not |kUnset|.
+//
+// The |kMetricCheckReaction| metric will only be reported if
+// |reaction| is not |kUnset|.
+//
+// The |kMetricCheckDownloadErrorCode| will only be reported if
+// |download_error_code| is not |kUnset|.
+//
+// The values for the |kMetricCheckTimeSinceLastCheckMinutes| and
+// |kMetricCheckTimeSinceLastCheckUptimeMinutes| metrics are
+// automatically reported and calculated by maintaining persistent
+// and process-local state variables.
+void ReportUpdateCheckMetrics(SystemState *system_state,
+ CheckResult result,
+ CheckReaction reaction,
+ DownloadErrorCode download_error_code);
+
+
+// Helper function to report metrics after the completion of each
+// update attempt. The following metrics are reported:
+//
+// |kMetricAttemptNumber|
+// |kMetricAttemptPayloadType|
+// |kMetricAttemptPayloadSizeMiB|
+// |kMetricAttemptDurationSeconds|
+// |kMetricAttemptDurationUptimeSeconds|
+// |kMetricAttemptTimeSinceLastAttemptMinutes|
+// |kMetricAttemptTimeSinceLastAttemptUptimeMinutes|
+// |kMetricAttemptPayloadBytesDownloadedMiB|
+// |kMetricAttemptPayloadDownloadSpeedKBps|
+// |kMetricAttemptDownloadSource|
+// |kMetricAttemptResult|
+// |kMetricAttemptInternalErrorCode|
+// |kMetricAttemptDownloadErrorCode|
+//
+// The |kMetricAttemptInternalErrorCode| metric will only be reported
+// if |internal_error_code| is not |kErrorSuccess|.
+//
+// The |kMetricAttemptDownloadErrorCode| metric will only be
+// reported if |payload_download_error_code| is not |kUnset|.
+//
+// The values for the |kMetricAttemptTimeSinceLastAttemptMinutes| and
+// |kMetricAttemptTimeSinceLastAttemptUptimeMinutes| metrics are
+// automatically calculated and reported by maintaining persistent and
+// process-local state variables.
+void ReportUpdateAttemptMetrics(
+ SystemState *system_state,
+ int attempt_number,
+ PayloadType payload_type,
+ base::TimeDelta duration,
+ base::TimeDelta duration_uptime,
+ int64_t payload_size,
+ int64_t payload_bytes_downloaded,
+ int64_t payload_download_speed_bps,
+ DownloadSource download_source,
+ AttemptResult attempt_result,
+ ErrorCode internal_error_code,
+ DownloadErrorCode payload_download_error_code,
+ ConnectionType connection_type);
+
+// Reports the |kAbnormalTermination| for the |kMetricAttemptResult|
+// metric. No other metrics in the UpdateEngine.Attempt.* namespace
+// will be reported.
+void ReportAbnormallyTerminatedUpdateAttemptMetrics(SystemState *system_state);
+
+// Helper function to report the after the completion of a successful
+// update attempt. The following metrics are reported:
+//
+// |kMetricSuccessfulUpdateAttemptCount|
+// |kMetricSuccessfulUpdateUpdatesAbandonedCount|
+// |kMetricSuccessfulUpdatePayloadType|
+// |kMetricSuccessfulUpdatePayloadSizeMiB|
+// |kMetricSuccessfulUpdateBytesDownloadedMiBHttpsServer|
+// |kMetricSuccessfulUpdateBytesDownloadedMiBHttpServer|
+// |kMetricSuccessfulUpdateBytesDownloadedMiBHttpPeer|
+// |kMetricSuccessfulUpdateBytesDownloadedMiB|
+// |kMetricSuccessfulUpdateDownloadSourcesUsed|
+// |kMetricSuccessfulUpdateDownloadOverheadPercentage|
+// |kMetricSuccessfulUpdateTotalDurationMinutes|
+// |kMetricSuccessfulUpdateRebootCount|
+// |kMetricSuccessfulUpdateUrlSwitchCount|
+//
+// The values for the |kMetricSuccessfulUpdateDownloadSourcesUsed| are
+// |kMetricSuccessfulUpdateBytesDownloadedMiB| metrics automatically
+// calculated from examining the |num_bytes_downloaded| array.
+void ReportSuccessfulUpdateMetrics(
+ SystemState *system_state,
+ int attempt_count,
+ int updates_abandoned_count,
+ PayloadType payload_type,
+ int64_t payload_size,
+ int64_t num_bytes_downloaded[kNumDownloadSources],
+ int download_overhead_percentage,
+ base::TimeDelta total_duration,
+ int reboot_count,
+ int url_switch_count);
+
+// Helper function to report the after the completion of a SSL certificate
+// check. One of the following metrics is reported:
+//
+// |kMetricCertificateCheckUpdateCheck|
+// |kMetricCertificateCheckDownload|
+void ReportCertificateCheckMetrics(SystemState* system_state,
+ ServerToCheck server_to_check,
+ CertificateCheckResult result);
+
+} // namespace metrics
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_METRICS_H_
diff --git a/metrics_utils.cc b/metrics_utils.cc
new file mode 100644
index 0000000..eb99c7d
--- /dev/null
+++ b/metrics_utils.cc
@@ -0,0 +1,301 @@
+//
+// 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/metrics_utils.h"
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/system_state.h"
+
+using base::Time;
+using base::TimeDelta;
+
+namespace chromeos_update_engine {
+namespace metrics_utils {
+
+metrics::AttemptResult GetAttemptResult(ErrorCode code) {
+ ErrorCode base_code = static_cast<ErrorCode>(
+ static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+ switch (base_code) {
+ case ErrorCode::kSuccess:
+ return metrics::AttemptResult::kUpdateSucceeded;
+
+ case ErrorCode::kDownloadTransferError:
+ return metrics::AttemptResult::kPayloadDownloadError;
+
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ case ErrorCode::kDownloadMetadataSignatureError:
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ case ErrorCode::kPayloadMismatchedType:
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ case ErrorCode::kDownloadManifestParseError:
+ case ErrorCode::kDownloadOperationHashMissingError:
+ return metrics::AttemptResult::kMetadataMalformed;
+
+ case ErrorCode::kDownloadOperationHashMismatch:
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ return metrics::AttemptResult::kOperationMalformed;
+
+ case ErrorCode::kDownloadOperationExecutionError:
+ case ErrorCode::kInstallDeviceOpenError:
+ case ErrorCode::kKernelDeviceOpenError:
+ case ErrorCode::kDownloadWriteError:
+ case ErrorCode::kFilesystemCopierError:
+ case ErrorCode::kFilesystemVerifierError:
+ return metrics::AttemptResult::kOperationExecutionError;
+
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ return metrics::AttemptResult::kMetadataVerificationFailed;
+
+ case ErrorCode::kPayloadSizeMismatchError:
+ case ErrorCode::kPayloadHashMismatchError:
+ case ErrorCode::kDownloadPayloadVerificationError:
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ return metrics::AttemptResult::kPayloadVerificationFailed;
+
+ case ErrorCode::kNewRootfsVerificationError:
+ case ErrorCode::kNewKernelVerificationError:
+ return metrics::AttemptResult::kVerificationFailed;
+
+ case ErrorCode::kPostinstallRunnerError:
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ return metrics::AttemptResult::kPostInstallFailed;
+
+ // We should never get these errors in the update-attempt stage so
+ // return internal error if this happens.
+ case ErrorCode::kError:
+ case ErrorCode::kOmahaRequestXMLParseError:
+ case ErrorCode::kOmahaRequestError:
+ case ErrorCode::kOmahaResponseHandlerError:
+ case ErrorCode::kDownloadStateInitializationError:
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ case ErrorCode::kOmahaResponseInvalid:
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaErrorInHTTPResponse:
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ case ErrorCode::kPostinstallPowerwashError:
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ return metrics::AttemptResult::kInternalError;
+
+ // 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|.
+ case ErrorCode::kUmaReportedMax:
+ case ErrorCode::kOmahaRequestHTTPResponseBase:
+ case ErrorCode::kDevModeFlag:
+ case ErrorCode::kResumedFlag:
+ case ErrorCode::kTestImageFlag:
+ case ErrorCode::kTestOmahaUrlFlag:
+ case ErrorCode::kSpecialFlags:
+ break;
+ }
+
+ LOG(ERROR) << "Unexpected error code " << base_code;
+ return metrics::AttemptResult::kInternalError;
+}
+
+metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code) {
+ ErrorCode base_code = static_cast<ErrorCode>(
+ static_cast<int>(code) & ~static_cast<int>(ErrorCode::kSpecialFlags));
+
+ if (base_code >= ErrorCode::kOmahaRequestHTTPResponseBase) {
+ int http_status =
+ static_cast<int>(base_code) -
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase);
+ if (http_status >= 200 && http_status <= 599) {
+ return static_cast<metrics::DownloadErrorCode>(
+ static_cast<int>(metrics::DownloadErrorCode::kHttpStatus200) +
+ http_status - 200);
+ } else if (http_status == 0) {
+ // The code is using HTTP Status 0 for "Unable to get http
+ // response code."
+ return metrics::DownloadErrorCode::kDownloadError;
+ }
+ LOG(WARNING) << "Unexpected HTTP status code " << http_status;
+ return metrics::DownloadErrorCode::kHttpStatusOther;
+ }
+
+ switch (base_code) {
+ // Unfortunately, ErrorCode::kDownloadTransferError is returned for a wide
+ // variety of errors (proxy errors, host not reachable, timeouts etc.).
+ //
+ // For now just map that to kDownloading. See http://crbug.com/355745
+ // for how we plan to add more detail in the future.
+ case ErrorCode::kDownloadTransferError:
+ return metrics::DownloadErrorCode::kDownloadError;
+
+ // All of these error codes are not related to downloading so break
+ // out so we can warn and return InputMalformed.
+ case ErrorCode::kSuccess:
+ case ErrorCode::kError:
+ case ErrorCode::kOmahaRequestError:
+ case ErrorCode::kOmahaResponseHandlerError:
+ case ErrorCode::kFilesystemCopierError:
+ case ErrorCode::kPostinstallRunnerError:
+ case ErrorCode::kPayloadMismatchedType:
+ case ErrorCode::kInstallDeviceOpenError:
+ case ErrorCode::kKernelDeviceOpenError:
+ case ErrorCode::kPayloadHashMismatchError:
+ case ErrorCode::kPayloadSizeMismatchError:
+ case ErrorCode::kDownloadPayloadVerificationError:
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ case ErrorCode::kDownloadWriteError:
+ case ErrorCode::kNewRootfsVerificationError:
+ case ErrorCode::kNewKernelVerificationError:
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ case ErrorCode::kDownloadStateInitializationError:
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ case ErrorCode::kDownloadManifestParseError:
+ case ErrorCode::kDownloadMetadataSignatureError:
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ case ErrorCode::kDownloadOperationExecutionError:
+ case ErrorCode::kDownloadOperationHashMismatch:
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ case ErrorCode::kOmahaRequestXMLParseError:
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ case ErrorCode::kOmahaResponseInvalid:
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaErrorInHTTPResponse:
+ case ErrorCode::kDownloadOperationHashMissingError:
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ case ErrorCode::kPostinstallPowerwashError:
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ case ErrorCode::kFilesystemVerifierError:
+ break;
+
+ // 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 |kInputMalformed|.
+ case ErrorCode::kUmaReportedMax:
+ case ErrorCode::kOmahaRequestHTTPResponseBase:
+ case ErrorCode::kDevModeFlag:
+ case ErrorCode::kResumedFlag:
+ case ErrorCode::kTestImageFlag:
+ case ErrorCode::kTestOmahaUrlFlag:
+ case ErrorCode::kSpecialFlags:
+ LOG(ERROR) << "Unexpected error code " << base_code;
+ break;
+ }
+
+ return metrics::DownloadErrorCode::kInputMalformed;
+}
+
+metrics::ConnectionType GetConnectionType(NetworkConnectionType type,
+ NetworkTethering tethering) {
+ switch (type) {
+ case NetworkConnectionType::kUnknown:
+ return metrics::ConnectionType::kUnknown;
+
+ case NetworkConnectionType::kEthernet:
+ if (tethering == NetworkTethering::kConfirmed)
+ return metrics::ConnectionType::kTetheredEthernet;
+ else
+ return metrics::ConnectionType::kEthernet;
+
+ case NetworkConnectionType::kWifi:
+ if (tethering == NetworkTethering::kConfirmed)
+ return metrics::ConnectionType::kTetheredWifi;
+ else
+ return metrics::ConnectionType::kWifi;
+
+ case NetworkConnectionType::kWimax:
+ return metrics::ConnectionType::kWimax;
+
+ case NetworkConnectionType::kBluetooth:
+ return metrics::ConnectionType::kBluetooth;
+
+ case NetworkConnectionType::kCellular:
+ return metrics::ConnectionType::kCellular;
+ }
+
+ LOG(ERROR) << "Unexpected network connection type: type="
+ << static_cast<int>(type)
+ << ", tethering=" << static_cast<int>(tethering);
+
+ 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;
+}
+
+} // namespace metrics_utils
+} // namespace chromeos_update_engine
diff --git a/metrics_utils.h b/metrics_utils.h
new file mode 100644
index 0000000..7c3b02d
--- /dev/null
+++ b/metrics_utils.h
@@ -0,0 +1,71 @@
+//
+// 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_METRICS_UTILS_H_
+#define UPDATE_ENGINE_METRICS_UTILS_H_
+
+#include "update_engine/connection_manager.h"
+#include "update_engine/metrics.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+namespace metrics_utils {
+
+// Transforms a ErrorCode value into a metrics::DownloadErrorCode.
+// This obviously only works for errors related to downloading so if |code|
+// is e.g. |ErrorCode::kFilesystemCopierError| then
+// |kDownloadErrorCodeInputMalformed| is returned.
+metrics::DownloadErrorCode GetDownloadErrorCode(ErrorCode code);
+
+// Transforms a ErrorCode value into a metrics::AttemptResult.
+//
+// If metrics::AttemptResult::kPayloadDownloadError is returned, you
+// can use utils::GetDownloadError() to get more detail.
+metrics::AttemptResult GetAttemptResult(ErrorCode code);
+
+// Calculates the internet connection type given |type| and |tethering|.
+metrics::ConnectionType GetConnectionType(NetworkConnectionType type,
+ NetworkTethering 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);
+
+} // namespace metrics_utils
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_METRICS_UTILS_H_
diff --git a/metrics_utils_unittest.cc b/metrics_utils_unittest.cc
new file mode 100644
index 0000000..e702c17
--- /dev/null
+++ b/metrics_utils_unittest.cc
@@ -0,0 +1,210 @@
+//
+// 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/metrics_utils.h"
+
+#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 {
+
+class MetricsUtilsTest : public ::testing::Test {};
+
+TEST(MetricsUtilsTest, GetConnectionType) {
+ // Check that expected combinations map to the right value.
+ EXPECT_EQ(metrics::ConnectionType::kUnknown,
+ GetConnectionType(NetworkConnectionType::kUnknown,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ GetConnectionType(NetworkConnectionType::kEthernet,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ GetConnectionType(NetworkConnectionType::kWifi,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kWimax,
+ GetConnectionType(NetworkConnectionType::kWimax,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kBluetooth,
+ GetConnectionType(NetworkConnectionType::kBluetooth,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kCellular,
+ GetConnectionType(NetworkConnectionType::kCellular,
+ NetworkTethering::kUnknown));
+ EXPECT_EQ(metrics::ConnectionType::kTetheredEthernet,
+ GetConnectionType(NetworkConnectionType::kEthernet,
+ NetworkTethering::kConfirmed));
+ EXPECT_EQ(metrics::ConnectionType::kTetheredWifi,
+ GetConnectionType(NetworkConnectionType::kWifi,
+ NetworkTethering::kConfirmed));
+
+ // Ensure that we don't report tethered ethernet unless it's confirmed.
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ GetConnectionType(NetworkConnectionType::kEthernet,
+ NetworkTethering::kNotDetected));
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ GetConnectionType(NetworkConnectionType::kEthernet,
+ NetworkTethering::kSuspected));
+ EXPECT_EQ(metrics::ConnectionType::kEthernet,
+ GetConnectionType(NetworkConnectionType::kEthernet,
+ NetworkTethering::kUnknown));
+
+ // Ditto for tethered wifi.
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ GetConnectionType(NetworkConnectionType::kWifi,
+ NetworkTethering::kNotDetected));
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ GetConnectionType(NetworkConnectionType::kWifi,
+ NetworkTethering::kSuspected));
+ EXPECT_EQ(metrics::ConnectionType::kWifi,
+ GetConnectionType(NetworkConnectionType::kWifi,
+ NetworkTethering::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_action.h b/mock_action.h
new file mode 100644
index 0000000..0ba796d
--- /dev/null
+++ b/mock_action.h
@@ -0,0 +1,45 @@
+//
+// 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_MOCK_ACTION_H_
+#define UPDATE_ENGINE_MOCK_ACTION_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/action.h"
+
+namespace chromeos_update_engine {
+
+class MockAction;
+
+template<>
+class ActionTraits<MockAction> {
+ public:
+ typedef NoneType OutputObjectType;
+ typedef NoneType InputObjectType;
+};
+
+class MockAction : public Action<MockAction> {
+ public:
+ MOCK_METHOD0(PerformAction, void());
+ MOCK_CONST_METHOD0(Type, std::string());
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_ACTION_H_
diff --git a/mock_action_processor.h b/mock_action_processor.h
new file mode 100644
index 0000000..c98ff3c
--- /dev/null
+++ b/mock_action_processor.h
@@ -0,0 +1,34 @@
+//
+// 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_MOCK_ACTION_PROCESSOR_H_
+#define UPDATE_ENGINE_MOCK_ACTION_PROCESSOR_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/action.h"
+
+namespace chromeos_update_engine {
+
+class MockActionProcessor : public ActionProcessor {
+ public:
+ MOCK_METHOD0(StartProcessing, void());
+ MOCK_METHOD1(EnqueueAction, void(AbstractAction* action));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_ACTION_PROCESSOR_H_
diff --git a/mock_connection_manager.h b/mock_connection_manager.h
new file mode 100644
index 0000000..109c529
--- /dev/null
+++ b/mock_connection_manager.h
@@ -0,0 +1,43 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
+#define UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
+
+#include <gmock/gmock.h>
+
+#include "update_engine/connection_manager_interface.h"
+
+namespace chromeos_update_engine {
+
+// This class mocks the generic interface to the connection manager
+// (e.g FlimFlam, Shill, etc.) to consolidate all connection-related
+// logic in update_engine.
+class MockConnectionManager : public ConnectionManagerInterface {
+ public:
+ MockConnectionManager() = default;
+
+ MOCK_METHOD2(GetConnectionProperties,
+ bool(NetworkConnectionType* out_type,
+ NetworkTethering* out_tethering));
+
+ MOCK_CONST_METHOD2(IsUpdateAllowedOver, bool(NetworkConnectionType type,
+ NetworkTethering tethering));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
diff --git a/mock_file_writer.h b/mock_file_writer.h
new file mode 100644
index 0000000..72d6a86
--- /dev/null
+++ b/mock_file_writer.h
@@ -0,0 +1,33 @@
+//
+// 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_MOCK_FILE_WRITER_H_
+#define UPDATE_ENGINE_MOCK_FILE_WRITER_H_
+
+#include <gmock/gmock.h>
+#include "update_engine/payload_consumer/file_writer.h"
+
+namespace chromeos_update_engine {
+
+class MockFileWriter : public FileWriter {
+ public:
+ MOCK_METHOD2(Write, ssize_t(const void* bytes, size_t count));
+ MOCK_METHOD0(Close, int());
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_FILE_WRITER_H_
diff --git a/mock_omaha_request_params.h b/mock_omaha_request_params.h
new file mode 100644
index 0000000..5d5d47b
--- /dev/null
+++ b/mock_omaha_request_params.h
@@ -0,0 +1,91 @@
+//
+// 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_MOCK_OMAHA_REQUEST_PARAMS_H_
+#define UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/omaha_request_params.h"
+
+namespace chromeos_update_engine {
+
+class MockOmahaRequestParams : public OmahaRequestParams {
+ public:
+ explicit MockOmahaRequestParams(SystemState* system_state)
+ : OmahaRequestParams(system_state) {
+ // 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.
+ ON_CALL(*this, to_more_stable_channel())
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::fake_to_more_stable_channel));
+ ON_CALL(*this, GetAppId())
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::FakeGetAppId));
+ ON_CALL(*this, SetTargetChannel(testing::_, testing::_, testing::_))
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::FakeSetTargetChannel));
+ ON_CALL(*this, UpdateDownloadChannel())
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::FakeUpdateDownloadChannel));
+ ON_CALL(*this, is_powerwash_allowed())
+ .WillByDefault(testing::Invoke(
+ this, &MockOmahaRequestParams::fake_is_powerwash_allowed));
+ }
+
+ MOCK_CONST_METHOD0(to_more_stable_channel, bool(void));
+ MOCK_CONST_METHOD0(GetAppId, std::string(void));
+ MOCK_METHOD3(SetTargetChannel, bool(const std::string& channel,
+ bool is_powerwash_allowed,
+ std::string* error));
+ MOCK_METHOD0(UpdateDownloadChannel, void(void));
+ MOCK_CONST_METHOD0(is_powerwash_allowed, bool(void));
+ MOCK_CONST_METHOD0(IsUpdateUrlOfficial, bool(void));
+
+ private:
+ // Wrappers to call the parent class and behave like the real object by
+ // default. See "Delegating Calls to a Parent Class" in gmock's documentation.
+ bool fake_to_more_stable_channel() const {
+ return OmahaRequestParams::to_more_stable_channel();
+ }
+
+ std::string FakeGetAppId() const {
+ return OmahaRequestParams::GetAppId();
+ }
+
+ bool FakeSetTargetChannel(const std::string& channel,
+ bool is_powerwash_allowed,
+ std::string* error) {
+ return OmahaRequestParams::SetTargetChannel(channel,
+ is_powerwash_allowed,
+ error);
+ }
+
+ void FakeUpdateDownloadChannel() {
+ return OmahaRequestParams::UpdateDownloadChannel();
+ }
+
+ bool fake_is_powerwash_allowed() const {
+ return OmahaRequestParams::is_powerwash_allowed();
+ }
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_OMAHA_REQUEST_PARAMS_H_
diff --git a/mock_p2p_manager.h b/mock_p2p_manager.h
new file mode 100644
index 0000000..5f4418e
--- /dev/null
+++ b/mock_p2p_manager.h
@@ -0,0 +1,109 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_MOCK_P2P_MANAGER_H_
+#define UPDATE_ENGINE_MOCK_P2P_MANAGER_H_
+
+#include <string>
+
+#include "update_engine/fake_p2p_manager.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+// A mocked, fake implementation of P2PManager.
+class MockP2PManager : public P2PManager {
+ public:
+ MockP2PManager() {
+ // Delegate all calls to the fake instance
+ ON_CALL(*this, SetDevicePolicy(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::SetDevicePolicy));
+ ON_CALL(*this, IsP2PEnabled())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::IsP2PEnabled));
+ ON_CALL(*this, EnsureP2PRunning())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::EnsureP2PRunning));
+ ON_CALL(*this, EnsureP2PNotRunning())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::EnsureP2PNotRunning));
+ ON_CALL(*this, PerformHousekeeping())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::PerformHousekeeping));
+ ON_CALL(*this, LookupUrlForFile(testing::_, testing::_, testing::_,
+ testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::LookupUrlForFile));
+ ON_CALL(*this, FileShare(testing::_, testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileShare));
+ ON_CALL(*this, FileGetPath(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileGetPath));
+ ON_CALL(*this, FileGetSize(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileGetSize));
+ ON_CALL(*this, FileGetExpectedSize(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileGetExpectedSize));
+ ON_CALL(*this, FileGetVisible(testing::_, testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileGetVisible));
+ ON_CALL(*this, FileMakeVisible(testing::_))
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::FileMakeVisible));
+ ON_CALL(*this, CountSharedFiles())
+ .WillByDefault(testing::Invoke(&fake_,
+ &FakeP2PManager::CountSharedFiles));
+ }
+
+ ~MockP2PManager() override {}
+
+ // P2PManager overrides.
+ MOCK_METHOD1(SetDevicePolicy, void(const policy::DevicePolicy*));
+ MOCK_METHOD0(IsP2PEnabled, bool());
+ MOCK_METHOD0(EnsureP2PRunning, bool());
+ MOCK_METHOD0(EnsureP2PNotRunning, bool());
+ MOCK_METHOD0(PerformHousekeeping, bool());
+ MOCK_METHOD4(LookupUrlForFile, void(const std::string&,
+ size_t,
+ base::TimeDelta,
+ LookupCallback));
+ MOCK_METHOD2(FileShare, bool(const std::string&, size_t));
+ MOCK_METHOD1(FileGetPath, base::FilePath(const std::string&));
+ MOCK_METHOD1(FileGetSize, ssize_t(const std::string&));
+ MOCK_METHOD1(FileGetExpectedSize, ssize_t(const std::string&));
+ MOCK_METHOD2(FileGetVisible, bool(const std::string&, bool*));
+ MOCK_METHOD1(FileMakeVisible, bool(const std::string&));
+ MOCK_METHOD0(CountSharedFiles, int());
+
+ // Returns a reference to the underlying FakeP2PManager.
+ FakeP2PManager& fake() {
+ return fake_;
+ }
+
+ private:
+ // The underlying FakeP2PManager.
+ FakeP2PManager fake_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockP2PManager);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_P2P_MANAGER_H_
diff --git a/mock_payload_state.h b/mock_payload_state.h
new file mode 100644
index 0000000..728f274
--- /dev/null
+++ b/mock_payload_state.h
@@ -0,0 +1,81 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
+#define UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/omaha_request_action.h"
+#include "update_engine/payload_state_interface.h"
+
+namespace chromeos_update_engine {
+
+class MockPayloadState: public PayloadStateInterface {
+ public:
+ bool Initialize(SystemState* system_state) {
+ return true;
+ }
+
+ // Significant methods.
+ MOCK_METHOD1(SetResponse, void(const OmahaResponse& response));
+ MOCK_METHOD0(DownloadComplete, void());
+ MOCK_METHOD1(DownloadProgress, void(size_t count));
+ MOCK_METHOD0(UpdateResumed, void());
+ MOCK_METHOD0(UpdateRestarted, void());
+ MOCK_METHOD0(UpdateSucceeded, void());
+ MOCK_METHOD1(UpdateFailed, void(ErrorCode error));
+ MOCK_METHOD0(ResetUpdateStatus, void());
+ MOCK_METHOD0(ShouldBackoffDownload, bool());
+ MOCK_METHOD0(UpdateEngineStarted, void());
+ MOCK_METHOD0(Rollback, void());
+ MOCK_METHOD1(ExpectRebootInNewVersion,
+ void(const std::string& target_version_uid));
+ MOCK_METHOD0(P2PNewAttempt, void());
+ MOCK_METHOD0(P2PAttemptAllowed, bool());
+ MOCK_METHOD1(SetUsingP2PForDownloading, void(bool value));
+ MOCK_METHOD1(SetUsingP2PForSharing, void(bool value));
+ MOCK_METHOD1(SetScatteringWaitPeriod, void(base::TimeDelta));
+ MOCK_METHOD1(SetP2PUrl, void(const std::string&));
+
+ // Getters.
+ MOCK_METHOD0(GetResponseSignature, std::string());
+ MOCK_METHOD0(GetPayloadAttemptNumber, int());
+ MOCK_METHOD0(GetFullPayloadAttemptNumber, int());
+ MOCK_METHOD0(GetCurrentUrl, std::string());
+ MOCK_METHOD0(GetUrlFailureCount, uint32_t());
+ MOCK_METHOD0(GetUrlSwitchCount, uint32_t());
+ MOCK_METHOD0(GetNumResponsesSeen, int());
+ MOCK_METHOD0(GetBackoffExpiryTime, base::Time());
+ MOCK_METHOD0(GetUpdateDuration, base::TimeDelta());
+ MOCK_METHOD0(GetUpdateDurationUptime, base::TimeDelta());
+ MOCK_METHOD1(GetCurrentBytesDownloaded, uint64_t(DownloadSource source));
+ MOCK_METHOD1(GetTotalBytesDownloaded, uint64_t(DownloadSource source));
+ MOCK_METHOD0(GetNumReboots, uint32_t());
+ MOCK_METHOD0(GetRollbackVersion, std::string());
+ MOCK_METHOD0(GetP2PNumAttempts, int());
+ MOCK_METHOD0(GetP2PFirstAttemptTimestamp, base::Time());
+ MOCK_CONST_METHOD0(GetUsingP2PForDownloading, bool());
+ MOCK_CONST_METHOD0(GetUsingP2PForSharing, bool());
+ MOCK_METHOD0(GetScatteringWaitPeriod, base::TimeDelta());
+ MOCK_CONST_METHOD0(GetP2PUrl, std::string());
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
diff --git a/mock_update_attempter.h b/mock_update_attempter.h
new file mode 100644
index 0000000..89f163e
--- /dev/null
+++ b/mock_update_attempter.h
@@ -0,0 +1,64 @@
+//
+// 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_MOCK_UPDATE_ATTEMPTER_H_
+#define UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_
+
+#include <string>
+
+#include "update_engine/update_attempter.h"
+
+#include <gmock/gmock.h>
+
+namespace chromeos_update_engine {
+
+class MockUpdateAttempter : public UpdateAttempter {
+ public:
+ using UpdateAttempter::UpdateAttempter;
+
+ MOCK_METHOD6(Update, void(const std::string& app_version,
+ const std::string& omaha_url,
+ const std::string& target_channel,
+ const std::string& target_version_prefix,
+ bool obey_proxies,
+ bool interactive));
+
+ MOCK_METHOD5(GetStatus, bool(int64_t* last_checked_time,
+ double* progress,
+ std::string* current_operation,
+ std::string* new_version,
+ int64_t* new_size));
+
+ MOCK_METHOD1(GetBootTimeAtUpdate, bool(base::Time* out_boot_time));
+
+ MOCK_METHOD0(ResetStatus, bool(void));
+
+ MOCK_METHOD3(CheckForUpdate, void(const std::string& app_version,
+ const std::string& omaha_url,
+ bool is_interactive));
+
+ MOCK_METHOD0(RefreshDevicePolicy, void(void));
+
+ MOCK_CONST_METHOD0(consecutive_failed_update_checks, unsigned int(void));
+
+ MOCK_CONST_METHOD0(server_dictated_poll_interval, unsigned int(void));
+
+ MOCK_METHOD0(IsAnyUpdateSourceAllowed, bool(void));
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
new file mode 100644
index 0000000..f2cd032
--- /dev/null
+++ b/omaha_request_action.cc
@@ -0,0 +1,1484 @@
+//
+// 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 <inttypes.h>
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#include <base/bind.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>
+#include <expat.h>
+#include <metrics/metrics_library.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/connection_manager.h"
+#include "update_engine/metrics.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::Time;
+using base::TimeDelta;
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// List of custom pair tags that we interpret in the Omaha Response:
+static const char* kTagDeadline = "deadline";
+static const char* kTagDisablePayloadBackoff = "DisablePayloadBackoff";
+static const char* kTagVersion = "version";
+// Deprecated: "IsDelta"
+static const char* kTagIsDeltaPayload = "IsDeltaPayload";
+static const char* kTagMaxFailureCountPerUrl = "MaxFailureCountPerUrl";
+static const char* kTagMaxDaysToScatter = "MaxDaysToScatter";
+// Deprecated: "ManifestSignatureRsa"
+// Deprecated: "ManifestSize"
+static const char* kTagMetadataSignatureRsa = "MetadataSignatureRsa";
+static const char* kTagMetadataSize = "MetadataSize";
+static const char* kTagMoreInfo = "MoreInfo";
+// Deprecated: "NeedsAdmin"
+static const char* kTagPrompt = "Prompt";
+static const char* kTagSha256 = "sha256";
+static const char* kTagDisableP2PForDownloading = "DisableP2PForDownloading";
+static const char* kTagDisableP2PForSharing = "DisableP2PForSharing";
+static const char* kTagPublicKeyRsa = "PublicKeyRsa";
+
+static const char* kOmahaUpdaterVersion = "0.1.0.0";
+
+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,
+ 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) {
+ app_body += base::StringPrintf(
+ " <updatecheck targetversionprefix=\"%s\""
+ "></updatecheck>\n",
+ XmlEncodeWithDefault(params->target_version_prefix(), "").c_str());
+
+ // 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
+ // existance 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());
+}
+
+// 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,
+ bool ping_only,
+ bool include_ping,
+ 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,
+ ping_active_days, ping_roll_call_days,
+ system_state->prefs());
+ string app_versions;
+
+ // If we are upgrading 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->to_more_stable_channel() && params->is_powerwash_allowed()) {
+ 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(params->app_version(), "0.0.0.0") + "\" ";
+ } else {
+ app_versions = "version=\"" +
+ XmlEncodeWithDefault(params->app_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 app_xml = " <app "
+ "appid=\"" + XmlEncodeWithDefault(params->GetAppId(), "") + "\" " +
+ app_cohort_args +
+ app_versions +
+ app_channels +
+ "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";
+
+ 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);
+ string app_xml = GetAppXml(event, params, ping_only, include_ping,
+ 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
+
+// Struct used for holding data obtained when parsing the XML.
+struct OmahaParserData {
+ explicit OmahaParserData(XML_Parser _xml_parser) : xml_parser(_xml_parser) {}
+
+ // Pointer to the expat XML_Parser object.
+ XML_Parser xml_parser;
+
+ // 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 app_cohort;
+ string app_cohorthint;
+ string app_cohortname;
+ bool app_cohort_set = false;
+ bool app_cohorthint_set = false;
+ bool app_cohortname_set = false;
+ string updatecheck_status;
+ string updatecheck_poll_interval;
+ string daystart_elapsed_days;
+ string daystart_elapsed_seconds;
+ vector<string> url_codebase;
+ string package_name;
+ string package_size;
+ string manifest_version;
+ map<string, string> action_postinstall_attrs;
+};
+
+namespace {
+
+// Callback function invoked by expat.
+void ParserHandlerStart(void* user_data, const XML_Char* element,
+ const XML_Char** attr) {
+ OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+
+ if (data->failed)
+ return;
+
+ data->current_path += string("/") + element;
+
+ map<string, string> attrs;
+ if (attr != nullptr) {
+ for (int n = 0; attr[n] != nullptr && attr[n+1] != nullptr; n += 2) {
+ string key = attr[n];
+ string value = attr[n + 1];
+ attrs[key] = value;
+ }
+ }
+
+ if (data->current_path == "/response/app") {
+ if (attrs.find("cohort") != attrs.end()) {
+ data->app_cohort_set = true;
+ data->app_cohort = attrs["cohort"];
+ }
+ if (attrs.find("cohorthint") != attrs.end()) {
+ data->app_cohorthint_set = true;
+ data->app_cohorthint = attrs["cohorthint"];
+ }
+ if (attrs.find("cohortname") != attrs.end()) {
+ data->app_cohortname_set = true;
+ data->app_cohortname = attrs["cohortname"];
+ }
+ } else if (data->current_path == "/response/app/updatecheck") {
+ // There is only supposed to be a single <updatecheck> element.
+ data->updatecheck_status = attrs["status"];
+ data->updatecheck_poll_interval = attrs["PollInterval"];
+ } else if (data->current_path == "/response/daystart") {
+ // Get the install-date.
+ data->daystart_elapsed_days = attrs["elapsed_days"];
+ data->daystart_elapsed_seconds = attrs["elapsed_seconds"];
+ } else if (data->current_path == "/response/app/updatecheck/urls/url") {
+ // Look at all <url> elements.
+ data->url_codebase.push_back(attrs["codebase"]);
+ } else if (data->package_name.empty() && data->current_path ==
+ "/response/app/updatecheck/manifest/packages/package") {
+ // Only look at the first <package>.
+ data->package_name = attrs["name"];
+ data->package_size = attrs["size"];
+ } else if (data->current_path == "/response/app/updatecheck/manifest") {
+ // Get the version.
+ data->manifest_version = attrs[kTagVersion];
+ } else if (data->current_path ==
+ "/response/app/updatecheck/manifest/actions/action") {
+ // We only care about the postinstall action.
+ if (attrs["event"] == "postinstall") {
+ data->action_postinstall_attrs = attrs;
+ }
+ }
+}
+
+// Callback function invoked by expat.
+void ParserHandlerEnd(void* user_data, const XML_Char* element) {
+ OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+ if (data->failed)
+ return;
+
+ const string path_suffix = string("/") + element;
+
+ if (!base::EndsWith(data->current_path, path_suffix, true)) {
+ LOG(ERROR) << "Unexpected end element '" << element
+ << "' with current_path='" << data->current_path << "'";
+ data->failed = true;
+ return;
+ }
+ data->current_path.resize(data->current_path.size() - path_suffix.size());
+}
+
+// Callback function invoked by expat.
+//
+// This is called for entity declarations. Since Omaha is guaranteed
+// to never return any XML with entities our course of action is to
+// just stop parsing. This avoids potential resource exhaustion
+// problems AKA the "billion laughs". CVE-2013-0340.
+void ParserHandlerEntityDecl(void *user_data,
+ const XML_Char *entity_name,
+ int is_parameter_entity,
+ const XML_Char *value,
+ int value_length,
+ const XML_Char *base,
+ const XML_Char *system_id,
+ const XML_Char *public_id,
+ const XML_Char *notation_name) {
+ OmahaParserData* data = reinterpret_cast<OmahaParserData*>(user_data);
+
+ LOG(ERROR) << "XML entities are not supported. Aborting parsing.";
+ data->failed = true;
+ data->entity_decl = true;
+ XML_StopParser(data->xml_parser, false);
+}
+
+} // 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),
+ event_(event),
+ http_fetcher_(std::move(http_fetcher)),
+ ping_only_(ping_only),
+ ping_active_days_(0),
+ ping_roll_call_days_(0) {
+ params_ = system_state->request_params();
+}
+
+OmahaRequestAction::~OmahaRequestAction() {}
+
+// Calculates the value to use for the ping days parameter.
+int OmahaRequestAction::CalculatePingDays(const string& key) {
+ int days = kNeverPinged;
+ int64_t last_ping = 0;
+ if (system_state_->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
+ // back in time since the ping was sent. Mark the value so that
+ // it doesn't get sent to the server but we still update the
+ // last ping daystart preference. This way the next ping time
+ // will be correct, hopefully.
+ days = kPingTimeJump;
+ LOG(WARNING) <<
+ "System clock jumped back in time. Resetting ping daystarts.";
+ }
+ }
+ return days;
+}
+
+void OmahaRequestAction::InitPingDays() {
+ // We send pings only along with update checks, not with events.
+ if (IsEvent()) {
+ return;
+ }
+ // TODO(petkov): Figure a way to distinguish active use pings
+ // vs. roll call pings. Currently, the two pings are identical. A
+ // fix needs to change this code as well as UpdateLastPingDays and ShouldPing.
+ ping_active_days_ = CalculatePingDays(kPrefsLastActivePingDay);
+ ping_roll_call_days_ = CalculatePingDays(kPrefsLastRollCallPingDay);
+}
+
+bool OmahaRequestAction::ShouldPing() const {
+ if (ping_active_days_ == OmahaRequestAction::kNeverPinged &&
+ ping_roll_call_days_ == OmahaRequestAction::kNeverPinged) {
+ int powerwash_count = system_state_->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;
+ }
+ return true;
+ }
+ return ping_active_days_ > 0 || ping_roll_call_days_ > 0;
+}
+
+// static
+int OmahaRequestAction::GetInstallDate(SystemState* system_state) {
+ PrefsInterface* prefs = system_state->prefs();
+ if (prefs == nullptr)
+ return -1;
+
+ // 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.
+ int install_date_days = static_cast<int>(stored_value);
+ if (install_date_days >= 0)
+ return install_date_days;
+ LOG(ERROR) << "Dropping stored Omaha InstallData since its value num_days="
+ << install_date_days << " looks suspicious.";
+ prefs->Delete(kPrefsInstallDateDays);
+ }
+
+ // Otherwise, if OOBE is not complete then do nothing and wait for
+ // ParseResponse() to call ParseInstallDate() and then
+ // PersistInstallDate() to set the kPrefsInstallDateDays state
+ // variable. Once that is done, we'll then report back in future
+ // Omaha requests. This works exactly because OOBE triggers an
+ // update check.
+ //
+ // However, if OOBE is complete and the kPrefsInstallDateDays state
+ // variable is not set, there are two possibilities
+ //
+ // 1. The update check in OOBE failed so we never got a response
+ // from Omaha (no network etc.); or
+ //
+ // 2. OOBE was done on an older version that didn't write to the
+ // kPrefsInstallDateDays state variable.
+ //
+ // In both cases, we approximate the install date by simply
+ // inspecting the timestamp of when OOBE happened.
+
+ Time time_of_oobe;
+ if (!system_state->hardware()->IsOOBEComplete(&time_of_oobe)) {
+ LOG(INFO) << "Not generating Omaha InstallData as we have "
+ << "no prefs file and OOBE is not complete.";
+ return -1;
+ }
+
+ int num_days;
+ if (!utils::ConvertToOmahaInstallDate(time_of_oobe, &num_days)) {
+ LOG(ERROR) << "Not generating Omaha InstallData from time of OOBE "
+ << "as its value '" << utils::ToString(time_of_oobe)
+ << "' looks suspicious.";
+ return -1;
+ }
+
+ // Persist this to disk, for future use.
+ if (!OmahaRequestAction::PersistInstallDate(system_state,
+ num_days,
+ kProvisionedFromOOBEMarker))
+ return -1;
+
+ LOG(INFO) << "Set the Omaha InstallDate from OOBE time-stamp to "
+ << num_days << " days";
+
+ return num_days;
+}
+
+void OmahaRequestAction::PerformAction() {
+ http_fetcher_->set_delegate(this);
+ InitPingDays();
+ if (ping_only_ && !ShouldPing()) {
+ processor_->ActionComplete(this, ErrorCode::kSuccess);
+ 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_));
+
+ http_fetcher_->SetPostData(request_post.data(), request_post.size(),
+ kHttpContentTypeTextXml);
+ LOG(INFO) << "Posting an Omaha request to " << params_->update_url();
+ LOG(INFO) << "Request: " << request_post;
+ http_fetcher_->BeginTransfer(params_->update_url());
+}
+
+void OmahaRequestAction::TerminateProcessing() {
+ http_fetcher_->TerminateTransfer();
+}
+
+// We just store the response in the buffer. Once we've received all bytes,
+// we'll look in the buffer and decide what to do.
+void OmahaRequestAction::ReceivedBytes(HttpFetcher *fetcher,
+ const void* bytes,
+ size_t length) {
+ const uint8_t* byte_ptr = reinterpret_cast<const uint8_t*>(bytes);
+ response_buffer_.insert(response_buffer_.end(), byte_ptr, byte_ptr + length);
+}
+
+namespace {
+
+// Parses a 64 bit base-10 int from a string and returns it. Returns 0
+// on error. If the string contains "0", that's indistinguishable from
+// error.
+off_t ParseInt(const string& str) {
+ off_t ret = 0;
+ int rc = sscanf(str.c_str(), "%" PRIi64, &ret); // NOLINT(runtime/printf)
+ if (rc < 1) {
+ // failure
+ return 0;
+ }
+ return ret;
+}
+
+// Parses |str| and returns |true| if, and only if, its value is "true".
+bool ParseBool(const string& str) {
+ return str == "true";
+}
+
+// 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) {
+ int64_t elapsed_seconds = 0;
+ TEST_AND_RETURN_FALSE(
+ base::StringToInt64(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.
+ Time daystart = Time::Now() - TimeDelta::FromSeconds(elapsed_seconds);
+ prefs->SetInt64(kPrefsLastActivePingDay, daystart.ToInternalValue());
+ prefs->SetInt64(kPrefsLastRollCallPingDay, daystart.ToInternalValue());
+ return true;
+}
+} // namespace
+
+bool OmahaRequestAction::ParseResponse(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ if (parser_data->updatecheck_status.empty()) {
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ // 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);
+
+ // 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_)) {
+ // 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,
+ kProvisionedFromOmahaResponse)) {
+ LOG(INFO) << "Set the Omaha InstallDate from Omaha Response to "
+ << install_date_days_rounded << " days";
+ }
+ }
+
+ // We persist the cohorts sent by omaha even if the status is "noupdate".
+ if (parser_data->app_cohort_set)
+ PersistCohortData(kPrefsOmahaCohort, parser_data->app_cohort);
+ if (parser_data->app_cohorthint_set)
+ PersistCohortData(kPrefsOmahaCohortHint, parser_data->app_cohorthint);
+ if (parser_data->app_cohortname_set)
+ PersistCohortData(kPrefsOmahaCohortName, parser_data->app_cohortname);
+
+ if (!ParseStatus(parser_data, output_object, completer))
+ return false;
+
+ // Note: ParseUrls MUST be called before ParsePackage as ParsePackage
+ // appends the package name to the URLs populated in this method.
+ if (!ParseUrls(parser_data, output_object, completer))
+ return false;
+
+ if (!ParsePackage(parser_data, output_object, completer))
+ return false;
+
+ if (!ParseParams(parser_data, output_object, completer))
+ return false;
+
+ return true;
+}
+
+bool OmahaRequestAction::ParseStatus(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ const string& status = parser_data->updatecheck_status;
+ if (status == "noupdate") {
+ LOG(INFO) << "No update.";
+ output_object->update_exists = false;
+ SetOutputObject(*output_object);
+ completer->set_code(ErrorCode::kSuccess);
+ return false;
+ }
+
+ if (status != "ok") {
+ LOG(ERROR) << "Unknown Omaha response status: " << status;
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ return true;
+}
+
+bool OmahaRequestAction::ParseUrls(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ if (parser_data->url_codebase.empty()) {
+ LOG(ERROR) << "No Omaha Response URLs";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ LOG(INFO) << "Found " << parser_data->url_codebase.size() << " url(s)";
+ output_object->payload_urls.clear();
+ for (const auto& codebase : parser_data->url_codebase) {
+ if (codebase.empty()) {
+ LOG(ERROR) << "Omaha Response URL has empty codebase";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ output_object->payload_urls.push_back(codebase);
+ }
+
+ return true;
+}
+
+bool OmahaRequestAction::ParsePackage(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ if (parser_data->package_name.empty()) {
+ LOG(ERROR) << "Omaha Response has empty package name";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ // Append the package name to each URL in our list so that we don't
+ // propagate the urlBase vs packageName distinctions beyond this point.
+ // From now on, we only need to use payload_urls.
+ for (auto& payload_url : output_object->payload_urls)
+ payload_url += parser_data->package_name;
+
+ // Parse the payload size.
+ off_t size = ParseInt(parser_data->package_size);
+ if (size <= 0) {
+ LOG(ERROR) << "Omaha Response has invalid payload size: " << size;
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+ output_object->size = size;
+
+ LOG(INFO) << "Payload size = " << output_object->size << " bytes";
+
+ return true;
+}
+
+bool OmahaRequestAction::ParseParams(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer) {
+ output_object->version = parser_data->manifest_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;
+
+ map<string, string> attrs = parser_data->action_postinstall_attrs;
+ if (attrs.empty()) {
+ LOG(ERROR) << "Omaha Response has no postinstall event action";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ output_object->hash = attrs[kTagSha256];
+ if (output_object->hash.empty()) {
+ LOG(ERROR) << "Omaha Response has empty sha256 value";
+ completer->set_code(ErrorCode::kOmahaResponseInvalid);
+ return false;
+ }
+
+ // Get the optional properties one by one.
+ output_object->more_info_url = attrs[kTagMoreInfo];
+ output_object->metadata_size = ParseInt(attrs[kTagMetadataSize]);
+ output_object->metadata_signature = attrs[kTagMetadataSignatureRsa];
+ output_object->prompt = ParseBool(attrs[kTagPrompt]);
+ output_object->deadline = attrs[kTagDeadline];
+ output_object->max_days_to_scatter = ParseInt(attrs[kTagMaxDaysToScatter]);
+ output_object->disable_p2p_for_downloading =
+ ParseBool(attrs[kTagDisableP2PForDownloading]);
+ output_object->disable_p2p_for_sharing =
+ ParseBool(attrs[kTagDisableP2PForSharing]);
+ output_object->public_key_rsa = attrs[kTagPublicKeyRsa];
+
+ string max = attrs[kTagMaxFailureCountPerUrl];
+ if (!base::StringToUint(max, &output_object->max_failure_count_per_url))
+ output_object->max_failure_count_per_url = kDefaultMaxFailureCountPerUrl;
+
+ output_object->is_delta_payload = ParseBool(attrs[kTagIsDeltaPayload]);
+
+ output_object->disable_payload_backoff =
+ ParseBool(attrs[kTagDisablePayloadBackoff]);
+
+ return true;
+}
+
+// If the transfer was successful, this uses expat to parse the response
+// and fill in the appropriate fields of the output object. Also, notifies
+// the processor that we're done.
+void OmahaRequestAction::TransferComplete(HttpFetcher *fetcher,
+ bool successful) {
+ ScopedActionCompleter completer(processor_, this);
+ string current_response(response_buffer_.begin(), response_buffer_.end());
+ LOG(INFO) << "Omaha request response: " << current_response;
+
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+ // Events are best effort transactions -- assume they always succeed.
+ if (IsEvent()) {
+ CHECK(!HasOutputPipe()) << "No output pipe allowed for event requests.";
+ if (event_->result == OmahaEvent::kResultError && successful &&
+ system_state_->hardware()->IsOfficialBuild()) {
+ LOG(INFO) << "Signalling Crash Reporter.";
+ utils::ScheduleCrashReporterUpload();
+ }
+ completer.set_code(ErrorCode::kSuccess);
+ return;
+ }
+
+ if (!successful) {
+ LOG(ERROR) << "Omaha request network transfer failed.";
+ int code = GetHTTPResponseCode();
+ // Makes sure we send sane error values.
+ if (code < 0 || code >= 1000) {
+ code = 999;
+ }
+ completer.set_code(static_cast<ErrorCode>(
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + code));
+ return;
+ }
+
+ XML_Parser parser = XML_ParserCreate(nullptr);
+ OmahaParserData parser_data(parser);
+ XML_SetUserData(parser, &parser_data);
+ XML_SetElementHandler(parser, ParserHandlerStart, ParserHandlerEnd);
+ XML_SetEntityDeclHandler(parser, ParserHandlerEntityDecl);
+ XML_Status res = XML_Parse(
+ parser,
+ reinterpret_cast<const char*>(response_buffer_.data()),
+ response_buffer_.size(),
+ XML_TRUE);
+ XML_ParserFree(parser);
+
+ if (res != XML_STATUS_OK || parser_data.failed) {
+ LOG(ERROR) << "Omaha response not valid XML: "
+ << XML_ErrorString(XML_GetErrorCode(parser))
+ << " at line " << XML_GetCurrentLineNumber(parser)
+ << " col " << XML_GetCurrentColumnNumber(parser);
+ ErrorCode error_code = ErrorCode::kOmahaRequestXMLParseError;
+ if (response_buffer_.empty()) {
+ error_code = ErrorCode::kOmahaRequestEmptyResponseError;
+ } else if (parser_data.entity_decl) {
+ error_code = ErrorCode::kOmahaRequestXMLHasEntityDecl;
+ }
+ completer.set_code(error_code);
+ return;
+ }
+
+ // 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()))
+ << "Failed to update the last ping day preferences!";
+
+ if (!HasOutputPipe()) {
+ // Just set success to whether or not the http transfer succeeded,
+ // which must be true at this point in the code.
+ completer.set_code(ErrorCode::kSuccess);
+ return;
+ }
+
+ OmahaResponse output_object;
+ if (!ParseResponse(&parser_data, &output_object, &completer))
+ return;
+ output_object.update_exists = true;
+ SetOutputObject(output_object);
+
+ if (ShouldIgnoreUpdate(output_object)) {
+ output_object.update_exists = false;
+ completer.set_code(ErrorCode::kOmahaUpdateIgnoredPerPolicy);
+ return;
+ }
+
+ // If Omaha says to disable p2p, respect that
+ if (output_object.disable_p2p_for_downloading) {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading as "
+ << "requested by Omaha.";
+ payload_state->SetUsingP2PForDownloading(false);
+ }
+ if (output_object.disable_p2p_for_sharing) {
+ LOG(INFO) << "Forcibly disabling use of p2p for sharing as "
+ << "requested by Omaha.";
+ payload_state->SetUsingP2PForSharing(false);
+ }
+
+ // Update the payload state with the current response. The payload state
+ // will automatically reset all stale state if this response is different
+ // from what's stored already. We are updating the payload state as late
+ // as possible in this method so that if a new release gets pushed and then
+ // got pulled back due to some issues, we don't want to clear our internal
+ // state unnecessarily.
+ payload_state->SetResponse(output_object);
+
+ // It could be we've already exceeded the deadline for when p2p is
+ // allowed or that we've tried too many times with p2p. Check that.
+ if (payload_state->GetUsingP2PForDownloading()) {
+ payload_state->P2PNewAttempt();
+ if (!payload_state->P2PAttemptAllowed()) {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading because "
+ << "of previous failures when using p2p.";
+ payload_state->SetUsingP2PForDownloading(false);
+ }
+ }
+
+ // From here on, we'll complete stuff in CompleteProcessing() so
+ // disable |completer| since we'll create a new one in that
+ // function.
+ completer.set_should_complete(false);
+
+ // If we're allowed to use p2p for downloading we do not pay
+ // attention to wall-clock-based waiting if the URL is indeed
+ // available via p2p. Therefore, check if the file is available via
+ // p2p before deferring...
+ if (payload_state->GetUsingP2PForDownloading()) {
+ LookupPayloadViaP2P(output_object);
+ } else {
+ CompleteProcessing();
+ }
+}
+
+void OmahaRequestAction::CompleteProcessing() {
+ ScopedActionCompleter completer(processor_, this);
+ OmahaResponse& output_object = const_cast<OmahaResponse&>(GetOutputObject());
+ PayloadStateInterface* payload_state = system_state_->payload_state();
+
+ if (ShouldDeferDownload(&output_object)) {
+ output_object.update_exists = false;
+ LOG(INFO) << "Ignoring Omaha updates as updates are deferred by policy.";
+ completer.set_code(ErrorCode::kOmahaUpdateDeferredPerPolicy);
+ return;
+ }
+
+ if (payload_state->ShouldBackoffDownload()) {
+ output_object.update_exists = false;
+ LOG(INFO) << "Ignoring Omaha updates in order to backoff our retry "
+ << "attempts";
+ completer.set_code(ErrorCode::kOmahaUpdateDeferredForBackoff);
+ return;
+ }
+ completer.set_code(ErrorCode::kSuccess);
+}
+
+void OmahaRequestAction::OnLookupPayloadViaP2PCompleted(const string& url) {
+ LOG(INFO) << "Lookup complete, p2p-client returned URL '" << url << "'";
+ if (!url.empty()) {
+ system_state_->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);
+ }
+ CompleteProcessing();
+}
+
+void OmahaRequestAction::LookupPayloadViaP2P(const OmahaResponse& response) {
+ // If the device is in the middle of an update, the state variables
+ // kPrefsUpdateStateNextDataOffset, kPrefsUpdateStateNextDataLength
+ // tracks the offset and length of the operation currently in
+ // progress. The offset is based from the end of the manifest which
+ // is kPrefsManifestMetadataSize bytes long.
+ //
+ // To make forward progress and avoid deadlocks, we need to find a
+ // peer that has at least the entire operation we're currently
+ // working on. Otherwise we may end up in a situation where two
+ // devices bounce back and forth downloading from each other,
+ // neither making any forward progress until one of them decides to
+ // stop using p2p (via kMaxP2PAttempts and kMaxP2PAttemptTimeSeconds
+ // safe-guards). See http://crbug.com/297170 for an example)
+ size_t minimum_size = 0;
+ int64_t manifest_metadata_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) &&
+ manifest_metadata_size != -1 &&
+ system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataOffset,
+ &next_data_offset) &&
+ next_data_offset != -1 &&
+ system_state_->prefs()->GetInt64(kPrefsUpdateStateNextDataLength,
+ &next_data_length)) {
+ minimum_size = manifest_metadata_size + next_data_offset + next_data_length;
+ }
+
+ string file_id = utils::CalculateP2PFileId(response.hash, response.size);
+ if (system_state_->p2p_manager()) {
+ LOG(INFO) << "Checking if payload is available via p2p, file_id="
+ << file_id << " minimum_size=" << minimum_size;
+ system_state_->p2p_manager()->LookupUrlForFile(
+ file_id,
+ minimum_size,
+ TimeDelta::FromSeconds(kMaxP2PNetworkWaitTimeSeconds),
+ base::Bind(&OmahaRequestAction::OnLookupPayloadViaP2PCompleted,
+ base::Unretained(this)));
+ }
+}
+
+bool OmahaRequestAction::ShouldDeferDownload(OmahaResponse* output_object) {
+ if (params_->interactive()) {
+ LOG(INFO) << "Not deferring download because update is interactive.";
+ return false;
+ }
+
+ // If we're using p2p to download _and_ we have a p2p URL, we never
+ // 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();
+ if (payload_state->GetUsingP2PForDownloading() &&
+ !payload_state->GetP2PUrl().empty()) {
+ LOG(INFO) << "Download not deferred because download "
+ << "will happen from a local peer (via p2p).";
+ return false;
+ }
+
+ // 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()) {
+ LOG(INFO) << "Wall-clock-based waiting period is not enabled,"
+ << " so no deferring needed.";
+ return false;
+ }
+
+ switch (IsWallClockBasedWaitingSatisfied(output_object)) {
+ case kWallClockWaitNotSatisfied:
+ // We haven't even satisfied the first condition, passing the
+ // wall-clock-based waiting period, so we should defer the downloads
+ // until that happens.
+ LOG(INFO) << "wall-clock-based-wait not satisfied.";
+ return true;
+
+ case kWallClockWaitDoneButUpdateCheckWaitRequired:
+ LOG(INFO) << "wall-clock-based-wait satisfied and "
+ << "update-check-based-wait required.";
+ return !IsUpdateCheckCountBasedWaitingSatisfied();
+
+ case kWallClockWaitDoneAndUpdateCheckWaitNotRequired:
+ // Wall-clock-based waiting period is satisfied, and it's determined
+ // that we do not need the update-check-based wait. so no need to
+ // defer downloads.
+ LOG(INFO) << "wall-clock-based-wait satisfied and "
+ << "update-check-based-wait is not required.";
+ return false;
+
+ default:
+ // Returning false for this default case so we err on the
+ // side of downloading updates than deferring in case of any bugs.
+ NOTREACHED();
+ return false;
+ }
+}
+
+OmahaRequestAction::WallClockWaitResult
+OmahaRequestAction::IsWallClockBasedWaitingSatisfied(
+ OmahaResponse* output_object) {
+ 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)) {
+ // 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,
+ // updates being pulled out from Omaha, changes in target version prefix,
+ // new update being rolled out, etc.). But for the purposes of scattering
+ // it doesn't matter which update the timestamp corresponds to. i.e.
+ // the clock starts ticking the first time we see an update and we're
+ // ready to apply when the random wait period is satisfied relative to
+ // that first seen timestamp.
+ update_first_seen_at = Time::FromInternalValue(update_first_seen_at_int);
+ LOG(INFO) << "Using persisted value of UpdateFirstSeenAt: "
+ << utils::ToString(update_first_seen_at);
+ } else {
+ // This seems like an unexpected error where the persisted value exists
+ // but it's not readable for some reason. Just skip scattering in this
+ // case to be safe.
+ LOG(INFO) << "Not scattering as UpdateFirstSeenAt value cannot be read";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+ } else {
+ update_first_seen_at = Time::Now();
+ update_first_seen_at_int = update_first_seen_at.ToInternalValue();
+ if (system_state_->prefs()->SetInt64(kPrefsUpdateFirstSeenAt,
+ update_first_seen_at_int)) {
+ LOG(INFO) << "Persisted the new value for UpdateFirstSeenAt: "
+ << utils::ToString(update_first_seen_at);
+ } else {
+ // This seems like an unexpected error where the value cannot be
+ // persisted for some reason. Just skip scattering in this
+ // case to be safe.
+ LOG(INFO) << "Not scattering as UpdateFirstSeenAt value "
+ << utils::ToString(update_first_seen_at)
+ << " cannot be persisted";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+ }
+
+ TimeDelta elapsed_time = Time::Now() - update_first_seen_at;
+ TimeDelta max_scatter_period = TimeDelta::FromDays(
+ output_object->max_days_to_scatter);
+
+ LOG(INFO) << "Waiting Period = "
+ << utils::FormatSecs(params_->waiting_period().InSeconds())
+ << ", Time Elapsed = "
+ << utils::FormatSecs(elapsed_time.InSeconds())
+ << ", MaxDaysToScatter = "
+ << max_scatter_period.InDays();
+
+ if (!output_object->deadline.empty()) {
+ // The deadline is set for all rules which serve a delta update from a
+ // 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";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ if (max_scatter_period.InDays() == 0) {
+ // This means the Omaha rule creator decides that this rule
+ // should not be scattered irrespective of the policy.
+ LOG(INFO) << "Not scattering as MaxDaysToScatter in rule is 0.";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ if (elapsed_time > max_scatter_period) {
+ // This means we've waited more than the upperbound wait in the rule
+ // from the time we first saw a valid update available to us.
+ // This will prevent update starvation.
+ LOG(INFO) << "Not scattering as we're past the MaxDaysToScatter limit.";
+ return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ // 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;
+ 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() ?
+ kWallClockWaitDoneButUpdateCheckWaitRequired :
+ kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
+ }
+
+ // Not our turn yet, so we have to wait until our turn to
+ // help scatter the downloads across all clients of the enterprise.
+ LOG(INFO) << "Update deferred for another "
+ << utils::FormatSecs(remaining_wait_time.InSeconds())
+ << " per policy.";
+ return kWallClockWaitNotSatisfied;
+}
+
+bool OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied() {
+ int64_t update_check_count_value;
+
+ if (system_state_->prefs()->Exists(kPrefsUpdateCheckCount)) {
+ if (!system_state_->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. "
+ << "Skipping update-check-count-based-wait.";
+ return true;
+ }
+ } else {
+ // 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());
+
+ 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)) {
+ // 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. "
+ << "Skipping update-check-count-based-wait.";
+ return true;
+ }
+ }
+
+ if (update_check_count_value == 0) {
+ LOG(INFO) << "Successfully passed the update-check-based-wait.";
+ return true;
+ }
+
+ if (update_check_count_value < 0 ||
+ 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. "
+ << "Skipping update-check-count-based-wait.";
+ return true;
+ }
+
+ // Legal value, we need to wait for more update checks to happen
+ // until this becomes 0.
+ LOG(INFO) << "Deferring Omaha updates for another "
+ << update_check_count_value
+ << " update checks per policy";
+ return false;
+}
+
+// static
+bool OmahaRequestAction::ParseInstallDate(OmahaParserData* parser_data,
+ OmahaResponse* output_object) {
+ int64_t elapsed_days = 0;
+ if (!base::StringToInt64(parser_data->daystart_elapsed_days,
+ &elapsed_days))
+ return false;
+
+ if (elapsed_days < 0)
+ return false;
+
+ output_object->install_date_days = elapsed_days;
+ return true;
+}
+
+// static
+bool OmahaRequestAction::HasInstallDate(SystemState *system_state) {
+ PrefsInterface* prefs = system_state->prefs();
+ if (prefs == nullptr)
+ return false;
+
+ return 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;
+
+ if (!prefs->SetInt64(kPrefsInstallDateDays, install_date_days))
+ return false;
+
+ string metric_name = metrics::kMetricInstallDateProvisioningSource;
+ system_state->metrics_lib()->SendEnumToUMA(
+ metric_name,
+ 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);
+ }
+ return true;
+}
+
+void OmahaRequestAction::ActionCompleted(ErrorCode code) {
+ // We only want to report this on "update check".
+ if (ping_only_ || event_ != nullptr)
+ return;
+
+ metrics::CheckResult result = metrics::CheckResult::kUnset;
+ metrics::CheckReaction reaction = metrics::CheckReaction::kUnset;
+ metrics::DownloadErrorCode download_error_code =
+ metrics::DownloadErrorCode::kUnset;
+
+ // Regular update attempt.
+ switch (code) {
+ case ErrorCode::kSuccess:
+ // OK, we parsed the response successfully but that does
+ // necessarily mean that an update is available.
+ if (HasOutputPipe()) {
+ const OmahaResponse& response = GetOutputObject();
+ if (response.update_exists) {
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kUpdating;
+ } else {
+ result = metrics::CheckResult::kNoUpdateAvailable;
+ }
+ } else {
+ result = metrics::CheckResult::kNoUpdateAvailable;
+ }
+ break;
+
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kIgnored;
+ break;
+
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kDeferring;
+ break;
+
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ result = metrics::CheckResult::kUpdateAvailable;
+ reaction = metrics::CheckReaction::kBackingOff;
+ break;
+
+ default:
+ // We report two flavors of errors, "Download errors" and "Parsing
+ // error". Try to convert to the former and if that doesn't work
+ // we know it's the latter.
+ metrics::DownloadErrorCode tmp_error =
+ metrics_utils::GetDownloadErrorCode(code);
+ if (tmp_error != metrics::DownloadErrorCode::kInputMalformed) {
+ result = metrics::CheckResult::kDownloadError;
+ download_error_code = tmp_error;
+ } else {
+ result = metrics::CheckResult::kParsingError;
+ }
+ break;
+ }
+
+ metrics::ReportUpdateCheckMetrics(system_state_,
+ result, reaction, download_error_code);
+}
+
+bool OmahaRequestAction::ShouldIgnoreUpdate(
+ const OmahaResponse& response) const {
+ // Note: policy decision to not update to a version we rolled back from.
+ string rollback_version =
+ system_state_->payload_state()->GetRollbackVersion();
+ if (!rollback_version.empty()) {
+ LOG(INFO) << "Detected previous rollback from version " << rollback_version;
+ if (rollback_version == response.version) {
+ LOG(INFO) << "Received version that we rolled back from. Ignoring.";
+ return true;
+ }
+ }
+
+ if (!IsUpdateAllowedOverCurrentConnection()) {
+ LOG(INFO) << "Update is not allowed over current connection.";
+ return true;
+ }
+
+ // 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
+ // turned on again. On the other hand, it also increases the chance of update
+ // starvation if an admin turns AU on/off more frequently. We choose to err on
+ // the side of preventing starvation at the cost of not applying scattering in
+ // those cases.
+ return false;
+}
+
+bool OmahaRequestAction::IsUpdateAllowedOverCurrentConnection() const {
+ NetworkConnectionType type;
+ NetworkTethering tethering;
+ ConnectionManagerInterface* connection_manager =
+ system_state_->connection_manager();
+ if (!connection_manager->GetConnectionProperties(&type, &tethering)) {
+ LOG(INFO) << "We could not determine our connection type. "
+ << "Defaulting to allow updates.";
+ return true;
+ }
+ bool is_allowed = connection_manager->IsUpdateAllowedOver(type, tethering);
+ LOG(INFO) << "We are connected via "
+ << ConnectionManager::StringForConnectionType(type)
+ << ", Updates allowed: " << (is_allowed ? "Yes" : "No");
+ return is_allowed;
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_request_action.h b/omaha_request_action.h
new file mode 100644
index 0000000..1aeaf8a
--- /dev/null
+++ b/omaha_request_action.h
@@ -0,0 +1,332 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_
+#define UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#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/omaha_response.h"
+#include "update_engine/system_state.h"
+
+// The Omaha Request action makes a request to Omaha and can output
+// the response on the output ActionPipe.
+
+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;
+
+template<>
+class ActionTraits<OmahaRequestAction> {
+ public:
+ // Takes parameters on the input pipe.
+ typedef NoneType InputObjectType;
+ // On UpdateCheck success, puts the Omaha response on output. Event
+ // requests do not have an output pipe.
+ typedef OmahaResponse OutputObjectType;
+};
+
+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
+ // 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;
+
+ // These are the possible outcome upon checking whether we satisfied
+ // the wall-clock-based-wait.
+ enum WallClockWaitResult {
+ kWallClockWaitNotSatisfied,
+ kWallClockWaitDoneButUpdateCheckWaitRequired,
+ kWallClockWaitDoneAndUpdateCheckWaitNotRequired,
+ };
+
+ // The ctor takes in all the parameters that will be used for making
+ // the request to Omaha. For some of them we have constants that
+ // should be used.
+ //
+ // Takes ownership of the passed in HttpFetcher. Useful for testing.
+ //
+ // Takes ownership of the passed in OmahaEvent. If |event| is null,
+ // this is an UpdateCheck request, otherwise it's an Event request.
+ // Event requests always succeed.
+ //
+ // A good calling pattern is:
+ // OmahaRequestAction(..., new OmahaEvent(...), new WhateverHttpFetcher);
+ // or
+ // OmahaRequestAction(..., nullptr, new WhateverHttpFetcher);
+ OmahaRequestAction(SystemState* system_state,
+ OmahaEvent* event,
+ std::unique_ptr<HttpFetcher> http_fetcher,
+ bool ping_only);
+ ~OmahaRequestAction() override;
+ typedef ActionTraits<OmahaRequestAction>::InputObjectType InputObjectType;
+ typedef ActionTraits<OmahaRequestAction>::OutputObjectType OutputObjectType;
+ void PerformAction() override;
+ void TerminateProcessing() override;
+ void ActionCompleted(ErrorCode code) override;
+
+ int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
+ // Debugging/logging
+ static std::string StaticType() { return "OmahaRequestAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ // Delegate methods (see http_fetcher.h)
+ void ReceivedBytes(HttpFetcher *fetcher,
+ const void* bytes, size_t length) override;
+
+ void TransferComplete(HttpFetcher *fetcher, bool successful) override;
+
+ // Returns true if this is an Event request, false if it's an UpdateCheck.
+ bool IsEvent() const { return event_.get() != nullptr; }
+
+ private:
+ FRIEND_TEST(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE);
+ FRIEND_TEST(OmahaRequestActionTest,
+ GetInstallDateWhenOOBECompletedWithInvalidDate);
+ FRIEND_TEST(OmahaRequestActionTest,
+ GetInstallDateWhenOOBECompletedWithValidDate);
+ FRIEND_TEST(OmahaRequestActionTest,
+ GetInstallDateWhenOOBECompletedDateChanges);
+
+ // Enumeration used in PersistInstallDate().
+ enum InstallDateProvisioningSource {
+ kProvisionedFromOmahaResponse,
+ kProvisionedFromOOBEMarker,
+
+ // kProvisionedMax is the count of the number of enums above. Add
+ // any new enums above this line only.
+ kProvisionedMax
+ };
+
+ // 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);
+
+ // Parses the Omaha Response in |doc| and sets the
+ // |install_date_days| field of |output_object| to the value of the
+ // elapsed_days attribute of the daystart element. Returns True if
+ // the value was set, False if it wasn't found.
+ static bool ParseInstallDate(OmahaParserData* parser_data,
+ OmahaResponse* output_object);
+
+ // Returns True if the kPrefsInstallDateDays state variable is set,
+ // False otherwise.
+ static bool HasInstallDate(SystemState *system_state);
+
+ // 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,
+ 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);
+
+ // If this is an update check request, initializes
+ // |ping_active_days_| and |ping_roll_call_days_| to values that may
+ // be sent as pings to Omaha.
+ void InitPingDays();
+
+ // Based on the persistent preference store values, calculates the
+ // number of days since the last ping sent for |key|.
+ int CalculatePingDays(const std::string& key);
+
+ // Returns whether we have "active_days" or "roll_call_days" ping values to
+ // send to Omaha and thus we should include them in the response.
+ bool ShouldPing() 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);
+
+ // Returns true if the basic wall-clock-based waiting period has been
+ // satisfied based on the scattering policy setting. False otherwise.
+ // If true, it also indicates whether the additional update-check-count-based
+ // waiting period also needs to be satisfied before the download can begin.
+ WallClockWaitResult IsWallClockBasedWaitingSatisfied(
+ OmahaResponse* output_object);
+
+ // Returns true if the update-check-count-based waiting period has been
+ // satisfied. False otherwise.
+ bool IsUpdateCheckCountBasedWaitingSatisfied();
+
+ // Parses the response from Omaha that's available in |doc| using the other
+ // helper methods below and populates the |output_object| with the relevant
+ // values. Returns true if we should continue the parsing. False otherwise,
+ // in which case it sets any error code using |completer|.
+ bool ParseResponse(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the status property in the given update_check_node and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParseStatus(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the URL nodes in the given XML document and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParseUrls(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the package node in the given XML document and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParsePackage(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Parses the other parameters in the given XML document and populates
+ // |output_object| if valid. Returns true if we should continue the parsing.
+ // False otherwise, in which case it sets any error code using |completer|.
+ bool ParseParams(OmahaParserData* parser_data,
+ OmahaResponse* output_object,
+ ScopedActionCompleter* completer);
+
+ // Called by TransferComplete() to complete processing, either
+ // asynchronously after looking up resources via p2p or directly.
+ void CompleteProcessing();
+
+ // Helper to asynchronously look up payload on the LAN.
+ void LookupPayloadViaP2P(const OmahaResponse& response);
+
+ // Callback used by LookupPayloadViaP2P().
+ void OnLookupPayloadViaP2PCompleted(const std::string& url);
+
+ // Returns true if the current update should be ignored.
+ bool ShouldIgnoreUpdate(const OmahaResponse& response) const;
+
+ // Returns true if updates are allowed over the current type of connection.
+ // False otherwise.
+ bool IsUpdateAllowedOverCurrentConnection() 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_;
+
+ // pointer to the HttpFetcher that does the http work
+ std::unique_ptr<HttpFetcher> http_fetcher_;
+
+ // If true, only include the <ping> element in the request.
+ bool ping_only_;
+
+ // Stores the response from the omaha server
+ brillo::Blob response_buffer_;
+
+ // Initialized by InitPingDays to values that may be sent to Omaha
+ // as part of a ping message. Note that only positive values and -1
+ // are sent to Omaha.
+ int ping_active_days_;
+ int ping_roll_call_days_;
+
+ DISALLOW_COPY_AND_ASSIGN(OmahaRequestAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_OMAHA_REQUEST_ACTION_H_
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
new file mode 100644
index 0000000..69585c8
--- /dev/null
+++ b/omaha_request_action_unittest.cc
@@ -0,0 +1,2176 @@
+//
+// 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 <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_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/bind_lambda.h>
+#include <brillo/make_unique_ptr.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 "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/common/utils.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/metrics.h"
+#include "update_engine/mock_connection_manager.h"
+#include "update_engine/mock_payload_state.h"
+#include "update_engine/omaha_request_params.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using std::vector;
+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::SaveArg;
+using testing::SetArgumentPointee;
+using testing::_;
+
+namespace {
+
+const char kTestAppId[] = "test-app-id";
+
+// This is a helper struct to allow unit tests build an update response with the
+// values they care about.
+struct FakeUpdateResponse {
+ 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></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\">"
+ "<urls><url codebase=\"" + codebase + "\"/></urls>"
+ "<manifest version=\"" + version + "\">"
+ "<packages><package hash=\"not-used\" name=\"" + filename + "\" "
+ "size=\"" + base::Int64ToString(size) + "\"/></packages>"
+ "<actions><action event=\"postinstall\" "
+ "ChromeOSVersion=\"" + version + "\" "
+ "MoreInfo=\"" + more_info_url + "\" Prompt=\"" + prompt + "\" "
+ "IsDelta=\"true\" "
+ "IsDeltaPayload=\"true\" "
+ "MaxDaysToScatter=\"" + max_days_to_scatter + "\" "
+ "sha256=\"" + hash + "\" "
+ "needsadmin=\"" + needsadmin + "\" " +
+ (deadline.empty() ? "" : ("deadline=\"" + deadline + "\" ")) +
+ (disable_p2p_for_downloading ?
+ "DisableP2PForDownloading=\"true\" " : "") +
+ (disable_p2p_for_sharing ? "DisableP2PForSharing=\"true\" " : "") +
+ "/></actions></manifest></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 version = "1.2.3.4";
+ string more_info_url = "http://more/info";
+ string prompt = "true";
+ string codebase = "http://code/base/";
+ string filename = "file.signed";
+ string hash = "HASH1234=";
+ string needsadmin = "false";
+ int64_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;
+
+ // 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;
+};
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+class OmahaRequestActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ 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(OmahaRequestParams* request_params,
+ 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);
+
+ // 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_;
+
+ // By default, all tests use these objects unless they replace them in the
+ // fake_system_state_.
+ OmahaRequestParams request_params_ = OmahaRequestParams{
+ &fake_system_state_,
+ constants::kOmahaPlatformName,
+ OmahaRequestParams::kOsVersion,
+ "service_pack",
+ "x86-generic",
+ kTestAppId,
+ "0.1.0.0",
+ "en-US",
+ "unittest",
+ "OEM MODEL 09235 7471",
+ "ChromeOSFirmware.1.0",
+ "0X0A1",
+ false, // delta okay
+ false, // interactive
+ "http://url",
+ ""}; // target_version_prefix
+
+ FakePrefs fake_prefs_;
+};
+
+namespace {
+class OmahaRequestActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ OmahaRequestActionTestProcessorDelegate()
+ : expected_code_(ErrorCode::kSuccess) {}
+ ~OmahaRequestActionTestProcessorDelegate() override {
+ }
+ 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);
+ else
+ EXPECT_EQ(ErrorCode::kSuccess, code);
+ }
+ ErrorCode expected_code_;
+};
+} // namespace
+
+class OutputObjectCollectorAction;
+
+template<>
+class ActionTraits<OutputObjectCollectorAction> {
+ public:
+ // Does not take an object for input
+ typedef OmahaResponse InputObjectType;
+ // On success, puts the output path on output
+ typedef NoneType OutputObjectType;
+};
+
+class OutputObjectCollectorAction : public Action<OutputObjectCollectorAction> {
+ public:
+ OutputObjectCollectorAction() : has_input_object_(false) {}
+ void PerformAction() {
+ // copy input object
+ has_input_object_ = HasInputObject();
+ if (has_input_object_)
+ omaha_response_ = GetInputObject();
+ processor_->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ // Should never be called
+ void TerminateProcessing() {
+ CHECK(false);
+ }
+ // Debugging/logging
+ static string StaticType() {
+ return "OutputObjectCollectorAction";
+ }
+ string Type() const { return StaticType(); }
+ bool has_input_object_;
+ OmahaResponse omaha_response_;
+};
+
+bool OmahaRequestActionTest::TestUpdateCheck(
+ OmahaRequestParams* request_params,
+ 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) {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+ MockHttpFetcher* fetcher = new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr);
+ if (fail_http_response_code >= 0) {
+ fetcher->FailTransfer(fail_http_response_code);
+ }
+ if (request_params)
+ fake_system_state_.set_request_params(request_params);
+ OmahaRequestAction action(&fake_system_state_,
+ nullptr,
+ brillo::make_unique_ptr(fetcher),
+ ping_only);
+ OmahaRequestActionTestProcessorDelegate delegate;
+ delegate.expected_code_ = expected_code;
+
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&action);
+
+ OutputObjectCollectorAction collector_action;
+ BondActions(&action, &collector_action);
+ processor.EnqueueAction(&collector_action);
+
+ EXPECT_CALL(*fake_system_state_.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+ SendEnumToUMA(metrics::kMetricCheckResult,
+ static_cast<int>(expected_check_result),
+ static_cast<int>(metrics::CheckResult::kNumConstants) - 1))
+ .Times(expected_check_result == metrics::CheckResult::kUnset ? 0 : 1);
+ EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+ SendEnumToUMA(metrics::kMetricCheckReaction,
+ static_cast<int>(expected_check_reaction),
+ static_cast<int>(metrics::CheckReaction::kNumConstants) - 1))
+ .Times(expected_check_reaction == metrics::CheckReaction::kUnset ? 0 : 1);
+ EXPECT_CALL(*fake_system_state_.mock_metrics_lib(),
+ SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode,
+ static_cast<int>(expected_download_error_code)))
+ .Times(expected_download_error_code == metrics::DownloadErrorCode::kUnset
+ ? 0 : 1);
+
+ loop.PostTask(base::Bind([&processor] { processor.StartProcessing(); }));
+ LOG(INFO) << "loop.PendingTasks() = " << loop.PendingTasks();
+ loop.Run();
+ LOG(INFO) << "loop.PendingTasks() = " << loop.PendingTasks();
+ EXPECT_FALSE(loop.PendingTasks());
+ if (collector_action.has_input_object_ && out_response)
+ *out_response = collector_action.omaha_response_;
+ if (out_post_data)
+ *out_post_data = fetcher->post_data();
+ return collector_action.has_input_object_;
+}
+
+// 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 TestEvent(OmahaRequestParams params,
+ OmahaEvent* event,
+ const string& http_response,
+ brillo::Blob* out_post_data) {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+ MockHttpFetcher* fetcher = new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr);
+ FakeSystemState fake_system_state;
+ fake_system_state.set_request_params(¶ms);
+ OmahaRequestAction action(&fake_system_state,
+ event,
+ brillo::make_unique_ptr(fetcher),
+ false);
+ OmahaRequestActionTestProcessorDelegate delegate;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&action);
+
+ loop.PostTask(base::Bind([&processor] { processor.StartProcessing(); }));
+ loop.Run();
+
+ // This test should schedule a callback to notify the crash reporter if
+ // the passed event is an error.
+ EXPECT_EQ(event->result == OmahaEvent::kResultError, loop.PendingTasks());
+
+ if (out_post_data)
+ *out_post_data = fetcher->post_data();
+}
+
+TEST_F(OmahaRequestActionTest, RejectEntities) {
+ OmahaResponse response;
+ fake_update_response_.include_entity = true;
+ ASSERT_FALSE(
+ TestUpdateCheck(nullptr, // request_params
+ 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(nullptr, // request_params
+ 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 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(nullptr, // request_params
+ 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.update_exists);
+ EXPECT_EQ(fake_update_response_.version, response.version);
+ EXPECT_EQ(fake_update_response_.GetPayloadUrl(), response.payload_urls[0]);
+ EXPECT_EQ(fake_update_response_.more_info_url, response.more_info_url);
+ EXPECT_EQ(fake_update_response_.hash, response.hash);
+ EXPECT_EQ(fake_update_response_.size, response.size);
+ EXPECT_EQ(fake_update_response_.prompt == "true", response.prompt);
+ EXPECT_EQ(fake_update_response_.deadline, response.deadline);
+ // Omaha cohort attribets 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, 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(SetArgumentPointee<0>(NetworkConnectionType::kEthernet),
+ SetArgumentPointee<1>(NetworkTethering::kUnknown),
+ Return(true)));
+ EXPECT_CALL(mock_cm, IsUpdateAllowedOver(NetworkConnectionType::kEthernet, _))
+ .WillRepeatedly(Return(false));
+
+ ASSERT_FALSE(
+ TestUpdateCheck(nullptr, // request_params
+ 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, 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(nullptr, // request_params
+ 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, WallClockBasedWaitAloneCausesScattering) {
+ OmahaResponse response;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_update_check_count_wait_enabled(false);
+ params.set_waiting_period(TimeDelta::FromDays(2));
+
+ ASSERT_FALSE(
+ TestUpdateCheck(¶ms,
+ 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.
+ params.set_interactive(true);
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ 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;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(false);
+ params.set_waiting_period(TimeDelta::FromDays(2));
+
+ params.set_update_check_count_wait_enabled(true);
+ params.set_min_update_checks_needed(1);
+ params.set_max_update_checks_allowed(8);
+
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ 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;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta::FromDays(2));
+
+ params.set_update_check_count_wait_enabled(true);
+ params.set_min_update_checks_needed(1);
+ params.set_max_update_checks_allowed(8);
+
+ fake_update_response_.max_days_to_scatter = "0";
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ 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;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta());
+
+ params.set_update_check_count_wait_enabled(true);
+ params.set_min_update_checks_needed(0);
+ params.set_max_update_checks_allowed(0);
+
+ ASSERT_TRUE(TestUpdateCheck(
+ ¶ms,
+ 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;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta());
+
+ params.set_update_check_count_wait_enabled(true);
+ params.set_min_update_checks_needed(1);
+ params.set_max_update_checks_allowed(8);
+
+ ASSERT_FALSE(TestUpdateCheck(
+ ¶ms,
+ 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.
+ params.set_interactive(true);
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ 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;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta());
+
+ params.set_update_check_count_wait_enabled(true);
+ params.set_min_update_checks_needed(1);
+ params.set_max_update_checks_allowed(8);
+
+ ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsUpdateCheckCount, 5));
+
+ ASSERT_FALSE(TestUpdateCheck(
+ ¶ms,
+ 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.
+ params.set_interactive(true);
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ 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;
+ OmahaRequestParams params = request_params_;
+ 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(¶ms,
+ 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;
+ OmahaRequestParams params = request_params_;
+ 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(¶ms,
+ 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;
+ OmahaRequestParams params = request_params_;
+ EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value"));
+
+ ASSERT_TRUE(TestUpdateCheck(¶ms,
+ 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;
+ OmahaRequestParams params = request_params_;
+ 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(¶ms,
+ 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, NoOutputPipeTest) {
+ const string http_response(fake_update_response_.GetNoUpdateResponse());
+
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ OmahaRequestParams params = request_params_;
+ fake_system_state_.set_request_params(¶ms);
+ OmahaRequestAction action(&fake_system_state_, nullptr,
+ brillo::make_unique_ptr(
+ new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr)),
+ false);
+ OmahaRequestActionTestProcessorDelegate delegate;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&action);
+
+ loop.PostTask(base::Bind([&processor] { processor.StartProcessing(); }));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+ EXPECT_FALSE(processor.IsRunning());
+}
+
+TEST_F(OmahaRequestActionTest, InvalidXmlTest) {
+ OmahaResponse response;
+ ASSERT_FALSE(
+ TestUpdateCheck(nullptr, // request_params
+ "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(nullptr, // request_params
+ "",
+ -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(
+ nullptr, // request_params
+ "<?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(
+ nullptr, // request_params
+ "<?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(
+ nullptr, // request_params
+ "<?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\"/>"
+ "<app appid=\"xyz\" 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\"/></packages>"
+ "<actions><action event=\"postinstall\" "
+ "ChromeOSVersion=\"10.2.3.4\" "
+ "Prompt=\"false\" "
+ "IsDelta=\"true\" "
+ "IsDeltaPayload=\"false\" "
+ "sha256=\"lkq34j5345\" "
+ "needsadmin=\"true\" "
+ "/></actions></manifest></updatecheck></app></response>";
+ LOG(INFO) << "Input Response = " << input_response;
+
+ OmahaResponse response;
+ ASSERT_TRUE(TestUpdateCheck(nullptr, // request_params
+ 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.payload_urls[0]);
+ EXPECT_EQ("", response.more_info_url);
+ EXPECT_EQ("lkq34j5345", response.hash);
+ EXPECT_EQ(587, response.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");
+ OmahaRequestAction action(&fake_system_state_, nullptr,
+ brillo::make_unique_ptr(
+ new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr)),
+ false);
+ TerminateEarlyTestProcessorDelegate delegate;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&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
+ OmahaRequestParams params(&fake_system_state_,
+ constants::kOmahaPlatformName,
+ OmahaRequestParams::kOsVersion,
+ "testtheservice_pack>",
+ "x86 generic<id",
+ kTestAppId,
+ "0.1.0.0",
+ "en-US",
+ "unittest_track<",
+ "<OEM MODEL>",
+ "ChromeOSFirmware.1.0",
+ "EC100",
+ false, // delta okay
+ false, // interactive
+ "http://url",
+ ""); // target_version_prefix
+ fake_prefs_.SetString(kPrefsOmahaCohort, "evil\nstring");
+ fake_prefs_.SetString(kPrefsOmahaCohortHint, "evil&string\\");
+ fake_prefs_.SetString(kPrefsOmahaCohortName,
+ JoinString(vector<string>(100, "My spoon is too big."),
+ ' '));
+ OmahaResponse response;
+ ASSERT_FALSE(
+ TestUpdateCheck(¶ms,
+ "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(nullptr, // request_params
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ EXPECT_EQ(response.more_info_url, "testthe<url");
+ EXPECT_EQ(response.payload_urls[0], "testthe&codebase/file.signed");
+ EXPECT_EQ(response.deadline, "<20110101");
+}
+
+TEST_F(OmahaRequestActionTest, ParseIntTest) {
+ OmahaResponse response;
+ // overflows int32_t:
+ fake_update_response_.size = 123123123123123ll;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetUpdateResponse(),
+ -1,
+ false, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUpdateAvailable,
+ metrics::CheckReaction::kUpdating,
+ metrics::DownloadErrorCode::kUnset,
+ &response,
+ nullptr));
+
+ EXPECT_EQ(response.size, 123123123123123ll);
+}
+
+TEST_F(OmahaRequestActionTest, FormatUpdateCheckOutputTest) {
+ brillo::Blob post_data;
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+
+ EXPECT_CALL(prefs, GetString(kPrefsPreviousVersion, _))
+ .WillOnce(DoAll(SetArgumentPointee<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(nullptr, // request_params
+ "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 targetversionprefix=\"\"></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(request_params_,
+ 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(request_params_,
+ 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");
+ // Create a copy of the OmahaRequestParams to reuse it later.
+ OmahaRequestParams params = request_params_;
+ fake_system_state_.set_request_params(¶ms);
+ OmahaRequestAction update_check_action(
+ &fake_system_state_,
+ nullptr,
+ brillo::make_unique_ptr(
+ new MockHttpFetcher(http_response.data(),
+ http_response.size(),
+ nullptr)),
+ false);
+ EXPECT_FALSE(update_check_action.IsEvent());
+
+ params = request_params_;
+ fake_system_state_.set_request_params(¶ms);
+ OmahaRequestAction event_action(
+ &fake_system_state_,
+ new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+ brillo::make_unique_ptr(
+ new 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;
+ OmahaRequestParams params(&fake_system_state_,
+ constants::kOmahaPlatformName,
+ OmahaRequestParams::kOsVersion,
+ "service_pack",
+ "x86-generic",
+ kTestAppId,
+ "0.1.0.0",
+ "en-US",
+ "unittest_track",
+ "OEM MODEL REV 1234",
+ "ChromeOSFirmware.1.0",
+ "EC100",
+ delta_okay,
+ false, // interactive
+ "http://url",
+ ""); // target_version_prefix
+ ASSERT_FALSE(TestUpdateCheck(¶ms,
+ "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;
+ OmahaRequestParams params(&fake_system_state_,
+ constants::kOmahaPlatformName,
+ OmahaRequestParams::kOsVersion,
+ "service_pack",
+ "x86-generic",
+ kTestAppId,
+ "0.1.0.0",
+ "en-US",
+ "unittest_track",
+ "OEM MODEL REV 1234",
+ "ChromeOSFirmware.1.0",
+ "EC100",
+ true, // delta_okay
+ interactive,
+ "http://url",
+ ""); // target_version_prefix
+ ASSERT_FALSE(TestUpdateCheck(¶ms,
+ "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, 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(SetArgumentPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(six_days_ago), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(five_days_ago), Return(true)));
+ brillo::Blob post_data;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ ping_only,
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUnset,
+ 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(SetArgumentPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(three_days_ago), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+ brillo::Blob post_data;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ 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(SetArgumentPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(now), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(four_days_ago), Return(true)));
+ brillo::Blob post_data;
+ ASSERT_TRUE(
+ TestUpdateCheck(nullptr, // request_params
+ 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(SetArgumentPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(one_hour_ago), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<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(nullptr, // request_params
+ 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(SetArgumentPointee<1>(now), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<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(nullptr, // request_params
+ fake_update_response_.GetNoUpdateResponse(),
+ -1,
+ true, // ping_only
+ ErrorCode::kSuccess,
+ metrics::CheckResult::kUnset,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset,
+ nullptr,
+ &post_data));
+ EXPECT_EQ(post_data.size(), 0);
+}
+
+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(SetArgumentPointee<1>(0), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastActivePingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<1>(future), Return(true)));
+ EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
+ .WillOnce(DoAll(SetArgumentPointee<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(nullptr, // request_params
+ "<?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(nullptr, // request_params
+ "<?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(nullptr, // request_params
+ "<?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(nullptr, // request_params
+ "<?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, NoUniqueIDTest) {
+ brillo::Blob post_data;
+ ASSERT_FALSE(TestUpdateCheck(nullptr, // request_params
+ "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(nullptr, // request_params
+ "",
+ 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(nullptr, // request_params
+ "",
+ 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;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta().FromDays(1));
+ params.set_update_check_count_wait_enabled(false);
+
+ ASSERT_FALSE(TestUpdateCheck(
+ ¶ms,
+ 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));
+ ASSERT_GT(timestamp, 0);
+ EXPECT_FALSE(response.update_exists);
+
+ // Verify if we are interactive check we don't defer.
+ params.set_interactive(true);
+ ASSERT_TRUE(
+ TestUpdateCheck(¶ms,
+ 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;
+ OmahaRequestParams params = request_params_;
+ params.set_wall_clock_based_wait_enabled(true);
+ params.set_waiting_period(TimeDelta().FromDays(1));
+ params.set_update_check_count_wait_enabled(false);
+
+ // Set the timestamp to a very old value such that it exceeds the
+ // waiting period set above.
+ Time t1;
+ Time::FromString("1/1/2012", &t1);
+ ASSERT_TRUE(fake_prefs_.SetInt64(
+ kPrefsUpdateFirstSeenAt, t1.ToInternalValue()));
+ ASSERT_TRUE(TestUpdateCheck(
+ ¶ms,
+ 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.
+ string test_dir;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "omaha_request_action-test-XXXXXX", &test_dir));
+
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+ kStatefulPartition + "/etc"));
+ brillo::Blob post_data;
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ ASSERT_TRUE(WriteFileString(
+ test_dir + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID={11111111-1111-1111-1111-111111111111}\n"
+ "CHROMEOS_BOARD_APPID={22222222-2222-2222-2222-222222222222}\n"
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_IS_POWERWASH_ALLOWED=true\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ OmahaRequestParams params = request_params_;
+ params.set_root(test_dir);
+ params.Init("1.2.3.4", "", 0);
+ EXPECT_EQ("canary-channel", params.current_channel());
+ EXPECT_EQ("stable-channel", params.target_channel());
+ EXPECT_TRUE(params.to_more_stable_channel());
+ EXPECT_TRUE(params.is_powerwash_allowed());
+ ASSERT_FALSE(TestUpdateCheck(¶ms,
+ "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\" "));
+
+ ASSERT_TRUE(base::DeleteFile(base::FilePath(test_dir), true));
+}
+
+TEST_F(OmahaRequestActionTest, TestChangingToLessStableChannel) {
+ // Create a uniquely named test directory.
+ string test_dir;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "omaha_request_action-test-XXXXXX", &test_dir));
+
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+ kStatefulPartition + "/etc"));
+ brillo::Blob post_data;
+ NiceMock<MockPrefs> prefs;
+ fake_system_state_.set_prefs(&prefs);
+ ASSERT_TRUE(WriteFileString(
+ test_dir + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID={11111111-1111-1111-1111-111111111111}\n"
+ "CHROMEOS_BOARD_APPID={22222222-2222-2222-2222-222222222222}\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+ OmahaRequestParams params = request_params_;
+ params.set_root(test_dir);
+ params.Init("5.6.7.8", "", 0);
+ EXPECT_EQ("stable-channel", params.current_channel());
+ EXPECT_EQ("canary-channel", params.target_channel());
+ EXPECT_FALSE(params.to_more_stable_channel());
+ EXPECT_FALSE(params.is_powerwash_allowed());
+ ASSERT_FALSE(TestUpdateCheck(¶ms,
+ "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"));
+}
+
+// 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(nullptr, // request_params
+ 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 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(nullptr, // request_params
+ 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;
+ OmahaRequestParams request_params = request_params_;
+ 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(&request_params,
+ 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(nullptr, // request_params
+ 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;
+
+ // 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) {
+ 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);
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
new file mode 100644
index 0000000..d0011f7
--- /dev/null
+++ b/omaha_request_params.cc
@@ -0,0 +1,215 @@
+//
+// 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/omaha_request_params.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/utsname.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/key_value_store.h>
+#include <brillo/strings/string_utils.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/system_state.h"
+
+#define CALL_MEMBER_FN(object, member) ((object).*(member))
+
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const char OmahaRequestParams::kOsVersion[] = "Indy";
+
+const char* kChannelsByStability[] = {
+ // This list has to be sorted from least stable to most stable channel.
+ "canary-channel",
+ "dev-channel",
+ "beta-channel",
+ "stable-channel",
+};
+
+OmahaRequestParams::~OmahaRequestParams() {
+ if (!root_.empty())
+ test::SetImagePropertiesRootPrefix(nullptr);
+}
+
+bool OmahaRequestParams::Init(const string& in_app_version,
+ const string& in_update_url,
+ bool in_interactive) {
+ LOG(INFO) << "Initializing parameters for this update attempt";
+ image_props_ = LoadImageProperties(system_state_);
+ mutable_image_props_ = LoadMutableImageProperties(system_state_);
+
+ // Sanity check the channel names.
+ if (!IsValidChannel(image_props_.current_channel))
+ image_props_.current_channel = "stable-channel";
+ if (!IsValidChannel(mutable_image_props_.target_channel))
+ mutable_image_props_.target_channel = image_props_.current_channel;
+ UpdateDownloadChannel();
+
+ LOG(INFO) << "Running from channel " << image_props_.current_channel;
+
+ os_platform_ = constants::kOmahaPlatformName;
+ os_version_ = OmahaRequestParams::kOsVersion;
+ if (!in_app_version.empty())
+ image_props_.version = in_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();
+ }
+
+ 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
+ // know (i.e. stat() returns some unexpected error), then err on the side of
+ // caution and say deltas are not okay.
+ struct stat stbuf;
+ delta_okay_ = (stat((root_ + "/.nodelta").c_str(), &stbuf) < 0) &&
+ (errno == ENOENT);
+ } else {
+ LOG(INFO) << "Disabling deltas as a channel change to "
+ << mutable_image_props_.target_channel
+ << " is pending, with is_powerwash_allowed="
+ << utils::ToString(mutable_image_props_.is_powerwash_allowed);
+ // For now, disable delta updates if the current channel is different from
+ // the channel that we're sending to the update server because such updates
+ // are destined to fail -- the current rootfs hash will be different than
+ // the expected hash due to the different channel in /etc/lsb-release.
+ delta_okay_ = false;
+ }
+
+ if (in_update_url.empty())
+ update_url_ = image_props_.omaha_url;
+ else
+ update_url_ = in_update_url;
+
+ // Set the interactive flag accordingly.
+ interactive_ = in_interactive;
+ return true;
+}
+
+bool OmahaRequestParams::IsUpdateUrlOfficial() const {
+ return (update_url_ == constants::kOmahaDefaultAUTestURL ||
+ update_url_ == image_props_.omaha_url);
+}
+
+bool OmahaRequestParams::CollectECFWVersions() const {
+ return base::StartsWithASCII(hwid_, string("SAMS ALEX"), true) ||
+ base::StartsWithASCII(hwid_, string("BUTTERFLY"), true) ||
+ base::StartsWithASCII(hwid_, string("LUMPY"), true) ||
+ base::StartsWithASCII(hwid_, string("PARROT"), true) ||
+ base::StartsWithASCII(hwid_, string("SPRING"), true) ||
+ base::StartsWithASCII(hwid_, string("SNOW"), true);
+}
+
+bool OmahaRequestParams::SetTargetChannel(const string& new_target_channel,
+ bool is_powerwash_allowed,
+ string* error_message) {
+ LOG(INFO) << "SetTargetChannel called with " << new_target_channel
+ << ", Is Powerwash Allowed = "
+ << utils::ToString(is_powerwash_allowed)
+ << ". Current channel = " << image_props_.current_channel
+ << ", existing target channel = "
+ << mutable_image_props_.target_channel
+ << ", download channel = " << download_channel_;
+ if (!IsValidChannel(new_target_channel)) {
+ string valid_channels = brillo::string_utils::JoinRange(
+ ", ",
+ std::begin(kChannelsByStability),
+ std::end(kChannelsByStability));
+ if (error_message) {
+ *error_message = base::StringPrintf(
+ "Invalid channel name \"%s\", valid names are: %s",
+ new_target_channel.c_str(), valid_channels.c_str());
+ }
+ return false;
+ }
+
+ MutableImageProperties new_props;
+ new_props.target_channel = new_target_channel;
+ new_props.is_powerwash_allowed = is_powerwash_allowed;
+
+ if (!StoreMutableImageProperties(system_state_, new_props)) {
+ if (error_message)
+ *error_message = "Error storing the new channel value.";
+ return false;
+ }
+ mutable_image_props_ = new_props;
+ return true;
+}
+
+void OmahaRequestParams::UpdateDownloadChannel() {
+ if (download_channel_ != mutable_image_props_.target_channel) {
+ download_channel_ = mutable_image_props_.target_channel;
+ LOG(INFO) << "Download channel for this attempt = " << download_channel_;
+ }
+}
+
+string OmahaRequestParams::GetMachineType() const {
+ struct utsname buf;
+ string ret;
+ if (uname(&buf) == 0)
+ ret = buf.machine;
+ return ret;
+}
+
+bool OmahaRequestParams::IsValidChannel(const string& channel) const {
+ return GetChannelIndex(channel) >= 0;
+}
+
+void OmahaRequestParams::set_root(const string& root) {
+ root_ = root;
+ test::SetImagePropertiesRootPrefix(root_.c_str());
+}
+
+int OmahaRequestParams::GetChannelIndex(const string& channel) const {
+ for (size_t t = 0; t < arraysize(kChannelsByStability); ++t)
+ if (channel == kChannelsByStability[t])
+ return t;
+
+ return -1;
+}
+
+bool OmahaRequestParams::to_more_stable_channel() const {
+ int current_channel_index = GetChannelIndex(image_props_.current_channel);
+ int download_channel_index = GetChannelIndex(download_channel_);
+
+ return download_channel_index > current_channel_index;
+}
+
+string OmahaRequestParams::GetAppId() const {
+ return download_channel_ == "canary-channel" ? image_props_.canary_product_id
+ : image_props_.product_id;
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_request_params.h b/omaha_request_params.h
new file mode 100644
index 0000000..b4534a1
--- /dev/null
+++ b/omaha_request_params.h
@@ -0,0 +1,336 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_
+#define UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_
+
+#include <stdint.h>
+
+#include <string>
+
+#include <base/macros.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/image_properties.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.
+//
+// TODO(jaysri): chromium-os:39752 tracks the need to rename this class to
+// reflect its lifetime more appropriately.
+class OmahaRequestParams {
+ public:
+ explicit OmahaRequestParams(SystemState* system_state)
+ : system_state_(system_state),
+ os_platform_(constants::kOmahaPlatformName),
+ os_version_(kOsVersion),
+ delta_okay_(true),
+ interactive_(false),
+ wall_clock_based_wait_enabled_(false),
+ update_check_count_wait_enabled_(false),
+ min_update_checks_needed_(kDefaultMinUpdateChecks),
+ max_update_checks_allowed_(kDefaultMaxUpdateChecks) {}
+
+ OmahaRequestParams(SystemState* system_state,
+ const std::string& in_os_platform,
+ const std::string& in_os_version,
+ const std::string& in_os_sp,
+ const std::string& in_os_board,
+ const std::string& in_app_id,
+ const std::string& in_app_version,
+ const std::string& in_app_lang,
+ const std::string& in_target_channel,
+ const std::string& in_hwid,
+ const std::string& in_fw_version,
+ const std::string& in_ec_version,
+ bool in_delta_okay,
+ bool in_interactive,
+ const std::string& in_update_url,
+ const std::string& in_target_version_prefix)
+ : system_state_(system_state),
+ os_platform_(in_os_platform),
+ os_version_(in_os_version),
+ os_sp_(in_os_sp),
+ app_lang_(in_app_lang),
+ hwid_(in_hwid),
+ fw_version_(in_fw_version),
+ ec_version_(in_ec_version),
+ delta_okay_(in_delta_okay),
+ interactive_(in_interactive),
+ update_url_(in_update_url),
+ target_version_prefix_(in_target_version_prefix),
+ wall_clock_based_wait_enabled_(false),
+ update_check_count_wait_enabled_(false),
+ min_update_checks_needed_(kDefaultMinUpdateChecks),
+ max_update_checks_allowed_(kDefaultMaxUpdateChecks) {
+ image_props_.board = in_os_board;
+ image_props_.product_id = in_app_id;
+ image_props_.canary_product_id = in_app_id;
+ image_props_.version = in_app_version;
+ image_props_.current_channel = in_target_channel;
+ mutable_image_props_.target_channel = in_target_channel;
+ mutable_image_props_.is_powerwash_allowed = false;
+ }
+
+ virtual ~OmahaRequestParams();
+
+ // 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_; }
+ inline std::string os_sp() const { return os_sp_; }
+ inline std::string os_board() const { return image_props_.board; }
+ inline std::string board_app_id() const { return image_props_.product_id; }
+ inline std::string canary_app_id() const {
+ return image_props_.canary_product_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 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 current_channel() const {
+ return image_props_.current_channel;
+ }
+ inline std::string target_channel() const {
+ return mutable_image_props_.target_channel;
+ }
+ inline std::string download_channel() const { return download_channel_; }
+
+ // Can client accept a delta ?
+ inline void set_delta_okay(bool ok) { delta_okay_ = ok; }
+ inline bool delta_okay() const { return delta_okay_; }
+
+ // True if this is a user-initiated update check.
+ inline void set_interactive(bool interactive) { interactive_ = interactive; }
+ inline bool interactive() const { return interactive_; }
+
+ inline void set_update_url(const std::string& url) { update_url_ = url; }
+ inline std::string update_url() const { return update_url_; }
+
+ inline void set_target_version_prefix(const std::string& prefix) {
+ target_version_prefix_ = prefix;
+ }
+
+ inline std::string target_version_prefix() const {
+ return target_version_prefix_;
+ }
+
+ inline void set_wall_clock_based_wait_enabled(bool enabled) {
+ wall_clock_based_wait_enabled_ = enabled;
+ }
+ inline bool wall_clock_based_wait_enabled() const {
+ return wall_clock_based_wait_enabled_;
+ }
+
+ inline void set_waiting_period(base::TimeDelta period) {
+ waiting_period_ = period;
+ }
+ base::TimeDelta waiting_period() const { return waiting_period_; }
+
+ inline void set_update_check_count_wait_enabled(bool enabled) {
+ update_check_count_wait_enabled_ = enabled;
+ }
+
+ inline bool update_check_count_wait_enabled() const {
+ return update_check_count_wait_enabled_;
+ }
+
+ inline void set_min_update_checks_needed(int64_t min) {
+ min_update_checks_needed_ = min;
+ }
+ inline int64_t min_update_checks_needed() const {
+ return min_update_checks_needed_;
+ }
+
+ inline void set_max_update_checks_allowed(int64_t max) {
+ max_update_checks_allowed_ = max;
+ }
+ inline int64_t max_update_checks_allowed() const {
+ return max_update_checks_allowed_;
+ }
+
+ // True if we're trying to update to a more stable channel.
+ // i.e. index(target_channel) > index(current_channel).
+ virtual bool to_more_stable_channel() const;
+
+ // Returns the app id corresponding to the current value of the
+ // download channel.
+ virtual std::string GetAppId() const;
+
+ // Suggested defaults
+ static const char kOsVersion[];
+ static const char kIsPowerwashAllowedKey[];
+ static const int64_t kDefaultMinUpdateChecks = 0;
+ static const int64_t kDefaultMaxUpdateChecks = 8;
+
+ // Initializes all the data in the object. Non-empty
+ // |in_app_version| or |in_update_url| prevents automatic detection
+ // 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);
+
+ // Permanently changes the release channel to |channel|. Performs a
+ // powerwash, if required and allowed.
+ // Returns true on success, false otherwise. Note: This call will fail if
+ // there's a channel change pending already. This is to serialize all the
+ // channel changes done by the user in order to avoid having to solve
+ // numerous edge cases around ensuring the powerwash happens as intended in
+ // all such cases.
+ virtual bool SetTargetChannel(const std::string& channel,
+ bool is_powerwash_allowed,
+ std::string* error_message);
+
+ // Updates the download channel for this particular attempt from the current
+ // value of target channel. This method takes a "snapshot" of the current
+ // value of target channel and uses it for all subsequent Omaha requests for
+ // this attempt (i.e. initial request as well as download progress/error
+ // event requests). The snapshot will be updated only when either this method
+ // or Init is called again.
+ virtual void UpdateDownloadChannel();
+
+ virtual bool is_powerwash_allowed() const {
+ return mutable_image_props_.is_powerwash_allowed;
+ }
+
+ // Check if the provided update URL is official, meaning either the default
+ // autoupdate server or the autoupdate autotest server.
+ virtual bool IsUpdateUrlOfficial() const;
+
+ // For unit-tests.
+ void set_root(const std::string& root);
+ void set_current_channel(const std::string& channel) {
+ image_props_.current_channel = channel;
+ }
+ void set_target_channel(const std::string& channel) {
+ mutable_image_props_.target_channel = channel;
+ }
+
+ private:
+ FRIEND_TEST(OmahaRequestParamsTest, IsValidChannelTest);
+ FRIEND_TEST(OmahaRequestParamsTest, ShouldLockDownTest);
+ FRIEND_TEST(OmahaRequestParamsTest, ChannelIndexTest);
+ FRIEND_TEST(OmahaRequestParamsTest, LsbPreserveTest);
+ FRIEND_TEST(OmahaRequestParamsTest, CollectECFWVersionsTest);
+
+ // Returns true if |channel| is a valid channel, false otherwise.
+ bool IsValidChannel(const std::string& channel) const;
+
+ // Returns the index of the given channel.
+ int GetChannelIndex(const std::string& channel) 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;
+
+ // These are individual helper methods to initialize the said properties from
+ // the LSB value.
+ void SetTargetChannelFromLsbValue();
+ void SetCurrentChannelFromLsbValue();
+ void SetIsPowerwashAllowedFromLsbValue();
+
+ // Initializes the required properties from the LSB value.
+ void InitFromLsbValue();
+
+ // 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_;
+
+ // Basic properties of the OS and Application that go into the Omaha request.
+ std::string os_platform_;
+ std::string os_version_;
+ std::string os_sp_;
+ std::string app_lang_;
+
+ // There are three channel values we deal with:
+ // * The channel we got the image we are running from or "current channel"
+ // stored in |image_props_.current_channel|.
+ //
+ // * The release channel we are tracking, where we should get updates from,
+ // stored in |mutable_image_props_.target_channel|. This channel is
+ // normally the same as the current_channel, except when the user changes
+ // the channel. In that case it'll have the release channel the user
+ // switched to, regardless of whether we downloaded an update from that
+ // channel or not, or if we are in the middle of a download from a
+ // previously selected channel (as opposed to download channel
+ // which gets updated only at the start of next download).
+ //
+ // * The channel from which we're downloading the payload. This should
+ // normally be the same as target channel. But if the user made another
+ // channel change after we started the download, then they'd be different,
+ // in which case, we'd detect elsewhere that the target channel has been
+ // 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 URL to send the Omaha request to.
+ std::string update_url_;
+
+ // Prefix of the target OS version that the enterprise wants this device
+ // to be pinned to. It's empty otherwise.
+ std::string target_version_prefix_;
+
+ // True if scattering is enabled, in which case waiting_period_ specifies the
+ // amount of absolute time that we've to wait for before sending a request to
+ // Omaha.
+ bool wall_clock_based_wait_enabled_;
+ base::TimeDelta waiting_period_;
+
+ // True if scattering is enabled to denote the number of update checks
+ // we've to skip before we can send a request to Omaha. The min and max
+ // values establish the bounds for a random number to be chosen within that
+ // range to enable such a wait.
+ bool update_check_count_wait_enabled_;
+ int64_t min_update_checks_needed_;
+ int64_t max_update_checks_allowed_;
+
+ // When reading files, prepend root_ to the paths. Useful for testing.
+ std::string root_;
+
+ // TODO(jaysri): Uncomment this after fixing unit tests, as part of
+ // chromium-os:39752
+ // DISALLOW_COPY_AND_ASSIGN(OmahaRequestParams);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_OMAHA_REQUEST_PARAMS_H_
diff --git a/omaha_request_params_unittest.cc b/omaha_request_params_unittest.cc
new file mode 100644
index 0000000..33dd6d5
--- /dev/null
+++ b/omaha_request_params_unittest.cc
@@ -0,0 +1,598 @@
+//
+// 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/omaha_request_params.h"
+
+#include <stdio.h>
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.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/payload_consumer/install_plan.h"
+
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+
+namespace chromeos_update_engine {
+
+class OmahaRequestParamsTest : public ::testing::Test {
+ public:
+ OmahaRequestParamsTest() : params_(&fake_system_state_) {}
+
+ protected:
+ // Return true iff the OmahaRequestParams::Init succeeded. If
+ // out is non-null, it's set w/ the generated data.
+ bool DoTest(OmahaRequestParams* out, const string& app_version,
+ const string& omaha_url);
+
+ void SetUp() override {
+ // Create a uniquely named test directory.
+ ASSERT_TRUE(utils::MakeTempDirectory(kTestDirTemplate, &test_dir_));
+ EXPECT_TRUE(base::CreateDirectory(base::FilePath(test_dir_ + "/etc")));
+ EXPECT_TRUE(base::CreateDirectory(
+ base::FilePath(test_dir_ + kStatefulPartition + "/etc")));
+ // Create a fresh copy of the params for each test, so there's no
+ // unintended reuse of state across tests.
+ OmahaRequestParams new_params(&fake_system_state_);
+ params_ = new_params;
+ params_.set_root(test_dir_);
+ SetLockDown(false);
+ }
+
+ void TearDown() override {
+ EXPECT_TRUE(base::DeleteFile(base::FilePath(test_dir_), true));
+ }
+
+ void SetLockDown(bool locked_down) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(locked_down);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(locked_down);
+ }
+
+ OmahaRequestParams params_;
+ FakeSystemState fake_system_state_;
+
+ static const char* kTestDirTemplate;
+ string test_dir_;
+};
+
+const char* OmahaRequestParamsTest::kTestDirTemplate =
+ "omaha_request_params-test-XXXXXX";
+
+bool OmahaRequestParamsTest::DoTest(OmahaRequestParams* out,
+ const string& app_version,
+ const string& omaha_url) {
+ bool success = params_.Init(app_version, omaha_url, false);
+ if (out)
+ *out = params_;
+ return success;
+}
+
+namespace {
+string GetMachineType() {
+ string machine_type;
+ if (!utils::ReadPipe("uname -m", &machine_type))
+ return "";
+ // Strip anything from the first newline char.
+ size_t newline_pos = machine_type.find('\n');
+ if (newline_pos != string::npos)
+ machine_type.erase(newline_pos);
+ return machine_type;
+}
+} // namespace
+
+TEST_F(OmahaRequestParamsTest, SimpleTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, AppIDTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_RELEASE_APPID={58c35cef-9d30-476e-9098-ce20377d535d}\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{58c35cef-9d30-476e-9098-ce20377d535d}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRXCK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ // By default, if no channel is set, we should track the stable-channel.
+ EXPECT_EQ("stable-channel", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ConfusingReleaseTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRXCK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ("stable-channel", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingVersionTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ForceVersionTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "ForcedVersion", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("ForcedVersion_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("ForcedVersion", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ForcedURLTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", "http://forced.google.com"));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ("http://forced.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, MissingURLTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ(constants::kOmahaDefaultProductionURL, out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, NoDeltasTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRXCK=dev-channel"));
+ ASSERT_TRUE(WriteFileString(test_dir_ + "/.nodelta", ""));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_FALSE(out.delta_okay());
+}
+
+TEST_F(OmahaRequestParamsTest, OverrideTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+ "CHROMEOS_RELEASE_TRACK=beta-channel\n"
+ "CHROMEOS_AUSERVER=https://www.google.com"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("x86-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_FALSE(out.delta_okay());
+ EXPECT_EQ("beta-channel", out.target_channel());
+ EXPECT_EQ("https://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, OverrideLockDownTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=https://www.google.com"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ SetLockDown(true);
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_FALSE(out.delta_okay());
+ EXPECT_EQ("stable-channel", out.target_channel());
+ EXPECT_EQ("https://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, OverrideSameChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("x86-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ {
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_root(test_dir_);
+ EXPECT_TRUE(params.Init("", "", false));
+ params.SetTargetChannel("canary-channel", false, nullptr);
+ EXPECT_FALSE(params.is_powerwash_allowed());
+ }
+ OmahaRequestParams out(&fake_system_state_);
+ out.set_root(test_dir_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("canary-channel", out.target_channel());
+ EXPECT_FALSE(out.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, SetIsPowerwashAllowedTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ {
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_root(test_dir_);
+ EXPECT_TRUE(params.Init("", "", false));
+ params.SetTargetChannel("canary-channel", true, nullptr);
+ EXPECT_TRUE(params.is_powerwash_allowed());
+ }
+ OmahaRequestParams out(&fake_system_state_);
+ out.set_root(test_dir_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("canary-channel", out.target_channel());
+ EXPECT_TRUE(out.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelInvalidTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ {
+ OmahaRequestParams params(&fake_system_state_);
+ params.set_root(test_dir_);
+ SetLockDown(true);
+ EXPECT_TRUE(params.Init("", "", false));
+ string error_message;
+ EXPECT_FALSE(
+ params.SetTargetChannel("dogfood-channel", true, &error_message));
+ // The error message should include a message about the valid channels.
+ EXPECT_NE(string::npos, error_message.find("stable-channel"));
+ EXPECT_FALSE(params.is_powerwash_allowed());
+ }
+ OmahaRequestParams out(&fake_system_state_);
+ out.set_root(test_dir_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_FALSE(out.is_powerwash_allowed());
+}
+
+TEST_F(OmahaRequestParamsTest, IsValidChannelTest) {
+ EXPECT_TRUE(params_.IsValidChannel("canary-channel"));
+ EXPECT_TRUE(params_.IsValidChannel("stable-channel"));
+ EXPECT_TRUE(params_.IsValidChannel("beta-channel"));
+ EXPECT_TRUE(params_.IsValidChannel("dev-channel"));
+ EXPECT_FALSE(params_.IsValidChannel("testimage-channel"));
+ EXPECT_FALSE(params_.IsValidChannel("dogfood-channel"));
+ EXPECT_FALSE(params_.IsValidChannel("some-channel"));
+ EXPECT_FALSE(params_.IsValidChannel(""));
+}
+
+TEST_F(OmahaRequestParamsTest, ValidChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ SetLockDown(true);
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("Chrome OS", out.os_platform());
+ EXPECT_EQ(string("0.2.2.3_") + GetMachineType(), out.os_sp());
+ EXPECT_EQ("arm-generic", out.os_board());
+ EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", out.GetAppId());
+ EXPECT_EQ("0.2.2.3", out.app_version());
+ EXPECT_EQ("en-US", out.app_lang());
+ EXPECT_EQ(fake_system_state_.hardware()->GetHardwareClass(), out.hwid());
+ EXPECT_TRUE(out.delta_okay());
+ EXPECT_EQ("dev-channel", out.target_channel());
+ EXPECT_EQ("http://www.google.com", out.update_url());
+}
+
+TEST_F(OmahaRequestParamsTest, SetTargetChannelWorks) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=dev-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+
+ // Check LSB value is used by default when SetTargetChannel is not called.
+ params_.Init("", "", false);
+ EXPECT_EQ("dev-channel", params_.target_channel());
+
+ // When an invalid value is set, it should be ignored and the
+ // value from lsb-release should be used instead.
+ params_.Init("", "", false);
+ EXPECT_FALSE(params_.SetTargetChannel("invalid-channel", false, nullptr));
+ EXPECT_EQ("dev-channel", params_.target_channel());
+
+ // When set to a valid value, it should take effect.
+ params_.Init("", "", false);
+ EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true, nullptr));
+ EXPECT_EQ("beta-channel", params_.target_channel());
+
+ // When set to the same value, it should be idempotent.
+ params_.Init("", "", false);
+ EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true, nullptr));
+ EXPECT_EQ("beta-channel", params_.target_channel());
+
+ // When set to a valid value while a change is already pending, it should
+ // succeed.
+ params_.Init("", "", false);
+ EXPECT_TRUE(params_.SetTargetChannel("stable-channel", true, nullptr));
+ EXPECT_EQ("stable-channel", params_.target_channel());
+
+ // Set a different channel in stateful LSB release.
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"
+ "CHROMEOS_IS_POWERWASH_ALLOWED=true\n"));
+
+ // When set to a valid value while a change is already pending, it should
+ // succeed.
+ params_.Init("", "", false);
+ 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 ...
+ EXPECT_EQ("beta-channel", params_.target_channel());
+ EXPECT_EQ("stable-channel", params_.download_channel());
+
+ // ... until we update the download channel explicitly.
+ params_.UpdateDownloadChannel();
+ EXPECT_EQ("beta-channel", params_.download_channel());
+ EXPECT_EQ("beta-channel", params_.target_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, ChannelIndexTest) {
+ int canary = params_.GetChannelIndex("canary-channel");
+ int dev = params_.GetChannelIndex("dev-channel");
+ int beta = params_.GetChannelIndex("beta-channel");
+ int stable = params_.GetChannelIndex("stable-channel");
+ EXPECT_LE(canary, dev);
+ EXPECT_LE(dev, beta);
+ EXPECT_LE(beta, stable);
+
+ // testimage-channel or other names are not recognized, so index will be -1.
+ int testimage = params_.GetChannelIndex("testimage-channel");
+ int bogus = params_.GetChannelIndex("bogus-channel");
+ EXPECT_EQ(-1, testimage);
+ EXPECT_EQ(-1, bogus);
+}
+
+TEST_F(OmahaRequestParamsTest, ToMoreStableChannelFlagTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=arm-generic\n"
+ "CHROMEOS_RELEASE_FOO=bar\n"
+ "CHROMEOS_RELEASE_VERSION=0.2.2.3\n"
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"
+ "CHROMEOS_AUSERVER=http://www.google.com"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_BOARD=x86-generic\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"
+ "CHROMEOS_AUSERVER=https://www.google.com"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("https://www.google.com", out.update_url());
+ EXPECT_FALSE(out.delta_okay());
+ EXPECT_EQ("stable-channel", out.target_channel());
+ EXPECT_TRUE(out.to_more_stable_channel());
+}
+
+TEST_F(OmahaRequestParamsTest, BoardAppIdUsedForNonCanaryChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_BOARD_APPID=b\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("stable-channel", out.download_channel());
+ EXPECT_EQ("b", out.GetAppId());
+}
+
+TEST_F(OmahaRequestParamsTest, CanaryAppIdUsedForCanaryChannelTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_BOARD_APPID=b\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("canary-channel", out.download_channel());
+ EXPECT_EQ("c", out.GetAppId());
+}
+
+TEST_F(OmahaRequestParamsTest, ReleaseAppIdUsedAsDefaultTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ OmahaRequestParams out(&fake_system_state_);
+ EXPECT_TRUE(DoTest(&out, "", ""));
+ EXPECT_EQ("stable-channel", out.download_channel());
+ EXPECT_EQ("r", out.GetAppId());
+}
+
+TEST_F(OmahaRequestParamsTest, CollectECFWVersionsTest) {
+ ASSERT_TRUE(WriteFileString(
+ test_dir_ + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_APPID=r\n"
+ "CHROMEOS_CANARY_APPID=c\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ OmahaRequestParams out(&fake_system_state_);
+ out.hwid_ = string("STUMPY ALEX 12345");
+ EXPECT_FALSE(out.CollectECFWVersions());
+
+ out.hwid_ = string("SNOW 12345");
+ EXPECT_TRUE(out.CollectECFWVersions());
+
+ out.hwid_ = string("SAMS ALEX 12345");
+ EXPECT_TRUE(out.CollectECFWVersions());
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_response.h b/omaha_response.h
new file mode 100644
index 0000000..3a5a889
--- /dev/null
+++ b/omaha_response.h
@@ -0,0 +1,85 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_OMAHA_RESPONSE_H_
+#define UPDATE_ENGINE_OMAHA_RESPONSE_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+// This struct encapsulates the data Omaha's response for the request.
+// The strings in this struct are not XML escaped.
+struct OmahaResponse {
+ // True iff there is an update to be downloaded.
+ bool update_exists = false;
+
+ // If non-zero, server-dictated poll interval in seconds.
+ int poll_interval = 0;
+
+ // These are only valid if update_exists is true:
+ std::string version;
+
+ // The ordered list of URLs in the Omaha response. Each item is a complete
+ // URL (i.e. in terms of Omaha XML, each value is a urlBase + packageName)
+ std::vector<std::string> payload_urls;
+
+ std::string more_info_url;
+ std::string hash;
+ std::string metadata_signature;
+ std::string deadline;
+ off_t size = 0;
+ off_t metadata_size = 0;
+ int max_days_to_scatter = 0;
+ // The number of URL-related failures to tolerate before moving on to the
+ // next URL in the current pass. This is a configurable value from the
+ // Omaha Response attribute, if ever we need to fine tune the behavior.
+ uint32_t max_failure_count_per_url = 0;
+ bool prompt = false;
+
+ // True if the payload described in this response is a delta payload.
+ // False if it's a full payload.
+ bool is_delta_payload = false;
+
+ // True if the Omaha rule instructs us to disable the back-off logic
+ // on the client altogether. False otherwise.
+ bool disable_payload_backoff = false;
+
+ // True if the Omaha rule instructs us to disable p2p for downloading.
+ bool disable_p2p_for_downloading = false;
+
+ // True if the Omaha rule instructs us to disable p2p for sharing.
+ bool disable_p2p_for_sharing = false;
+
+ // If not blank, a base-64 encoded representation of the PEM-encoded
+ // public key in the response.
+ std::string public_key_rsa;
+
+ // If not -1, the number of days since the epoch Jan 1, 2007 0:00
+ // PST, according to the Omaha Server's clock and timezone (PST8PDT,
+ // aka "Pacific Time".)
+ int install_date_days = -1;
+};
+COMPILE_ASSERT(sizeof(off_t) == 8, off_t_not_64bit);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_OMAHA_RESPONSE_H_
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
new file mode 100644
index 0000000..3fa9348
--- /dev/null
+++ b/omaha_response_handler_action.cc
@@ -0,0 +1,206 @@
+//
+// 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/omaha_response_handler_action.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.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/utils.h"
+#include "update_engine/connection_manager_interface.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_state_interface.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+OmahaResponseHandlerAction::OmahaResponseHandlerAction(
+ SystemState* system_state)
+ : OmahaResponseHandlerAction(system_state,
+ constants::kOmahaResponseDeadlineFile) {}
+
+OmahaResponseHandlerAction::OmahaResponseHandlerAction(
+ SystemState* system_state, const string& deadline_file)
+ : system_state_(system_state),
+ got_no_update_response_(false),
+ key_path_(constants::kUpdatePayloadPublicKeyPath),
+ deadline_file_(deadline_file) {}
+
+void OmahaResponseHandlerAction::PerformAction() {
+ CHECK(HasInputObject());
+ ScopedActionCompleter completer(processor_, this);
+ const OmahaResponse& response = GetInputObject();
+ if (!response.update_exists) {
+ got_no_update_response_ = true;
+ LOG(INFO) << "There are no updates. Aborting.";
+ return;
+ }
+
+ // 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();
+ if (current_url.empty()) {
+ // This shouldn't happen as we should always supply the HTTPS backup URL.
+ // Handling this anyway, just in case.
+ LOG(ERROR) << "There are no suitable URLs in the response to use.";
+ completer.set_code(ErrorCode::kOmahaResponseInvalid);
+ return;
+ }
+
+ install_plan_.download_url = current_url;
+ install_plan_.version = response.version;
+
+ OmahaRequestParams* const params = system_state_->request_params();
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+ // If we're using p2p to download and there is a local peer, use it.
+ if (payload_state->GetUsingP2PForDownloading() &&
+ !payload_state->GetP2PUrl().empty()) {
+ LOG(INFO) << "Replacing URL " << install_plan_.download_url
+ << " with local URL " << payload_state->GetP2PUrl()
+ << " since p2p is enabled.";
+ install_plan_.download_url = payload_state->GetP2PUrl();
+ payload_state->SetUsingP2PForDownloading(true);
+ }
+
+ // Fill up the other properties based on the response.
+ install_plan_.payload_size = response.size;
+ install_plan_.payload_hash = response.hash;
+ install_plan_.metadata_size = response.metadata_size;
+ install_plan_.metadata_signature = response.metadata_signature;
+ 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(), response.hash);
+ if (install_plan_.is_resume) {
+ payload_state->UpdateResumed();
+ } else {
+ payload_state->UpdateRestarted();
+ LOG_IF(WARNING, !DeltaPerformer::ResetUpdateProgress(
+ system_state_->prefs(), false))
+ << "Unable to reset the update progress.";
+ LOG_IF(WARNING, !system_state_->prefs()->SetString(
+ kPrefsUpdateCheckResponseHash, response.hash))
+ << "Unable to save the update check response hash.";
+ }
+ install_plan_.is_full_update = !response.is_delta_payload;
+
+ install_plan_.source_slot = system_state_->boot_control()->GetCurrentSlot();
+ install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
+
+ // The Omaha response doesn't include the channel name for this image, so we
+ // use the download_channel we used during the request to tag the target slot.
+ // This will be used in the next boot to know the channel the image was
+ // downloaded from.
+ string current_channel_key =
+ kPrefsChannelOnSlotPrefix + std::to_string(install_plan_.target_slot);
+ system_state_->prefs()->SetString(current_channel_key,
+ params->download_channel());
+
+ if (params->to_more_stable_channel() && params->is_powerwash_allowed())
+ install_plan_.powerwash_required = true;
+
+ 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
+ // hacky solution but should be OK for now.
+ //
+ // TODO(petkov): Re-architect this to avoid communication through a
+ // file. Ideally, we would include this information in D-Bus's GetStatus
+ // method and UpdateStatus signal. A potential issue is that update_engine may
+ // be unresponsive during an update download.
+ if (!deadline_file_.empty()) {
+ utils::WriteFile(deadline_file_.c_str(),
+ response.deadline.data(),
+ response.deadline.size());
+ chmod(deadline_file_.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ }
+
+ completer.set_code(ErrorCode::kSuccess);
+}
+
+bool OmahaResponseHandlerAction::AreHashChecksMandatory(
+ const OmahaResponse& response) {
+ // We sometimes need to waive the hash checks in order to download from
+ // sources that don't provide hashes, such as dev server.
+ // At this point UpdateAttempter::IsAnyUpdateSourceAllowed() has already been
+ // checked, so an unofficial update URL won't get this far unless it's OK to
+ // use without a hash. Additionally, we want to always waive hash checks on
+ // unofficial builds (i.e. dev/test images).
+ // The end result is this:
+ // * Base image:
+ // - Official URLs require a hash.
+ // - Unofficial URLs only get this far if the IsAnyUpdateSourceAllowed()
+ // 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()) {
+ // 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
+ // in log-files. Keep in sync.
+ LOG(INFO) << "Mandating payload hash checks since Omaha Response "
+ << "for unofficial build includes public RSA key.";
+ return true;
+ } else {
+ LOG(INFO) << "Waiving payload hash checks for unofficial update URL.";
+ return false;
+ }
+ }
+
+ // If we're using p2p, |install_plan_.download_url| may contain a
+ // HTTP URL even if |response.payload_urls| contain only HTTPS URLs.
+ if (!base::StartsWithASCII(install_plan_.download_url, "https://", false)) {
+ LOG(INFO) << "Mandating hash checks since download_url is not HTTPS.";
+ return true;
+ }
+
+ // TODO(jaysri): VALIDATION: For official builds, we currently waive hash
+ // checks for HTTPS until we have rolled out at least once and are confident
+ // nothing breaks. chromium-os:37082 tracks turning this on for HTTPS
+ // eventually.
+
+ // Even if there's a single non-HTTPS URL, make the hash checks as
+ // mandatory because we could be downloading the payload from any URL later
+ // on. It's really hard to do book-keeping based on each byte being
+ // downloaded to see whether we only used HTTPS throughout.
+ for (size_t i = 0; i < response.payload_urls.size(); i++) {
+ if (!base::StartsWithASCII(response.payload_urls[i], "https://", false)) {
+ LOG(INFO) << "Mandating payload hash checks since Omaha response "
+ << "contains non-HTTPS URL(s)";
+ return true;
+ }
+ }
+
+ LOG(INFO) << "Waiving payload hash checks since Omaha response "
+ << "only has HTTPS URL(s)";
+ return false;
+}
+
+} // namespace chromeos_update_engine
diff --git a/omaha_response_handler_action.h b/omaha_response_handler_action.h
new file mode 100644
index 0000000..51dfa7a
--- /dev/null
+++ b/omaha_response_handler_action.h
@@ -0,0 +1,98 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_
+#define UPDATE_ENGINE_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/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.
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerAction;
+
+template<>
+class ActionTraits<OmahaResponseHandlerAction> {
+ public:
+ typedef OmahaResponse InputObjectType;
+ typedef InstallPlan OutputObjectType;
+};
+
+class OmahaResponseHandlerAction : public Action<OmahaResponseHandlerAction> {
+ public:
+ explicit OmahaResponseHandlerAction(SystemState* system_state);
+
+ typedef ActionTraits<OmahaResponseHandlerAction>::InputObjectType
+ InputObjectType;
+ typedef ActionTraits<OmahaResponseHandlerAction>::OutputObjectType
+ OutputObjectType;
+ void PerformAction() override;
+
+ // This is a synchronous action, and thus TerminateProcessing() should
+ // never be called
+ void TerminateProcessing() override { CHECK(false); }
+
+ bool GotNoUpdateResponse() const { return got_no_update_response_; }
+ const InstallPlan& install_plan() const { return install_plan_; }
+
+ // Debugging/logging
+ static std::string StaticType() { return "OmahaResponseHandlerAction"; }
+ std::string Type() const override { return StaticType(); }
+ void set_key_path(const std::string& path) { key_path_ = path; }
+
+ private:
+ // Returns true if payload hash checks are mandatory based on the state
+ // 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_;
+
+ // True only if we got a response and the response said no updates
+ bool got_no_update_response_;
+
+ // Public key path to use for payload verification.
+ std::string key_path_;
+
+ // File used for communication deadline to Chrome.
+ const std::string deadline_file_;
+
+ // Special ctor + friend declarations for testing purposes.
+ OmahaResponseHandlerAction(SystemState* system_state,
+ const std::string& deadline_file);
+
+ friend class OmahaResponseHandlerActionTest;
+
+ FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+
+ DISALLOW_COPY_AND_ASSIGN(OmahaResponseHandlerAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_OMAHA_RESPONSE_HANDLER_ACTION_H_
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
new file mode 100644
index 0000000..b996d38
--- /dev/null
+++ b/omaha_response_handler_action_unittest.cc
@@ -0,0 +1,435 @@
+//
+// 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/omaha_response_handler_action.h"
+
+#include <string>
+
+#include <base/files/file_util.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.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/mock_payload_state.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using testing::Return;
+
+namespace chromeos_update_engine {
+
+class OmahaResponseHandlerActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ FakeBootControl* fake_boot_control = fake_system_state_.fake_boot_control();
+ fake_boot_control->SetPartitionDevice(
+ kLegacyPartitionNameKernel, 0, "/dev/sdz2");
+ fake_boot_control->SetPartitionDevice(
+ kLegacyPartitionNameRoot, 0, "/dev/sdz3");
+ fake_boot_control->SetPartitionDevice(
+ kLegacyPartitionNameKernel, 1, "/dev/sdz4");
+ fake_boot_control->SetPartitionDevice(
+ kLegacyPartitionNameRoot, 1, "/dev/sdz5");
+ }
+
+ // Return true iff the OmahaResponseHandlerAction succeeded.
+ // If out is non-null, it's set w/ the response from the action.
+ bool DoTest(const OmahaResponse& in,
+ const string& deadline_file,
+ InstallPlan* out);
+
+ FakeSystemState fake_system_state_;
+};
+
+class OmahaResponseHandlerActionProcessorDelegate
+ : public ActionProcessorDelegate {
+ public:
+ OmahaResponseHandlerActionProcessorDelegate()
+ : code_(ErrorCode::kError),
+ code_set_(false) {}
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == OmahaResponseHandlerAction::StaticType()) {
+ code_ = code;
+ code_set_ = true;
+ }
+ }
+ ErrorCode code_;
+ bool code_set_;
+};
+
+namespace {
+const char* const kLongName =
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "very_long_name_and_no_slashes-very_long_name_and_no_slashes"
+ "-the_update_a.b.c.d_DELTA_.tgz";
+const char* const kBadVersion = "don't update me";
+} // namespace
+
+bool OmahaResponseHandlerActionTest::DoTest(
+ const OmahaResponse& in,
+ const string& test_deadline_file,
+ InstallPlan* out) {
+ ActionProcessor processor;
+ OmahaResponseHandlerActionProcessorDelegate delegate;
+ processor.set_delegate(&delegate);
+
+ ObjectFeederAction<OmahaResponse> feeder_action;
+ feeder_action.set_obj(in);
+ if (in.update_exists && in.version != kBadVersion) {
+ EXPECT_CALL(*(fake_system_state_.mock_prefs()),
+ SetString(kPrefsUpdateCheckResponseHash, in.hash))
+ .WillOnce(Return(true));
+
+ int slot = 1 - fake_system_state_.fake_boot_control()->GetCurrentSlot();
+ string key = kPrefsChannelOnSlotPrefix + std::to_string(slot);
+ EXPECT_CALL(*(fake_system_state_.mock_prefs()), SetString(key, testing::_))
+ .WillOnce(Return(true));
+ }
+
+ string current_url = in.payload_urls.size() ? in.payload_urls[0] : "";
+ EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl())
+ .WillRepeatedly(Return(current_url));
+
+ OmahaResponseHandlerAction response_handler_action(
+ &fake_system_state_,
+ (test_deadline_file.empty() ?
+ constants::kOmahaResponseDeadlineFile : test_deadline_file));
+ BondActions(&feeder_action, &response_handler_action);
+ ObjectCollectorAction<InstallPlan> collector_action;
+ BondActions(&response_handler_action, &collector_action);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&response_handler_action);
+ processor.EnqueueAction(&collector_action);
+ processor.StartProcessing();
+ EXPECT_TRUE(!processor.IsRunning())
+ << "Update test to handle non-async actions";
+ if (out)
+ *out = collector_action.object();
+ EXPECT_TRUE(delegate.code_set_);
+ return delegate.code_ == ErrorCode::kSuccess;
+}
+
+TEST_F(OmahaResponseHandlerActionTest, SimpleTest) {
+ string test_deadline_file;
+ CHECK(utils::MakeTempFile(
+ "omaha_response_handler_action_unittest-XXXXXX",
+ &test_deadline_file, nullptr));
+ ScopedPathUnlinker deadline_unlinker(test_deadline_file);
+ {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://foo/the_update_a.b.c.d.tgz");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASH+";
+ in.size = 12;
+ in.prompt = false;
+ in.deadline = "20101020";
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_EQ(1, install_plan.target_slot);
+ string deadline;
+ EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
+ EXPECT_EQ("20101020", deadline);
+ struct stat deadline_stat;
+ EXPECT_EQ(0, stat(test_deadline_file.c_str(), &deadline_stat));
+ EXPECT_EQ(S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
+ deadline_stat.st_mode);
+ EXPECT_EQ(in.version, install_plan.version);
+ }
+ {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://foo/the_update_a.b.c.d.tgz");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ in.prompt = true;
+ InstallPlan install_plan;
+ // Set the other slot as current.
+ fake_system_state_.fake_boot_control()->SetCurrentSlot(1);
+ EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_EQ(0, install_plan.target_slot);
+ string deadline;
+ EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline) &&
+ deadline.empty());
+ EXPECT_EQ(in.version, install_plan.version);
+ }
+ {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back(kLongName);
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ in.prompt = true;
+ in.deadline = "some-deadline";
+ InstallPlan install_plan;
+ fake_system_state_.fake_boot_control()->SetCurrentSlot(0);
+ EXPECT_TRUE(DoTest(in, test_deadline_file, &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_EQ(1, install_plan.target_slot);
+ string deadline;
+ EXPECT_TRUE(utils::ReadFile(test_deadline_file, &deadline));
+ EXPECT_EQ("some-deadline", deadline);
+ EXPECT_EQ(in.version, install_plan.version);
+ }
+}
+
+TEST_F(OmahaResponseHandlerActionTest, NoUpdatesTest) {
+ OmahaResponse in;
+ in.update_exists = false;
+ InstallPlan install_plan;
+ EXPECT_FALSE(DoTest(in, "", &install_plan));
+ EXPECT_TRUE(install_plan.partitions.empty());
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://test.should/need/hash.checks.signed");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ // Hash checks are always skipped for non-official update URLs.
+ EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_TRUE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForUnofficialUpdateUrl) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://url.normally/needs/hash.checks.signed");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(false));
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_FALSE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
+ HashChecksForOfficialUrlUnofficialBuildTest) {
+ // Official URLs for unofficial builds (dev/test images) don't require hash.
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://url.normally/needs/hash.checks.signed");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_FALSE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForHttpsTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("https://test.should.not/need/hash.checks.signed");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_FALSE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, HashChecksForBothHttpAndHttpsTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("http://test.should.still/need/hash.checks");
+ in.payload_urls.push_back("https://test.should.still/need/hash.checks");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+ EXPECT_CALL(*(fake_system_state_.mock_request_params()),
+ IsUpdateUrlOfficial())
+ .WillRepeatedly(Return(true));
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(in.payload_urls[0], install_plan.download_url);
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_TRUE(install_plan.hash_checks_mandatory);
+ EXPECT_EQ(in.version, install_plan.version);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, ChangeToMoreStableChannelTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("https://MoreStableChannelTest");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHjk";
+ in.size = 15;
+
+ // Create a uniquely named test directory.
+ string test_dir;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "omaha_response_handler_action-test-XXXXXX", &test_dir));
+
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+ kStatefulPartition + "/etc"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_IS_POWERWASH_ALLOWED=true\n"
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+
+ OmahaRequestParams params(&fake_system_state_);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ params.set_root(test_dir);
+ params.Init("1.2.3.4", "", 0);
+ EXPECT_EQ("canary-channel", params.current_channel());
+ EXPECT_EQ("stable-channel", params.target_channel());
+ EXPECT_TRUE(params.to_more_stable_channel());
+ EXPECT_TRUE(params.is_powerwash_allowed());
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_TRUE(install_plan.powerwash_required);
+
+ ASSERT_TRUE(base::DeleteFile(base::FilePath(test_dir), true));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, ChangeToLessStableChannelTest) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("https://LessStableChannelTest");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHjk";
+ in.size = 15;
+
+ // Create a uniquely named test directory.
+ string test_dir;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "omaha_response_handler_action-test-XXXXXX", &test_dir));
+
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir + "/etc"));
+ ASSERT_EQ(0, System(string("mkdir -p ") + test_dir +
+ kStatefulPartition + "/etc"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_TRACK=stable-channel\n"));
+ ASSERT_TRUE(WriteFileString(
+ test_dir + kStatefulPartition + "/etc/lsb-release",
+ "CHROMEOS_RELEASE_TRACK=canary-channel\n"));
+
+ OmahaRequestParams params(&fake_system_state_);
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+ params.set_root(test_dir);
+ params.Init("5.6.7.8", "", 0);
+ EXPECT_EQ("stable-channel", params.current_channel());
+ params.SetTargetChannel("canary-channel", false, nullptr);
+ EXPECT_EQ("canary-channel", params.target_channel());
+ EXPECT_FALSE(params.to_more_stable_channel());
+ EXPECT_FALSE(params.is_powerwash_allowed());
+
+ fake_system_state_.set_request_params(¶ms);
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_FALSE(install_plan.powerwash_required);
+
+ ASSERT_TRUE(base::DeleteFile(base::FilePath(test_dir), true));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, P2PUrlIsUsedAndHashChecksMandatory) {
+ OmahaResponse in;
+ in.update_exists = true;
+ in.version = "a.b.c.d";
+ in.payload_urls.push_back("https://would.not/cause/hash/checks");
+ in.more_info_url = "http://more/info";
+ in.hash = "HASHj+";
+ in.size = 12;
+
+ OmahaRequestParams params(&fake_system_state_);
+ // 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);
+
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ SetUsingP2PForDownloading(true));
+
+ string p2p_url = "http://9.8.7.6/p2p";
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(), GetP2PUrl())
+ .WillRepeatedly(Return(p2p_url));
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ GetUsingP2PForDownloading()).WillRepeatedly(Return(true));
+
+ InstallPlan install_plan;
+ EXPECT_TRUE(DoTest(in, "", &install_plan));
+ EXPECT_EQ(in.hash, install_plan.payload_hash);
+ EXPECT_EQ(install_plan.download_url, p2p_url);
+ EXPECT_TRUE(install_plan.hash_checks_mandatory);
+}
+
+} // namespace chromeos_update_engine
diff --git a/p2p_manager.cc b/p2p_manager.cc
new file mode 100644
index 0000000..658630c
--- /dev/null
+++ b/p2p_manager.cc
@@ -0,0 +1,736 @@
+//
+// 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.
+//
+
+// This provides access to timestamps with nanosecond resolution in
+// struct stat, See NOTES in stat(2) for details.
+#ifndef _BSD_SOURCE
+#define _BSD_SOURCE
+#endif
+
+#include "update_engine/p2p_manager.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/falloc.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_enumerator.h>
+#include <base/files/file_path.h>
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+
+using base::Bind;
+using base::Callback;
+using base::FilePath;
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+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;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The default p2p directory.
+const char kDefaultP2PDir[] = "/var/cache/p2p";
+
+// The p2p xattr used for conveying the final size of a file - see the
+// p2p ddoc for details.
+const char kCrosP2PFileSizeXAttrName[] = "user.cros-p2p-filesize";
+
+} // namespace
+
+// The default P2PManager::Configuration implementation.
+class ConfigurationImpl : public P2PManager::Configuration {
+ public:
+ ConfigurationImpl() {}
+
+ FilePath GetP2PDir() override {
+ return FilePath(kDefaultP2PDir);
+ }
+
+ vector<string> GetInitctlArgs(bool is_start) override {
+ vector<string> args;
+ args.push_back("initctl");
+ args.push_back(is_start ? "start" : "stop");
+ args.push_back("p2p");
+ return args;
+ }
+
+ vector<string> GetP2PClientArgs(const string &file_id,
+ size_t minimum_size) override {
+ vector<string> args;
+ args.push_back("p2p-client");
+ args.push_back(string("--get-url=") + file_id);
+ args.push_back(StringPrintf("--minimum-size=%" PRIuS, minimum_size));
+ return args;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConfigurationImpl);
+};
+
+// The default P2PManager implementation.
+class P2PManagerImpl : public P2PManager {
+ public:
+ P2PManagerImpl(Configuration *configuration,
+ ClockInterface *clock,
+ UpdateManager* update_manager,
+ const string& file_extension,
+ const int num_files_to_keep,
+ const TimeDelta& max_file_age);
+
+ // P2PManager methods.
+ void SetDevicePolicy(const policy::DevicePolicy* device_policy) override;
+ bool IsP2PEnabled() override;
+ bool EnsureP2PRunning() override;
+ bool EnsureP2PNotRunning() override;
+ bool PerformHousekeeping() override;
+ void LookupUrlForFile(const string& file_id,
+ size_t minimum_size,
+ TimeDelta max_time_to_wait,
+ LookupCallback callback) override;
+ bool FileShare(const string& file_id,
+ size_t expected_size) override;
+ FilePath FileGetPath(const string& file_id) override;
+ ssize_t FileGetSize(const string& file_id) override;
+ ssize_t FileGetExpectedSize(const string& file_id) override;
+ bool FileGetVisible(const string& file_id,
+ bool *out_result) override;
+ bool FileMakeVisible(const string& file_id) override;
+ int CountSharedFiles() override;
+
+ private:
+ // Enumeration for specifying visibility.
+ enum Visibility {
+ kVisible,
+ kNonVisible
+ };
+
+ // Returns "." + |file_extension_| + ".p2p" if |visibility| is
+ // |kVisible|. Returns the same concatenated with ".tmp" otherwise.
+ string GetExt(Visibility visibility);
+
+ // Gets the on-disk path for |file_id| depending on if the file
+ // is visible or not.
+ FilePath GetPath(const string& file_id, Visibility visibility);
+
+ // Utility function used by EnsureP2PRunning() and EnsureP2PNotRunning().
+ bool EnsureP2P(bool should_be_running);
+
+ // Utility function to delete a file given by |path| and log the
+ // path as well as |reason|. Returns false on failure.
+ bool DeleteP2PFile(const FilePath& path, const string& reason);
+
+ // Schedules an async request for tracking changes in P2P enabled status.
+ void ScheduleEnabledStatusChange();
+
+ // An async callback used by the above.
+ void OnEnabledStatusChange(EvalStatus status, const bool& result);
+
+ // The device policy being used or null if no policy is being used.
+ const policy::DevicePolicy* device_policy_ = nullptr;
+
+ // Configuration object.
+ unique_ptr<Configuration> configuration_;
+
+ // Object for telling the time.
+ ClockInterface* clock_;
+
+ // A pointer to the global Update Manager.
+ UpdateManager* update_manager_;
+
+ // A short string unique to the application (for example "cros_au")
+ // used to mark a file as being owned by a particular application.
+ const string file_extension_;
+
+ // If non-zero, this number denotes how many files in /var/cache/p2p
+ // owned by the application (cf. |file_extension_|) to keep after
+ // performing housekeeping.
+ const int num_files_to_keep_;
+
+ // If non-zero, files older than this will not be kept after
+ // performing housekeeping.
+ const TimeDelta max_file_age_;
+
+ // The string ".p2p".
+ static const char kP2PExtension[];
+
+ // The string ".tmp".
+ static const char kTmpExtension[];
+
+ // Whether P2P service may be running; initially, we assume it may be.
+ bool may_be_running_ = true;
+
+ // The current known enabled status of the P2P feature (initialized lazily),
+ // and whether an async status check has been scheduled.
+ bool is_enabled_;
+ bool waiting_for_enabled_status_change_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(P2PManagerImpl);
+};
+
+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),
+ file_extension_(file_extension),
+ num_files_to_keep_(num_files_to_keep),
+ max_file_age_(max_file_age) {
+ configuration_.reset(configuration != nullptr ? configuration :
+ new ConfigurationImpl());
+}
+
+void P2PManagerImpl::SetDevicePolicy(
+ const policy::DevicePolicy* device_policy) {
+ device_policy_ = device_policy;
+}
+
+bool P2PManagerImpl::IsP2PEnabled() {
+ if (!waiting_for_enabled_status_change_) {
+ // Get and store an initial value.
+ if (update_manager_->PolicyRequest(&Policy::P2PEnabled, &is_enabled_) ==
+ EvalStatus::kFailed) {
+ is_enabled_ = false;
+ LOG(ERROR) << "Querying P2P enabled status failed, disabling.";
+ }
+
+ // Track future changes (async).
+ ScheduleEnabledStatusChange();
+ }
+
+ return is_enabled_;
+}
+
+bool P2PManagerImpl::EnsureP2P(bool should_be_running) {
+ int return_code = 0;
+ string output;
+
+ 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)) {
+ LOG(ERROR) << "Error spawning " << utils::StringVectorToString(args);
+ return false;
+ }
+
+ // If initctl(8) does not exit normally (exit status other than zero), ensure
+ // that the error message is not benign by scanning stderr; this is a
+ // necessity because initctl does not offer actions such as "start if not
+ // running" or "stop if running".
+ // TODO(zeuthen,chromium:277051): Avoid doing this.
+ if (return_code != 0) {
+ const char *expected_error_message = should_be_running ?
+ "initctl: Job is already running: p2p\n" :
+ "initctl: Unknown instance \n";
+ if (output != expected_error_message)
+ return false;
+ }
+
+ may_be_running_ = should_be_running; // Successful after all.
+ return true;
+}
+
+bool P2PManagerImpl::EnsureP2PRunning() {
+ return EnsureP2P(true);
+}
+
+bool P2PManagerImpl::EnsureP2PNotRunning() {
+ return EnsureP2P(false);
+}
+
+// Returns True if the timestamp in the first pair is greater than the
+// timestamp in the latter. If used with std::sort() this will yield a
+// sequence of elements where newer (high timestamps) elements precede
+// older ones (low timestamps).
+static bool MatchCompareFunc(const pair<FilePath, Time>& a,
+ const pair<FilePath, Time>& b) {
+ return a.second > b.second;
+}
+
+string P2PManagerImpl::GetExt(Visibility visibility) {
+ string ext = string(".") + file_extension_ + kP2PExtension;
+ switch (visibility) {
+ case kVisible:
+ break;
+ case kNonVisible:
+ ext += kTmpExtension;
+ break;
+ // Don't add a default case to let the compiler warn about newly
+ // added enum values.
+ }
+ return ext;
+}
+
+FilePath P2PManagerImpl::GetPath(const string& file_id, Visibility visibility) {
+ return configuration_->GetP2PDir().Append(file_id + GetExt(visibility));
+}
+
+bool P2PManagerImpl::DeleteP2PFile(const FilePath& path,
+ const string& reason) {
+ LOG(INFO) << "Deleting p2p file " << path.value()
+ << " (reason: " << reason << ")";
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting p2p file " << path.value();
+ return false;
+ }
+ return true;
+}
+
+
+bool P2PManagerImpl::PerformHousekeeping() {
+ // Open p2p dir.
+ FilePath p2p_dir = configuration_->GetP2PDir();
+ const string ext_visible = GetExt(kVisible);
+ const string ext_non_visible = GetExt(kNonVisible);
+
+ bool deletion_failed = false;
+ vector<pair<FilePath, Time>> matches;
+
+ base::FileEnumerator dir(p2p_dir, false, base::FileEnumerator::FILES);
+ // Go through all files and collect their mtime.
+ for (FilePath name = dir.Next(); !name.empty(); name = dir.Next()) {
+ if (!(base::EndsWith(name.value(), ext_visible, true) ||
+ base::EndsWith(name.value(), ext_non_visible, true)))
+ continue;
+
+ Time time = dir.GetInfo().GetLastModifiedTime();
+
+ // 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 (!DeleteP2PFile(name, "file too old"))
+ deletion_failed = true;
+ } else {
+ matches.push_back(std::make_pair(name, time));
+ }
+ }
+
+ // If instructed to only keep N files (|max_files_to_keep_ != 0),
+ // sort list of matches, newest (biggest time) to oldest (lowest
+ // time). Then delete starting at element |num_files_to_keep_|.
+ if (num_files_to_keep_ > 0) {
+ std::sort(matches.begin(), matches.end(), MatchCompareFunc);
+ vector<pair<FilePath, Time>>::const_iterator i;
+ for (i = matches.begin() + num_files_to_keep_; i < matches.end(); ++i) {
+ if (!DeleteP2PFile(i->first, "too many files"))
+ deletion_failed = true;
+ }
+ }
+
+ return !deletion_failed;
+}
+
+// Helper class for implementing LookupUrlForFile().
+class LookupData {
+ public:
+ explicit LookupData(P2PManager::LookupCallback callback)
+ : callback_(callback) {}
+
+ ~LookupData() {
+ if (timeout_task_ != MessageLoop::kTaskIdNull)
+ MessageLoop::current()->CancelTask(timeout_task_);
+ if (child_pid_)
+ Subprocess::Get().KillExec(child_pid_);
+ }
+
+ void InitiateLookup(const vector<string>& cmd, TimeDelta timeout) {
+ // NOTE: if we fail early (i.e. in this method), we need to schedule
+ // an idle to report the error. This is because we guarantee that
+ // the callback is always called from the message loop (this
+ // guarantee is useful for testing).
+
+ // We expect to run just "p2p-client" and find it in the path.
+ child_pid_ = Subprocess::Get().ExecFlags(
+ cmd, Subprocess::kSearchPath,
+ Bind(&LookupData::OnLookupDone, base::Unretained(this)));
+
+ if (!child_pid_) {
+ LOG(ERROR) << "Error spawning " << utils::StringVectorToString(cmd);
+ ReportErrorAndDeleteInIdle();
+ return;
+ }
+
+ if (timeout > TimeDelta()) {
+ timeout_task_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ Bind(&LookupData::OnTimeout, base::Unretained(this)),
+ timeout);
+ }
+ }
+
+ private:
+ void ReportErrorAndDeleteInIdle() {
+ MessageLoop::current()->PostTask(FROM_HERE, Bind(
+ &LookupData::OnIdleForReportErrorAndDelete,
+ base::Unretained(this)));
+ }
+
+ void OnIdleForReportErrorAndDelete() {
+ ReportError();
+ delete this;
+ }
+
+ void IssueCallback(const string& url) {
+ if (!callback_.is_null())
+ callback_.Run(url);
+ }
+
+ void ReportError() {
+ if (reported_)
+ return;
+ IssueCallback("");
+ reported_ = true;
+ }
+
+ void ReportSuccess(const string& output) {
+ if (reported_)
+ return;
+ string url = output;
+ size_t newline_pos = url.find('\n');
+ if (newline_pos != string::npos)
+ url.resize(newline_pos);
+
+ // Since p2p-client(1) is constructing this URL itself strictly
+ // speaking there's no need to validate it... but, anyway, can't
+ // hurt.
+ if (url.compare(0, 7, "http://") == 0) {
+ IssueCallback(url);
+ } else {
+ LOG(ERROR) << "p2p URL '" << url << "' does not look right. Ignoring.";
+ ReportError();
+ }
+ reported_ = true;
+ }
+
+ void OnLookupDone(int return_code, const string& output) {
+ child_pid_ = 0;
+ if (return_code != 0) {
+ LOG(INFO) << "Child exited with non-zero exit code "
+ << return_code;
+ ReportError();
+ } else {
+ ReportSuccess(output);
+ }
+ delete this;
+ }
+
+ void OnTimeout() {
+ timeout_task_ = MessageLoop::kTaskIdNull;
+ ReportError();
+ delete this;
+ }
+
+ P2PManager::LookupCallback callback_;
+
+ // The Subprocess tag of the running process. A value of 0 means that the
+ // process is not running.
+ pid_t child_pid_{0};
+
+ // The timeout task_id we are waiting on, if any.
+ MessageLoop::TaskId timeout_task_{MessageLoop::kTaskIdNull};
+
+ bool reported_{false};
+};
+
+void P2PManagerImpl::LookupUrlForFile(const string& file_id,
+ size_t minimum_size,
+ TimeDelta max_time_to_wait,
+ LookupCallback callback) {
+ LookupData *lookup_data = new LookupData(callback);
+ string file_id_with_ext = file_id + "." + file_extension_;
+ vector<string> args = configuration_->GetP2PClientArgs(file_id_with_ext,
+ minimum_size);
+ lookup_data->InitiateLookup(args, max_time_to_wait);
+}
+
+bool P2PManagerImpl::FileShare(const string& file_id,
+ size_t expected_size) {
+ // Check if file already exist.
+ FilePath path = FileGetPath(file_id);
+ if (!path.empty()) {
+ // File exists - double check its expected size though.
+ ssize_t file_expected_size = FileGetExpectedSize(file_id);
+ if (file_expected_size == -1 ||
+ static_cast<size_t>(file_expected_size) != expected_size) {
+ LOG(ERROR) << "Existing p2p file " << path.value()
+ << " with expected_size=" << file_expected_size
+ << " does not match the passed in"
+ << " expected_size=" << expected_size;
+ return false;
+ }
+ return true;
+ }
+
+ // Before creating the file, bail if statvfs(3) indicates that at
+ // least twice the size is not available in P2P_DIR.
+ struct statvfs statvfsbuf;
+ FilePath p2p_dir = configuration_->GetP2PDir();
+ if (statvfs(p2p_dir.value().c_str(), &statvfsbuf) != 0) {
+ PLOG(ERROR) << "Error calling statvfs() for dir " << p2p_dir.value();
+ return false;
+ }
+ size_t free_bytes =
+ static_cast<size_t>(statvfsbuf.f_bsize) * statvfsbuf.f_bavail;
+ if (free_bytes < 2 * expected_size) {
+ // This can easily happen and is worth reporting.
+ LOG(INFO) << "Refusing to allocate p2p file of " << expected_size
+ << " bytes since the directory " << p2p_dir.value()
+ << " only has " << free_bytes
+ << " bytes available and this is less than twice the"
+ << " requested size.";
+ return false;
+ }
+
+ // Okie-dokey looks like enough space is available - create the file.
+ path = GetPath(file_id, kNonVisible);
+ int fd = open(path.value().c_str(), O_CREAT | O_RDWR, 0644);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error creating file with path " << path.value();
+ return false;
+ }
+ ScopedFdCloser fd_closer(&fd);
+
+ // If the final size is known, allocate the file (e.g. reserve disk
+ // space) and set the user.cros-p2p-filesize xattr.
+ if (expected_size != 0) {
+ if (fallocate(fd,
+ FALLOC_FL_KEEP_SIZE, // Keep file size as 0.
+ 0,
+ expected_size) != 0) {
+ if (errno == ENOSYS || errno == EOPNOTSUPP) {
+ // If the filesystem doesn't support the fallocate, keep
+ // going. This is helpful when running unit tests on build
+ // machines with ancient filesystems and/or OSes.
+ PLOG(WARNING) << "Ignoring fallocate(2) failure";
+ } else {
+ // ENOSPC can happen (funky race though, cf. the statvfs() check
+ // above), handle it gracefully, e.g. use logging level INFO.
+ PLOG(INFO) << "Error allocating " << expected_size
+ << " bytes for file " << path.value();
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting file with path " << path.value();
+ }
+ return false;
+ }
+ }
+
+ string decimal_size = std::to_string(expected_size);
+ if (fsetxattr(fd, kCrosP2PFileSizeXAttrName,
+ decimal_size.c_str(), decimal_size.size(), 0) != 0) {
+ PLOG(ERROR) << "Error setting xattr " << path.value();
+ return false;
+ }
+ }
+
+ return true;
+}
+
+FilePath P2PManagerImpl::FileGetPath(const string& file_id) {
+ struct stat statbuf;
+ FilePath path;
+
+ path = GetPath(file_id, kVisible);
+ if (stat(path.value().c_str(), &statbuf) == 0) {
+ return path;
+ }
+
+ path = GetPath(file_id, kNonVisible);
+ if (stat(path.value().c_str(), &statbuf) == 0) {
+ return path;
+ }
+
+ path.clear();
+ return path;
+}
+
+bool P2PManagerImpl::FileGetVisible(const string& file_id,
+ bool *out_result) {
+ FilePath path = FileGetPath(file_id);
+ if (path.empty()) {
+ LOG(ERROR) << "No file for id " << file_id;
+ return false;
+ }
+ if (out_result != nullptr)
+ *out_result = path.MatchesExtension(kP2PExtension);
+ return true;
+}
+
+bool P2PManagerImpl::FileMakeVisible(const string& file_id) {
+ FilePath path = FileGetPath(file_id);
+ if (path.empty()) {
+ LOG(ERROR) << "No file for id " << file_id;
+ return false;
+ }
+
+ // Already visible?
+ if (path.MatchesExtension(kP2PExtension))
+ return true;
+
+ LOG_ASSERT(path.MatchesExtension(kTmpExtension));
+ FilePath new_path = path.RemoveExtension();
+ LOG_ASSERT(new_path.MatchesExtension(kP2PExtension));
+ if (rename(path.value().c_str(), new_path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error renaming " << path.value()
+ << " to " << new_path.value();
+ return false;
+ }
+
+ return true;
+}
+
+ssize_t P2PManagerImpl::FileGetSize(const string& file_id) {
+ FilePath path = FileGetPath(file_id);
+ if (path.empty())
+ return -1;
+
+ return utils::FileSize(path.value());
+}
+
+ssize_t P2PManagerImpl::FileGetExpectedSize(const string& file_id) {
+ FilePath path = FileGetPath(file_id);
+ if (path.empty())
+ return -1;
+
+ char ea_value[64] = { 0 };
+ ssize_t ea_size;
+ ea_size = getxattr(path.value().c_str(), kCrosP2PFileSizeXAttrName,
+ &ea_value, sizeof(ea_value) - 1);
+ if (ea_size == -1) {
+ PLOG(ERROR) << "Error calling getxattr() on file " << path.value();
+ return -1;
+ }
+
+ char* endp = nullptr;
+ long long int val = strtoll(ea_value, &endp, 0); // NOLINT(runtime/int)
+ if (*endp != '\0') {
+ LOG(ERROR) << "Error parsing the value '" << ea_value
+ << "' of the xattr " << kCrosP2PFileSizeXAttrName
+ << " as an integer";
+ return -1;
+ }
+
+ return val;
+}
+
+int P2PManagerImpl::CountSharedFiles() {
+ int num_files = 0;
+
+ FilePath p2p_dir = configuration_->GetP2PDir();
+ const string ext_visible = GetExt(kVisible);
+ const string ext_non_visible = GetExt(kNonVisible);
+
+ base::FileEnumerator dir(p2p_dir, false, base::FileEnumerator::FILES);
+ for (FilePath name = dir.Next(); !name.empty(); name = dir.Next()) {
+ if (base::EndsWith(name.value(), ext_visible, true) ||
+ base::EndsWith(name.value(), ext_non_visible, true))
+ num_files += 1;
+ }
+
+ return num_files;
+}
+
+void P2PManagerImpl::ScheduleEnabledStatusChange() {
+ if (waiting_for_enabled_status_change_)
+ return;
+
+ Callback<void(EvalStatus, const bool&)> callback = Bind(
+ &P2PManagerImpl::OnEnabledStatusChange, base::Unretained(this));
+ update_manager_->AsyncPolicyRequest(callback, &Policy::P2PEnabledChanged,
+ is_enabled_);
+ waiting_for_enabled_status_change_ = true;
+}
+
+void P2PManagerImpl::OnEnabledStatusChange(EvalStatus status,
+ const bool& result) {
+ waiting_for_enabled_status_change_ = false;
+
+ if (status == EvalStatus::kSucceeded) {
+ if (result == is_enabled_) {
+ LOG(WARNING) << "P2P enabled status did not change, which means that it "
+ "is permanent; not scheduling further checks.";
+ waiting_for_enabled_status_change_ = true;
+ return;
+ }
+
+ is_enabled_ = result;
+
+ // If P2P is running but shouldn't be, make sure it isn't.
+ if (may_be_running_ && !is_enabled_ && !EnsureP2PNotRunning()) {
+ LOG(WARNING) << "Failed to stop P2P service.";
+ }
+ } else {
+ LOG(WARNING)
+ << "P2P enabled tracking failed (possibly timed out); retrying.";
+ }
+
+ ScheduleEnabledStatusChange();
+}
+
+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,
+ max_file_age);
+}
+
+} // namespace chromeos_update_engine
diff --git a/p2p_manager.h b/p2p_manager.h
new file mode 100644
index 0000000..4ffab9a
--- /dev/null
+++ b/p2p_manager.h
@@ -0,0 +1,188 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_P2P_MANAGER_H_
+#define UPDATE_ENGINE_P2P_MANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/callback.h>
+#include <base/files/file_path.h>
+#include <base/memory/ref_counted.h>
+#include <base/time/time.h>
+#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"
+
+namespace chromeos_update_engine {
+
+// Interface for sharing and discovering files via p2p.
+class P2PManager {
+ public:
+ // Interface used for P2PManager implementations. The sole reason
+ // for this interface is unit testing.
+ class Configuration {
+ public:
+ virtual ~Configuration() {}
+
+ // Gets the path to the p2p dir being used, e.g. /var/cache/p2p.
+ virtual base::FilePath GetP2PDir() = 0;
+
+ // Gets the argument vector for starting (if |is_start| is True)
+ // resp. stopping (if |is_start| is False) the p2p service
+ // e.g. ["initctl", "start", "p2p"] or ["initctl", "stop", "p2p"].
+ virtual std::vector<std::string> GetInitctlArgs(bool is_start) = 0;
+
+ // Gets the argument vector for invoking p2p-client, e.g.
+ // "p2p-client --get-url=file_id_we_want --minimum-size=42123".
+ virtual std::vector<std::string> GetP2PClientArgs(
+ const std::string& file_id, size_t minimum_size) = 0;
+ };
+
+ virtual ~P2PManager() {}
+
+ // The type for the callback used in LookupUrlForFile().
+ // If the lookup failed, |url| is empty.
+ typedef base::Callback<void(const std::string& url)> LookupCallback;
+
+ // Use the device policy specified by |device_policy|. If this is
+ // null, then no device policy is used.
+ virtual void SetDevicePolicy(const policy::DevicePolicy* device_policy) = 0;
+
+ // Returns true iff P2P is currently allowed for use on this device. This
+ // value is determined and maintained by the Update Manager.
+ virtual bool IsP2PEnabled() = 0;
+
+ // Ensures that the p2p subsystem is running (e.g. starts it if it's
+ // not already running) and blocks until this is so. Returns false
+ // if an error occurred.
+ virtual bool EnsureP2PRunning() = 0;
+
+ // Ensures that the p2p subsystem is not running (e.g. stops it if
+ // it's running) and blocks until this is so. Returns false if an
+ // error occurred.
+ virtual bool EnsureP2PNotRunning() = 0;
+
+ // Cleans up files in /var/cache/p2p owned by this application as
+ // per the |file_extension| and |num_files_to_keep| values passed
+ // when the object was constructed. This may be called even if
+ // the p2p subsystem is not running.
+ virtual bool PerformHousekeeping() = 0;
+
+ // Asynchronously finds a peer that serves the file identified by
+ // |file_id|. If |minimum_size| is non-zero, will find a peer that
+ // has at least that many bytes. When the result is ready |callback|
+ // is called from the current message loop.
+ //
+ // This operation may take a very long time to complete because part
+ // of the p2p protocol involves waiting for the LAN-wide sum of all
+ // num-connections to drop below a given threshold. However, if
+ // |max_time_to_wait| is non-zero, the operation is guaranteed to
+ // not exceed this duration.
+ //
+ // If the file is not available on the LAN (or if mDNS/DNS-SD is
+ // filtered), this is guaranteed to not take longer than 5 seconds.
+ virtual void LookupUrlForFile(const std::string& file_id,
+ size_t minimum_size,
+ base::TimeDelta max_time_to_wait,
+ LookupCallback callback) = 0;
+
+ // Shares a file identified by |file_id| in the directory
+ // /var/cache/p2p. Initially the file will not be visible, that is,
+ // it will have a .tmp extension and not be shared via p2p. Use the
+ // FileMakeVisible() method to change this.
+ //
+ // If you know the final size of the file, pass it in the
+ // |expected_size| parameter. Otherwise pass zero. If non-zero, the
+ // amount of free space in /var/cache/p2p is checked and if there is
+ // less than twice the amount of space available, this method
+ // fails. Additionally, disk space will be reserved via fallocate(2)
+ // and |expected_size| is written to the user.cros-p2p-filesize
+ // xattr of the created file.
+ //
+ // If the file already exists, true is returned. Any on-disk xattr
+ // is not updated.
+ virtual bool FileShare(const std::string& file_id,
+ size_t expected_size) = 0;
+
+ // Gets a fully qualified path for the file identified by |file_id|.
+ // If the file has not been shared already using the FileShare()
+ // method, an empty base::FilePath is returned - use FilePath::empty() to
+ // find out.
+ virtual base::FilePath FileGetPath(const std::string& file_id) = 0;
+
+ // Gets the actual size of the file identified by |file_id|. This is
+ // equivalent to reading the value of the st_size field of the
+ // struct stat on the file given by FileGetPath(). Returns -1 if an
+ // error occurs.
+ //
+ // For a file just created with FileShare() this will return 0.
+ virtual ssize_t FileGetSize(const std::string& file_id) = 0;
+
+ // Gets the expected size of the file identified by |file_id|. This
+ // is equivalent to reading the value of the user.cros-p2p-filesize
+ // xattr on the file given by FileGetPath(). Returns -1 if an error
+ // occurs.
+ //
+ // For a file just created with FileShare() this will return the
+ // value of the |expected_size| parameter passed to that method.
+ virtual ssize_t FileGetExpectedSize(const std::string& file_id) = 0;
+
+ // Gets whether the file identified by |file_id| is publicly
+ // visible. If |out_result| is not null, the result is returned
+ // there. Returns false if an error occurs.
+ virtual bool FileGetVisible(const std::string& file_id,
+ bool *out_result) = 0;
+
+ // Makes the file identified by |file_id| publicly visible
+ // (e.g. removes the .tmp extension). If the file is already
+ // visible, this method does nothing. Returns False if
+ // the method fails or there is no file for |file_id|.
+ virtual bool FileMakeVisible(const std::string& file_id) = 0;
+
+ // Counts the number of shared files used by this application
+ // (cf. the |file_extension parameter|. Returns -1 if an error
+ // occurred.
+ virtual int CountSharedFiles() = 0;
+
+ // Creates a suitable P2PManager instance and initializes the object
+ // so it's ready for use. The |file_extension| parameter is used to
+ // identify your application, use e.g. "cros_au". If
+ // |configuration| is non-null, the P2PManager will take ownership
+ // of the Configuration object and use it (hence, it must be
+ // heap-allocated).
+ //
+ // The |num_files_to_keep| parameter specifies how many files to
+ // keep after performing housekeeping (cf. the PerformHousekeeping()
+ // method) - pass zero to allow infinitely many files. The
+ // |max_file_age| parameter specifies the maximum file age after
+ // 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,
+ const base::TimeDelta& max_file_age);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_P2P_MANAGER_H_
diff --git a/p2p_manager_unittest.cc b/p2p_manager_unittest.cc
new file mode 100644
index 0000000..463c0e2
--- /dev/null
+++ b/p2p_manager_unittest.cc
@@ -0,0 +1,515 @@
+//
+// 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/p2p_manager.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/callback.h>
+#include <base/files/file_util.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/asynchronous_signal_handler.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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+#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/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/fake_p2p_manager_configuration.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+#include "update_engine/update_manager/mock_policy.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+// Test fixture that sets up a testing configuration (with e.g. a
+// temporary p2p dir) for P2PManager and cleans up when the test is
+// done.
+class P2PManagerTest : public testing::Test {
+ protected:
+ P2PManagerTest() : fake_um_(&fake_clock_) {}
+ ~P2PManagerTest() override {}
+
+ // Derived from testing::Test.
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ async_signal_handler_.Init();
+ subprocess_.Init(&async_signal_handler_);
+ test_conf_ = new FakeP2PManagerConfiguration();
+
+ // 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_);
+ 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)));
+ }
+
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop loop_{&base_loop_};
+ 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_;
+
+ unique_ptr<P2PManager> manager_;
+};
+
+
+// Check that IsP2PEnabled() polls the policy correctly, with the value not
+// changing between calls.
+TEST_F(P2PManagerTest, P2PEnabledInitAndNotChanged) {
+ EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
+
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+ brillo::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+}
+
+// Check that IsP2PEnabled() polls the policy correctly, with the value changing
+// between calls.
+TEST_F(P2PManagerTest, P2PEnabledInitAndChanged) {
+ EXPECT_CALL(*mock_policy_, P2PEnabled(_, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<3>(true),
+ Return(chromeos_update_manager::EvalStatus::kSucceeded)));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, true));
+ EXPECT_CALL(*mock_policy_, P2PEnabledChanged(_, _, _, _, false));
+
+ EXPECT_TRUE(manager_->IsP2PEnabled());
+ brillo::MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(manager_->IsP2PEnabled());
+}
+
+// Check that we keep the $N newest files with the .$EXT.p2p extension.
+TEST_F(P2PManagerTest, HousekeepingCountLimit) {
+ // Specifically pass 0 for |max_file_age| to allow files of any age. 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", 3,
+ TimeDelta() /* max_file_age */));
+ EXPECT_EQ(manager_->CountSharedFiles(), 0);
+
+ base::Time start_time = base::Time::FromDoubleT(1246996800.);
+ // Generate files with different timestamps matching our pattern and generate
+ // other files not matching the pattern.
+ for (int n = 0; n < 5; n++) {
+ base::FilePath path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+ "file_%d.cros_au.p2p", n));
+ base::Time file_time = start_time + TimeDelta::FromMinutes(n);
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
+
+ path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+ "file_%d.OTHER.p2p", n));
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_time, file_time));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 5);
+
+ EXPECT_TRUE(manager_->PerformHousekeeping());
+
+ // At this point - after HouseKeeping - we should only have
+ // eight files left.
+ for (int n = 0; n < 5; n++) {
+ string file_name;
+ bool expect;
+
+ expect = (n >= 2);
+ file_name = base::StringPrintf(
+ "%s/file_%d.cros_au.p2p",
+ test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
+
+ file_name = base::StringPrintf(
+ "%s/file_%d.OTHER.p2p",
+ test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_TRUE(utils::FileExists(file_name.c_str()));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 3);
+}
+
+// Check that we keep files with the .$EXT.p2p extension not older
+// than some specificed age (5 days, in this test).
+TEST_F(P2PManagerTest, HousekeepingAgeLimit) {
+ // We set the cutoff time to be 1 billion seconds (01:46:40 UTC on 9
+ // September 2001 - arbitrary number, but constant to avoid test
+ // flakiness) since the epoch and then we put two files before that
+ // date and three files after.
+ base::Time cutoff_time = base::Time::FromTimeT(1000000000);
+ TimeDelta age_limit = TimeDelta::FromDays(5);
+
+ // Set the clock just so files with a timestamp before |cutoff_time|
+ // will be deleted at housekeeping.
+ 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 */, age_limit));
+ EXPECT_EQ(manager_->CountSharedFiles(), 0);
+
+ // Generate files with different timestamps matching our pattern and generate
+ // other files not matching the pattern.
+ for (int n = 0; n < 5; n++) {
+ base::FilePath path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+ "file_%d.cros_au.p2p", n));
+
+ // With five files and aiming for two of them to be before
+ // |cutoff_time|, we distribute it like this:
+ //
+ // -------- 0 -------- 1 -------- 2 -------- 3 -------- 4 --------
+ // |
+ // cutoff_time
+ //
+ base::Time file_date = cutoff_time + (n - 2) * TimeDelta::FromDays(1)
+ + TimeDelta::FromHours(12);
+
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
+
+ path = test_conf_->GetP2PDir().Append(base::StringPrintf(
+ "file_%d.OTHER.p2p", n));
+ EXPECT_EQ(0, base::WriteFile(path, nullptr, 0));
+ EXPECT_TRUE(base::TouchFile(path, file_date, file_date));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 5);
+
+ EXPECT_TRUE(manager_->PerformHousekeeping());
+
+ // At this point - after HouseKeeping - we should only have
+ // eight files left.
+ for (int n = 0; n < 5; n++) {
+ string file_name;
+ bool expect;
+
+ expect = (n >= 2);
+ file_name = base::StringPrintf(
+ "%s/file_%d.cros_au.p2p",
+ test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_EQ(expect, utils::FileExists(file_name.c_str()));
+
+ file_name = base::StringPrintf(
+ "%s/file_%d.OTHER.p2p",
+ test_conf_->GetP2PDir().value().c_str(), n);
+ EXPECT_TRUE(utils::FileExists(file_name.c_str()));
+ }
+ // CountSharedFiles() only counts 'cros_au' files.
+ EXPECT_EQ(manager_->CountSharedFiles(), 3);
+}
+
+static bool CheckP2PFile(const string& p2p_dir, const string& file_name,
+ ssize_t expected_size, ssize_t expected_size_xattr) {
+ string path = p2p_dir + "/" + file_name;
+ char ea_value[64] = { 0 };
+ ssize_t ea_size;
+
+ off_t p2p_size = utils::FileSize(path);
+ if (p2p_size < 0) {
+ LOG(ERROR) << "File " << path << " does not exist";
+ return false;
+ }
+
+ if (expected_size != 0) {
+ if (p2p_size != expected_size) {
+ LOG(ERROR) << "Expected size " << expected_size
+ << " but size was " << p2p_size;
+ return false;
+ }
+ }
+
+ if (expected_size_xattr == 0) {
+ ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize",
+ &ea_value, sizeof ea_value - 1);
+ if (ea_size == -1 && errno == ENODATA) {
+ // This is valid behavior as we support files without the xattr set.
+ } else {
+ PLOG(ERROR) << "getxattr() didn't fail with ENODATA as expected, "
+ << "ea_size=" << ea_size << ", errno=" << errno;
+ return false;
+ }
+ } else {
+ ea_size = getxattr(path.c_str(), "user.cros-p2p-filesize",
+ &ea_value, sizeof ea_value - 1);
+ if (ea_size < 0) {
+ LOG(ERROR) << "Error getting xattr attribute";
+ return false;
+ }
+ char* endp = nullptr;
+ long long int val = strtoll(ea_value, &endp, 0); // NOLINT(runtime/int)
+ if (endp == nullptr || *endp != '\0') {
+ LOG(ERROR) << "Error parsing xattr '" << ea_value
+ << "' as an integer";
+ return false;
+ }
+ if (val != expected_size_xattr) {
+ LOG(ERROR) << "Expected xattr size " << expected_size_xattr
+ << " but size was " << val;
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool CreateP2PFile(string p2p_dir, string file_name,
+ size_t size, size_t size_xattr) {
+ string path = p2p_dir + "/" + file_name;
+
+ int fd = open(path.c_str(), O_CREAT|O_RDWR, 0644);
+ if (fd == -1) {
+ PLOG(ERROR) << "Error creating file with path " << path;
+ return false;
+ }
+ if (ftruncate(fd, size) != 0) {
+ PLOG(ERROR) << "Error truncating " << path << " to size " << size;
+ close(fd);
+ return false;
+ }
+
+ if (size_xattr != 0) {
+ string decimal_size = std::to_string(size_xattr);
+ if (fsetxattr(fd, "user.cros-p2p-filesize",
+ decimal_size.c_str(), decimal_size.size(), 0) != 0) {
+ PLOG(ERROR) << "Error setting xattr on " << path;
+ close(fd);
+ return false;
+ }
+ }
+
+ close(fd);
+ return true;
+}
+
+// Check that sharing a *new* file works.
+TEST_F(P2PManagerTest, ShareFile) {
+ if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+ const int kP2PTestFileSize = 1000 * 1000; // 1 MB
+
+ EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p.tmp", 0, kP2PTestFileSize));
+
+ // Sharing it again - with the same expected size - should return true
+ EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
+
+ // ... but if we use the wrong size, it should fail
+ EXPECT_FALSE(manager_->FileShare("foo", kP2PTestFileSize + 1));
+}
+
+// Check that making a shared file visible, does what is expected.
+TEST_F(P2PManagerTest, MakeFileVisible) {
+ if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+ const int kP2PTestFileSize = 1000 * 1000; // 1 MB
+
+ // First, check that it's not visible.
+ manager_->FileShare("foo", kP2PTestFileSize);
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p.tmp"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p.tmp", 0, kP2PTestFileSize));
+ // Make the file visible and check that it changed its name. Do it
+ // twice to check that FileMakeVisible() is idempotent.
+ for (int n = 0; n < 2; n++) {
+ manager_->FileMakeVisible("foo");
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+ EXPECT_TRUE(CheckP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p", 0, kP2PTestFileSize));
+ }
+}
+
+// Check that we return the right values for existing files in P2P_DIR.
+TEST_F(P2PManagerTest, ExistingFiles) {
+ if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ bool visible;
+
+ // Check that errors are returned if the file does not exist
+ EXPECT_EQ(manager_->FileGetPath("foo"), base::FilePath());
+ EXPECT_EQ(manager_->FileGetSize("foo"), -1);
+ EXPECT_EQ(manager_->FileGetExpectedSize("foo"), -1);
+ EXPECT_FALSE(manager_->FileGetVisible("foo", nullptr));
+ // ... then create the file ...
+ EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(),
+ "foo.cros_au.p2p", 42, 43));
+ // ... and then check that the expected values are returned
+ EXPECT_EQ(manager_->FileGetPath("foo"),
+ test_conf_->GetP2PDir().Append("foo.cros_au.p2p"));
+ EXPECT_EQ(manager_->FileGetSize("foo"), 42);
+ EXPECT_EQ(manager_->FileGetExpectedSize("foo"), 43);
+ EXPECT_TRUE(manager_->FileGetVisible("foo", &visible));
+ EXPECT_TRUE(visible);
+
+ // One more time, this time with a .tmp variant. First ensure it errors out..
+ EXPECT_EQ(manager_->FileGetPath("bar"), base::FilePath());
+ EXPECT_EQ(manager_->FileGetSize("bar"), -1);
+ EXPECT_EQ(manager_->FileGetExpectedSize("bar"), -1);
+ EXPECT_FALSE(manager_->FileGetVisible("bar", nullptr));
+ // ... then create the file ...
+ EXPECT_TRUE(CreateP2PFile(test_conf_->GetP2PDir().value(),
+ "bar.cros_au.p2p.tmp", 44, 45));
+ // ... and then check that the expected values are returned
+ EXPECT_EQ(manager_->FileGetPath("bar"),
+ test_conf_->GetP2PDir().Append("bar.cros_au.p2p.tmp"));
+ EXPECT_EQ(manager_->FileGetSize("bar"), 44);
+ EXPECT_EQ(manager_->FileGetExpectedSize("bar"), 45);
+ EXPECT_TRUE(manager_->FileGetVisible("bar", &visible));
+ EXPECT_FALSE(visible);
+}
+
+// This is a little bit ugly but short of mocking a 'p2p' service this
+// will have to do. E.g. we essentially simulate the various
+// behaviours of initctl(8) that we rely on.
+TEST_F(P2PManagerTest, StartP2P) {
+ // Check that we can start the service
+ test_conf_->SetInitctlStartCommand({"true"});
+ EXPECT_TRUE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand({"false"});
+ EXPECT_FALSE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand({
+ "sh", "-c", "echo \"initctl: Job is already running: p2p\" >&2; false"});
+ EXPECT_TRUE(manager_->EnsureP2PRunning());
+ test_conf_->SetInitctlStartCommand({
+ "sh", "-c", "echo something else >&2; false"});
+ EXPECT_FALSE(manager_->EnsureP2PRunning());
+}
+
+// Same comment as for StartP2P
+TEST_F(P2PManagerTest, StopP2P) {
+ // Check that we can start the service
+ test_conf_->SetInitctlStopCommand({"true"});
+ EXPECT_TRUE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand({"false"});
+ EXPECT_FALSE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand({
+ "sh", "-c", "echo \"initctl: Unknown instance \" >&2; false"});
+ EXPECT_TRUE(manager_->EnsureP2PNotRunning());
+ test_conf_->SetInitctlStopCommand({
+ "sh", "-c", "echo something else >&2; false"});
+ EXPECT_FALSE(manager_->EnsureP2PNotRunning());
+}
+
+static void ExpectUrl(const string& expected_url,
+ const string& url) {
+ EXPECT_EQ(url, expected_url);
+ MessageLoop::current()->BreakLoop();
+}
+
+// Like StartP2P, we're mocking the different results that p2p-client
+// can return. It's not pretty but it works.
+TEST_F(P2PManagerTest, LookupURL) {
+ // Emulate p2p-client returning valid URL with "fooX", 42 and "cros_au"
+ // being propagated in the right places.
+ test_conf_->SetP2PClientCommand({
+ "echo", "http://1.2.3.4/{file_id}_{minsize}"});
+ manager_->LookupUrlForFile("fooX", 42, TimeDelta(),
+ base::Bind(ExpectUrl,
+ "http://1.2.3.4/fooX.cros_au_42"));
+ loop_.Run();
+
+ // Emulate p2p-client returning invalid URL.
+ test_conf_->SetP2PClientCommand({"echo", "not_a_valid_url"});
+ manager_->LookupUrlForFile("foobar", 42, TimeDelta(),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client conveying failure.
+ test_conf_->SetP2PClientCommand({"false"});
+ manager_->LookupUrlForFile("foobar", 42, TimeDelta(),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client not existing.
+ test_conf_->SetP2PClientCommand({"/path/to/non/existent/helper/program"});
+ manager_->LookupUrlForFile("foobar", 42,
+ TimeDelta(),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client crashing.
+ test_conf_->SetP2PClientCommand({"sh", "-c", "kill -SEGV $$"});
+ manager_->LookupUrlForFile("foobar", 42, TimeDelta(),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+
+ // Emulate p2p-client exceeding its timeout.
+ test_conf_->SetP2PClientCommand({
+ "sh", "-c", "echo http://1.2.3.4/; sleep 2"});
+ manager_->LookupUrlForFile("foobar", 42, TimeDelta::FromMilliseconds(500),
+ base::Bind(ExpectUrl, ""));
+ loop_.Run();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/bzip_extent_writer.cc b/payload_consumer/bzip_extent_writer.cc
new file mode 100644
index 0000000..0fcc8ba
--- /dev/null
+++ b/payload_consumer/bzip_extent_writer.cc
@@ -0,0 +1,91 @@
+//
+// 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_consumer/bzip_extent_writer.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const brillo::Blob::size_type kOutputBufferLength = 16 * 1024;
+}
+
+bool BzipExtentWriter::Init(FileDescriptorPtr fd,
+ const vector<Extent>& extents,
+ uint32_t block_size) {
+ // Init bzip2 stream
+ int rc = BZ2_bzDecompressInit(&stream_,
+ 0, // verbosity. (0 == silent)
+ 0); // 0 = faster algo, more memory
+
+ TEST_AND_RETURN_FALSE(rc == BZ_OK);
+
+ return next_->Init(fd, extents, block_size);
+}
+
+bool BzipExtentWriter::Write(const void* bytes, size_t count) {
+ brillo::Blob output_buffer(kOutputBufferLength);
+
+ // Copy the input data into |input_buffer_| only if |input_buffer_| already
+ // contains unconsumed data. Otherwise, process the data directly from the
+ // source.
+ const uint8_t* input = reinterpret_cast<const uint8_t*>(bytes);
+ const uint8_t* input_end = input + count;
+ if (!input_buffer_.empty()) {
+ input_buffer_.insert(input_buffer_.end(), input, input_end);
+ input = input_buffer_.data();
+ input_end = input + input_buffer_.size();
+ }
+ stream_.next_in = reinterpret_cast<char*>(const_cast<uint8_t*>(input));
+ stream_.avail_in = input_end - input;
+
+ for (;;) {
+ stream_.next_out = reinterpret_cast<char*>(output_buffer.data());
+ stream_.avail_out = output_buffer.size();
+
+ int rc = BZ2_bzDecompress(&stream_);
+ TEST_AND_RETURN_FALSE(rc == BZ_OK || rc == BZ_STREAM_END);
+
+ if (stream_.avail_out == output_buffer.size())
+ break; // got no new bytes
+
+ TEST_AND_RETURN_FALSE(
+ next_->Write(output_buffer.data(),
+ output_buffer.size() - stream_.avail_out));
+
+ if (rc == BZ_STREAM_END)
+ CHECK_EQ(stream_.avail_in, 0u);
+ if (stream_.avail_in == 0)
+ break; // no more input to process
+ }
+
+ // Store unconsumed data (if any) in |input_buffer_|.
+ if (stream_.avail_in || !input_buffer_.empty()) {
+ brillo::Blob new_input_buffer(input_end - stream_.avail_in, input_end);
+ new_input_buffer.swap(input_buffer_);
+ }
+
+ return true;
+}
+
+bool BzipExtentWriter::EndImpl() {
+ TEST_AND_RETURN_FALSE(input_buffer_.empty());
+ TEST_AND_RETURN_FALSE(BZ2_bzDecompressEnd(&stream_) == BZ_OK);
+ return next_->End();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/bzip_extent_writer.h b/payload_consumer/bzip_extent_writer.h
new file mode 100644
index 0000000..0ad542e
--- /dev/null
+++ b/payload_consumer/bzip_extent_writer.h
@@ -0,0 +1,57 @@
+//
+// 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_CONSUMER_BZIP_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_BZIP_EXTENT_WRITER_H_
+
+#include <bzlib.h>
+#include <memory>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+
+// BzipExtentWriter is a concrete ExtentWriter subclass that bzip-decompresses
+// what it's given in Write. It passes the decompressed data to an underlying
+// ExtentWriter.
+
+namespace chromeos_update_engine {
+
+class BzipExtentWriter : public ExtentWriter {
+ public:
+ explicit BzipExtentWriter(std::unique_ptr<ExtentWriter> next)
+ : next_(std::move(next)) {
+ memset(&stream_, 0, sizeof(stream_));
+ }
+ ~BzipExtentWriter() override = default;
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) override;
+ bool Write(const void* bytes, size_t count) override;
+ bool EndImpl() override;
+
+ private:
+ std::unique_ptr<ExtentWriter> next_; // The underlying ExtentWriter.
+ bz_stream stream_; // the libbz2 stream
+ brillo::Blob input_buffer_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_BZIP_EXTENT_WRITER_H_
diff --git a/payload_consumer/bzip_extent_writer_unittest.cc b/payload_consumer/bzip_extent_writer_unittest.cc
new file mode 100644
index 0000000..a52a286
--- /dev/null
+++ b/payload_consumer/bzip_extent_writer_unittest.cc
@@ -0,0 +1,145 @@
+//
+// 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_consumer/bzip_extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <brillo/make_unique_ptr.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const char kPathTemplate[] = "./BzipExtentWriterTest-file.XXXXXX";
+const uint32_t kBlockSize = 4096;
+}
+
+class BzipExtentWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ memcpy(path_, kPathTemplate, sizeof(kPathTemplate));
+ fd_.reset(new EintrSafeFileDescriptor);
+ int fd = mkstemp(path_);
+ ASSERT_TRUE(fd_->Open(path_, O_RDWR, 0600));
+ close(fd);
+ }
+ void TearDown() override {
+ fd_->Close();
+ unlink(path_);
+ }
+ void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+ void TestZeroPad(bool aligned_size);
+
+ FileDescriptorPtr fd_;
+ char path_[sizeof(kPathTemplate)];
+};
+
+TEST_F(BzipExtentWriterTest, SimpleTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ // 'echo test | bzip2 | hexdump' yields:
+ static const char test_uncompressed[] = "test\n";
+ static const uint8_t test[] = {
+ 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xcc, 0xc3,
+ 0x71, 0xd4, 0x00, 0x00, 0x02, 0x41, 0x80, 0x00, 0x10, 0x02, 0x00, 0x0c,
+ 0x00, 0x20, 0x00, 0x21, 0x9a, 0x68, 0x33, 0x4d, 0x19, 0x97, 0x8b, 0xb9,
+ 0x22, 0x9c, 0x28, 0x48, 0x66, 0x61, 0xb8, 0xea, 0x00,
+ };
+
+ BzipExtentWriter bzip_writer(
+ brillo::make_unique_ptr(new DirectExtentWriter()));
+ EXPECT_TRUE(bzip_writer.Init(fd_, extents, kBlockSize));
+ EXPECT_TRUE(bzip_writer.Write(test, sizeof(test)));
+ EXPECT_TRUE(bzip_writer.End());
+
+ brillo::Blob buf;
+ EXPECT_TRUE(utils::ReadFile(path_, &buf));
+ EXPECT_EQ(strlen(test_uncompressed), buf.size());
+ EXPECT_EQ(string(buf.begin(), buf.end()), string(test_uncompressed));
+}
+
+TEST_F(BzipExtentWriterTest, ChunkedTest) {
+ const brillo::Blob::size_type kDecompressedLength = 2048 * 1024; // 2 MiB
+ string decompressed_path;
+ ASSERT_TRUE(utils::MakeTempFile("BzipExtentWriterTest-decompressed-XXXXXX",
+ &decompressed_path, nullptr));
+ string compressed_path;
+ ASSERT_TRUE(utils::MakeTempFile("BzipExtentWriterTest-compressed-XXXXXX",
+ &compressed_path, nullptr));
+ const size_t kChunkSize = 3;
+
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(0);
+ extent.set_num_blocks(kDecompressedLength / kBlockSize + 1);
+ extents.push_back(extent);
+
+ brillo::Blob decompressed_data(kDecompressedLength);
+ test_utils::FillWithData(&decompressed_data);
+
+ EXPECT_TRUE(test_utils::WriteFileVector(
+ decompressed_path, decompressed_data));
+
+ EXPECT_EQ(0, test_utils::System(
+ string("cat ") + decompressed_path + "|bzip2>" + compressed_path));
+
+ brillo::Blob compressed_data;
+ EXPECT_TRUE(utils::ReadFile(compressed_path, &compressed_data));
+
+ BzipExtentWriter bzip_writer(
+ brillo::make_unique_ptr(new DirectExtentWriter()));
+ EXPECT_TRUE(bzip_writer.Init(fd_, extents, kBlockSize));
+
+ brillo::Blob original_compressed_data = compressed_data;
+ for (brillo::Blob::size_type i = 0; i < compressed_data.size();
+ i += kChunkSize) {
+ size_t this_chunk_size = min(kChunkSize, compressed_data.size() - i);
+ EXPECT_TRUE(bzip_writer.Write(&compressed_data[i], this_chunk_size));
+ }
+ EXPECT_TRUE(bzip_writer.End());
+
+ // Check that the const input has not been clobbered.
+ test_utils::ExpectVectorsEq(original_compressed_data, compressed_data);
+
+ brillo::Blob output;
+ EXPECT_TRUE(utils::ReadFile(path_, &output));
+ EXPECT_EQ(kDecompressedLength, output.size());
+ test_utils::ExpectVectorsEq(decompressed_data, output);
+
+ unlink(decompressed_path.c_str());
+ unlink(compressed_path.c_str());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
new file mode 100644
index 0000000..6261cb1
--- /dev/null
+++ b/payload_consumer/delta_performer.cc
@@ -0,0 +1,1794 @@
+//
+// 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_consumer/delta_performer.h"
+
+#include <endian.h>
+#include <errno.h>
+#include <linux/fs.h>
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/data_encoding.h>
+#include <brillo/make_unique_ptr.h>
+#include <google/protobuf/repeated_field.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/subprocess.h"
+#include "update_engine/common/terminator.h"
+#include "update_engine/payload_consumer/bzip_extent_writer.h"
+#include "update_engine/payload_consumer/download_action.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+#if USE_MTD
+#include "update_engine/payload_consumer/mtd_file_descriptor.h"
+#endif
+#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"
+
+using google::protobuf::RepeatedPtrField;
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const uint64_t DeltaPerformer::kDeltaVersionOffset = sizeof(kDeltaMagic);
+const uint64_t DeltaPerformer::kDeltaVersionSize = 8;
+const uint64_t DeltaPerformer::kDeltaManifestSizeOffset =
+ kDeltaVersionOffset + kDeltaVersionSize;
+const uint64_t DeltaPerformer::kDeltaManifestSizeSize = 8;
+const uint64_t DeltaPerformer::kDeltaMetadataSignatureSizeSize = 4;
+const uint64_t DeltaPerformer::kMaxPayloadHeaderSize = 24;
+const uint64_t DeltaPerformer::kSupportedMajorPayloadVersion = 2;
+const uint32_t DeltaPerformer::kSupportedMinorPayloadVersion = 3;
+
+const unsigned DeltaPerformer::kProgressLogMaxChunks = 10;
+const unsigned DeltaPerformer::kProgressLogTimeoutSeconds = 30;
+const unsigned DeltaPerformer::kProgressDownloadWeight = 50;
+const unsigned DeltaPerformer::kProgressOperationsWeight = 50;
+
+namespace {
+const int kUpdateStateOperationInvalid = -1;
+const int kMaxResumedUpdateFailures = 10;
+#if USE_MTD
+const int kUbiVolumeAttachTimeout = 5 * 60;
+#endif
+
+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, int* err) {
+ FileDescriptorPtr fd = CreateFileDescriptor(path);
+#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;
+}
+} // namespace
+
+
+// Computes the ratio of |part| and |total|, scaled to |norm|, using integer
+// arithmetic.
+static uint64_t IntRatio(uint64_t part, uint64_t total, uint64_t norm) {
+ return part * norm / total;
+}
+
+void DeltaPerformer::LogProgress(const char* message_prefix) {
+ // Format operations total count and percentage.
+ string total_operations_str("?");
+ string completed_percentage_str("");
+ if (num_total_operations_) {
+ total_operations_str = std::to_string(num_total_operations_);
+ // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
+ completed_percentage_str =
+ base::StringPrintf(" (%" PRIu64 "%%)",
+ IntRatio(next_operation_num_, num_total_operations_,
+ 100));
+ }
+
+ // Format download total count and percentage.
+ size_t payload_size = install_plan_->payload_size;
+ string payload_size_str("?");
+ string downloaded_percentage_str("");
+ if (payload_size) {
+ payload_size_str = std::to_string(payload_size);
+ // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
+ downloaded_percentage_str =
+ base::StringPrintf(" (%" PRIu64 "%%)",
+ IntRatio(total_bytes_received_, payload_size, 100));
+ }
+
+ LOG(INFO) << (message_prefix ? message_prefix : "") << next_operation_num_
+ << "/" << total_operations_str << " operations"
+ << completed_percentage_str << ", " << total_bytes_received_
+ << "/" << payload_size_str << " bytes downloaded"
+ << downloaded_percentage_str << ", overall progress "
+ << overall_progress_ << "%";
+}
+
+void DeltaPerformer::UpdateOverallProgress(bool force_log,
+ const char* message_prefix) {
+ // Compute our download and overall progress.
+ unsigned new_overall_progress = 0;
+ COMPILE_ASSERT(kProgressDownloadWeight + kProgressOperationsWeight == 100,
+ progress_weight_dont_add_up);
+ // Only consider download progress if its total size is known; otherwise
+ // adjust the operations weight to compensate for the absence of download
+ // progress. Also, make sure to cap the download portion at
+ // kProgressDownloadWeight, in case we end up downloading more than we
+ // initially expected (this indicates a problem, but could generally happen).
+ // TODO(garnold) the correction of operations weight when we do not have the
+ // total payload size, as well as the conditional guard below, should both be
+ // eliminated once we ensure that the payload_size in the install plan is
+ // always given and is non-zero. This currently isn't the case during unit
+ // tests (see chromium-os:37969).
+ size_t payload_size = install_plan_->payload_size;
+ unsigned actual_operations_weight = kProgressOperationsWeight;
+ if (payload_size)
+ new_overall_progress += min(
+ static_cast<unsigned>(IntRatio(total_bytes_received_, payload_size,
+ kProgressDownloadWeight)),
+ kProgressDownloadWeight);
+ else
+ actual_operations_weight += kProgressDownloadWeight;
+
+ // Only add completed operations if their total number is known; we definitely
+ // expect an update to have at least one operation, so the expectation is that
+ // this will eventually reach |actual_operations_weight|.
+ if (num_total_operations_)
+ new_overall_progress += IntRatio(next_operation_num_, num_total_operations_,
+ actual_operations_weight);
+
+ // Progress ratio cannot recede, unless our assumptions about the total
+ // payload size, total number of operations, or the monotonicity of progress
+ // is breached.
+ if (new_overall_progress < overall_progress_) {
+ LOG(WARNING) << "progress counter receded from " << overall_progress_
+ << "% down to " << new_overall_progress << "%; this is a bug";
+ force_log = true;
+ }
+ overall_progress_ = new_overall_progress;
+
+ // Update chunk index, log as needed: if forced by called, or we completed a
+ // progress chunk, or a timeout has expired.
+ base::Time curr_time = base::Time::Now();
+ unsigned curr_progress_chunk =
+ overall_progress_ * kProgressLogMaxChunks / 100;
+ if (force_log || curr_progress_chunk > last_progress_chunk_ ||
+ curr_time > forced_progress_log_time_) {
+ forced_progress_log_time_ = curr_time + forced_progress_log_wait_;
+ LogProgress(message_prefix);
+ }
+ last_progress_chunk_ = curr_progress_chunk;
+}
+
+
+size_t DeltaPerformer::CopyDataToBuffer(const char** bytes_p, size_t* count_p,
+ size_t max) {
+ const size_t count = *count_p;
+ if (!count)
+ return 0; // Special case shortcut.
+ size_t read_len = min(count, max - buffer_.size());
+ const char* bytes_start = *bytes_p;
+ const char* bytes_end = bytes_start + read_len;
+ buffer_.insert(buffer_.end(), bytes_start, bytes_end);
+ *bytes_p = bytes_end;
+ *count_p = count - read_len;
+ return read_len;
+}
+
+
+bool DeltaPerformer::HandleOpResult(bool op_result, const char* op_type_name,
+ ErrorCode* error) {
+ if (op_result)
+ return true;
+
+ LOG(ERROR) << "Failed to perform " << op_type_name << " operation "
+ << next_operation_num_;
+ *error = ErrorCode::kDownloadOperationExecutionError;
+ return false;
+}
+
+int DeltaPerformer::Close() {
+ int err = -CloseCurrentPartition();
+ LOG_IF(ERROR, !payload_hash_calculator_.Finalize() ||
+ !signed_hash_calculator_.Finalize())
+ << "Unable to finalize the hash.";
+ if (!buffer_.empty()) {
+ LOG(INFO) << "Discarding " << buffer_.size() << " unused downloaded bytes";
+ if (err >= 0)
+ err = 1;
+ }
+ return -err;
+}
+
+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();
+ 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();
+ return -err;
+}
+
+bool DeltaPerformer::OpenCurrentPartition() {
+ if (current_partition_ >= partitions_.size())
+ return false;
+
+ const PartitionUpdate& partition = partitions_[current_partition_];
+ // Open source fds if we have a delta payload with minor version >= 2.
+ if (!install_plan_->is_full_update &&
+ GetMinorVersion() != kInPlaceMinorPayloadVersion) {
+ source_path_ = install_plan_->partitions[current_partition_].source_path;
+ int err;
+ source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, &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_plan_->partitions[current_partition_].target_path;
+ int err;
+ target_fd_ = OpenFile(target_path_.c_str(), O_RDWR, &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;
+ }
+ return true;
+}
+
+namespace {
+
+void LogPartitionInfoHash(const PartitionInfo& info, const string& tag) {
+ string sha256 = brillo::data_encoding::Base64Encode(info.hash());
+ LOG(INFO) << "PartitionInfo " << tag << " sha256: " << sha256
+ << " size: " << info.size();
+}
+
+void LogPartitionInfo(const vector<PartitionUpdate>& partitions) {
+ for (const PartitionUpdate& partition : partitions) {
+ LogPartitionInfoHash(partition.old_partition_info(),
+ "old " + partition.partition_name());
+ LogPartitionInfoHash(partition.new_partition_info(),
+ "new " + partition.partition_name());
+ }
+}
+
+} // namespace
+
+bool DeltaPerformer::GetMetadataSignatureSizeOffset(
+ uint64_t* out_offset) const {
+ if (GetMajorVersion() == kBrilloMajorPayloadVersion) {
+ *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize;
+ return true;
+ }
+ return false;
+}
+
+bool DeltaPerformer::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 DeltaPerformer::GetMetadataSize() const {
+ return metadata_size_;
+}
+
+uint64_t DeltaPerformer::GetMajorVersion() const {
+ return major_payload_version_;
+}
+
+uint32_t DeltaPerformer::GetMinorVersion() const {
+ if (manifest_.has_minor_version()) {
+ return manifest_.minor_version();
+ } else {
+ return (install_plan_->is_full_update ?
+ kFullPayloadMinorVersion :
+ kSupportedMinorPayloadVersion);
+ }
+}
+
+bool DeltaPerformer::GetManifest(DeltaArchiveManifest* out_manifest_p) const {
+ if (!manifest_parsed_)
+ return false;
+ *out_manifest_p = manifest_;
+ return true;
+}
+
+bool DeltaPerformer::IsHeaderParsed() const {
+ return metadata_size_ != 0;
+}
+
+DeltaPerformer::MetadataParseResult DeltaPerformer::ParsePayloadMetadata(
+ const brillo::Blob& payload, ErrorCode* error) {
+ *error = ErrorCode::kSuccess;
+ uint64_t manifest_offset;
+
+ if (!IsHeaderParsed()) {
+ // Ensure we have data to cover the major payload version.
+ if (payload.size() < kDeltaManifestSizeOffset)
+ return kMetadataParseInsufficientData;
+
+ // Validate the magic string.
+ if (memcmp(payload.data(), kDeltaMagic, sizeof(kDeltaMagic)) != 0) {
+ LOG(ERROR) << "Bad payload format -- invalid delta magic.";
+ *error = ErrorCode::kDownloadInvalidMetadataMagicString;
+ return kMetadataParseError;
+ }
+
+ // Extract the payload version from the metadata.
+ COMPILE_ASSERT(sizeof(major_payload_version_) == kDeltaVersionSize,
+ major_payload_version_size_mismatch);
+ memcpy(&major_payload_version_,
+ &payload[kDeltaVersionOffset],
+ kDeltaVersionSize);
+ // switch big endian to host
+ major_payload_version_ = be64toh(major_payload_version_);
+
+ if (major_payload_version_ != supported_major_version_ &&
+ major_payload_version_ != kChromeOSMajorPayloadVersion) {
+ LOG(ERROR) << "Bad payload format -- unsupported payload version: "
+ << major_payload_version_;
+ *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+ return kMetadataParseError;
+ }
+
+ // Get the manifest offset now that we have payload version.
+ if (!GetManifestOffset(&manifest_offset)) {
+ *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+ return kMetadataParseError;
+ }
+ // Check again with the manifest offset.
+ if (payload.size() < manifest_offset)
+ return kMetadataParseInsufficientData;
+
+ // Next, parse the manifest size.
+ COMPILE_ASSERT(sizeof(manifest_size_) == kDeltaManifestSizeSize,
+ manifest_size_size_mismatch);
+ memcpy(&manifest_size_,
+ &payload[kDeltaManifestSizeOffset],
+ kDeltaManifestSizeSize);
+ manifest_size_ = be64toh(manifest_size_); // switch big endian to host
+
+ if (GetMajorVersion() == kBrilloMajorPayloadVersion) {
+ // Parse the metadata signature size.
+ COMPILE_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 kMetadataParseError;
+ }
+ memcpy(&metadata_signature_size_,
+ &payload[metadata_signature_size_offset],
+ kDeltaMetadataSignatureSizeSize);
+ metadata_signature_size_ = be32toh(metadata_signature_size_);
+ }
+
+ // If the metadata size is present in install plan, check for it immediately
+ // even before waiting for that many number of bytes to be downloaded in the
+ // payload. This will prevent any attack which relies on us downloading data
+ // beyond the expected metadata size.
+ metadata_size_ = manifest_offset + manifest_size_;
+ if (install_plan_->hash_checks_mandatory) {
+ if (install_plan_->metadata_size != metadata_size_) {
+ LOG(ERROR) << "Mandatory metadata size in Omaha response ("
+ << install_plan_->metadata_size
+ << ") is missing/incorrect, actual = " << metadata_size_;
+ *error = ErrorCode::kDownloadInvalidMetadataSize;
+ return kMetadataParseError;
+ }
+ }
+ }
+
+ // Now that we have validated the metadata size, we should wait for the full
+ // metadata and its signature (if exist) to be read in before we can parse it.
+ if (payload.size() < metadata_size_ + metadata_signature_size_)
+ return kMetadataParseInsufficientData;
+
+ // Log whether we validated the size or simply trusting what's in the payload
+ // here. This is logged here (after we received the full metadata data) so
+ // that we just log once (instead of logging n times) if it takes n
+ // DeltaPerformer::Write calls to download the full manifest.
+ if (install_plan_->metadata_size == metadata_size_) {
+ LOG(INFO) << "Manifest size in payload matches expected value from Omaha";
+ } else {
+ // For mandatory-cases, we'd have already returned a kMetadataParseError
+ // above. We'll be here only for non-mandatory cases. Just send a UMA stat.
+ LOG(WARNING) << "Ignoring missing/incorrect metadata size ("
+ << install_plan_->metadata_size
+ << ") in Omaha response as validation is not mandatory. "
+ << "Trusting metadata size in payload = " << metadata_size_;
+ }
+
+ // We have the full metadata in |payload|. Verify its integrity
+ // and authenticity based on the information we have in Omaha response.
+ *error = ValidateMetadataSignature(payload);
+ if (*error != ErrorCode::kSuccess) {
+ if (install_plan_->hash_checks_mandatory) {
+ // The autoupdate_CatchBadSignatures test checks for this string
+ // in log-files. Keep in sync.
+ LOG(ERROR) << "Mandatory metadata signature validation failed";
+ return kMetadataParseError;
+ }
+
+ // For non-mandatory cases, just send a UMA stat.
+ LOG(WARNING) << "Ignoring metadata signature validation failures";
+ *error = ErrorCode::kSuccess;
+ }
+
+ if (!GetManifestOffset(&manifest_offset)) {
+ *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+ return kMetadataParseError;
+ }
+ // The payload metadata is deemed valid, it's safe to parse the protobuf.
+ if (!manifest_.ParseFromArray(&payload[manifest_offset], manifest_size_)) {
+ LOG(ERROR) << "Unable to parse manifest in update file.";
+ *error = ErrorCode::kDownloadManifestParseError;
+ return kMetadataParseError;
+ }
+
+ manifest_parsed_ = true;
+ return kMetadataParseSuccess;
+}
+
+// Wrapper around write. Returns true if all requested bytes
+// were written, or false on any error, regardless of progress
+// and stores an action exit code in |error|.
+bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode *error) {
+ *error = ErrorCode::kSuccess;
+
+ const char* c_bytes = reinterpret_cast<const char*>(bytes);
+
+ // Update the total byte downloaded count and the progress logs.
+ total_bytes_received_ += count;
+ UpdateOverallProgress(false, "Completed ");
+
+ while (!manifest_valid_) {
+ // Read data up to the needed limit; this is either maximium payload header
+ // size, or the full metadata size (once it becomes known).
+ const bool do_read_header = !IsHeaderParsed();
+ CopyDataToBuffer(&c_bytes, &count,
+ (do_read_header ? kMaxPayloadHeaderSize :
+ metadata_size_ + metadata_signature_size_));
+
+ MetadataParseResult result = ParsePayloadMetadata(buffer_, error);
+ if (result == kMetadataParseError)
+ return false;
+ if (result == kMetadataParseInsufficientData) {
+ // If we just processed the header, make an attempt on the manifest.
+ if (do_read_header && IsHeaderParsed())
+ continue;
+
+ return true;
+ }
+
+ // Checks the integrity of the payload manifest.
+ if ((*error = ValidateManifest()) != ErrorCode::kSuccess)
+ return false;
+ manifest_valid_ = true;
+
+ // Clear the download buffer.
+ DiscardBuffer(false, metadata_size_);
+
+ // This populates |partitions_| and the |install_plan.partitions| with the
+ // list of partitions from the manifest.
+ if (!ParseManifestPartitions(error))
+ return false;
+
+ num_total_operations_ = 0;
+ for (const auto& partition : partitions_) {
+ num_total_operations_ += partition.operations_size();
+ acc_num_operations_.push_back(num_total_operations_);
+ }
+
+ LOG_IF(WARNING, !prefs_->SetInt64(kPrefsManifestMetadataSize,
+ metadata_size_))
+ << "Unable to save the manifest metadata size.";
+
+ if (!PrimeUpdateState()) {
+ *error = ErrorCode::kDownloadStateInitializationError;
+ LOG(ERROR) << "Unable to prime the update state.";
+ return false;
+ }
+
+ if (!OpenCurrentPartition()) {
+ *error = ErrorCode::kInstallDeviceOpenError;
+ return false;
+ }
+
+ if (next_operation_num_ > 0)
+ UpdateOverallProgress(true, "Resuming after ");
+ LOG(INFO) << "Starting to apply update payload operations";
+ }
+
+ while (next_operation_num_ < num_total_operations_) {
+ // Check if we should cancel the current attempt for any reason.
+ // In this case, *error will have already been populated with the reason
+ // why we're canceling.
+ if (download_delegate_ && download_delegate_->ShouldCancel(error))
+ return false;
+
+ // We know there are more operations to perform because we didn't reach the
+ // |num_total_operations_| limit yet.
+ while (next_operation_num_ >= acc_num_operations_[current_partition_]) {
+ CloseCurrentPartition();
+ current_partition_++;
+ if (!OpenCurrentPartition()) {
+ *error = ErrorCode::kInstallDeviceOpenError;
+ 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);
+
+ CopyDataToBuffer(&c_bytes, &count, op.data_length());
+
+ // Check whether we received all of the next operation's data payload.
+ if (!CanPerformInstallOperation(op))
+ return true;
+
+ // Validate the operation only if the metadata signature is present.
+ // Otherwise, keep the old behavior. This serves as a knob to disable
+ // the validation logic in case we find some regression after rollout.
+ // NOTE: If hash checks are mandatory and if metadata_signature is empty,
+ // we would have already failed in ParsePayloadMetadata method and thus not
+ // even be here. So no need to handle that case again here.
+ if (!install_plan_->metadata_signature.empty()) {
+ // Note: Validate must be called only if CanPerformInstallOperation is
+ // called. Otherwise, we might be failing operations before even if there
+ // isn't sufficient data to compute the proper hash.
+ *error = ValidateOperationHash(op);
+ if (*error != ErrorCode::kSuccess) {
+ if (install_plan_->hash_checks_mandatory) {
+ LOG(ERROR) << "Mandatory operation hash check failed";
+ return false;
+ }
+
+ // For non-mandatory cases, just send a UMA stat.
+ LOG(WARNING) << "Ignoring operation validation errors";
+ *error = ErrorCode::kSuccess;
+ }
+ }
+
+ // Makes sure we unblock exit when this operation completes.
+ ScopedTerminatorExitUnblocker exit_unblocker =
+ ScopedTerminatorExitUnblocker(); // Avoids a compiler unused var bug.
+
+ bool op_result;
+ switch (op.type()) {
+ case InstallOperation::REPLACE:
+ case InstallOperation::REPLACE_BZ:
+ case InstallOperation::REPLACE_XZ:
+ op_result = PerformReplaceOperation(op);
+ break;
+ case InstallOperation::ZERO:
+ case InstallOperation::DISCARD:
+ op_result = PerformZeroOrDiscardOperation(op);
+ break;
+ case InstallOperation::MOVE:
+ op_result = PerformMoveOperation(op);
+ break;
+ case InstallOperation::BSDIFF:
+ op_result = PerformBsdiffOperation(op);
+ break;
+ case InstallOperation::SOURCE_COPY:
+ op_result = PerformSourceCopyOperation(op);
+ break;
+ case InstallOperation::SOURCE_BSDIFF:
+ op_result = PerformSourceBsdiffOperation(op);
+ break;
+ default:
+ op_result = false;
+ }
+ if (!HandleOpResult(op_result, InstallOperationTypeName(op.type()), error))
+ return false;
+
+ next_operation_num_++;
+ UpdateOverallProgress(false, "Completed ");
+ CheckpointUpdateProgress();
+ }
+
+ // In major version 2, we don't add dummy operation to the payload.
+ if (major_payload_version_ == kBrilloMajorPayloadVersion &&
+ manifest_.has_signatures_offset() && manifest_.has_signatures_size()) {
+ if (manifest_.signatures_offset() != buffer_offset_) {
+ LOG(ERROR) << "Payload signatures offset points to blob offset "
+ << manifest_.signatures_offset()
+ << " but signatures are expected at offset "
+ << buffer_offset_;
+ *error = ErrorCode::kDownloadPayloadVerificationError;
+ return false;
+ }
+ CopyDataToBuffer(&c_bytes, &count, manifest_.signatures_size());
+ // Needs more data to cover entire signature.
+ if (buffer_.size() < manifest_.signatures_size())
+ return true;
+ if (!ExtractSignatureMessage()) {
+ LOG(ERROR) << "Extract payload signature failed.";
+ *error = ErrorCode::kDownloadPayloadVerificationError;
+ return false;
+ }
+ DiscardBuffer(true, 0);
+ }
+
+ return true;
+}
+
+bool DeltaPerformer::IsManifestValid() {
+ return manifest_valid_;
+}
+
+bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) {
+ if (major_payload_version_ == kBrilloMajorPayloadVersion) {
+ partitions_.clear();
+ for (const PartitionUpdate& partition : manifest_.partitions()) {
+ partitions_.push_back(partition);
+ }
+ manifest_.clear_partitions();
+ } else if (major_payload_version_ == kChromeOSMajorPayloadVersion) {
+ LOG(INFO) << "Converting update information from old format.";
+ PartitionUpdate root_part;
+ root_part.set_partition_name(kLegacyPartitionNameRoot);
+#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();
+ }
+ if (manifest_.has_new_rootfs_info()) {
+ *root_part.mutable_new_partition_info() = manifest_.new_rootfs_info();
+ manifest_.clear_new_rootfs_info();
+ }
+ *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(kLegacyPartitionNameKernel);
+ 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();
+ }
+ if (manifest_.has_new_kernel_info()) {
+ *kern_part.mutable_new_partition_info() = manifest_.new_kernel_info();
+ manifest_.clear_new_kernel_info();
+ }
+ *kern_part.mutable_operations() = manifest_.kernel_install_operations();
+ manifest_.clear_kernel_install_operations();
+ partitions_.push_back(std::move(kern_part));
+ }
+
+ // TODO(deymo): Remove this block of code once we switched to optional
+ // source partition verification. This list of partitions in the InstallPlan
+ // is initialized with the expected hashes in the payload major version 1,
+ // so we need to check those now if already set. See b/23182225.
+ if (!install_plan_->partitions.empty()) {
+ if (!VerifySourcePartitions()) {
+ *error = ErrorCode::kDownloadStateInitializationError;
+ return false;
+ }
+ }
+
+ // Fill in the InstallPlan::partitions based on the partitions from the
+ // payload.
+ install_plan_->partitions.clear();
+ for (const auto& partition : partitions_) {
+ InstallPlan::Partition install_part;
+ install_part.name = partition.partition_name();
+ install_part.run_postinstall =
+ partition.has_run_postinstall() && partition.run_postinstall();
+
+ if (partition.has_old_partition_info()) {
+ const PartitionInfo& info = partition.old_partition_info();
+ install_part.source_size = info.size();
+ install_part.source_hash.assign(info.hash().begin(), info.hash().end());
+ }
+
+ if (!partition.has_new_partition_info()) {
+ LOG(ERROR) << "Unable to get new partition hash info on partition "
+ << install_part.name << ".";
+ *error = ErrorCode::kDownloadNewPartitionInfoError;
+ return false;
+ }
+ const PartitionInfo& info = partition.new_partition_info();
+ install_part.target_size = info.size();
+ install_part.target_hash.assign(info.hash().begin(), info.hash().end());
+
+ install_plan_->partitions.push_back(install_part);
+ }
+
+ if (!install_plan_->LoadPartitionsFromSlots(boot_control_)) {
+ LOG(ERROR) << "Unable to determine all the partition devices.";
+ *error = ErrorCode::kInstallDeviceOpenError;
+ return false;
+ }
+ LogPartitionInfo(partitions_);
+ return true;
+}
+
+bool DeltaPerformer::CanPerformInstallOperation(
+ const chromeos_update_engine::InstallOperation& operation) {
+ // Move and source_copy operations don't require any data blob, so they can
+ // always be performed.
+ if (operation.type() == InstallOperation::MOVE ||
+ operation.type() == InstallOperation::SOURCE_COPY)
+ return true;
+
+ // See if we have the entire data blob in the buffer
+ if (operation.data_offset() < buffer_offset_) {
+ LOG(ERROR) << "we threw away data it seems?";
+ return false;
+ }
+
+ return (operation.data_offset() + operation.data_length() <=
+ buffer_offset_ + buffer_.size());
+}
+
+bool DeltaPerformer::PerformReplaceOperation(
+ const InstallOperation& operation) {
+ CHECK(operation.type() == InstallOperation::REPLACE ||
+ operation.type() == InstallOperation::REPLACE_BZ ||
+ operation.type() == InstallOperation::REPLACE_XZ);
+
+ // 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 =
+ brillo::make_unique_ptr(new ZeroPadExtentWriter(
+ brillo::make_unique_ptr(new 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)));
+ }
+
+ // Create a vector of extents to pass to the ExtentWriter.
+ vector<Extent> extents;
+ for (int i = 0; i < operation.dst_extents_size(); i++) {
+ extents.push_back(operation.dst_extents(i));
+ }
+
+ TEST_AND_RETURN_FALSE(writer->Init(target_fd_, extents, block_size_));
+ TEST_AND_RETURN_FALSE(writer->Write(buffer_.data(), operation.data_length()));
+ TEST_AND_RETURN_FALSE(writer->End());
+
+ // Update buffer
+ DiscardBuffer(true, buffer_.size());
+ return true;
+}
+
+bool DeltaPerformer::PerformZeroOrDiscardOperation(
+ const InstallOperation& operation) {
+ CHECK(operation.type() == InstallOperation::DISCARD ||
+ operation.type() == InstallOperation::ZERO);
+
+ // These operations have no blob.
+ TEST_AND_RETURN_FALSE(!operation.has_data_offset());
+ TEST_AND_RETURN_FALSE(!operation.has_data_length());
+
+ int request =
+ (operation.type() == InstallOperation::ZERO ? BLKZEROOUT : BLKDISCARD);
+
+ bool attempt_ioctl = true;
+ brillo::Blob zeros;
+ for (int i = 0; i < operation.dst_extents_size(); i++) {
+ Extent extent = operation.dst_extents(i);
+ 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;
+ zeros.resize(16 * block_size_);
+ }
+ // In case of failure, we fall back to writing 0 to the selected region.
+ 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;
+}
+
+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;
+}
+
+namespace {
+
+// Takes |extents| and fills an empty vector |blocks| with a block index for
+// each block in |extents|. For example, [(3, 2), (8, 1)] would give [3, 4, 8].
+void ExtentsToBlocks(const RepeatedPtrField<Extent>& extents,
+ vector<uint64_t>* blocks) {
+ for (Extent ext : extents) {
+ for (uint64_t j = 0; j < ext.num_blocks(); j++)
+ blocks->push_back(ext.start_block() + j);
+ }
+}
+
+// Takes |extents| and returns the number of blocks in those extents.
+uint64_t GetBlockCount(const RepeatedPtrField<Extent>& extents) {
+ uint64_t sum = 0;
+ for (Extent ext : extents) {
+ sum += ext.num_blocks();
+ }
+ return sum;
+}
+
+// Compare |calculated_hash| with source hash in |operation|, return false and
+// dump hash if don't match.
+bool ValidateSourceHash(const brillo::Blob& calculated_hash,
+ const InstallOperation& operation) {
+ brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(),
+ operation.src_sha256_hash().end());
+ if (calculated_hash != expected_source_hash) {
+ LOG(ERROR) << "Hash verification failed. Expected hash = ";
+ utils::HexDumpVector(expected_source_hash);
+ LOG(ERROR) << "Calculated hash = ";
+ utils::HexDumpVector(calculated_hash);
+ return false;
+ }
+ return true;
+}
+
+} // namespace
+
+bool DeltaPerformer::PerformSourceCopyOperation(
+ const InstallOperation& operation) {
+ if (operation.has_src_length())
+ 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);
+
+ uint64_t blocks_to_read = GetBlockCount(operation.src_extents());
+ uint64_t blocks_to_write = GetBlockCount(operation.dst_extents());
+ TEST_AND_RETURN_FALSE(blocks_to_write == blocks_to_read);
+
+ // Create vectors of all the individual src/dst blocks.
+ vector<uint64_t> src_blocks;
+ vector<uint64_t> dst_blocks;
+ ExtentsToBlocks(operation.src_extents(), &src_blocks);
+ ExtentsToBlocks(operation.dst_extents(), &dst_blocks);
+ DCHECK_EQ(src_blocks.size(), blocks_to_read);
+ DCHECK_EQ(src_blocks.size(), dst_blocks.size());
+
+ brillo::Blob buf(block_size_);
+ ssize_t bytes_read = 0;
+ HashCalculator source_hasher;
+ // Read/write one block at a time.
+ for (uint64_t i = 0; i < blocks_to_read; i++) {
+ ssize_t bytes_read_this_iteration = 0;
+ uint64_t src_block = src_blocks[i];
+ uint64_t dst_block = dst_blocks[i];
+
+ // Read in bytes.
+ TEST_AND_RETURN_FALSE(
+ utils::PReadAll(source_fd_,
+ buf.data(),
+ block_size_,
+ src_block * block_size_,
+ &bytes_read_this_iteration));
+
+ // Write bytes out.
+ TEST_AND_RETURN_FALSE(
+ utils::PWriteAll(target_fd_,
+ buf.data(),
+ block_size_,
+ dst_block * block_size_));
+
+ bytes_read += bytes_read_this_iteration;
+ TEST_AND_RETURN_FALSE(bytes_read_this_iteration ==
+ static_cast<ssize_t>(block_size_));
+
+ if (operation.has_src_sha256_hash())
+ TEST_AND_RETURN_FALSE(source_hasher.Update(buf.data(), buf.size()));
+ }
+
+ if (operation.has_src_sha256_hash()) {
+ TEST_AND_RETURN_FALSE(source_hasher.Finalize());
+ TEST_AND_RETURN_FALSE(
+ ValidateSourceHash(source_hasher.raw_hash(), operation));
+ }
+
+ DCHECK_EQ(bytes_read, static_cast<ssize_t>(blocks_to_read * block_size_));
+ return true;
+}
+
+bool DeltaPerformer::ExtentsToBsdiffPositionsString(
+ const RepeatedPtrField<Extent>& extents,
+ uint64_t block_size,
+ uint64_t full_length,
+ string* positions_string) {
+ string ret;
+ uint64_t length = 0;
+ for (int i = 0; i < extents.size(); i++) {
+ Extent extent = extents.Get(i);
+ int64_t start = extent.start_block() * block_size;
+ uint64_t this_length = min(full_length - length,
+ extent.num_blocks() * block_size);
+ ret += base::StringPrintf("%" PRIi64 ":%" PRIu64 ",", start, this_length);
+ length += this_length;
+ }
+ TEST_AND_RETURN_FALSE(length == full_length);
+ if (!ret.empty())
+ ret.resize(ret.size() - 1); // Strip trailing comma off
+ *positions_string = ret;
+ 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));
+
+ string temp_filename;
+ TEST_AND_RETURN_FALSE(utils::MakeTempFile("au_patch.XXXXXX",
+ &temp_filename,
+ nullptr));
+ ScopedPathUnlinker path_unlinker(temp_filename);
+ {
+ int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ ScopedFdCloser fd_closer(&fd);
+ TEST_AND_RETURN_FALSE(
+ utils::WriteAll(fd, buffer_.data(), operation.data_length()));
+ }
+
+ // Update the buffer to release the patch data memory as soon as the patch
+ // file is written out.
+ DiscardBuffer(true, buffer_.size());
+
+ vector<string> cmd{kBspatchPath, target_path_, target_path_, temp_filename,
+ input_positions, output_positions};
+
+ int return_code = 0;
+ TEST_AND_RETURN_FALSE(
+ Subprocess::SynchronousExecFlags(cmd, Subprocess::kSearchPath,
+ &return_code, nullptr));
+ TEST_AND_RETURN_FALSE(return_code == 0);
+
+ 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;
+}
+
+bool DeltaPerformer::PerformSourceBsdiffOperation(
+ 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());
+ if (operation.has_src_length())
+ 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);
+
+ if (operation.has_src_sha256_hash()) {
+ HashCalculator source_hasher;
+ const uint64_t kMaxBlocksToRead = 512; // 2MB if block size is 4KB
+ brillo::Blob buf(kMaxBlocksToRead * block_size_);
+ for (const Extent& extent : operation.src_extents()) {
+ for (uint64_t i = 0; i < extent.num_blocks(); i += kMaxBlocksToRead) {
+ uint64_t blocks_to_read =
+ min(kMaxBlocksToRead, extent.num_blocks() - i);
+ ssize_t bytes_to_read = blocks_to_read * block_size_;
+ ssize_t bytes_read_this_iteration = 0;
+ TEST_AND_RETURN_FALSE(
+ utils::PReadAll(source_fd_, buf.data(), bytes_to_read,
+ (extent.start_block() + i) * block_size_,
+ &bytes_read_this_iteration));
+ TEST_AND_RETURN_FALSE(bytes_read_this_iteration == bytes_to_read);
+ TEST_AND_RETURN_FALSE(source_hasher.Update(buf.data(), bytes_to_read));
+ }
+ }
+ TEST_AND_RETURN_FALSE(source_hasher.Finalize());
+ TEST_AND_RETURN_FALSE(
+ ValidateSourceHash(source_hasher.raw_hash(), operation));
+ }
+
+ 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));
+
+ string temp_filename;
+ TEST_AND_RETURN_FALSE(utils::MakeTempFile("au_patch.XXXXXX",
+ &temp_filename,
+ nullptr));
+ ScopedPathUnlinker path_unlinker(temp_filename);
+ {
+ int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ ScopedFdCloser fd_closer(&fd);
+ TEST_AND_RETURN_FALSE(
+ utils::WriteAll(fd, buffer_.data(), operation.data_length()));
+ }
+
+ // Update the buffer to release the patch data memory as soon as the patch
+ // file is written out.
+ DiscardBuffer(true, buffer_.size());
+
+ vector<string> cmd{kBspatchPath, source_path_, target_path_, temp_filename,
+ input_positions, output_positions};
+
+ int return_code = 0;
+ TEST_AND_RETURN_FALSE(
+ Subprocess::SynchronousExecFlags(cmd, Subprocess::kSearchPath,
+ &return_code, nullptr));
+ TEST_AND_RETURN_FALSE(return_code == 0);
+ 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());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= manifest_.signatures_size());
+ 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,
+ string(signatures_message_data_.begin(),
+ signatures_message_data_.end())))
+ << "Unable to store the signature blob.";
+
+ LOG(INFO) << "Extracted signature data of size "
+ << manifest_.signatures_size() << " at "
+ << manifest_.signatures_offset();
+ return true;
+}
+
+bool DeltaPerformer::GetPublicKeyFromResponse(base::FilePath *out_tmp_key) {
+ if (hardware_->IsOfficialBuild() ||
+ utils::FileExists(public_key_path_.c_str()) ||
+ install_plan_->public_key_rsa.empty())
+ return false;
+
+ if (!utils::DecodeAndStoreBase64String(install_plan_->public_key_rsa,
+ out_tmp_key))
+ return false;
+
+ return true;
+}
+
+ErrorCode DeltaPerformer::ValidateMetadataSignature(
+ const brillo::Blob& payload) {
+ if (payload.size() < metadata_size_ + metadata_signature_size_)
+ return ErrorCode::kDownloadMetadataSignatureError;
+
+ brillo::Blob metadata_signature_blob, metadata_signature_protobuf_blob;
+ if (!install_plan_->metadata_signature.empty()) {
+ // Convert base64-encoded signature to raw bytes.
+ if (!brillo::data_encoding::Base64Decode(
+ install_plan_->metadata_signature, &metadata_signature_blob)) {
+ LOG(ERROR) << "Unable to decode base64 metadata signature: "
+ << install_plan_->metadata_signature;
+ return ErrorCode::kDownloadMetadataSignatureError;
+ }
+ } else if (major_payload_version_ == kBrilloMajorPayloadVersion) {
+ metadata_signature_protobuf_blob.assign(payload.begin() + metadata_size_,
+ payload.begin() + metadata_size_ +
+ metadata_signature_size_);
+ }
+
+ if (metadata_signature_blob.empty() &&
+ metadata_signature_protobuf_blob.empty()) {
+ if (install_plan_->hash_checks_mandatory) {
+ LOG(ERROR) << "Missing mandatory metadata signature in both Omaha "
+ << "response and payload.";
+ return ErrorCode::kDownloadMetadataSignatureMissingError;
+ }
+
+ LOG(WARNING) << "Cannot validate metadata as the signature is empty";
+ return ErrorCode::kSuccess;
+ }
+
+ // See if we should use the public RSA key in the Omaha response.
+ base::FilePath path_to_public_key(public_key_path_);
+ base::FilePath tmp_key;
+ if (GetPublicKeyFromResponse(&tmp_key))
+ path_to_public_key = tmp_key;
+ ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+ if (tmp_key.empty())
+ tmp_key_remover.set_should_remove(false);
+
+ LOG(INFO) << "Verifying metadata hash signature using public key: "
+ << path_to_public_key.value();
+
+ HashCalculator metadata_hasher;
+ metadata_hasher.Update(payload.data(), metadata_size_);
+ if (!metadata_hasher.Finalize()) {
+ LOG(ERROR) << "Unable to compute actual hash of manifest";
+ return ErrorCode::kDownloadMetadataSignatureVerificationError;
+ }
+
+ brillo::Blob calculated_metadata_hash = metadata_hasher.raw_hash();
+ PayloadVerifier::PadRSA2048SHA256Hash(&calculated_metadata_hash);
+ if (calculated_metadata_hash.empty()) {
+ LOG(ERROR) << "Computed actual hash of metadata is empty.";
+ return ErrorCode::kDownloadMetadataSignatureVerificationError;
+ }
+
+ if (!metadata_signature_blob.empty()) {
+ brillo::Blob expected_metadata_hash;
+ if (!PayloadVerifier::GetRawHashFromSignature(metadata_signature_blob,
+ path_to_public_key.value(),
+ &expected_metadata_hash)) {
+ LOG(ERROR) << "Unable to compute expected hash from metadata signature";
+ return ErrorCode::kDownloadMetadataSignatureError;
+ }
+ if (calculated_metadata_hash != expected_metadata_hash) {
+ LOG(ERROR) << "Manifest hash verification failed. Expected hash = ";
+ utils::HexDumpVector(expected_metadata_hash);
+ LOG(ERROR) << "Calculated hash = ";
+ utils::HexDumpVector(calculated_metadata_hash);
+ return ErrorCode::kDownloadMetadataSignatureMismatch;
+ }
+ } else {
+ if (!PayloadVerifier::VerifySignature(metadata_signature_protobuf_blob,
+ path_to_public_key.value(),
+ calculated_metadata_hash)) {
+ LOG(ERROR) << "Manifest hash verification failed.";
+ return ErrorCode::kDownloadMetadataSignatureMismatch;
+ }
+ }
+
+ // The autoupdate_CatchBadSignatures test checks for this string in
+ // log-files. Keep in sync.
+ LOG(INFO) << "Metadata hash signature matches value in Omaha response.";
+ return ErrorCode::kSuccess;
+}
+
+ErrorCode DeltaPerformer::ValidateManifest() {
+ // Perform assorted checks to sanity check the manifest, make sure it
+ // matches data from other sources, and that it is a supported version.
+ //
+ // TODO(garnold) in general, the presence of an old partition hash should be
+ // the sole indicator for a delta update, as we would generally like update
+ // payloads to be self contained and not assume an Omaha response to tell us
+ // that. However, since this requires some massive reengineering of the update
+ // flow (making filesystem copying happen conditionally only *after*
+ // downloading and parsing of the update manifest) we'll put it off for now.
+ // See chromium-os:7597 for further discussion.
+ if (install_plan_->is_full_update) {
+ if (manifest_.has_old_kernel_info() || manifest_.has_old_rootfs_info()) {
+ LOG(ERROR) << "Purported full payload contains old partition "
+ "hash(es), aborting update";
+ return ErrorCode::kPayloadMismatchedType;
+ }
+
+ for (const PartitionUpdate& partition : manifest_.partitions()) {
+ if (partition.has_old_partition_info()) {
+ LOG(ERROR) << "Purported full payload contains old partition "
+ "hash(es), aborting update";
+ return ErrorCode::kPayloadMismatchedType;
+ }
+ }
+
+ if (manifest_.minor_version() != kFullPayloadMinorVersion) {
+ LOG(ERROR) << "Manifest contains minor version "
+ << manifest_.minor_version()
+ << ", but all full payloads should have version "
+ << kFullPayloadMinorVersion << ".";
+ return ErrorCode::kUnsupportedMinorPayloadVersion;
+ }
+ } else {
+ if (manifest_.minor_version() != supported_minor_version_) {
+ LOG(ERROR) << "Manifest contains minor version "
+ << manifest_.minor_version()
+ << " not the supported "
+ << supported_minor_version_;
+ return ErrorCode::kUnsupportedMinorPayloadVersion;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ // TODO(garnold) we should be adding more and more manifest checks, such as
+ // partition boundaries etc (see chromium-os:37661).
+
+ return ErrorCode::kSuccess;
+}
+
+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.
+ return ErrorCode::kSuccess;
+ }
+
+ // No hash is present for an operation that has data blobs. This shouldn't
+ // happen normally for any client that has this code, because the
+ // 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
+ // 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.
+ if (manifest_.signatures_offset() &&
+ manifest_.signatures_offset() == operation.data_offset()) {
+ LOG(INFO) << "Skipping hash verification for signature operation "
+ << next_operation_num_ + 1;
+ } else {
+ if (install_plan_->hash_checks_mandatory) {
+ LOG(ERROR) << "Missing mandatory operation hash for operation "
+ << next_operation_num_ + 1;
+ return ErrorCode::kDownloadOperationHashMissingError;
+ }
+
+ LOG(WARNING) << "Cannot validate operation " << next_operation_num_ + 1
+ << " as there's no operation hash in manifest";
+ }
+ return ErrorCode::kSuccess;
+ }
+
+ brillo::Blob expected_op_hash;
+ expected_op_hash.assign(operation.data_sha256_hash().data(),
+ (operation.data_sha256_hash().data() +
+ operation.data_sha256_hash().size()));
+
+ HashCalculator operation_hasher;
+ operation_hasher.Update(buffer_.data(), operation.data_length());
+ if (!operation_hasher.Finalize()) {
+ LOG(ERROR) << "Unable to compute actual hash of operation "
+ << next_operation_num_;
+ return ErrorCode::kDownloadOperationHashVerificationError;
+ }
+
+ brillo::Blob calculated_op_hash = operation_hasher.raw_hash();
+ if (calculated_op_hash != expected_op_hash) {
+ LOG(ERROR) << "Hash verification failed for operation "
+ << next_operation_num_ << ". Expected hash = ";
+ utils::HexDumpVector(expected_op_hash);
+ LOG(ERROR) << "Calculated hash over " << operation.data_length()
+ << " bytes at offset: " << operation.data_offset() << " = ";
+ utils::HexDumpVector(calculated_op_hash);
+ return ErrorCode::kDownloadOperationHashMismatch;
+ }
+
+ return ErrorCode::kSuccess;
+}
+
+#define TEST_AND_RETURN_VAL(_retval, _condition) \
+ do { \
+ if (!(_condition)) { \
+ LOG(ERROR) << "VerifyPayload failure: " << #_condition; \
+ return _retval; \
+ } \
+ } while (0);
+
+ErrorCode DeltaPerformer::VerifyPayload(
+ const string& update_check_response_hash,
+ const uint64_t update_check_response_size) {
+
+ // See if we should use the public RSA key in the Omaha response.
+ base::FilePath path_to_public_key(public_key_path_);
+ base::FilePath tmp_key;
+ if (GetPublicKeyFromResponse(&tmp_key))
+ path_to_public_key = tmp_key;
+ ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+ if (tmp_key.empty())
+ tmp_key_remover.set_should_remove(false);
+
+ LOG(INFO) << "Verifying payload using public key: "
+ << path_to_public_key.value();
+
+ // Verifies the download size.
+ TEST_AND_RETURN_VAL(ErrorCode::kPayloadSizeMismatchError,
+ update_check_response_size ==
+ metadata_size_ + metadata_signature_size_ +
+ buffer_offset_);
+
+ // Verifies the payload hash.
+ const string& payload_hash_data = payload_hash_calculator_.hash();
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadVerificationError,
+ !payload_hash_data.empty());
+ TEST_AND_RETURN_VAL(ErrorCode::kPayloadHashMismatchError,
+ payload_hash_data == update_check_response_hash);
+
+ // Verifies the signed payload hash.
+ if (!utils::FileExists(path_to_public_key.value().c_str())) {
+ LOG(WARNING) << "Not verifying signed delta payload -- missing public key.";
+ return ErrorCode::kSuccess;
+ }
+ TEST_AND_RETURN_VAL(ErrorCode::kSignedDeltaPayloadExpectedError,
+ !signatures_message_data_.empty());
+ brillo::Blob hash_data = signed_hash_calculator_.raw_hash();
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+ PayloadVerifier::PadRSA2048SHA256Hash(&hash_data));
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+ !hash_data.empty());
+
+ if (!PayloadVerifier::VerifySignature(
+ signatures_message_data_, path_to_public_key.value(), hash_data)) {
+ // The autoupdate_CatchBadSignatures test checks for this string
+ // in log-files. Keep in sync.
+ LOG(ERROR) << "Public key verification failed, thus update failed.";
+ return ErrorCode::kDownloadPayloadPubKeyVerificationError;
+ }
+
+ LOG(INFO) << "Payload hash matches value in payload.";
+
+ // At this point, we are guaranteed to have downloaded a full payload, i.e
+ // the one whose size matches the size mentioned in Omaha response. If any
+ // errors happen after this, it's likely a problem with the payload itself or
+ // the state of the system and not a problem with the URL or network. So,
+ // indicate that to the download delegate so that AU can backoff
+ // appropriately.
+ if (download_delegate_)
+ download_delegate_->DownloadComplete();
+
+ return ErrorCode::kSuccess;
+}
+
+namespace {
+void LogVerifyError(const string& type,
+ const string& device,
+ uint64_t size,
+ const string& local_hash,
+ const string& expected_hash) {
+ LOG(ERROR) << "This is a server-side error due to "
+ << "mismatched delta update image!";
+ LOG(ERROR) << "The delta I've been given contains a " << type << " delta "
+ << "update that must be applied over a " << type << " with "
+ << "a specific checksum, but the " << type << " we're starting "
+ << "with doesn't have that checksum! This means that "
+ << "the delta I've been given doesn't match my existing "
+ << "system. The " << type << " partition I have has hash: "
+ << local_hash << " but the update expected me to have "
+ << expected_hash << " .";
+ LOG(INFO) << "To get the checksum of the " << type << " partition run this"
+ "command: dd if=" << device << " bs=1M count=" << size
+ << " iflag=count_bytes 2>/dev/null | openssl dgst -sha256 -binary "
+ "| openssl base64";
+ LOG(INFO) << "To get the checksum of partitions in a bin file, "
+ << "run: .../src/scripts/sha256_partitions.sh .../file.bin";
+}
+
+string StringForHashBytes(const void* bytes, size_t size) {
+ return brillo::data_encoding::Base64Encode(bytes, size);
+}
+} // namespace
+
+bool DeltaPerformer::VerifySourcePartitions() {
+ LOG(INFO) << "Verifying source partitions.";
+ CHECK(manifest_valid_);
+ CHECK(install_plan_);
+ if (install_plan_->partitions.size() != partitions_.size()) {
+ DLOG(ERROR) << "The list of partitions in the InstallPlan doesn't match the "
+ "list received in the payload. The InstallPlan has "
+ << install_plan_->partitions.size()
+ << " partitions while the payload has " << partitions_.size()
+ << " partitions.";
+ return false;
+ }
+ for (size_t i = 0; i < partitions_.size(); ++i) {
+ if (partitions_[i].partition_name() != install_plan_->partitions[i].name) {
+ DLOG(ERROR) << "The InstallPlan's partition " << i << " is \""
+ << install_plan_->partitions[i].name
+ << "\" but the payload expects it to be \""
+ << partitions_[i].partition_name()
+ << "\". This is an error in the DeltaPerformer setup.";
+ return false;
+ }
+ if (!partitions_[i].has_old_partition_info())
+ continue;
+ const PartitionInfo& info = partitions_[i].old_partition_info();
+ const InstallPlan::Partition& plan_part = install_plan_->partitions[i];
+ bool valid =
+ !plan_part.source_hash.empty() &&
+ plan_part.source_hash.size() == info.hash().size() &&
+ memcmp(plan_part.source_hash.data(),
+ info.hash().data(),
+ plan_part.source_hash.size()) == 0;
+ if (!valid) {
+ LogVerifyError(partitions_[i].partition_name(),
+ plan_part.source_path,
+ info.hash().size(),
+ StringForHashBytes(plan_part.source_hash.data(),
+ plan_part.source_hash.size()),
+ StringForHashBytes(info.hash().data(),
+ info.hash().size()));
+ return false;
+ }
+ }
+ return true;
+}
+
+void DeltaPerformer::DiscardBuffer(bool do_advance_offset,
+ size_t signed_hash_buffer_size) {
+ // Update the buffer offset.
+ if (do_advance_offset)
+ buffer_offset_ += buffer_.size();
+
+ // Hash the content.
+ payload_hash_calculator_.Update(buffer_.data(), buffer_.size());
+ signed_hash_calculator_.Update(buffer_.data(), signed_hash_buffer_size);
+
+ // Swap content with an empty vector to ensure that all memory is released.
+ brillo::Blob().swap(buffer_);
+}
+
+bool DeltaPerformer::CanResumeUpdate(PrefsInterface* prefs,
+ string update_check_response_hash) {
+ int64_t next_operation = kUpdateStateOperationInvalid;
+ if (!(prefs->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) &&
+ next_operation != kUpdateStateOperationInvalid &&
+ next_operation > 0))
+ return false;
+
+ string interrupted_hash;
+ if (!(prefs->GetString(kPrefsUpdateCheckResponseHash, &interrupted_hash) &&
+ !interrupted_hash.empty() &&
+ interrupted_hash == update_check_response_hash))
+ return false;
+
+ int64_t resumed_update_failures;
+ if (!(prefs->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures)
+ && resumed_update_failures > kMaxResumedUpdateFailures))
+ return false;
+
+ // Sanity check the rest.
+ int64_t next_data_offset = -1;
+ if (!(prefs->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset) &&
+ next_data_offset >= 0))
+ return false;
+
+ string sha256_context;
+ if (!(prefs->GetString(kPrefsUpdateStateSHA256Context, &sha256_context) &&
+ !sha256_context.empty()))
+ return false;
+
+ int64_t manifest_metadata_size = 0;
+ if (!(prefs->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size) &&
+ manifest_metadata_size > 0))
+ return false;
+
+ return true;
+}
+
+bool DeltaPerformer::ResetUpdateProgress(PrefsInterface* prefs, bool quick) {
+ TEST_AND_RETURN_FALSE(prefs->SetInt64(kPrefsUpdateStateNextOperation,
+ kUpdateStateOperationInvalid));
+ if (!quick) {
+ prefs->SetString(kPrefsUpdateCheckResponseHash, "");
+ prefs->SetInt64(kPrefsUpdateStateNextDataOffset, -1);
+ prefs->SetInt64(kPrefsUpdateStateNextDataLength, 0);
+ prefs->SetString(kPrefsUpdateStateSHA256Context, "");
+ prefs->SetString(kPrefsUpdateStateSignedSHA256Context, "");
+ prefs->SetString(kPrefsUpdateStateSignatureBlob, "");
+ prefs->SetInt64(kPrefsManifestMetadataSize, -1);
+ prefs->SetInt64(kPrefsResumedUpdateFailures, 0);
+ }
+ return true;
+}
+
+bool DeltaPerformer::CheckpointUpdateProgress() {
+ Terminator::set_exit_blocked(true);
+ if (last_updated_buffer_offset_ != buffer_offset_) {
+ // Resets the progress in case we die in the middle of the state update.
+ ResetUpdateProgress(prefs_, true);
+ TEST_AND_RETURN_FALSE(
+ prefs_->SetString(kPrefsUpdateStateSHA256Context,
+ payload_hash_calculator_.GetContext()));
+ TEST_AND_RETURN_FALSE(
+ prefs_->SetString(kPrefsUpdateStateSignedSHA256Context,
+ signed_hash_calculator_.GetContext()));
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataOffset,
+ buffer_offset_));
+ last_updated_buffer_offset_ = buffer_offset_;
+
+ if (next_operation_num_ < num_total_operations_) {
+ size_t partition_index = current_partition_;
+ 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);
+ const InstallOperation& op =
+ partitions_[partition_index].operations(partition_operation_num);
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+ op.data_length()));
+ } else {
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+ 0));
+ }
+ }
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextOperation,
+ next_operation_num_));
+ return true;
+}
+
+bool DeltaPerformer::PrimeUpdateState() {
+ CHECK(manifest_valid_);
+ block_size_ = manifest_.block_size();
+
+ int64_t next_operation = kUpdateStateOperationInvalid;
+ if (!prefs_->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) ||
+ next_operation == kUpdateStateOperationInvalid ||
+ next_operation <= 0) {
+ // Initiating a new update, no more state needs to be initialized.
+ return true;
+ }
+ next_operation_num_ = next_operation;
+
+ // Resuming an update -- load the rest of the update state.
+ int64_t next_data_offset = -1;
+ TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsUpdateStateNextDataOffset,
+ &next_data_offset) &&
+ next_data_offset >= 0);
+ buffer_offset_ = next_data_offset;
+
+ // The signed hash context and the signature blob may be empty if the
+ // interrupted update didn't reach the signature.
+ string signed_hash_context;
+ if (prefs_->GetString(kPrefsUpdateStateSignedSHA256Context,
+ &signed_hash_context)) {
+ TEST_AND_RETURN_FALSE(
+ signed_hash_calculator_.SetContext(signed_hash_context));
+ }
+
+ string signature_blob;
+ if (prefs_->GetString(kPrefsUpdateStateSignatureBlob, &signature_blob)) {
+ signatures_message_data_.assign(signature_blob.begin(),
+ signature_blob.end());
+ }
+
+ string hash_context;
+ TEST_AND_RETURN_FALSE(prefs_->GetString(kPrefsUpdateStateSHA256Context,
+ &hash_context) &&
+ payload_hash_calculator_.SetContext(hash_context));
+
+ int64_t manifest_metadata_size = 0;
+ TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsManifestMetadataSize,
+ &manifest_metadata_size) &&
+ manifest_metadata_size > 0);
+ metadata_size_ = manifest_metadata_size;
+
+ // Advance the download progress to reflect what doesn't need to be
+ // re-downloaded.
+ total_bytes_received_ += buffer_offset_;
+
+ // Speculatively count the resume as a failure.
+ int64_t resumed_update_failures;
+ if (prefs_->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures)) {
+ resumed_update_failures++;
+ } else {
+ resumed_update_failures = 1;
+ }
+ prefs_->SetInt64(kPrefsResumedUpdateFailures, resumed_update_failures);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h
new file mode 100644
index 0000000..47ecfd8
--- /dev/null
+++ b/payload_consumer/delta_performer.h
@@ -0,0 +1,407 @@
+//
+// 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_CONSUMER_DELTA_PERFORMER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_DELTA_PERFORMER_H_
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+#include <brillo/secure_blob.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/platform_constants.h"
+#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/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class DownloadActionDelegate;
+class BootControlInterface;
+class HardwareInterface;
+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:
+ enum MetadataParseResult {
+ kMetadataParseSuccess,
+ kMetadataParseError,
+ kMetadataParseInsufficientData,
+ };
+
+ static const uint64_t kDeltaVersionOffset;
+ static const uint64_t kDeltaVersionSize;
+ static const uint64_t kDeltaManifestSizeOffset;
+ static const uint64_t kDeltaManifestSizeSize;
+ static const uint64_t kDeltaMetadataSignatureSizeSize;
+ static const uint64_t kMaxPayloadHeaderSize;
+ static const uint64_t kSupportedMajorPayloadVersion;
+ static const uint32_t kSupportedMinorPayloadVersion;
+
+ // Defines the granularity of progress logging in terms of how many "completed
+ // chunks" we want to report at the most.
+ static const unsigned kProgressLogMaxChunks;
+ // Defines a timeout since the last progress was logged after which we want to
+ // force another log message (even if the current chunk was not completed).
+ static const unsigned kProgressLogTimeoutSeconds;
+ // These define the relative weights (0-100) we give to the different work
+ // components associated with an update when computing an overall progress.
+ // Currently they include the download progress and the number of completed
+ // operations. They must add up to one hundred (100).
+ static const unsigned kProgressDownloadWeight;
+ static const unsigned kProgressOperationsWeight;
+
+ DeltaPerformer(PrefsInterface* prefs,
+ BootControlInterface* boot_control,
+ HardwareInterface* hardware,
+ DownloadActionDelegate* download_delegate,
+ InstallPlan* install_plan)
+ : prefs_(prefs),
+ boot_control_(boot_control),
+ hardware_(hardware),
+ download_delegate_(download_delegate),
+ install_plan_(install_plan) {}
+
+ // FileWriter's Write implementation where caller doesn't care about
+ // error codes.
+ bool Write(const void* bytes, size_t count) override {
+ ErrorCode error;
+ return Write(bytes, count, &error);
+ }
+
+ // FileWriter's Write implementation that returns a more specific |error| code
+ // in case of failures in Write operation.
+ bool Write(const void* bytes, size_t count, ErrorCode *error) override;
+
+ // Wrapper around close. Returns 0 on success or -errno on error.
+ // Closes both 'path' given to Open() and the kernel path.
+ int Close() override;
+
+ // Open the target and source (if delta payload) file descriptors for the
+ // |current_partition_|. The manifest needs to be already parsed for this to
+ // work. Returns whether the required file descriptors were successfully open.
+ bool OpenCurrentPartition();
+
+ // Closes the current partition file descriptors if open. Returns 0 on success
+ // or -errno on error.
+ int CloseCurrentPartition();
+
+ // Returns |true| only if the manifest has been processed and it's valid.
+ bool IsManifestValid();
+
+ // Verifies the downloaded payload against the signed hash included in the
+ // payload, against the update check hash (which is in base64 format) and
+ // size using the public key and returns ErrorCode::kSuccess on success, an
+ // error code on failure. This method should be called after closing the
+ // stream. Note this method skips the signed hash check if the public key is
+ // unavailable; it returns ErrorCode::kSignedDeltaPayloadExpectedError if the
+ // public key is available but the delta payload doesn't include a signature.
+ ErrorCode VerifyPayload(const std::string& update_check_response_hash,
+ const uint64_t update_check_response_size);
+
+ // Converts an ordered collection of Extent objects which contain data of
+ // length full_length to a comma-separated string. For each Extent, the
+ // string will have the start offset and then the length in bytes.
+ // The length value of the last extent in the string may be short, since
+ // the full length of all extents in the string is capped to full_length.
+ // Also, an extent starting at kSparseHole, appears as -1 in the string.
+ // For example, if the Extents are {1, 1}, {4, 2}, {kSparseHole, 1},
+ // {0, 1}, block_size is 4096, and full_length is 5 * block_size - 13,
+ // the resulting string will be: "4096:4096,16384:8192,-1:4096,0:4083"
+ static bool ExtentsToBsdiffPositionsString(
+ const google::protobuf::RepeatedPtrField<Extent>& extents,
+ uint64_t block_size,
+ uint64_t full_length,
+ std::string* positions_string);
+
+ // Returns true if a previous update attempt can be continued based on the
+ // persistent preferences and the new update check response hash.
+ static bool CanResumeUpdate(PrefsInterface* prefs,
+ std::string update_check_response_hash);
+
+ // Resets the persistent update progress state to indicate that an update
+ // can't be resumed. Performs a quick update-in-progress reset if |quick| is
+ // true, otherwise resets all progress-related update state. Returns true on
+ // success, false otherwise.
+ static bool ResetUpdateProgress(PrefsInterface* prefs, bool quick);
+
+ // Attempts to parse the update metadata starting from the beginning of
+ // |payload|. On success, returns kMetadataParseSuccess. Returns
+ // kMetadataParseInsufficientData if more data is needed to parse the complete
+ // metadata. Returns kMetadataParseError if the metadata can't be parsed given
+ // the payload.
+ MetadataParseResult ParsePayloadMetadata(const brillo::Blob& payload,
+ ErrorCode* error);
+
+ void set_public_key_path(const std::string& public_key_path) {
+ public_key_path_ = public_key_path;
+ }
+
+ // 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;
+
+ // 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;
+
+ // Returns the size of the payload metadata, which includes the payload header
+ // and the manifest. If the header was not yet parsed, returns zero.
+ uint64_t GetMetadataSize() const;
+
+ // If the manifest was successfully parsed, copies it to |*out_manifest_p|.
+ // Returns true on success.
+ bool GetManifest(DeltaArchiveManifest* out_manifest_p) const;
+
+ // Return true if header parsing is finished and no errors occurred.
+ bool IsHeaderParsed() const;
+
+ // Returns the major payload version. If the version was not yet parsed,
+ // returns zero.
+ uint64_t GetMajorVersion() 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;
+
+ private:
+ friend class DeltaPerformerTest;
+ friend class DeltaPerformerIntegrationTest;
+ FRIEND_TEST(DeltaPerformerTest, BrilloMetadataSignatureSizeTest);
+ FRIEND_TEST(DeltaPerformerTest, BrilloVerifyMetadataSignatureTest);
+ FRIEND_TEST(DeltaPerformerTest, UsePublicKeyFromResponse);
+
+ // 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.
+ bool ParseManifestPartitions(ErrorCode* error);
+
+ // Appends up to |*count_p| bytes from |*bytes_p| to |buffer_|, but only to
+ // the extent that the size of |buffer_| does not exceed |max|. Advances
+ // |*cbytes_p| and decreases |*count_p| by the actual number of bytes copied,
+ // and returns this number.
+ size_t CopyDataToBuffer(const char** bytes_p, size_t* count_p, size_t max);
+
+ // If |op_result| is false, emits an error message using |op_type_name| and
+ // sets |*error| accordingly. Otherwise does nothing. Returns |op_result|.
+ bool HandleOpResult(bool op_result, const char* op_type_name,
+ ErrorCode* error);
+
+ // Logs the progress of downloading/applying an update.
+ void LogProgress(const char* message_prefix);
+
+ // Update overall progress metrics, log as necessary.
+ void UpdateOverallProgress(bool force_log, const char* message_prefix);
+
+ // Verifies that the expected source partition hashes (if present) match the
+ // hashes for the current partitions. Returns true if there are no expected
+ // hashes in the payload (e.g., if it's a new-style full update) or if the
+ // hashes match; returns false otherwise.
+ bool VerifySourcePartitions();
+
+ // Returns true if enough of the delta file has been passed via Write()
+ // to be able to perform a given install operation.
+ bool CanPerformInstallOperation(const InstallOperation& operation);
+
+ // Checks the integrity of the payload manifest. Returns true upon success,
+ // false otherwise.
+ ErrorCode ValidateManifest();
+
+ // Validates that the hash of the blobs corresponding to the given |operation|
+ // matches what's specified in the manifest in the payload.
+ // Returns ErrorCode::kSuccess on match or a suitable error code otherwise.
+ ErrorCode ValidateOperationHash(const InstallOperation& operation);
+
+ // Given the |payload|, verifies that the signed hash of its metadata matches
+ // what's specified in the install plan from Omaha (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 to the payload server
+ // doesn't exploit any vulnerability in the code that parses the protocol
+ // buffer.
+ ErrorCode ValidateMetadataSignature(const brillo::Blob& payload);
+
+ // Returns true on success.
+ bool PerformInstallOperation(const InstallOperation& operation);
+
+ // These perform a specific type of operation and return true on success.
+ 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);
+ bool PerformSourceBsdiffOperation(const InstallOperation& operation);
+
+ // 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.
+ bool ExtractSignatureMessage();
+
+ // Updates the payload hash calculator with the bytes in |buffer_|, also
+ // updates the signed hash calculator with the first |signed_hash_buffer_size|
+ // bytes in |buffer_|. Then discard the content, ensuring that memory is being
+ // deallocated. If |do_advance_offset|, advances the internal offset counter
+ // 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.
+ bool CheckpointUpdateProgress();
+
+ // 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.
+ bool PrimeUpdateState();
+
+ // If the Omaha response contains a public RSA key and we're allowed
+ // to use it (e.g. if we're in developer mode), extract the key from
+ // the response and store it in a temporary file and return true. In
+ // the affirmative the path to the temporary file is stored in
+ // |out_tmp_key| and it is the responsibility of the caller to clean
+ // it up.
+ bool GetPublicKeyFromResponse(base::FilePath *out_tmp_key);
+
+ // Update Engine preference store.
+ PrefsInterface* prefs_;
+
+ // BootControl and Hardware interface references.
+ BootControlInterface* boot_control_;
+ HardwareInterface* hardware_;
+
+ // The DownloadActionDelegate instance monitoring the DownloadAction, or a
+ // nullptr if not used.
+ DownloadActionDelegate* download_delegate_;
+
+ // Install Plan based on Omaha Response.
+ InstallPlan* install_plan_;
+
+ // 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 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_;
+
+ // Parsed manifest. Set after enough bytes to parse the manifest were
+ // downloaded.
+ DeltaArchiveManifest manifest_;
+ bool manifest_parsed_{false};
+ bool manifest_valid_{false};
+ uint64_t metadata_size_{0};
+ uint64_t manifest_size_{0};
+ uint32_t metadata_signature_size_{0};
+ uint64_t major_payload_version_{0};
+
+ // Accumulated number of operations per partition. The i-th element is the
+ // sum of the number of operations for all the partitions from 0 to i
+ // inclusive. Valid when |manifest_valid_| is true.
+ std::vector<size_t> acc_num_operations_;
+
+ // The total operations in a payload. Valid when |manifest_valid_| is true,
+ // 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.
+ 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.
+ 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.
+ 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_{kuint64max};
+
+ // The block size (parsed from the manifest).
+ uint32_t block_size_{0};
+
+ // Calculates the whole payload file hash, including headers and signatures.
+ HashCalculator payload_hash_calculator_;
+
+ // Calculates the hash of the portion of the payload signed by the payload
+ // signature. This hash skips the metadata signature portion, located after
+ // the metadata and doesn't include the payload signature itself.
+ HashCalculator signed_hash_calculator_;
+
+ // Signatures message blob extracted directly from the payload.
+ brillo::Blob signatures_message_data_;
+
+ // The public key to be used. Provided as a member so that tests can
+ // override with test keys.
+ std::string public_key_path_{constants::kUpdatePayloadPublicKeyPath};
+
+ // The number of bytes received so far, used for progress tracking.
+ size_t total_bytes_received_{0};
+
+ // An overall progress counter, which should reflect both download progress
+ // and the ratio of applied operations. Range is 0-100.
+ unsigned overall_progress_{0};
+
+ // The last progress chunk recorded.
+ unsigned last_progress_chunk_{0};
+
+ // 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::Time forced_progress_log_time_;
+
+ // The payload major payload version supported by DeltaPerformer.
+ uint64_t supported_major_version_{kSupportedMajorPayloadVersion};
+
+ // The delta minor payload version supported by DeltaPerformer.
+ uint32_t supported_minor_version_{kSupportedMinorPayloadVersion};
+
+ DISALLOW_COPY_AND_ASSIGN(DeltaPerformer);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_DELTA_PERFORMER_H_
diff --git a/payload_consumer/delta_performer_integration_test.cc b/payload_consumer/delta_performer_integration_test.cc
new file mode 100644
index 0000000..bcf7db2
--- /dev/null
+++ b/payload_consumer/delta_performer_integration_test.cc
@@ -0,0 +1,1035 @@
+//
+// 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_consumer/delta_performer.h"
+
+#include <inttypes.h>
+#include <sys/mount.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest.h>
+
+#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/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/payload_constants.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+using test_utils::ScopedLoopMounter;
+using test_utils::System;
+using test_utils::kRandomString;
+using testing::Return;
+using testing::_;
+
+extern const char* kUnittestPrivateKeyPath;
+extern const char* kUnittestPublicKeyPath;
+extern const char* kUnittestPrivateKey2Path;
+extern const char* kUnittestPublicKey2Path;
+
+static const int kDefaultKernelSize = 4096; // Something small for a test
+static const uint8_t kNewData[] = {'T', 'h', 'i', 's', ' ', 'i', 's', ' ',
+ 'n', 'e', 'w', ' ', 'd', 'a', 't', 'a', '.'};
+
+namespace {
+struct DeltaState {
+ string a_img;
+ string b_img;
+ string result_img;
+ size_t image_size;
+
+ string delta_path;
+ uint64_t metadata_size;
+
+ string old_kernel;
+ brillo::Blob old_kernel_data;
+
+ string new_kernel;
+ brillo::Blob new_kernel_data;
+
+ string result_kernel;
+ brillo::Blob result_kernel_data;
+ size_t kernel_size;
+
+ // The InstallPlan referenced by the DeltaPerformer. This needs to outlive
+ // 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_;
+ MockDownloadActionDelegate mock_delegate_;
+};
+
+enum SignatureTest {
+ kSignatureNone, // No payload signing.
+ kSignatureGenerator, // Sign the payload at generation time.
+ kSignatureGenerated, // Sign the payload after it's generated.
+ kSignatureGeneratedPlaceholder, // Insert placeholder signatures, then real.
+ kSignatureGeneratedPlaceholderMismatch, // Insert a wrong sized placeholder.
+ kSignatureGeneratedShell, // Sign the generated payload through shell cmds.
+ kSignatureGeneratedShellBadKey, // Sign with a bad key through shell cmds.
+ kSignatureGeneratedShellRotateCl1, // Rotate key, test client v1
+ kSignatureGeneratedShellRotateCl2, // Rotate key, test client v2
+};
+
+enum OperationHashTest {
+ kInvalidOperationData,
+ kValidOperationData,
+};
+
+} // namespace
+
+class DeltaPerformerIntegrationTest : public ::testing::Test {
+ public:
+ static void SetSupportedVersion(DeltaPerformer* performer,
+ uint64_t minor_version) {
+ performer->supported_minor_version_ = minor_version;
+ }
+};
+
+static void CompareFilesByBlock(const string& a_file, const string& b_file,
+ size_t image_size) {
+ EXPECT_EQ(0, image_size % kBlockSize);
+
+ brillo::Blob a_data, b_data;
+ EXPECT_TRUE(utils::ReadFile(a_file, &a_data)) << "file failed: " << a_file;
+ EXPECT_TRUE(utils::ReadFile(b_file, &b_data)) << "file failed: " << b_file;
+
+ EXPECT_GE(a_data.size(), image_size);
+ EXPECT_GE(b_data.size(), image_size);
+ for (size_t i = 0; i < image_size; i += kBlockSize) {
+ EXPECT_EQ(0, i % kBlockSize);
+ brillo::Blob a_sub(&a_data[i], &a_data[i + kBlockSize]);
+ brillo::Blob b_sub(&b_data[i], &b_data[i + kBlockSize]);
+ EXPECT_TRUE(a_sub == b_sub) << "Block " << (i/kBlockSize) << " differs";
+ }
+ if (::testing::Test::HasNonfatalFailure()) {
+ LOG(INFO) << "Compared filesystems with size " << image_size
+ << ", partition A " << a_file << " size: " << a_data.size()
+ << ", partition B " << b_file << " size: " << b_data.size();
+ }
+}
+
+static bool WriteSparseFile(const string& path, off_t size) {
+ int fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
+ TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+ ScopedFdCloser fd_closer(&fd);
+ off_t rc = lseek(fd, size + 1, SEEK_SET);
+ TEST_AND_RETURN_FALSE_ERRNO(rc != static_cast<off_t>(-1));
+ int return_code = ftruncate(fd, size);
+ TEST_AND_RETURN_FALSE_ERRNO(return_code == 0);
+ return true;
+}
+
+static size_t GetSignatureSize(const string& private_key_path) {
+ const brillo::Blob data(1, 'x');
+ brillo::Blob hash;
+ EXPECT_TRUE(HashCalculator::RawHashOfData(data, &hash));
+ brillo::Blob signature;
+ EXPECT_TRUE(PayloadSigner::SignHash(hash,
+ private_key_path,
+ &signature));
+ return signature.size();
+}
+
+static bool InsertSignaturePlaceholder(int signature_size,
+ const string& payload_path,
+ uint64_t* out_metadata_size) {
+ vector<brillo::Blob> signatures;
+ signatures.push_back(brillo::Blob(signature_size, 0));
+
+ return PayloadSigner::AddSignatureToPayload(
+ payload_path,
+ signatures,
+ {},
+ payload_path,
+ out_metadata_size);
+}
+
+static void SignGeneratedPayload(const string& payload_path,
+ uint64_t* out_metadata_size) {
+ int signature_size = GetSignatureSize(kUnittestPrivateKeyPath);
+ brillo::Blob hash;
+ ASSERT_TRUE(PayloadSigner::HashPayloadForSigning(
+ payload_path,
+ vector<int>(1, signature_size),
+ &hash,
+ nullptr));
+ brillo::Blob signature;
+ ASSERT_TRUE(PayloadSigner::SignHash(hash,
+ kUnittestPrivateKeyPath,
+ &signature));
+ ASSERT_TRUE(PayloadSigner::AddSignatureToPayload(
+ payload_path,
+ vector<brillo::Blob>(1, signature),
+ {},
+ payload_path,
+ out_metadata_size));
+ EXPECT_TRUE(PayloadSigner::VerifySignedPayload(
+ payload_path,
+ kUnittestPublicKeyPath));
+}
+
+static void SignGeneratedShellPayload(SignatureTest signature_test,
+ const string& payload_path) {
+ string private_key_path = kUnittestPrivateKeyPath;
+ if (signature_test == kSignatureGeneratedShellBadKey) {
+ ASSERT_TRUE(utils::MakeTempFile("key.XXXXXX",
+ &private_key_path,
+ nullptr));
+ } else {
+ ASSERT_TRUE(signature_test == kSignatureGeneratedShell ||
+ signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2);
+ }
+ ScopedPathUnlinker key_unlinker(private_key_path);
+ key_unlinker.set_should_remove(signature_test ==
+ kSignatureGeneratedShellBadKey);
+ // Generates a new private key that will not match the public key.
+ if (signature_test == kSignatureGeneratedShellBadKey) {
+ LOG(INFO) << "Generating a mismatched private key.";
+ ASSERT_EQ(0, System(base::StringPrintf(
+ "openssl genrsa -out %s 2048", private_key_path.c_str())));
+ }
+ int signature_size = GetSignatureSize(private_key_path);
+ string hash_file;
+ ASSERT_TRUE(utils::MakeTempFile("hash.XXXXXX", &hash_file, nullptr));
+ ScopedPathUnlinker hash_unlinker(hash_file);
+ string signature_size_string;
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2)
+ signature_size_string = base::StringPrintf("%d:%d",
+ signature_size, signature_size);
+ else
+ signature_size_string = base::StringPrintf("%d", signature_size);
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "./delta_generator -in_file=%s -signature_size=%s "
+ "-out_hash_file=%s",
+ payload_path.c_str(),
+ signature_size_string.c_str(),
+ hash_file.c_str())));
+
+ // Pad the hash
+ brillo::Blob hash;
+ ASSERT_TRUE(utils::ReadFile(hash_file, &hash));
+ ASSERT_TRUE(PayloadVerifier::PadRSA2048SHA256Hash(&hash));
+ ASSERT_TRUE(test_utils::WriteFileVector(hash_file, hash));
+
+ string sig_file;
+ ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file, nullptr));
+ ScopedPathUnlinker sig_unlinker(sig_file);
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "openssl rsautl -raw -sign -inkey %s -in %s -out %s",
+ private_key_path.c_str(),
+ hash_file.c_str(),
+ sig_file.c_str())));
+ string sig_file2;
+ ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file2, nullptr));
+ ScopedPathUnlinker sig2_unlinker(sig_file2);
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2) {
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "openssl rsautl -raw -sign -inkey %s -in %s -out %s",
+ kUnittestPrivateKey2Path,
+ hash_file.c_str(),
+ sig_file2.c_str())));
+ // Append second sig file to first path
+ sig_file += ":" + sig_file2;
+ }
+
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "./delta_generator -in_file=%s -signature_file=%s "
+ "-out_file=%s",
+ payload_path.c_str(),
+ sig_file.c_str(),
+ payload_path.c_str())));
+ int verify_result =
+ System(base::StringPrintf(
+ "./delta_generator -in_file=%s -public_key=%s -public_key_version=%d",
+ payload_path.c_str(),
+ signature_test == kSignatureGeneratedShellRotateCl2 ?
+ kUnittestPublicKey2Path : kUnittestPublicKeyPath,
+ signature_test == kSignatureGeneratedShellRotateCl2 ? 2 : 1));
+ if (signature_test == kSignatureGeneratedShellBadKey) {
+ ASSERT_NE(0, verify_result);
+ } else {
+ ASSERT_EQ(0, verify_result);
+ }
+}
+
+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));
+
+ // 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));
+ test_utils::CreateExtImageAtPath(state->a_img, nullptr);
+
+ 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");
+ }
+
+ 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");
+
+ // Make some changes to the A image.
+ {
+ string a_mnt;
+ ScopedLoopMounter b_mounter(state->a_img, &a_mnt, 0);
+
+ brillo::Blob hardtocompress;
+ while (hardtocompress.size() < 3 * kBlockSize) {
+ hardtocompress.insert(hardtocompress.end(),
+ std::begin(kRandomString), std::end(kRandomString));
+ }
+ EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/hardtocompress",
+ a_mnt.c_str()).c_str(),
+ hardtocompress.data(),
+ hardtocompress.size()));
+
+ brillo::Blob zeros(16 * 1024, 0);
+ EXPECT_EQ(zeros.size(),
+ base::WriteFile(base::FilePath(base::StringPrintf(
+ "%s/move-to-sparse", a_mnt.c_str())),
+ reinterpret_cast<const char*>(zeros.data()),
+ zeros.size()));
+
+ EXPECT_TRUE(
+ WriteSparseFile(base::StringPrintf("%s/move-from-sparse",
+ a_mnt.c_str()), 16 * 1024));
+
+ EXPECT_EQ(0,
+ System(base::StringPrintf("dd if=/dev/zero of=%s/move-semi-sparse"
+ " bs=1 seek=4096 count=1 status=none",
+ a_mnt.c_str()).c_str()));
+
+ // Write 1 MiB of 0xff to try to catch the case where writing a bsdiff
+ // patch fails to zero out the final block.
+ brillo::Blob ones(1024 * 1024, 0xff);
+ EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/ones",
+ a_mnt.c_str()).c_str(),
+ ones.data(),
+ 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));
+ }
+
+ test_utils::CreateExtImageAtPath(state->b_img, nullptr);
+
+ // Make some changes to the B image.
+ string b_mnt;
+ ScopedLoopMounter b_mounter(state->b_img, &b_mnt, 0);
+
+ EXPECT_EQ(0, System(base::StringPrintf("cp %s/hello %s/hello2",
+ b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("rm %s/hello",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("mv %s/hello2 %s/hello",
+ b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("echo foo > %s/foo",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("touch %s/emptyfile",
+ b_mnt.c_str()).c_str()));
+ EXPECT_TRUE(WriteSparseFile(base::StringPrintf("%s/fullsparse",
+ b_mnt.c_str()),
+ 1024 * 1024));
+
+ EXPECT_TRUE(
+ WriteSparseFile(base::StringPrintf("%s/move-to-sparse", b_mnt.c_str()),
+ 16 * 1024));
+
+ brillo::Blob zeros(16 * 1024, 0);
+ EXPECT_EQ(zeros.size(),
+ base::WriteFile(base::FilePath(base::StringPrintf(
+ "%s/move-from-sparse", b_mnt.c_str())),
+ reinterpret_cast<const char*>(zeros.data()),
+ zeros.size()));
+
+ EXPECT_EQ(0, System(base::StringPrintf("dd if=/dev/zero "
+ "of=%s/move-semi-sparse "
+ "bs=1 seek=4096 count=1 status=none",
+ b_mnt.c_str()).c_str()));
+
+ EXPECT_EQ(0, System(base::StringPrintf("dd if=/dev/zero "
+ "of=%s/partsparse bs=1 "
+ "seek=4096 count=1 status=none",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("cp %s/srchardlink0 %s/tmp && "
+ "mv %s/tmp %s/srchardlink1",
+ b_mnt.c_str(),
+ b_mnt.c_str(),
+ b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(
+ base::StringPrintf("rm %s/boguslink && echo foobar > %s/boguslink",
+ b_mnt.c_str(), b_mnt.c_str()).c_str()));
+
+ brillo::Blob hardtocompress;
+ while (hardtocompress.size() < 3 * kBlockSize) {
+ hardtocompress.insert(hardtocompress.end(),
+ std::begin(kRandomString), std::end(kRandomString));
+ }
+ EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/hardtocompress",
+ b_mnt.c_str()).c_str(),
+ hardtocompress.data(),
+ 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->kernel_size = kDefaultKernelSize;
+ state->old_kernel_data.resize(kDefaultKernelSize);
+ state->new_kernel_data.resize(state->old_kernel_data.size());
+ state->result_kernel_data.resize(state->old_kernel_data.size());
+ test_utils::FillWithData(&state->old_kernel_data);
+ test_utils::FillWithData(&state->new_kernel_data);
+ test_utils::FillWithData(&state->result_kernel_data);
+
+ // change the new kernel data
+ 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(),
+ state->old_kernel_data.data(),
+ state->old_kernel_data.size()));
+ EXPECT_TRUE(utils::WriteFile(state->new_kernel.c_str(),
+ state->new_kernel_data.data(),
+ state->new_kernel_data.size()));
+ EXPECT_TRUE(utils::WriteFile(state->result_kernel.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;
+ {
+ const string private_key =
+ signature_test == kSignatureGenerator ? kUnittestPrivateKeyPath : "";
+
+ PayloadGenerationConfig payload_config;
+ payload_config.is_delta = !full_rootfs;
+ payload_config.hard_chunk_size = chunk_size;
+ payload_config.rootfs_partition_size = kRootFSPartitionSize;
+ payload_config.major_version = kChromeOSMajorPayloadVersion;
+ payload_config.minor_version = minor_version;
+ if (!full_rootfs) {
+ payload_config.source.partitions.emplace_back(kLegacyPartitionNameRoot);
+ payload_config.source.partitions.emplace_back(kLegacyPartitionNameKernel);
+ payload_config.source.partitions.front().path = state->a_img;
+ if (!full_kernel)
+ payload_config.source.partitions.back().path = state->old_kernel;
+ payload_config.source.image_info = old_image_info;
+ EXPECT_TRUE(payload_config.source.LoadImageSize());
+ for (PartitionConfig& part : payload_config.source.partitions)
+ EXPECT_TRUE(part.OpenFilesystem());
+ } else {
+ if (payload_config.hard_chunk_size == -1)
+ // Use 1 MiB chunk size for the full unittests.
+ payload_config.hard_chunk_size = 1024 * 1024;
+ }
+ payload_config.target.partitions.emplace_back(kLegacyPartitionNameRoot);
+ payload_config.target.partitions.back().path = state->b_img;
+ payload_config.target.partitions.emplace_back(kLegacyPartitionNameKernel);
+ payload_config.target.partitions.back().path = state->new_kernel;
+ payload_config.target.image_info = new_image_info;
+ 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));
+ }
+ // Extend the "partitions" holding the file system a bit.
+ EXPECT_EQ(0, HANDLE_EINTR(truncate(state->a_img.c_str(),
+ state->image_size + 1024 * 1024)));
+ EXPECT_EQ(state->image_size + 1024 * 1024, utils::FileSize(state->a_img));
+ EXPECT_EQ(0, HANDLE_EINTR(truncate(state->b_img.c_str(),
+ state->image_size + 1024 * 1024)));
+ EXPECT_EQ(state->image_size + 1024 * 1024, utils::FileSize(state->b_img));
+
+ if (signature_test == kSignatureGeneratedPlaceholder ||
+ signature_test == kSignatureGeneratedPlaceholderMismatch) {
+ int signature_size = GetSignatureSize(kUnittestPrivateKeyPath);
+ LOG(INFO) << "Inserting placeholder signature.";
+ ASSERT_TRUE(InsertSignaturePlaceholder(signature_size, state->delta_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));
+ return;
+ }
+ }
+
+ if (signature_test == kSignatureGenerated ||
+ signature_test == kSignatureGeneratedPlaceholder ||
+ signature_test == kSignatureGeneratedPlaceholderMismatch) {
+ // Generate the signed payload and update the metadata size in state to
+ // reflect the new size after adding the signature operation to the
+ // manifest.
+ LOG(INFO) << "Signing payload.";
+ SignGeneratedPayload(state->delta_path, &state->metadata_size);
+ } else if (signature_test == kSignatureGeneratedShell ||
+ signature_test == kSignatureGeneratedShellBadKey ||
+ signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2) {
+ SignGeneratedShellPayload(signature_test, state->delta_path);
+ }
+}
+
+static void ApplyDeltaFile(bool full_kernel, bool full_rootfs, bool noop,
+ SignatureTest signature_test, DeltaState* state,
+ bool hash_checks_mandatory,
+ OperationHashTest op_hash_test,
+ DeltaPerformer** performer,
+ uint32_t minor_version) {
+ // Check the metadata.
+ {
+ DeltaArchiveManifest manifest;
+ EXPECT_TRUE(PayloadSigner::LoadPayload(state->delta_path,
+ &state->delta,
+ &manifest,
+ nullptr,
+ &state->metadata_size,
+ nullptr));
+ LOG(INFO) << "Metadata size: " << state->metadata_size;
+
+ if (signature_test == kSignatureNone) {
+ EXPECT_FALSE(manifest.has_signatures_offset());
+ EXPECT_FALSE(manifest.has_signatures_size());
+ } else {
+ EXPECT_TRUE(manifest.has_signatures_offset());
+ EXPECT_TRUE(manifest.has_signatures_size());
+ Signatures sigs_message;
+ EXPECT_TRUE(sigs_message.ParseFromArray(
+ &state->delta[state->metadata_size + manifest.signatures_offset()],
+ manifest.signatures_size()));
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2)
+ EXPECT_EQ(2, sigs_message.signatures_size());
+ else
+ EXPECT_EQ(1, sigs_message.signatures_size());
+ const Signatures_Signature& signature = sigs_message.signatures(0);
+ EXPECT_EQ(1, signature.version());
+
+ uint64_t expected_sig_data_length = 0;
+ vector<string> key_paths{kUnittestPrivateKeyPath};
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2) {
+ key_paths.push_back(kUnittestPrivateKey2Path);
+ }
+ EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+ key_paths,
+ &expected_sig_data_length));
+ EXPECT_EQ(expected_sig_data_length, manifest.signatures_size());
+ EXPECT_FALSE(signature.data().empty());
+ }
+
+ if (noop) {
+ EXPECT_EQ(0, manifest.install_operations_size());
+ EXPECT_EQ(1, manifest.kernel_install_operations_size());
+ }
+
+ if (full_kernel) {
+ EXPECT_FALSE(manifest.has_old_kernel_info());
+ } else {
+ EXPECT_EQ(state->old_kernel_data.size(),
+ manifest.old_kernel_info().size());
+ EXPECT_FALSE(manifest.old_kernel_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");
+ }
+ }
+
+
+ if (full_rootfs) {
+ EXPECT_FALSE(manifest.has_old_rootfs_info());
+ EXPECT_FALSE(manifest.has_old_image_info());
+ EXPECT_TRUE(manifest.has_new_image_info());
+ } else {
+ EXPECT_EQ(state->image_size, manifest.old_rootfs_info().size());
+ EXPECT_FALSE(manifest.old_rootfs_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());
+ }
+
+ MockPrefs prefs;
+ EXPECT_CALL(prefs, SetInt64(kPrefsManifestMetadataSize,
+ state->metadata_size)).WillOnce(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextOperation, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStateNextOperation, _))
+ .WillOnce(Return(false));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataOffset, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataLength, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSHA256Context, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignedSHA256Context, _))
+ .WillRepeatedly(Return(true));
+ if (op_hash_test == kValidOperationData && signature_test != kSignatureNone) {
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignatureBlob, _))
+ .WillOnce(Return(true));
+ }
+
+ EXPECT_CALL(state->mock_delegate_, ShouldCancel(_))
+ .WillRepeatedly(Return(false));
+
+ // Update the A image in place.
+ InstallPlan* install_plan = &state->install_plan;
+ install_plan->hash_checks_mandatory = hash_checks_mandatory;
+ install_plan->metadata_size = state->metadata_size;
+ install_plan->is_full_update = full_kernel && full_rootfs;
+ install_plan->source_slot = 0;
+ install_plan->target_slot = 1;
+
+ InstallPlan::Partition root_part;
+ root_part.name = kLegacyPartitionNameRoot;
+
+ InstallPlan::Partition kernel_part;
+ kernel_part.name = kLegacyPartitionNameKernel;
+
+ LOG(INFO) << "Setting payload metadata size in Omaha = "
+ << state->metadata_size;
+ ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
+ state->delta.data(),
+ state->metadata_size,
+ kUnittestPrivateKeyPath,
+ &install_plan->metadata_signature));
+ EXPECT_FALSE(install_plan->metadata_signature.empty());
+
+ *performer = new DeltaPerformer(&prefs,
+ &state->fake_boot_control_,
+ &state->fake_hardware_,
+ &state->mock_delegate_,
+ install_plan);
+ EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
+ (*performer)->set_public_key_path(kUnittestPublicKeyPath);
+ DeltaPerformerIntegrationTest::SetSupportedVersion(*performer, minor_version);
+
+ EXPECT_EQ(state->image_size,
+ HashCalculator::RawHashOfFile(
+ state->a_img,
+ state->image_size,
+ &root_part.source_hash));
+ EXPECT_TRUE(HashCalculator::RawHashOfData(
+ state->old_kernel_data,
+ &kernel_part.source_hash));
+
+ // This partitions are normally filed by the FilesystemVerifierAction with
+ // the source hashes used for deltas.
+ install_plan->partitions = {root_part, kernel_part};
+
+ // 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(
+ kLegacyPartitionNameRoot, install_plan->source_slot, state->a_img);
+ state->fake_boot_control_.SetPartitionDevice(
+ kLegacyPartitionNameKernel, install_plan->source_slot, state->old_kernel);
+ state->fake_boot_control_.SetPartitionDevice(
+ kLegacyPartitionNameRoot, install_plan->target_slot, target_root);
+ state->fake_boot_control_.SetPartitionDevice(
+ kLegacyPartitionNameKernel, install_plan->target_slot, target_kernel);
+
+ ErrorCode expected_error, actual_error;
+ bool continue_writing;
+ switch (op_hash_test) {
+ case kInvalidOperationData: {
+ // Muck with some random offset post the metadata size so that
+ // some operation hash will result in a mismatch.
+ int some_offset = state->metadata_size + 300;
+ LOG(INFO) << "Tampered value at offset: " << some_offset;
+ state->delta[some_offset]++;
+ expected_error = ErrorCode::kDownloadOperationHashMismatch;
+ continue_writing = false;
+ break;
+ }
+
+ case kValidOperationData:
+ default:
+ // no change.
+ expected_error = ErrorCode::kSuccess;
+ continue_writing = true;
+ break;
+ }
+
+ // Write at some number of bytes per operation. Arbitrarily chose 5.
+ const size_t kBytesPerWrite = 5;
+ for (size_t i = 0; i < state->delta.size(); i += kBytesPerWrite) {
+ size_t count = std::min(state->delta.size() - i, kBytesPerWrite);
+ bool write_succeeded = ((*performer)->Write(&state->delta[i],
+ count,
+ &actual_error));
+ // Normally write_succeeded should be true every time and
+ // actual_error should be ErrorCode::kSuccess. If so, continue the loop.
+ // But if we seeded an operation hash error above, then write_succeeded
+ // will be false. The failure may happen at any operation n. So, all
+ // Writes until n-1 should succeed and the nth operation will fail with
+ // actual_error. In this case, we should bail out of the loop because
+ // we cannot proceed applying the delta.
+ if (!write_succeeded) {
+ LOG(INFO) << "Write failed. Checking if it failed with expected error";
+ EXPECT_EQ(expected_error, actual_error);
+ if (!continue_writing) {
+ LOG(INFO) << "Cannot continue writing. Bailing out.";
+ break;
+ }
+ }
+
+ EXPECT_EQ(ErrorCode::kSuccess, actual_error);
+ }
+
+ // If we had continued all the way through, Close should succeed.
+ // Otherwise, it should fail. Check appropriately.
+ bool close_result = (*performer)->Close();
+ if (continue_writing)
+ EXPECT_EQ(0, close_result);
+ else
+ EXPECT_LE(0, close_result);
+}
+
+void VerifyPayloadResult(DeltaPerformer* performer,
+ DeltaState* state,
+ ErrorCode expected_result,
+ uint32_t minor_version) {
+ if (!performer) {
+ EXPECT_TRUE(!"Skipping payload verification since performer is null.");
+ return;
+ }
+
+ int expected_times = (expected_result == ErrorCode::kSuccess) ? 1 : 0;
+ EXPECT_CALL(state->mock_delegate_, DownloadComplete()).Times(expected_times);
+
+ LOG(INFO) << "Verifying payload for expected result "
+ << expected_result;
+ EXPECT_EQ(expected_result, performer->VerifyPayload(
+ HashCalculator::HashOfData(state->delta),
+ state->delta.size()));
+ LOG(INFO) << "Verified payload.";
+
+ if (expected_result != ErrorCode::kSuccess) {
+ // no need to verify new partition if VerifyPayload failed.
+ 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));
+ }
+
+ ASSERT_GE(updated_kernel_partition.size(), arraysize(kNewData));
+ EXPECT_TRUE(std::equal(std::begin(kNewData), std::end(kNewData),
+ updated_kernel_partition.begin()));
+
+ const auto& partitions = state->install_plan.partitions;
+ EXPECT_EQ(2, partitions.size());
+ EXPECT_EQ(kLegacyPartitionNameRoot, partitions[0].name);
+ EXPECT_EQ(kLegacyPartitionNameKernel, partitions[1].name);
+
+ EXPECT_EQ(kDefaultKernelSize, partitions[1].target_size);
+ brillo::Blob expected_new_kernel_hash;
+ EXPECT_TRUE(HashCalculator::RawHashOfData(state->new_kernel_data,
+ &expected_new_kernel_hash));
+ EXPECT_EQ(expected_new_kernel_hash, partitions[1].target_hash);
+
+ EXPECT_EQ(state->image_size, partitions[0].target_size);
+ brillo::Blob expected_new_rootfs_hash;
+ EXPECT_EQ(state->image_size,
+ HashCalculator::RawHashOfFile(state->b_img,
+ state->image_size,
+ &expected_new_rootfs_hash));
+ EXPECT_EQ(expected_new_rootfs_hash, partitions[0].target_hash);
+}
+
+void VerifyPayload(DeltaPerformer* performer,
+ DeltaState* state,
+ SignatureTest signature_test,
+ uint32_t minor_version) {
+ ErrorCode expected_result = ErrorCode::kSuccess;
+ switch (signature_test) {
+ case kSignatureNone:
+ expected_result = ErrorCode::kSignedDeltaPayloadExpectedError;
+ break;
+ case kSignatureGeneratedShellBadKey:
+ expected_result = ErrorCode::kDownloadPayloadPubKeyVerificationError;
+ break;
+ default: break; // appease gcc
+ }
+
+ VerifyPayloadResult(performer, state, expected_result, minor_version);
+}
+
+void DoSmallImageTest(bool full_kernel, bool full_rootfs, bool noop,
+ ssize_t chunk_size,
+ SignatureTest signature_test,
+ bool hash_checks_mandatory, uint32_t minor_version) {
+ DeltaState state;
+ 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, kValidOperationData,
+ &performer, minor_version);
+ VerifyPayload(performer, &state, signature_test, minor_version);
+ delete performer;
+}
+
+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);
+ DeltaPerformer *performer = nullptr;
+ ApplyDeltaFile(true, true, false, kSignatureGenerated, &state,
+ hash_checks_mandatory, op_hash_test, &performer,
+ minor_version);
+ delete performer;
+}
+
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignaturePlaceholderTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedPlaceholder,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignaturePlaceholderMismatchTest) {
+ DeltaState state;
+ GenerateDeltaFile(false, false, false, -1,
+ kSignatureGeneratedPlaceholderMismatch, &state,
+ kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageChunksTest) {
+ DoSmallImageTest(false, false, false, kBlockSize, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootFullKernelSmallImageTest) {
+ DoSmallImageTest(true, false, false, -1, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootFullSmallImageTest) {
+ DoSmallImageTest(true, true, false, -1, kSignatureGenerator,
+ true, kFullPayloadMinorVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootNoopSmallImageTest) {
+ DoSmallImageTest(false, false, true, -1, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignNoneTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureNone,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGenerated,
+ true, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShell,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellBadKeyTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellBadKey,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellRotateCl1Test) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellRotateCl1,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellRotateCl2Test) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellRotateCl2,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSourceOpsTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGenerator,
+ false, kSourceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootMandatoryOperationHashMismatchTest) {
+ DoOperationHashMismatchTest(kInvalidOperationData, true);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
new file mode 100644
index 0000000..8192632
--- /dev/null
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -0,0 +1,788 @@
+//
+// 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_consumer/delta_performer.h"
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.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/fake_boot_control.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/fake_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/payload_constants.h"
+#include "update_engine/payload_generator/bzip.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+using test_utils::System;
+using test_utils::kRandomString;
+using testing::_;
+
+extern const char* kUnittestPrivateKeyPath;
+extern const char* kUnittestPublicKeyPath;
+
+static const char* kBogusMetadataSignature1 =
+ "awSFIUdUZz2VWFiR+ku0Pj00V7bPQPQFYQSXjEXr3vaw3TE4xHV5CraY3/YrZpBv"
+ "J5z4dSBskoeuaO1TNC/S6E05t+yt36tE4Fh79tMnJ/z9fogBDXWgXLEUyG78IEQr"
+ "YH6/eBsQGT2RJtBgXIXbZ9W+5G9KmGDoPOoiaeNsDuqHiBc/58OFsrxskH8E6vMS"
+ "BmMGGk82mvgzic7ApcoURbCGey1b3Mwne/hPZ/bb9CIyky8Og9IfFMdL2uAweOIR"
+ "fjoTeLYZpt+WN65Vu7jJ0cQN8e1y+2yka5112wpRf/LLtPgiAjEZnsoYpLUd7CoV"
+ "pLRtClp97kN2+tXGNBQqkA==";
+
+namespace {
+// Different options that determine what we should fill into the
+// install_plan.metadata_signature to simulate the contents received in the
+// Omaha response.
+enum MetadataSignatureTest {
+ kEmptyMetadataSignature,
+ kInvalidMetadataSignature,
+ kValidMetadataSignature,
+};
+
+// Compressed data without checksum, generated with:
+// echo -n a | xz -9 --check=none | hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kXzCompressedData[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00, 0xff, 0x12, 0xd9, 0x41,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0x01, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x01,
+ 0xad, 0xa6, 0x58, 0x04, 0x06, 0x72, 0x9e, 0x7a, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x59, 0x5a,
+};
+
+} // namespace
+
+class DeltaPerformerTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ install_plan_.source_slot = 0;
+ install_plan_.target_slot = 1;
+ EXPECT_CALL(mock_delegate_, ShouldCancel(_))
+ .WillRepeatedly(testing::Return(false));
+ }
+
+ // Test helper placed where it can easily be friended from DeltaPerformer.
+ void RunManifestValidation(const DeltaArchiveManifest& manifest,
+ uint64_t major_version,
+ bool full_payload,
+ ErrorCode expected) {
+ // The install plan is for Full or Delta.
+ install_plan_.is_full_update = full_payload;
+
+ // The Manifest we are validating.
+ performer_.manifest_.CopyFrom(manifest);
+ performer_.major_payload_version_ = major_version;
+
+ EXPECT_EQ(expected, performer_.ValidateManifest());
+ }
+
+ brillo::Blob GeneratePayload(const brillo::Blob& blob_data,
+ const vector<AnnotatedOperation>& aops,
+ bool sign_payload) {
+ return GeneratePayload(blob_data, aops, sign_payload,
+ DeltaPerformer::kSupportedMajorPayloadVersion,
+ DeltaPerformer::kSupportedMinorPayloadVersion);
+ }
+
+ brillo::Blob GeneratePayload(const brillo::Blob& blob_data,
+ const vector<AnnotatedOperation>& aops,
+ bool sign_payload,
+ uint64_t major_version,
+ uint32_t minor_version) {
+ string blob_path;
+ EXPECT_TRUE(utils::MakeTempFile("Blob-XXXXXX", &blob_path, nullptr));
+ ScopedPathUnlinker blob_unlinker(blob_path);
+ EXPECT_TRUE(utils::WriteFile(blob_path.c_str(),
+ blob_data.data(),
+ blob_data.size()));
+
+ PayloadGenerationConfig config;
+ config.major_version = major_version;
+ config.minor_version = minor_version;
+
+ PayloadFile payload;
+ EXPECT_TRUE(payload.Init(config));
+
+ PartitionConfig old_part(kLegacyPartitionNameRoot);
+ PartitionConfig new_part(kLegacyPartitionNameRoot);
+ new_part.path = "/dev/zero";
+ new_part.size = 1234;
+
+ payload.AddPartition(old_part, new_part, aops);
+
+ // We include a kernel partition without operations.
+ old_part.name = kLegacyPartitionNameKernel;
+ new_part.name = kLegacyPartitionNameKernel;
+ new_part.size = 0;
+ payload.AddPartition(old_part, new_part, {});
+
+ string payload_path;
+ EXPECT_TRUE(utils::MakeTempFile("Payload-XXXXXX", &payload_path, nullptr));
+ ScopedPathUnlinker payload_unlinker(payload_path);
+ EXPECT_TRUE(payload.WritePayload(payload_path, blob_path,
+ sign_payload ? kUnittestPrivateKeyPath : "",
+ &install_plan_.metadata_size));
+
+ brillo::Blob payload_data;
+ EXPECT_TRUE(utils::ReadFile(payload_path, &payload_data));
+ return payload_data;
+ }
+
+ // Apply |payload_data| on partition specified in |source_path|.
+ // Expect result of performer_.Write() to be |expect_success|.
+ // Returns the result of the payload application.
+ brillo::Blob ApplyPayload(const brillo::Blob& payload_data,
+ const string& source_path,
+ bool expect_success) {
+ return ApplyPayloadToData(payload_data, source_path, brillo::Blob(),
+ expect_success);
+ }
+
+ // Apply the payload provided in |payload_data| reading from the |source_path|
+ // file and writing the contents to a new partition. The existing data in the
+ // 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,
+ const string& source_path,
+ const brillo::Blob& target_data,
+ bool expect_success) {
+ string new_part;
+ EXPECT_TRUE(utils::MakeTempFile("Partition-XXXXXX", &new_part, nullptr));
+ ScopedPathUnlinker partition_unlinker(new_part);
+ EXPECT_TRUE(utils::WriteFile(new_part.c_str(), target_data.data(),
+ target_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(
+ kLegacyPartitionNameRoot, install_plan_.target_slot, new_part);
+ fake_boot_control_.SetPartitionDevice(
+ kLegacyPartitionNameRoot, install_plan_.source_slot, source_path);
+ fake_boot_control_.SetPartitionDevice(
+ kLegacyPartitionNameKernel, install_plan_.target_slot, "/dev/null");
+ fake_boot_control_.SetPartitionDevice(
+ kLegacyPartitionNameKernel, install_plan_.source_slot, "/dev/null");
+
+ EXPECT_EQ(expect_success,
+ performer_.Write(payload_data.data(), payload_data.size()));
+ EXPECT_EQ(0, performer_.Close());
+
+ brillo::Blob partition_data;
+ EXPECT_TRUE(utils::ReadFile(new_part, &partition_data));
+ return partition_data;
+ }
+
+ // Calls delta performer's Write method by pretending to pass in bytes from a
+ // delta file whose metadata size is actual_metadata_size and tests if all
+ // checks are correctly performed if the install plan contains
+ // expected_metadata_size and that the result of the parsing are as per
+ // hash_checks_mandatory flag.
+ void DoMetadataSizeTest(uint64_t expected_metadata_size,
+ uint64_t actual_metadata_size,
+ bool hash_checks_mandatory) {
+ install_plan_.hash_checks_mandatory = hash_checks_mandatory;
+
+ // Set a valid magic string and version number 1.
+ EXPECT_TRUE(performer_.Write("CrAU", 4));
+ uint64_t version = htobe64(kChromeOSMajorPayloadVersion);
+ EXPECT_TRUE(performer_.Write(&version, 8));
+
+ install_plan_.metadata_size = expected_metadata_size;
+ 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);
+ if (expected_metadata_size == actual_metadata_size ||
+ !hash_checks_mandatory) {
+ EXPECT_TRUE(result);
+ } else {
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kDownloadInvalidMetadataSize, error_code);
+ }
+
+ EXPECT_LT(performer_.Close(), 0);
+ }
+
+ // Generates a valid delta file but tests the delta performer by suppling
+ // 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,
+ bool sign_payload,
+ bool hash_checks_mandatory) {
+
+ // Loads the payload and parses the manifest.
+ brillo::Blob payload = GeneratePayload(brillo::Blob(),
+ vector<AnnotatedOperation>(), sign_payload,
+ kChromeOSMajorPayloadVersion, kFullPayloadMinorVersion);
+
+ LOG(INFO) << "Payload size: " << payload.size();
+
+ install_plan_.hash_checks_mandatory = hash_checks_mandatory;
+
+ DeltaPerformer::MetadataParseResult expected_result, actual_result;
+ ErrorCode expected_error, actual_error;
+
+ // Fill up the metadata signature in install plan according to the test.
+ switch (metadata_signature_test) {
+ case kEmptyMetadataSignature:
+ install_plan_.metadata_signature.clear();
+ expected_result = DeltaPerformer::kMetadataParseError;
+ expected_error = ErrorCode::kDownloadMetadataSignatureMissingError;
+ break;
+
+ case kInvalidMetadataSignature:
+ install_plan_.metadata_signature = kBogusMetadataSignature1;
+ expected_result = DeltaPerformer::kMetadataParseError;
+ expected_error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ break;
+
+ case kValidMetadataSignature:
+ default:
+ // Set the install plan's metadata size to be the same as the one
+ // in the manifest so that we pass the metadata size checks. Only
+ // then we can get to manifest signature checks.
+ ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
+ payload.data(),
+ install_plan_.metadata_size,
+ kUnittestPrivateKeyPath,
+ &install_plan_.metadata_signature));
+ EXPECT_FALSE(install_plan_.metadata_signature.empty());
+ expected_result = DeltaPerformer::kMetadataParseSuccess;
+ expected_error = ErrorCode::kSuccess;
+ break;
+ }
+
+ // Ignore the expected result/error if hash checks are not mandatory.
+ if (!hash_checks_mandatory) {
+ expected_result = DeltaPerformer::kMetadataParseSuccess;
+ expected_error = ErrorCode::kSuccess;
+ }
+
+ // Use the public key corresponding to the private key used above to
+ // sign the metadata.
+ EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
+ performer_.set_public_key_path(kUnittestPublicKeyPath);
+
+ // Init actual_error with an invalid value so that we make sure
+ // ParsePayloadMetadata properly populates it in all cases.
+ actual_error = ErrorCode::kUmaReportedMax;
+ actual_result = performer_.ParsePayloadMetadata(payload, &actual_error);
+
+ EXPECT_EQ(expected_result, actual_result);
+ EXPECT_EQ(expected_error, actual_error);
+
+ // Check that the parsed metadata size is what's expected. This test
+ // implicitly confirms that the metadata signature is valid, if required.
+ EXPECT_EQ(install_plan_.metadata_size, performer_.GetMetadataSize());
+ }
+
+ void SetSupportedMajorVersion(uint64_t major_version) {
+ performer_.supported_major_version_ = major_version;
+ }
+ FakePrefs prefs_;
+ InstallPlan install_plan_;
+ FakeBootControl fake_boot_control_;
+ FakeHardware fake_hardware_;
+ MockDownloadActionDelegate mock_delegate_;
+ DeltaPerformer performer_{
+ &prefs_, &fake_boot_control_, &fake_hardware_, &mock_delegate_, &install_plan_};
+};
+
+TEST_F(DeltaPerformerTest, FullPayloadWriteTest) {
+ install_plan_.is_full_update = true;
+ 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,
+ kChromeOSMajorPayloadVersion, kFullPayloadMinorVersion);
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
+}
+
+TEST_F(DeltaPerformerTest, ShouldCancelTest) {
+ install_plan_.is_full_update = true;
+ 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,
+ kChromeOSMajorPayloadVersion, kFullPayloadMinorVersion);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_delegate_);
+ EXPECT_CALL(mock_delegate_, ShouldCancel(_))
+ .WillOnce(
+ testing::DoAll(testing::SetArgumentPointee<0>(ErrorCode::kError),
+ testing::Return(true)));
+
+ ApplyPayload(payload_data, "/dev/null", false);
+}
+
+TEST_F(DeltaPerformerTest, ReplaceOperationTest) {
+ 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);
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
+}
+
+TEST_F(DeltaPerformerTest, ReplaceBzOperationTest) {
+ brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
+ std::end(kRandomString));
+ expected_data.resize(4096); // block size
+ brillo::Blob bz_data;
+ EXPECT_TRUE(BzipCompress(expected_data, &bz_data));
+
+ vector<AnnotatedOperation> aops;
+ AnnotatedOperation aop;
+ *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+ aop.op.set_data_offset(0);
+ aop.op.set_data_length(bz_data.size());
+ aop.op.set_type(InstallOperation::REPLACE_BZ);
+ aops.push_back(aop);
+
+ brillo::Blob payload_data = GeneratePayload(bz_data, aops, false);
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
+}
+
+TEST_F(DeltaPerformerTest, ReplaceXzOperationTest) {
+ brillo::Blob xz_data(std::begin(kXzCompressedData),
+ std::end(kXzCompressedData));
+ // The compressed xz data contains only a single "a", but the operation should
+ // pad the rest of the two blocks with zeros.
+ brillo::Blob expected_data = brillo::Blob(4096, 0);
+ expected_data[0] = 'a';
+
+ AnnotatedOperation aop;
+ *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+ aop.op.set_data_offset(0);
+ aop.op.set_data_length(xz_data.size());
+ aop.op.set_type(InstallOperation::REPLACE_XZ);
+ vector<AnnotatedOperation> aops = {aop};
+
+ brillo::Blob payload_data = GeneratePayload(xz_data, aops, false);
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true));
+}
+
+TEST_F(DeltaPerformerTest, ZeroOperationTest) {
+ brillo::Blob existing_data = brillo::Blob(4096 * 10, 'a');
+ brillo::Blob expected_data = existing_data;
+ // Blocks 4, 5 and 7 should have zeros instead of 'a' after the operation is
+ // applied.
+ std::fill(expected_data.data() + 4096 * 4, expected_data.data() + 4096 * 6,
+ 0);
+ std::fill(expected_data.data() + 4096 * 7, expected_data.data() + 4096 * 8,
+ 0);
+
+ AnnotatedOperation aop;
+ *(aop.op.add_dst_extents()) = ExtentForRange(4, 2);
+ *(aop.op.add_dst_extents()) = ExtentForRange(7, 1);
+ aop.op.set_type(InstallOperation::ZERO);
+ vector<AnnotatedOperation> aops = {aop};
+
+ brillo::Blob payload_data = GeneratePayload(brillo::Blob(), aops, false);
+
+ EXPECT_EQ(expected_data,
+ ApplyPayloadToData(payload_data, "/dev/null", existing_data, true));
+}
+
+TEST_F(DeltaPerformerTest, SourceCopyOperationTest) {
+ brillo::Blob expected_data(std::begin(kRandomString),
+ std::end(kRandomString));
+ expected_data.resize(4096); // block size
+ 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;
+ EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash));
+ aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
+
+ brillo::Blob payload_data = GeneratePayload(brillo::Blob(), {aop}, false);
+
+ string source_path;
+ EXPECT_TRUE(utils::MakeTempFile("Source-XXXXXX",
+ &source_path, nullptr));
+ ScopedPathUnlinker path_unlinker(source_path);
+ EXPECT_TRUE(utils::WriteFile(source_path.c_str(),
+ expected_data.data(),
+ expected_data.size()));
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, source_path, true));
+}
+
+TEST_F(DeltaPerformerTest, SourceHashMismatchTest) {
+ brillo::Blob expected_data = {'f', 'o', 'o'};
+ brillo::Blob actual_data = {'b', 'a', 'r'};
+ expected_data.resize(4096); // block size
+ actual_data.resize(4096); // block size
+
+ 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;
+ EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash));
+ aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
+
+ brillo::Blob payload_data = GeneratePayload(brillo::Blob(), {aop}, false);
+
+ string source_path;
+ EXPECT_TRUE(utils::MakeTempFile("Source-XXXXXX", &source_path, nullptr));
+ ScopedPathUnlinker path_unlinker(source_path);
+ EXPECT_TRUE(utils::WriteFile(source_path.c_str(), actual_data.data(),
+ actual_data.size()));
+
+ EXPECT_EQ(actual_data, ApplyPayload(payload_data, source_path, false));
+}
+
+TEST_F(DeltaPerformerTest, ExtentsToByteStringTest) {
+ uint64_t test[] = {1, 1, 4, 2, 0, 1};
+ COMPILE_ASSERT(arraysize(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) {
+ *(extents.Add()) = ExtentForRange(test[i], test[i + 1]);
+ }
+
+ string expected_output = "4096:4096,16384:8192,0:4083";
+ string actual_output;
+ EXPECT_TRUE(DeltaPerformer::ExtentsToBsdiffPositionsString(extents,
+ block_size,
+ file_length,
+ &actual_output));
+ EXPECT_EQ(expected_output, actual_output);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullGoodTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ manifest.mutable_new_kernel_info();
+ manifest.mutable_new_rootfs_info();
+ manifest.set_minor_version(kFullPayloadMinorVersion);
+
+ RunManifestValidation(manifest, kChromeOSMajorPayloadVersion, true,
+ ErrorCode::kSuccess);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestDeltaGoodTest) {
+ // 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();
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ RunManifestValidation(manifest, kChromeOSMajorPayloadVersion, false,
+ ErrorCode::kSuccess);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullUnsetMinorVersion) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+
+ RunManifestValidation(manifest, DeltaPerformer::kSupportedMajorPayloadVersion,
+ true, ErrorCode::kSuccess);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestDeltaUnsetMinorVersion) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+
+ RunManifestValidation(manifest, DeltaPerformer::kSupportedMajorPayloadVersion,
+ false, ErrorCode::kUnsupportedMinorPayloadVersion);
+}
+
+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(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ RunManifestValidation(manifest, kChromeOSMajorPayloadVersion, true,
+ 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(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ RunManifestValidation(manifest, kChromeOSMajorPayloadVersion, true,
+ ErrorCode::kPayloadMismatchedType);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullPartitionUpdateTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ PartitionUpdate* partition = manifest.add_partitions();
+ partition->mutable_old_partition_info();
+ partition->mutable_new_partition_info();
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ RunManifestValidation(manifest, kBrilloMajorPayloadVersion, true,
+ ErrorCode::kPayloadMismatchedType);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestBadMinorVersion) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+
+ // Generate a bad version number.
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion +
+ 10000);
+
+ RunManifestValidation(manifest, DeltaPerformer::kSupportedMajorPayloadVersion,
+ false, ErrorCode::kUnsupportedMinorPayloadVersion);
+}
+
+TEST_F(DeltaPerformerTest, BrilloMetadataSignatureSizeTest) {
+ EXPECT_TRUE(performer_.Write(kDeltaMagic, sizeof(kDeltaMagic)));
+
+ uint64_t major_version = htobe64(kBrilloMajorPayloadVersion);
+ EXPECT_TRUE(performer_.Write(&major_version, 8));
+
+ uint64_t manifest_size = rand() % 256;
+ uint64_t manifest_size_be = htobe64(manifest_size);
+ EXPECT_TRUE(performer_.Write(&manifest_size_be, 8));
+
+ uint32_t metadata_signature_size = rand() % 256;
+ uint32_t metadata_signature_size_be = htobe32(metadata_signature_size);
+ EXPECT_TRUE(performer_.Write(&metadata_signature_size_be, 4));
+
+ EXPECT_LT(performer_.Close(), 0);
+
+ EXPECT_TRUE(performer_.IsHeaderParsed());
+ EXPECT_EQ(kBrilloMajorPayloadVersion, performer_.GetMajorVersion());
+ uint64_t manifest_offset;
+ EXPECT_TRUE(performer_.GetManifestOffset(&manifest_offset));
+ EXPECT_EQ(24, manifest_offset); // 4 + 8 + 8 + 4
+ EXPECT_EQ(manifest_offset + manifest_size, performer_.GetMetadataSize());
+ EXPECT_EQ(metadata_signature_size, performer_.metadata_signature_size_);
+}
+
+TEST_F(DeltaPerformerTest, BrilloVerifyMetadataSignatureTest) {
+ brillo::Blob payload_data = GeneratePayload({}, {}, true,
+ kBrilloMajorPayloadVersion,
+ kSourceMinorPayloadVersion);
+ install_plan_.hash_checks_mandatory = true;
+ // Just set these value so that we can use ValidateMetadataSignature directly.
+ performer_.major_payload_version_ = kBrilloMajorPayloadVersion;
+ performer_.metadata_size_ = install_plan_.metadata_size;
+ uint64_t signature_length;
+ EXPECT_TRUE(PayloadSigner::SignatureBlobLength({kUnittestPrivateKeyPath},
+ &signature_length));
+ performer_.metadata_signature_size_ = signature_length;
+ performer_.set_public_key_path(kUnittestPublicKeyPath);
+ EXPECT_EQ(ErrorCode::kSuccess,
+ performer_.ValidateMetadataSignature(payload_data));
+}
+
+TEST_F(DeltaPerformerTest, BadDeltaMagicTest) {
+ EXPECT_TRUE(performer_.Write("junk", 4));
+ EXPECT_FALSE(performer_.Write("morejunk", 8));
+ EXPECT_LT(performer_.Close(), 0);
+}
+
+TEST_F(DeltaPerformerTest, MissingMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(0, 75456, true);
+}
+
+TEST_F(DeltaPerformerTest, MissingNonMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(0, 123456, false);
+}
+
+TEST_F(DeltaPerformerTest, InvalidMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(13000, 140000, true);
+}
+
+TEST_F(DeltaPerformerTest, InvalidNonMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(40000, 50000, false);
+}
+
+TEST_F(DeltaPerformerTest, ValidMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(85376, 85376, true);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryEmptyMetadataSignatureTest) {
+ DoMetadataSignatureTest(kEmptyMetadataSignature, true, true);
+}
+
+TEST_F(DeltaPerformerTest, NonMandatoryEmptyMetadataSignatureTest) {
+ DoMetadataSignatureTest(kEmptyMetadataSignature, true, false);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryInvalidMetadataSignatureTest) {
+ DoMetadataSignatureTest(kInvalidMetadataSignature, true, true);
+}
+
+TEST_F(DeltaPerformerTest, NonMandatoryInvalidMetadataSignatureTest) {
+ DoMetadataSignatureTest(kInvalidMetadataSignature, true, false);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryValidMetadataSignature1Test) {
+ DoMetadataSignatureTest(kValidMetadataSignature, false, true);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryValidMetadataSignature2Test) {
+ DoMetadataSignatureTest(kValidMetadataSignature, true, true);
+}
+
+TEST_F(DeltaPerformerTest, NonMandatoryValidMetadataSignatureTest) {
+ DoMetadataSignatureTest(kValidMetadataSignature, true, false);
+}
+
+TEST_F(DeltaPerformerTest, UsePublicKeyFromResponse) {
+ base::FilePath key_path;
+
+ // The result of the GetPublicKeyResponse() method is based on three things
+ //
+ // 1. Whether it's an official build; and
+ // 2. Whether the Public RSA key to be used is in the root filesystem; and
+ // 3. Whether the response has a public key
+ //
+ // We test all eight combinations to ensure that we only use the
+ // public key in the response if
+ //
+ // a. it's not an official build; and
+ // b. there is no key in the root filesystem.
+
+ string temp_dir;
+ EXPECT_TRUE(utils::MakeTempDirectory("PublicKeyFromResponseTests.XXXXXX",
+ &temp_dir));
+ string non_existing_file = temp_dir + "/non-existing";
+ string existing_file = temp_dir + "/existing";
+ EXPECT_EQ(0, System(base::StringPrintf("touch %s", existing_file.c_str())));
+
+ // Non-official build, non-existing public-key, key in response -> true
+ fake_hardware_.SetIsOfficialBuild(false);
+ performer_.public_key_path_ = non_existing_file;
+ install_plan_.public_key_rsa = "VGVzdAo="; // result of 'echo "Test" | base64'
+ EXPECT_TRUE(performer_.GetPublicKeyFromResponse(&key_path));
+ EXPECT_FALSE(key_path.empty());
+ EXPECT_EQ(unlink(key_path.value().c_str()), 0);
+ // Same with official build -> false
+ fake_hardware_.SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, existing public-key, key in response -> false
+ fake_hardware_.SetIsOfficialBuild(false);
+ performer_.public_key_path_ = existing_file;
+ install_plan_.public_key_rsa = "VGVzdAo="; // result of 'echo "Test" | base64'
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+ // Same with official build -> false
+ fake_hardware_.SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, non-existing public-key, no key in response -> false
+ fake_hardware_.SetIsOfficialBuild(false);
+ performer_.public_key_path_ = non_existing_file;
+ install_plan_.public_key_rsa = "";
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+ // Same with official build -> false
+ fake_hardware_.SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, existing public-key, no key in response -> false
+ fake_hardware_.SetIsOfficialBuild(false);
+ performer_.public_key_path_ = existing_file;
+ install_plan_.public_key_rsa = "";
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+ // Same with official build -> false
+ fake_hardware_.SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, non-existing public-key, key in response
+ // but invalid base64 -> false
+ fake_hardware_.SetIsOfficialBuild(false);
+ performer_.public_key_path_ = non_existing_file;
+ install_plan_.public_key_rsa = "not-valid-base64";
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+ EXPECT_TRUE(base::DeleteFile(base::FilePath(temp_dir), true));
+}
+
+TEST_F(DeltaPerformerTest, ConfVersionsMatch) {
+ // Test that the versions in update_engine.conf that is installed to the
+ // image match the supported delta versions in the update engine.
+ uint32_t minor_version;
+ brillo::KeyValueStore store;
+ EXPECT_TRUE(store.Load(base::FilePath("update_engine.conf")));
+ EXPECT_TRUE(utils::GetMinorVersion(store, &minor_version));
+ EXPECT_EQ(DeltaPerformer::kSupportedMinorPayloadVersion, minor_version);
+
+ string major_version_str;
+ uint64_t major_version;
+ EXPECT_TRUE(store.GetString("PAYLOAD_MAJOR_VERSION", &major_version_str));
+ EXPECT_TRUE(base::StringToUint64(major_version_str, &major_version));
+ EXPECT_EQ(DeltaPerformer::kSupportedMajorPayloadVersion, major_version);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/download_action.cc b/payload_consumer/download_action.cc
new file mode 100644
index 0000000..cb404b8
--- /dev/null
+++ b/payload_consumer/download_action.cc
@@ -0,0 +1,318 @@
+//
+// 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/payload_consumer/download_action.h"
+
+#include <errno.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.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/utils.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+
+using base::FilePath;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+DownloadAction::DownloadAction(PrefsInterface* prefs,
+ SystemState* system_state,
+ HttpFetcher* http_fetcher)
+ : prefs_(prefs),
+ system_state_(system_state),
+ http_fetcher_(http_fetcher),
+ writer_(nullptr),
+ code_(ErrorCode::kSuccess),
+ delegate_(nullptr),
+ bytes_received_(0),
+ p2p_sharing_fd_(-1),
+ p2p_visible_(true) {}
+
+DownloadAction::~DownloadAction() {}
+
+void DownloadAction::CloseP2PSharingFd(bool delete_p2p_file) {
+ if (p2p_sharing_fd_ != -1) {
+ if (close(p2p_sharing_fd_) != 0) {
+ PLOG(ERROR) << "Error closing p2p sharing fd";
+ }
+ p2p_sharing_fd_ = -1;
+ }
+
+ if (delete_p2p_file) {
+ FilePath path =
+ system_state_->p2p_manager()->FileGetPath(p2p_file_id_);
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting p2p file " << path.value();
+ } else {
+ LOG(INFO) << "Deleted p2p file " << path.value();
+ }
+ }
+
+ // Don't use p2p from this point onwards.
+ p2p_file_id_.clear();
+}
+
+bool DownloadAction::SetupP2PSharingFd() {
+ P2PManager *p2p_manager = system_state_->p2p_manager();
+
+ if (!p2p_manager->FileShare(p2p_file_id_, install_plan_.payload_size)) {
+ LOG(ERROR) << "Unable to share file via p2p";
+ CloseP2PSharingFd(true); // delete p2p file
+ return false;
+ }
+
+ // File has already been created (and allocated, xattrs been
+ // populated etc.) by FileShare() so just open it for writing.
+ FilePath path = p2p_manager->FileGetPath(p2p_file_id_);
+ p2p_sharing_fd_ = open(path.value().c_str(), O_WRONLY);
+ if (p2p_sharing_fd_ == -1) {
+ PLOG(ERROR) << "Error opening file " << path.value();
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return false;
+ }
+
+ // Ensure file to share is world-readable, otherwise
+ // p2p-server and p2p-http-server can't access it.
+ //
+ // (Q: Why doesn't the file have mode 0644 already? A: Because
+ // the process-wide umask is set to 0700 in main.cc.)
+ if (fchmod(p2p_sharing_fd_, 0644) != 0) {
+ PLOG(ERROR) << "Error setting mode 0644 on " << path.value();
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return false;
+ }
+
+ // All good.
+ LOG(INFO) << "Writing payload contents to " << path.value();
+ p2p_manager->FileGetVisible(p2p_file_id_, &p2p_visible_);
+ return true;
+}
+
+void DownloadAction::WriteToP2PFile(const void* data,
+ size_t length,
+ off_t file_offset) {
+ if (p2p_sharing_fd_ == -1) {
+ if (!SetupP2PSharingFd())
+ return;
+ }
+
+ // Check that the file is at least |file_offset| bytes long - if
+ // it's not something is wrong and we must immediately delete the
+ // file to avoid propagating this problem to other peers.
+ //
+ // How can this happen? It could be that we're resuming an update
+ // after a system crash... in this case, it could be that
+ //
+ // 1. the p2p file didn't get properly synced to stable storage; or
+ // 2. the file was deleted at bootup (it's in /var/cache after all); or
+ // 3. other reasons
+ off_t p2p_size = utils::FileSize(p2p_sharing_fd_);
+ if (p2p_size < 0) {
+ PLOG(ERROR) << "Error getting file status for p2p file";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return;
+ }
+ if (p2p_size < file_offset) {
+ LOG(ERROR) << "Wanting to write to file offset " << file_offset
+ << " but existing p2p file is only " << p2p_size
+ << " bytes.";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return;
+ }
+
+ off_t cur_file_offset = lseek(p2p_sharing_fd_, file_offset, SEEK_SET);
+ if (cur_file_offset != static_cast<off_t>(file_offset)) {
+ PLOG(ERROR) << "Error seeking to position "
+ << file_offset << " in p2p file";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ } else {
+ // OK, seeking worked, now write the data
+ ssize_t bytes_written = write(p2p_sharing_fd_, data, length);
+ if (bytes_written != static_cast<ssize_t>(length)) {
+ PLOG(ERROR) << "Error writing "
+ << length << " bytes at file offset "
+ << file_offset << " in p2p file";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ }
+ }
+}
+
+void DownloadAction::PerformAction() {
+ http_fetcher_->set_delegate(this);
+
+ // Get the InstallPlan and read it
+ CHECK(HasInputObject());
+ install_plan_ = GetInputObject();
+ bytes_received_ = 0;
+
+ install_plan_.Dump();
+
+ LOG(INFO) << "Marking new slot as unbootable";
+ if (!system_state_->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.";
+ }
+
+ if (writer_) {
+ LOG(INFO) << "Using writer for test.";
+ } else {
+ delta_performer_.reset(new DeltaPerformer(prefs_,
+ system_state_->boot_control(),
+ system_state_->hardware(),
+ delegate_,
+ &install_plan_));
+ writer_ = delta_performer_.get();
+ }
+ download_active_= true;
+
+ if (system_state_ != nullptr) {
+ const PayloadStateInterface* payload_state = system_state_->payload_state();
+ string file_id = utils::CalculateP2PFileId(install_plan_.payload_hash,
+ install_plan_.payload_size);
+ if (payload_state->GetUsingP2PForSharing()) {
+ // If we're sharing the update, store the file_id to convey
+ // that we should write to the file.
+ p2p_file_id_ = file_id;
+ LOG(INFO) << "p2p file id: " << p2p_file_id_;
+ } else {
+ // Even if we're not sharing the update, it could be that
+ // there's a partial file from a previous attempt with the same
+ // 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);
+ if (!path.empty()) {
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting p2p file " << path.value();
+ } else {
+ LOG(INFO) << "Deleting partial p2p file " << path.value()
+ << " since we're not using p2p to share.";
+ }
+ }
+ }
+
+ // Tweak timeouts on the HTTP fetcher if we're downloading from a
+ // local peer.
+ if (payload_state->GetUsingP2PForDownloading() &&
+ payload_state->GetP2PUrl() == install_plan_.download_url) {
+ LOG(INFO) << "Tweaking HTTP fetcher since we're downloading via p2p";
+ http_fetcher_->set_low_speed_limit(kDownloadP2PLowSpeedLimitBps,
+ kDownloadP2PLowSpeedTimeSeconds);
+ http_fetcher_->set_max_retry_count(kDownloadP2PMaxRetryCount);
+ http_fetcher_->set_connect_timeout(kDownloadP2PConnectTimeoutSeconds);
+ }
+ }
+
+ http_fetcher_->BeginTransfer(install_plan_.download_url);
+}
+
+void DownloadAction::TerminateProcessing() {
+ if (writer_) {
+ writer_->Close();
+ writer_ = nullptr;
+ }
+ download_active_= false;
+ CloseP2PSharingFd(false); // Keep p2p file.
+ // 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;
+}
+
+void DownloadAction::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_);
+ }
+
+ bytes_received_ += length;
+ if (delegate_ && download_active_) {
+ delegate_->BytesReceived(
+ length, bytes_received_, install_plan_.payload_size);
+ }
+ if (writer_ && !writer_->Write(bytes, length, &code_)) {
+ LOG(ERROR) << "Error " << code_ << " in DeltaPerformer's Write method when "
+ << "processing the received payload -- Terminating processing";
+ // Delete p2p file, if applicable.
+ if (!p2p_file_id_.empty())
+ CloseP2PSharingFd(true);
+ // 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;
+ }
+
+ // Call p2p_manager_->FileMakeVisible() when we've successfully
+ // verified the manifest!
+ if (!p2p_visible_ &&
+ delta_performer_.get() && delta_performer_->IsManifestValid()) {
+ LOG(INFO) << "Manifest has been validated. Making p2p file visible.";
+ system_state_->p2p_manager()->FileMakeVisible(p2p_file_id_);
+ p2p_visible_ = true;
+ }
+}
+
+void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
+ if (writer_) {
+ LOG_IF(WARNING, writer_->Close() != 0) << "Error closing the writer.";
+ writer_ = nullptr;
+ }
+ download_active_= false;
+ ErrorCode code =
+ successful ? ErrorCode::kSuccess : ErrorCode::kDownloadTransferError;
+ if (code == ErrorCode::kSuccess && delta_performer_.get()) {
+ code = delta_performer_->VerifyPayload(install_plan_.payload_hash,
+ install_plan_.payload_size);
+ if (code != ErrorCode::kSuccess) {
+ LOG(ERROR) << "Download of " << install_plan_.download_url
+ << " failed due to payload verification error.";
+ // Delete p2p file, if applicable.
+ if (!p2p_file_id_.empty())
+ CloseP2PSharingFd(true);
+ }
+ }
+
+ // 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_);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/download_action.h b/payload_consumer/download_action.h
new file mode 100644
index 0000000..300d97e
--- /dev/null
+++ b/payload_consumer/download_action.h
@@ -0,0 +1,175 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <string>
+
+#include <curl/curl.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/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
+// DeltaPerformer that will apply the delta to the disk.
+
+namespace chromeos_update_engine {
+
+class DownloadActionDelegate {
+ public:
+ virtual ~DownloadActionDelegate() = default;
+
+ // Called periodically after bytes are received. This method will be invoked
+ // only if the DownloadAction is running. |bytes_progressed| is the number of
+ // bytes downloaded since the last call of this method, |bytes_received|
+ // the number of bytes downloaded thus far and |total| is the number of bytes
+ // expected.
+ virtual void BytesReceived(uint64_t bytes_progressed,
+ uint64_t bytes_received,
+ uint64_t total) = 0;
+
+ // Returns whether the download should be canceled, in which case the
+ // |cancel_reason| error should be set to the reason why the download was
+ // canceled.
+ virtual bool ShouldCancel(ErrorCode* cancel_reason) = 0;
+
+ // Called once the complete payload has been downloaded. Note that any errors
+ // while applying or downloading the partial payload will result in this
+ // method not being called.
+ virtual void DownloadComplete() = 0;
+};
+
+class PrefsInterface;
+
+class DownloadAction : public InstallPlanAction,
+ public HttpFetcherDelegate {
+ public:
+ // Takes ownership of the passed in HttpFetcher. Useful for testing.
+ // A good calling pattern is:
+ // DownloadAction(prefs, system_state, new WhateverHttpFetcher);
+ DownloadAction(PrefsInterface* prefs,
+ SystemState* system_state,
+ HttpFetcher* http_fetcher);
+ ~DownloadAction() override;
+ void PerformAction() override;
+ void TerminateProcessing() override;
+
+ // Testing
+ void SetTestFileWriter(FileWriter* writer) {
+ writer_ = writer;
+ }
+
+ int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
+ // Debugging/logging
+ static std::string StaticType() { return "DownloadAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ // HttpFetcherDelegate methods (see http_fetcher.h)
+ void 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;
+ }
+
+ 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);
+
+ // The InstallPlan passed in
+ InstallPlan install_plan_;
+
+ // Update Engine preference store.
+ PrefsInterface* prefs_;
+
+ // Global context for the system.
+ SystemState* system_state_;
+
+ // Pointer to the HttpFetcher that does the http work.
+ std::unique_ptr<HttpFetcher> http_fetcher_;
+
+ // 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_;
+ 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadAction);
+};
+
+// We want to be sure that we're compiled with large file support on linux,
+// just in case we find ourselves downloading large images.
+COMPILE_ASSERT(8 == sizeof(off_t), off_t_not_64_bit);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_
diff --git a/payload_consumer/download_action_unittest.cc b/payload_consumer/download_action_unittest.cc
new file mode 100644
index 0000000..08053bf
--- /dev/null
+++ b/payload_consumer/download_action_unittest.cc
@@ -0,0 +1,626 @@
+//
+// 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/payload_consumer/download_action.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.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/location.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/hash_calculator.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/payload_consumer/mock_download_action.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+
+namespace chromeos_update_engine {
+
+using base::FilePath;
+using base::ReadFileToString;
+using base::WriteFile;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using test_utils::ScopedTempFile;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Return;
+using testing::_;
+
+class DownloadActionTest : public ::testing::Test { };
+
+namespace {
+
+class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ explicit DownloadActionTestProcessorDelegate(ErrorCode expected_code)
+ : processing_done_called_(false),
+ expected_code_(expected_code) {}
+ ~DownloadActionTestProcessorDelegate() override {
+ EXPECT_TRUE(processing_done_called_);
+ }
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) override {
+ brillo::MessageLoop::current()->BreakLoop();
+ brillo::Blob found_data;
+ ASSERT_TRUE(utils::ReadFile(path_, &found_data));
+ if (expected_code_ != ErrorCode::kDownloadWriteError) {
+ ASSERT_EQ(expected_data_.size(), found_data.size());
+ for (unsigned i = 0; i < expected_data_.size(); i++) {
+ EXPECT_EQ(expected_data_[i], found_data[i]);
+ }
+ }
+ processing_done_called_ = true;
+ }
+
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) override {
+ const string type = action->Type();
+ if (type == DownloadAction::StaticType()) {
+ EXPECT_EQ(expected_code_, code);
+ } else {
+ EXPECT_EQ(ErrorCode::kSuccess, code);
+ }
+ }
+
+ string path_;
+ brillo::Blob expected_data_;
+ bool processing_done_called_;
+ ErrorCode expected_code_;
+};
+
+class TestDirectFileWriter : public DirectFileWriter {
+ public:
+ TestDirectFileWriter() : fail_write_(0), current_write_(0) {}
+ void set_fail_write(int fail_write) { fail_write_ = fail_write; }
+
+ virtual bool Write(const void* bytes, size_t count) {
+ if (++current_write_ == fail_write_) {
+ return false;
+ }
+ return DirectFileWriter::Write(bytes, count);
+ }
+
+ private:
+ // If positive, fail on the |fail_write_| call to Write.
+ int fail_write_;
+ int current_write_;
+};
+
+void StartProcessorInRunLoop(ActionProcessor* processor,
+ MockHttpFetcher* http_fetcher) {
+ processor->StartProcessing();
+ http_fetcher->SetOffset(1);
+}
+
+void TestWithData(const brillo::Blob& data,
+ int fail_write,
+ bool use_download_delegate) {
+ 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(0, writer.Open(output_temp_file.GetPath().c_str(),
+ O_WRONLY | O_CREAT,
+ 0));
+ writer.set_fail_write(fail_write);
+
+ // We pull off the first byte from data and seek past it.
+ string hash = HashCalculator::HashOfBytes(&data[1], data.size() - 1);
+ uint64_t size = data.size();
+ InstallPlan install_plan(false,
+ false,
+ "",
+ size,
+ hash,
+ 0,
+ "",
+ "");
+ install_plan.source_slot = 0;
+ 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(
+ install_plan.source_slot, true);
+ fake_system_state.fake_boot_control()->SetSlotBootable(
+ install_plan.target_slot, true);
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ MockHttpFetcher* http_fetcher = new MockHttpFetcher(data.data(),
+ data.size(),
+ nullptr);
+ // takes ownership of passed in HttpFetcher
+ DownloadAction download_action(&prefs, &fake_system_state, http_fetcher);
+ download_action.SetTestFileWriter(&writer);
+ BondActions(&feeder_action, &download_action);
+ MockDownloadActionDelegate download_delegate;
+ if (use_download_delegate) {
+ InSequence s;
+ download_action.set_delegate(&download_delegate);
+ if (data.size() > kMockHttpFetcherChunkSize)
+ EXPECT_CALL(download_delegate,
+ BytesReceived(_, 1 + kMockHttpFetcherChunkSize, _));
+ EXPECT_CALL(download_delegate, BytesReceived(_, _, _)).Times(AtLeast(1));
+ }
+ ErrorCode expected_code = ErrorCode::kSuccess;
+ if (fail_write > 0)
+ expected_code = ErrorCode::kDownloadWriteError;
+ DownloadActionTestProcessorDelegate delegate(expected_code);
+ delegate.expected_data_ = brillo::Blob(data.begin() + 1, data.end());
+ delegate.path_ = output_temp_file.GetPath();
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&download_action);
+
+ loop.PostTask(FROM_HERE,
+ base::Bind(&StartProcessorInRunLoop, &processor, http_fetcher));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+
+ EXPECT_TRUE(fake_system_state.fake_boot_control()->IsSlotBootable(
+ install_plan.source_slot));
+ EXPECT_FALSE(fake_system_state.fake_boot_control()->IsSlotBootable(
+ install_plan.target_slot));
+}
+} // namespace
+
+TEST(DownloadActionTest, SimpleTest) {
+ brillo::Blob small;
+ const char* foo = "foo";
+ small.insert(small.end(), foo, foo + strlen(foo));
+ TestWithData(small,
+ 0, // fail_write
+ true); // use_download_delegate
+}
+
+TEST(DownloadActionTest, LargeTest) {
+ brillo::Blob big(5 * kMockHttpFetcherChunkSize);
+ char c = '0';
+ for (unsigned int i = 0; i < big.size(); i++) {
+ big[i] = c;
+ c = ('9' == c) ? '0' : c + 1;
+ }
+ TestWithData(big,
+ 0, // fail_write
+ true); // use_download_delegate
+}
+
+TEST(DownloadActionTest, FailWriteTest) {
+ brillo::Blob big(5 * kMockHttpFetcherChunkSize);
+ char c = '0';
+ for (unsigned int i = 0; i < big.size(); i++) {
+ big[i] = c;
+ c = ('9' == c) ? '0' : c + 1;
+ }
+ TestWithData(big,
+ 2, // fail_write
+ true); // use_download_delegate
+}
+
+TEST(DownloadActionTest, NoDownloadDelegateTest) {
+ brillo::Blob small;
+ const char* foo = "foofoo";
+ small.insert(small.end(), foo, foo + strlen(foo));
+ TestWithData(small,
+ 0, // fail_write
+ false); // use_download_delegate
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ void ProcessingStopped(const ActionProcessor* processor) {
+ brillo::MessageLoop::current()->BreakLoop();
+ }
+};
+
+void TerminateEarlyTestStarter(ActionProcessor* processor) {
+ processor->StartProcessing();
+ CHECK(processor->IsRunning());
+ processor->StopProcessing();
+}
+
+void TestTerminateEarly(bool use_download_delegate) {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ brillo::Blob data(kMockHttpFetcherChunkSize +
+ kMockHttpFetcherChunkSize / 2);
+ memset(data.data(), 0, data.size());
+
+ ScopedTempFile temp_file;
+ {
+ DirectFileWriter writer;
+ EXPECT_EQ(0, writer.Open(temp_file.GetPath().c_str(),
+ O_WRONLY | O_CREAT,
+ 0));
+
+ // takes ownership of passed in HttpFetcher
+ ObjectFeederAction<InstallPlan> feeder_action;
+ InstallPlan install_plan(false, false, "", 0, "", 0, "", "");
+ feeder_action.set_obj(install_plan);
+ FakeSystemState fake_system_state_;
+ MockPrefs prefs;
+ DownloadAction download_action(&prefs, &fake_system_state_,
+ new MockHttpFetcher(data.data(),
+ data.size(),
+ nullptr));
+ download_action.SetTestFileWriter(&writer);
+ MockDownloadActionDelegate download_delegate;
+ if (use_download_delegate) {
+ download_action.set_delegate(&download_delegate);
+ EXPECT_CALL(download_delegate, BytesReceived(_, _, _)).Times(0);
+ }
+ TerminateEarlyTestProcessorDelegate delegate;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&download_action);
+ BondActions(&feeder_action, &download_action);
+
+ loop.PostTask(FROM_HERE,
+ base::Bind(&TerminateEarlyTestStarter, &processor));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+ }
+
+ // 1 or 0 chunks should have come through
+ const off_t resulting_file_size(utils::FileSize(temp_file.GetPath()));
+ EXPECT_GE(resulting_file_size, 0);
+ if (resulting_file_size != 0)
+ EXPECT_EQ(kMockHttpFetcherChunkSize, resulting_file_size);
+}
+
+} // namespace
+
+TEST(DownloadActionTest, TerminateEarlyTest) {
+ TestTerminateEarly(true);
+}
+
+TEST(DownloadActionTest, TerminateEarlyNoDownloadDelegateTest) {
+ TestTerminateEarly(false);
+}
+
+class DownloadActionTestAction;
+
+template<>
+class ActionTraits<DownloadActionTestAction> {
+ public:
+ typedef InstallPlan OutputObjectType;
+ typedef InstallPlan InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class DownloadActionTestAction : public Action<DownloadActionTestAction> {
+ public:
+ DownloadActionTestAction() : did_run_(false) {}
+ typedef InstallPlan InputObjectType;
+ typedef InstallPlan OutputObjectType;
+ ActionPipe<InstallPlan>* in_pipe() { return in_pipe_.get(); }
+ ActionPipe<InstallPlan>* out_pipe() { return out_pipe_.get(); }
+ ActionProcessor* processor() { return processor_; }
+ void PerformAction() {
+ did_run_ = true;
+ ASSERT_TRUE(HasInputObject());
+ EXPECT_TRUE(expected_input_object_ == GetInputObject());
+ ASSERT_TRUE(processor());
+ processor()->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ string Type() const { return "DownloadActionTestAction"; }
+ InstallPlan expected_input_object_;
+ bool did_run_;
+};
+
+namespace {
+// This class is an ActionProcessorDelegate that simply terminates the
+// run loop when the ActionProcessor has completed processing. It's used
+// only by the test PassObjectOutTest.
+class PassObjectOutTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+ brillo::MessageLoop::current()->BreakLoop();
+ }
+};
+
+} // namespace
+
+TEST(DownloadActionTest, PassObjectOutTest) {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ DirectFileWriter writer;
+ EXPECT_EQ(0, writer.Open("/dev/null", O_WRONLY | O_CREAT, 0));
+
+ // takes ownership of passed in HttpFetcher
+ InstallPlan install_plan(false,
+ false,
+ "",
+ 1,
+ HashCalculator::HashOfString("x"),
+ 0,
+ "",
+ "");
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ FakeSystemState fake_system_state_;
+ DownloadAction download_action(&prefs, &fake_system_state_,
+ new MockHttpFetcher("x", 1, nullptr));
+ download_action.SetTestFileWriter(&writer);
+
+ DownloadActionTestAction test_action;
+ test_action.expected_input_object_ = install_plan;
+ BondActions(&feeder_action, &download_action);
+ BondActions(&download_action, &test_action);
+
+ ActionProcessor processor;
+ PassObjectOutTestProcessorDelegate delegate;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&download_action);
+ processor.EnqueueAction(&test_action);
+
+ loop.PostTask(FROM_HERE,
+ base::Bind([&processor] { processor.StartProcessing(); }));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+
+ EXPECT_EQ(true, test_action.did_run_);
+}
+
+// Test fixture for P2P tests.
+class P2PDownloadActionTest : public testing::Test {
+ protected:
+ P2PDownloadActionTest()
+ : start_at_offset_(0),
+ fake_um_(fake_system_state_.fake_clock()) {}
+
+ ~P2PDownloadActionTest() override {}
+
+ // Derived from testing::Test.
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ // Derived from testing::Test.
+ void TearDown() override {
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ // To be called by tests to setup the download. The
+ // |starting_offset| parameter is for where to resume.
+ void SetupDownload(off_t starting_offset) {
+ start_at_offset_ = starting_offset;
+ // Prepare data 10 kB of data.
+ data_.clear();
+ for (unsigned int i = 0; i < 10 * 1000; i++)
+ data_ += 'a' + (i % 25);
+
+ // 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());
+ }
+
+ // 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(),
+ GetUsingP2PForSharing())
+ .WillRepeatedly(Return(use_p2p_to_share));
+
+ ScopedTempFile output_temp_file;
+ TestDirectFileWriter writer;
+ EXPECT_EQ(0, writer.Open(output_temp_file.GetPath().c_str(),
+ O_WRONLY | O_CREAT,
+ 0));
+ InstallPlan install_plan(false,
+ false,
+ "",
+ data_.length(),
+ "1234hash",
+ 0,
+ "",
+ "");
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ http_fetcher_ = new MockHttpFetcher(data_.c_str(),
+ data_.length(),
+ nullptr);
+ // Note that DownloadAction takes ownership of the passed in HttpFetcher.
+ download_action_.reset(new DownloadAction(&prefs, &fake_system_state_,
+ http_fetcher_));
+ download_action_->SetTestFileWriter(&writer);
+ BondActions(&feeder_action, download_action_.get());
+ DownloadActionTestProcessorDelegate delegate(ErrorCode::kSuccess);
+ delegate.expected_data_ = brillo::Blob(data_.begin() + start_at_offset_,
+ data_.end());
+ delegate.path_ = output_temp_file.GetPath();
+ processor_.set_delegate(&delegate);
+ processor_.EnqueueAction(&feeder_action);
+ processor_.EnqueueAction(download_action_.get());
+
+ loop_.PostTask(FROM_HERE, base::Bind(
+ &P2PDownloadActionTest::StartProcessorInRunLoopForP2P,
+ base::Unretained(this)));
+ loop_.Run();
+ }
+
+ // Mainloop used to make StartDownload() synchronous.
+ brillo::FakeMessageLoop loop_{nullptr};
+
+ // The DownloadAction instance under test.
+ unique_ptr<DownloadAction> download_action_;
+
+ // The HttpFetcher used in the test.
+ MockHttpFetcher* http_fetcher_;
+
+ // The P2PManager used in the test.
+ unique_ptr<P2PManager> p2p_manager_;
+
+ // The ActionProcessor used for running the actions.
+ ActionProcessor processor_;
+
+ // A fake system state.
+ FakeSystemState fake_system_state_;
+
+ // The data being downloaded.
+ string data_;
+
+ private:
+ // Callback used in StartDownload() method.
+ void StartProcessorInRunLoopForP2P() {
+ processor_.StartProcessing();
+ http_fetcher_->SetOffset(start_at_offset_);
+ }
+
+ // The requested starting offset passed to SetupDownload().
+ off_t start_at_offset_;
+
+ chromeos_update_manager::FakeUpdateManager fake_um_;
+};
+
+TEST_F(P2PDownloadActionTest, IsWrittenTo) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(0); // starting_offset
+ StartDownload(true); // use_p2p_to_share
+
+ // Check the p2p file and its content matches what was sent.
+ string file_id = download_action_->p2p_file_id();
+ EXPECT_NE("", file_id);
+ EXPECT_EQ(data_.length(), p2p_manager_->FileGetSize(file_id));
+ EXPECT_EQ(data_.length(), p2p_manager_->FileGetExpectedSize(file_id));
+ string p2p_file_contents;
+ EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+ &p2p_file_contents));
+ EXPECT_EQ(data_, p2p_file_contents);
+}
+
+TEST_F(P2PDownloadActionTest, DeleteIfHoleExists) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(1000); // starting_offset
+ StartDownload(true); // use_p2p_to_share
+
+ // DownloadAction should convey that the file is not being shared.
+ // and that we don't have any p2p files.
+ EXPECT_EQ(download_action_->p2p_file_id(), "");
+ EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
+TEST_F(P2PDownloadActionTest, CanAppend) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(1000); // starting_offset
+
+ // Prepare the file with existing data before starting to write to
+ // it via DownloadAction.
+ string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+ ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+ string existing_data;
+ for (unsigned int i = 0; i < 1000; i++)
+ existing_data += '0' + (i % 10);
+ ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+ 1000), 1000);
+
+ StartDownload(true); // use_p2p_to_share
+
+ // DownloadAction should convey the same file_id and the file should
+ // have the expected size.
+ EXPECT_EQ(download_action_->p2p_file_id(), file_id);
+ EXPECT_EQ(p2p_manager_->FileGetSize(file_id), data_.length());
+ EXPECT_EQ(p2p_manager_->FileGetExpectedSize(file_id), data_.length());
+ string p2p_file_contents;
+ // Check that the first 1000 bytes wasn't touched and that we
+ // appended the remaining as appropriate.
+ EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+ &p2p_file_contents));
+ EXPECT_EQ(existing_data, p2p_file_contents.substr(0, 1000));
+ EXPECT_EQ(data_.substr(1000), p2p_file_contents.substr(1000));
+}
+
+TEST_F(P2PDownloadActionTest, DeletePartialP2PFileIfResumingWithoutP2P) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(1000); // starting_offset
+
+ // Prepare the file with all existing data before starting to write
+ // to it via DownloadAction.
+ string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+ ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+ string existing_data;
+ for (unsigned int i = 0; i < 1000; i++)
+ existing_data += '0' + (i % 10);
+ ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+ 1000), 1000);
+
+ // Check that the file is there.
+ EXPECT_EQ(p2p_manager_->FileGetSize(file_id), 1000);
+ EXPECT_EQ(p2p_manager_->CountSharedFiles(), 1);
+
+ StartDownload(false); // use_p2p_to_share
+
+ // DownloadAction should have deleted the p2p file. Check that it's gone.
+ EXPECT_EQ(p2p_manager_->FileGetSize(file_id), -1);
+ EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/extent_writer.cc b/payload_consumer/extent_writer.cc
new file mode 100644
index 0000000..5501e22
--- /dev/null
+++ b/payload_consumer/extent_writer.cc
@@ -0,0 +1,71 @@
+//
+// 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_consumer/extent_writer.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::min;
+
+namespace chromeos_update_engine {
+
+bool DirectExtentWriter::Write(const void* bytes, size_t count) {
+ if (count == 0)
+ return true;
+ const char* c_bytes = reinterpret_cast<const char*>(bytes);
+ size_t bytes_written = 0;
+ while (count - bytes_written > 0) {
+ TEST_AND_RETURN_FALSE(next_extent_index_ < extents_.size());
+ uint64_t bytes_remaining_next_extent =
+ extents_[next_extent_index_].num_blocks() * block_size_ -
+ extent_bytes_written_;
+ CHECK_NE(bytes_remaining_next_extent, static_cast<uint64_t>(0));
+ size_t bytes_to_write =
+ static_cast<size_t>(min(static_cast<uint64_t>(count - bytes_written),
+ bytes_remaining_next_extent));
+ TEST_AND_RETURN_FALSE(bytes_to_write > 0);
+
+ if (extents_[next_extent_index_].start_block() != kSparseHole) {
+ const off64_t offset =
+ extents_[next_extent_index_].start_block() * block_size_ +
+ extent_bytes_written_;
+ TEST_AND_RETURN_FALSE_ERRNO(fd_->Seek(offset, SEEK_SET) !=
+ static_cast<off64_t>(-1));
+ TEST_AND_RETURN_FALSE(
+ utils::WriteAll(fd_, c_bytes + bytes_written, bytes_to_write));
+ }
+ bytes_written += bytes_to_write;
+ extent_bytes_written_ += bytes_to_write;
+ if (bytes_remaining_next_extent == bytes_to_write) {
+ // We filled this extent
+ CHECK_EQ(extent_bytes_written_,
+ extents_[next_extent_index_].num_blocks() * block_size_);
+ // move to next extent
+ extent_bytes_written_ = 0;
+ next_extent_index_++;
+ }
+ }
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/extent_writer.h b/payload_consumer/extent_writer.h
new file mode 100644
index 0000000..6484ebf
--- /dev/null
+++ b/payload_consumer/extent_writer.h
@@ -0,0 +1,134 @@
+//
+// 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_CONSUMER_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_EXTENT_WRITER_H_
+
+#include <vector>
+
+#include <base/logging.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/update_metadata.pb.h"
+
+// ExtentWriter is an abstract class which synchronously writes to a given
+// file descriptor at the extents given.
+
+namespace chromeos_update_engine {
+
+class ExtentWriter {
+ public:
+ ExtentWriter() = default;
+ virtual ~ExtentWriter() {
+ LOG_IF(ERROR, !end_called_) << "End() not called on ExtentWriter.";
+ }
+
+ // Returns true on success.
+ virtual bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) = 0;
+
+ // Returns true on success.
+ virtual bool Write(const void* bytes, size_t count) = 0;
+
+ // Should be called when all writing is complete. Returns true on success.
+ // The fd is not closed. Caller is responsible for closing it.
+ bool End() {
+ end_called_ = true;
+ return EndImpl();
+ }
+ virtual bool EndImpl() = 0;
+ private:
+ bool end_called_{false};
+};
+
+// DirectExtentWriter is probably the simplest ExtentWriter implementation.
+// It writes the data directly into the extents.
+
+class DirectExtentWriter : public ExtentWriter {
+ public:
+ DirectExtentWriter() = default;
+ ~DirectExtentWriter() override = default;
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) override {
+ fd_ = fd;
+ block_size_ = block_size;
+ extents_ = extents;
+ return true;
+ }
+ bool Write(const void* bytes, size_t count) override;
+ bool EndImpl() override { return true; }
+
+ private:
+ FileDescriptorPtr fd_{nullptr};
+
+ size_t block_size_{0};
+ // Bytes written into next_extent_index_ thus far
+ uint64_t extent_bytes_written_{0};
+ std::vector<Extent> extents_;
+ // The next call to write should correspond to extents_[next_extent_index_]
+ std::vector<Extent>::size_type next_extent_index_{0};
+};
+
+// Takes an underlying ExtentWriter to which all operations are delegated.
+// When End() is called, ZeroPadExtentWriter ensures that the total number
+// of bytes written is a multiple of block_size_. If not, it writes zeros
+// to pad as needed.
+
+class ZeroPadExtentWriter : public ExtentWriter {
+ public:
+ explicit ZeroPadExtentWriter(
+ std::unique_ptr<ExtentWriter> underlying_extent_writer)
+ : underlying_extent_writer_(std::move(underlying_extent_writer)) {}
+ ~ZeroPadExtentWriter() override = default;
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) override {
+ block_size_ = block_size;
+ return underlying_extent_writer_->Init(fd, extents, block_size);
+ }
+ bool Write(const void* bytes, size_t count) override {
+ if (underlying_extent_writer_->Write(bytes, count)) {
+ bytes_written_mod_block_size_ += count;
+ bytes_written_mod_block_size_ %= block_size_;
+ return true;
+ }
+ return false;
+ }
+ bool EndImpl() override {
+ if (bytes_written_mod_block_size_) {
+ const size_t write_size = block_size_ - bytes_written_mod_block_size_;
+ brillo::Blob zeros(write_size, 0);
+ TEST_AND_RETURN_FALSE(underlying_extent_writer_->Write(zeros.data(),
+ write_size));
+ }
+ return underlying_extent_writer_->End();
+ }
+
+ private:
+ std::unique_ptr<ExtentWriter> underlying_extent_writer_;
+ size_t block_size_{0};
+ size_t bytes_written_mod_block_size_{0};
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_EXTENT_WRITER_H_
diff --git a/payload_consumer/extent_writer_unittest.cc b/payload_consumer/extent_writer_unittest.cc
new file mode 100644
index 0000000..24ea5bf
--- /dev/null
+++ b/payload_consumer/extent_writer_unittest.cc
@@ -0,0 +1,270 @@
+//
+// 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_consumer/extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <brillo/make_unique_ptr.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/payload_constants.h"
+
+using chromeos_update_engine::test_utils::ExpectVectorsEq;
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+COMPILE_ASSERT(sizeof(off_t) == 8, off_t_not_64_bit);
+
+namespace {
+const char kPathTemplate[] = "./ExtentWriterTest-file.XXXXXX";
+const size_t kBlockSize = 4096;
+}
+
+class ExtentWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ memcpy(path_, kPathTemplate, sizeof(kPathTemplate));
+ fd_.reset(new EintrSafeFileDescriptor);
+ int fd = mkstemp(path_);
+ ASSERT_TRUE(fd_->Open(path_, O_RDWR, 0600));
+ close(fd);
+ }
+ void TearDown() override {
+ fd_->Close();
+ unlink(path_);
+ }
+
+ // Writes data to an extent writer in 'chunk_size' chunks with
+ // the first chunk of size first_chunk_size. It calculates what the
+ // resultant file should look like and ensure that the extent writer
+ // wrote the file correctly.
+ void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+ void TestZeroPad(bool aligned_size);
+
+ FileDescriptorPtr fd_;
+ char path_[sizeof(kPathTemplate)];
+};
+
+TEST_F(ExtentWriterTest, SimpleTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ const string bytes = "1234";
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+ EXPECT_TRUE(direct_writer.Write(bytes.data(), bytes.size()));
+ EXPECT_TRUE(direct_writer.End());
+
+ EXPECT_EQ(kBlockSize + bytes.size(), utils::FileSize(path_));
+
+ brillo::Blob result_file;
+ EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+ brillo::Blob expected_file(kBlockSize);
+ expected_file.insert(expected_file.end(),
+ bytes.data(), bytes.data() + bytes.size());
+ ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroLengthTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+ EXPECT_TRUE(direct_writer.Write(nullptr, 0));
+ EXPECT_TRUE(direct_writer.End());
+}
+
+TEST_F(ExtentWriterTest, OverflowExtentTest) {
+ WriteAlignedExtents(kBlockSize * 3, kBlockSize * 3);
+}
+
+TEST_F(ExtentWriterTest, UnalignedWriteTest) {
+ WriteAlignedExtents(7, 7);
+}
+
+TEST_F(ExtentWriterTest, LargeUnalignedWriteTest) {
+ WriteAlignedExtents(kBlockSize * 2, kBlockSize / 2);
+}
+
+void ExtentWriterTest::WriteAlignedExtents(size_t chunk_size,
+ size_t first_chunk_size) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(2);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ brillo::Blob data(kBlockSize * 3);
+ test_utils::FillWithData(&data);
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+
+ size_t bytes_written = 0;
+ while (bytes_written < data.size()) {
+ size_t bytes_to_write = min(data.size() - bytes_written, chunk_size);
+ if (bytes_written == 0) {
+ bytes_to_write = min(data.size() - bytes_written, first_chunk_size);
+ }
+ EXPECT_TRUE(direct_writer.Write(&data[bytes_written], bytes_to_write));
+ bytes_written += bytes_to_write;
+ }
+ EXPECT_TRUE(direct_writer.End());
+
+ EXPECT_EQ(data.size(), utils::FileSize(path_));
+
+ brillo::Blob result_file;
+ EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+ brillo::Blob expected_file;
+ expected_file.insert(expected_file.end(),
+ data.begin() + kBlockSize,
+ data.begin() + kBlockSize * 2);
+ expected_file.insert(expected_file.end(),
+ data.begin(), data.begin() + kBlockSize);
+ expected_file.insert(expected_file.end(),
+ data.begin() + kBlockSize * 2, data.end());
+ ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadNullTest) {
+ TestZeroPad(true);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadFillTest) {
+ TestZeroPad(false);
+}
+
+void ExtentWriterTest::TestZeroPad(bool aligned_size) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ brillo::Blob data(kBlockSize * 2);
+ test_utils::FillWithData(&data);
+
+ ZeroPadExtentWriter zero_pad_writer(
+ brillo::make_unique_ptr(new DirectExtentWriter()));
+
+ EXPECT_TRUE(zero_pad_writer.Init(fd_, extents, kBlockSize));
+ size_t bytes_to_write = data.size();
+ const size_t missing_bytes = (aligned_size ? 0 : 9);
+ bytes_to_write -= missing_bytes;
+ fd_->Seek(kBlockSize - missing_bytes, SEEK_SET);
+ EXPECT_EQ(3, fd_->Write("xxx", 3));
+ ASSERT_TRUE(zero_pad_writer.Write(data.data(), bytes_to_write));
+ EXPECT_TRUE(zero_pad_writer.End());
+
+ EXPECT_EQ(data.size(), utils::FileSize(path_));
+
+ brillo::Blob result_file;
+ EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+ brillo::Blob expected_file;
+ expected_file.insert(expected_file.end(),
+ data.begin() + kBlockSize,
+ data.begin() + kBlockSize * 2);
+ expected_file.insert(expected_file.end(),
+ data.begin(), data.begin() + kBlockSize);
+ if (missing_bytes) {
+ memset(&expected_file[kBlockSize - missing_bytes], 0, missing_bytes);
+ }
+
+ ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, SparseFileTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(kSparseHole);
+ extent.set_num_blocks(2);
+ extents.push_back(extent);
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ const int block_count = 4;
+ const int on_disk_count = 2;
+
+ brillo::Blob data(17);
+ test_utils::FillWithData(&data);
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+
+ size_t bytes_written = 0;
+ while (bytes_written < (block_count * kBlockSize)) {
+ size_t bytes_to_write = min(block_count * kBlockSize - bytes_written,
+ data.size());
+ EXPECT_TRUE(direct_writer.Write(data.data(), bytes_to_write));
+ bytes_written += bytes_to_write;
+ }
+ EXPECT_TRUE(direct_writer.End());
+
+ // check file size, then data inside
+ ASSERT_EQ(2 * kBlockSize, utils::FileSize(path_));
+
+ brillo::Blob resultant_data;
+ EXPECT_TRUE(utils::ReadFile(path_, &resultant_data));
+
+ // Create expected data
+ brillo::Blob expected_data(on_disk_count * kBlockSize);
+ brillo::Blob big(block_count * kBlockSize);
+ for (brillo::Blob::size_type i = 0; i < big.size(); i++) {
+ big[i] = data[i % data.size()];
+ }
+ memcpy(&expected_data[kBlockSize], &big[0], kBlockSize);
+ memcpy(&expected_data[0], &big[3 * kBlockSize], kBlockSize);
+ ExpectVectorsEq(expected_data, resultant_data);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/fake_extent_writer.h b/payload_consumer/fake_extent_writer.h
new file mode 100644
index 0000000..762c6d5
--- /dev/null
+++ b/payload_consumer/fake_extent_writer.h
@@ -0,0 +1,71 @@
+//
+// 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_CONSUMER_FAKE_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FAKE_EXTENT_WRITER_H_
+
+#include <memory>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/extent_writer.h"
+
+namespace chromeos_update_engine {
+
+// FakeExtentWriter is a concrete ExtentWriter subclass that keeps track of all
+// the written data, useful for testing.
+class FakeExtentWriter : public ExtentWriter {
+ public:
+ FakeExtentWriter() = default;
+ ~FakeExtentWriter() override = default;
+
+ // ExtentWriter overrides.
+ bool Init(FileDescriptorPtr /* fd */,
+ const std::vector<Extent>& /* extents */,
+ uint32_t /* block_size */) override {
+ init_called_ = true;
+ return true;
+ };
+ bool Write(const void* bytes, size_t count) override {
+ if (!init_called_ || end_called_)
+ return false;
+ written_data_.insert(written_data_.end(),
+ reinterpret_cast<const uint8_t*>(bytes),
+ reinterpret_cast<const uint8_t*>(bytes) + count);
+ return true;
+ }
+ bool EndImpl() override {
+ end_called_ = true;
+ return true;
+ }
+
+ // Fake methods.
+ bool InitCalled() { return init_called_; }
+ bool EndCalled() { return end_called_; }
+ brillo::Blob WrittenData() { return written_data_; }
+
+ private:
+ bool init_called_{false};
+ bool end_called_{false};
+ brillo::Blob written_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeExtentWriter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_FAKE_EXTENT_WRITER_H_
diff --git a/payload_consumer/file_descriptor.cc b/payload_consumer/file_descriptor.cc
new file mode 100644
index 0000000..f26be28
--- /dev/null
+++ b/payload_consumer/file_descriptor.cc
@@ -0,0 +1,116 @@
+//
+// 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_consumer/file_descriptor.h"
+
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <base/posix/eintr_wrapper.h>
+
+namespace chromeos_update_engine {
+
+bool EintrSafeFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+ CHECK_EQ(fd_, -1);
+ return ((fd_ = HANDLE_EINTR(open(path, flags, mode))) >= 0);
+}
+
+bool EintrSafeFileDescriptor::Open(const char* path, int flags) {
+ CHECK_EQ(fd_, -1);
+ return ((fd_ = HANDLE_EINTR(open(path, flags))) >= 0);
+}
+
+ssize_t EintrSafeFileDescriptor::Read(void* buf, size_t count) {
+ CHECK_GE(fd_, 0);
+ return HANDLE_EINTR(read(fd_, buf, count));
+}
+
+ssize_t EintrSafeFileDescriptor::Write(const void* buf, size_t count) {
+ CHECK_GE(fd_, 0);
+
+ // Attempt repeated writes, as long as some progress is being made.
+ char* char_buf = const_cast<char*>(reinterpret_cast<const char*>(buf));
+ ssize_t written = 0;
+ while (count > 0) {
+ ssize_t ret = HANDLE_EINTR(write(fd_, char_buf, count));
+
+ // Fail on either an error or no progress.
+ if (ret <= 0)
+ return (written ? written : ret);
+ written += ret;
+ count -= ret;
+ char_buf += ret;
+ }
+ return written;
+}
+
+off64_t EintrSafeFileDescriptor::Seek(off64_t offset, int whence) {
+ CHECK_GE(fd_, 0);
+ return lseek64(fd_, offset, whence);
+}
+
+bool EintrSafeFileDescriptor::BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) {
+ DCHECK(request == BLKDISCARD || request == BLKZEROOUT ||
+ request == BLKSECDISCARD);
+ // On some devices, the BLKDISCARD will actually read back as zeros, instead
+ // of "undefined" data. The BLKDISCARDZEROES ioctl tells whether that's the
+ // case, so we issue a BLKDISCARD in those cases to speed up the writes.
+ unsigned int arg;
+ if (request == BLKZEROOUT && ioctl(fd_, BLKDISCARDZEROES, &arg) == 0 && arg)
+ request = BLKDISCARD;
+
+ // Ensure the |fd_| is in O_DIRECT mode during this operation, so the write
+ // cache for this region is invalidated. This is required since otherwise
+ // reading back this region could consume stale data from the cache.
+ int flags = fcntl(fd_, F_GETFL, 0);
+ if (flags == -1) {
+ PLOG(WARNING) << "Couldn't get flags on fd " << fd_;
+ return false;
+ }
+ if ((flags & O_DIRECT) == 0 && fcntl(fd_, F_SETFL, flags | O_DIRECT) == -1) {
+ PLOG(WARNING) << "Couldn't set O_DIRECT on fd " << fd_;
+ return false;
+ }
+
+ uint64_t range[2] = {start, length};
+ *result = ioctl(fd_, request, range);
+
+ if ((flags & O_DIRECT) == 0 && fcntl(fd_, F_SETFL, flags) == -1) {
+ PLOG(WARNING) << "Couldn't remove O_DIRECT on fd " << fd_;
+ return false;
+ }
+ return true;
+}
+
+bool EintrSafeFileDescriptor::Close() {
+ CHECK_GE(fd_, 0);
+ if (IGNORE_EINTR(close(fd_)))
+ return false;
+ Reset();
+ return true;
+}
+
+void EintrSafeFileDescriptor::Reset() {
+ fd_ = -1;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/file_descriptor.h b/payload_consumer/file_descriptor.h
new file mode 100644
index 0000000..3c15415
--- /dev/null
+++ b/payload_consumer/file_descriptor.h
@@ -0,0 +1,141 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_H_
+
+#include <errno.h>
+#include <memory>
+#include <sys/types.h>
+
+#include <base/logging.h>
+
+// Abstraction for managing opening, reading, writing and closing of file
+// descriptors. This includes an abstract class and one standard implementation
+// based on POSIX system calls.
+//
+// TODO(garnold) this class is modeled after (and augments the functionality of)
+// the FileWriter class; ultimately, the latter should be replaced by the former
+// throughout the codebase. A few deviations from the original FileWriter:
+//
+// * Providing two flavors of Open()
+//
+// * A FileDescriptor is reusable and can be used to read/write multiple files
+// as long as open/close preconditions are respected.
+//
+// * Write() returns the number of bytes written: this appears to be more useful
+// for clients, who may wish to retry or otherwise do something useful with
+// the remaining data that was not written.
+//
+// * Provides a Reset() method, which will force to abandon a currently open
+// file descriptor and allow opening another file, without necessarily
+// properly closing the old one. This may be useful in cases where a "closer"
+// class does not care whether Close() was successful, but may need to reuse
+// the same file descriptor again.
+
+namespace chromeos_update_engine {
+
+class FileDescriptor;
+using FileDescriptorPtr = std::shared_ptr<FileDescriptor>;
+
+// An abstract class defining the file descriptor API.
+class FileDescriptor {
+ public:
+ FileDescriptor() {}
+ virtual ~FileDescriptor() {}
+
+ // Opens a file descriptor. The descriptor must be in the closed state prior
+ // to this call. Returns true on success, false otherwise. Specific
+ // implementations may set errno accordingly.
+ virtual bool Open(const char* path, int flags, mode_t mode) = 0;
+ virtual bool Open(const char* path, int flags) = 0;
+
+ // Reads from a file descriptor up to a given count. The descriptor must be
+ // open prior to this call. Returns the number of bytes read, or -1 on error.
+ // Specific implementations may set errno accordingly.
+ virtual ssize_t Read(void* buf, size_t count) = 0;
+
+ // Writes to a file descriptor. The descriptor must be open prior to this
+ // call. Returns the number of bytes written, or -1 if an error occurred and
+ // no bytes were written. Specific implementations may set errno accordingly.
+ virtual ssize_t Write(const void* buf, size_t count) = 0;
+
+ // Seeks to an offset. Returns the resulting offset location as measured in
+ // bytes from the beginning. On error, return -1. Specific implementations
+ // may set errno accordingly.
+ virtual off64_t Seek(off64_t offset, int whence) = 0;
+
+ // Runs a ioctl() on the file descriptor if supported. Returns whether
+ // the operation is supported. The |request| can be one of BLKDISCARD,
+ // BLKZEROOUT and BLKSECDISCARD to discard, write zeros or securely discard
+ // the blocks. These ioctls accept a range of bytes (|start| and |length|)
+ // over which they perform the operation. The return value from the ioctl is
+ // stored in |result|.
+ virtual bool BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) = 0;
+
+ // Closes a file descriptor. The descriptor must be open prior to this call.
+ // Returns true on success, false otherwise. Specific implementations may set
+ // errno accordingly.
+ virtual bool Close() = 0;
+
+ // Resets the file descriptor, abandoning a currently open file and returning
+ // the descriptor to the closed state.
+ virtual void Reset() = 0;
+
+ // Indicates whether or not an implementation sets meaningful errno.
+ virtual bool IsSettingErrno() = 0;
+
+ // Indicates whether the descriptor is currently open.
+ virtual bool IsOpen() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileDescriptor);
+};
+
+// A simple EINTR-immune wrapper implementation around standard system calls.
+class EintrSafeFileDescriptor : public FileDescriptor {
+ public:
+ EintrSafeFileDescriptor() : fd_(-1) {}
+
+ // Interface methods.
+ 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;
+ bool BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) override;
+ bool Close() override;
+ void Reset() override;
+ bool IsSettingErrno() override {
+ return true;
+ }
+ bool IsOpen() override {
+ return (fd_ >= 0);
+ }
+
+ protected:
+ int fd_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_H_
diff --git a/payload_consumer/file_writer.cc b/payload_consumer/file_writer.cc
new file mode 100644
index 0000000..d280ddb
--- /dev/null
+++ b/payload_consumer/file_writer.cc
@@ -0,0 +1,60 @@
+//
+// 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_consumer/file_writer.h"
+
+#include <errno.h>
+
+namespace chromeos_update_engine {
+
+int DirectFileWriter::Open(const char* path, int flags, mode_t mode) {
+ CHECK_EQ(fd_, -1);
+ fd_ = open(path, flags, mode);
+ if (fd_ < 0)
+ return -errno;
+ return 0;
+}
+
+bool DirectFileWriter::Write(const void* bytes, size_t count) {
+ CHECK_GE(fd_, 0);
+ const char* char_bytes = reinterpret_cast<const char*>(bytes);
+
+ size_t bytes_written = 0;
+ while (bytes_written < count) {
+ ssize_t rc = write(fd_, char_bytes + bytes_written,
+ count - bytes_written);
+ if (rc < 0)
+ return false;
+ bytes_written += rc;
+ }
+ CHECK_EQ(bytes_written, count);
+ return bytes_written == count;
+}
+
+int DirectFileWriter::Close() {
+ CHECK_GE(fd_, 0);
+ int rc = close(fd_);
+
+ // This can be any negative number that's not -1. This way, this FileWriter
+ // won't be used again for another file.
+ fd_ = -2;
+
+ if (rc < 0)
+ return -errno;
+ return rc;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/file_writer.h b/payload_consumer/file_writer.h
new file mode 100644
index 0000000..96ebde6
--- /dev/null
+++ b/payload_consumer/file_writer.h
@@ -0,0 +1,103 @@
+//
+// 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_CONSUMER_FILE_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_WRITER_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/logging.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/utils.h"
+
+// FileWriter is a class that is used to (synchronously, for now) write to
+// a file. This file is a thin wrapper around open/write/close system calls,
+// but provides and interface that can be customized by subclasses that wish
+// to filter the data.
+
+namespace chromeos_update_engine {
+
+class FileWriter {
+ public:
+ FileWriter() {}
+ virtual ~FileWriter() {}
+
+ // Wrapper around write. Returns true if all requested bytes
+ // were written, or false on any error, regardless of progress.
+ virtual bool Write(const void* bytes, size_t count) = 0;
+
+ // Same as the Write method above but returns a detailed |error| code
+ // in addition if the returned value is false. By default this method
+ // returns kActionExitDownloadWriteError as the error code, but subclasses
+ // can override if they wish to return more specific error codes.
+ virtual bool Write(const void* bytes,
+ size_t count,
+ ErrorCode* error) {
+ *error = ErrorCode::kDownloadWriteError;
+ return Write(bytes, count);
+ }
+
+ // Wrapper around close. Returns 0 on success or -errno on error.
+ virtual int Close() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileWriter);
+};
+
+// Direct file writer is probably the simplest FileWriter implementation.
+// It calls the system calls directly.
+
+class DirectFileWriter : public FileWriter {
+ public:
+ DirectFileWriter() = default;
+
+ // FileWriter overrides.
+ bool Write(const void* bytes, size_t count) override;
+ int Close() override;
+
+ // Wrapper around open. Returns 0 on success or -errno on error.
+ int Open(const char* path, int flags, mode_t mode);
+
+ int fd() const { return fd_; }
+
+ private:
+ int fd_{-1};
+
+ DISALLOW_COPY_AND_ASSIGN(DirectFileWriter);
+};
+
+class ScopedFileWriterCloser {
+ public:
+ explicit ScopedFileWriterCloser(FileWriter* writer) : writer_(writer) {}
+ ~ScopedFileWriterCloser() {
+ int err = writer_->Close();
+ if (err)
+ LOG(ERROR) << "FileWriter::Close failed: "
+ << utils::ErrnoNumberAsString(-err);
+ }
+ private:
+ FileWriter* writer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedFileWriterCloser);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_WRITER_H_
diff --git a/payload_consumer/file_writer_unittest.cc b/payload_consumer/file_writer_unittest.cc
new file mode 100644
index 0000000..debb4c3
--- /dev/null
+++ b/payload_consumer/file_writer_unittest.cc
@@ -0,0 +1,79 @@
+//
+// 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_consumer/file_writer.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FileWriterTest : public ::testing::Test { };
+
+TEST(FileWriterTest, SimpleTest) {
+ // Create a uniquely named file for testing.
+ string path;
+ ASSERT_TRUE(utils::MakeTempFile("FileWriterTest-XXXXXX", &path, nullptr));
+ ScopedPathUnlinker path_unlinker(path);
+
+ DirectFileWriter file_writer;
+ EXPECT_EQ(0, file_writer.Open(path.c_str(),
+ O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY,
+ 0644));
+ EXPECT_TRUE(file_writer.Write("test", 4));
+ brillo::Blob actual_data;
+ EXPECT_TRUE(utils::ReadFile(path, &actual_data));
+
+ EXPECT_FALSE(memcmp("test", actual_data.data(), actual_data.size()));
+ EXPECT_EQ(0, file_writer.Close());
+}
+
+TEST(FileWriterTest, ErrorTest) {
+ DirectFileWriter file_writer;
+ const string path("/tmp/ENOENT/FileWriterTest");
+ EXPECT_EQ(-ENOENT, file_writer.Open(path.c_str(),
+ O_CREAT | O_LARGEFILE | O_TRUNC, 0644));
+}
+
+TEST(FileWriterTest, WriteErrorTest) {
+ // Create a uniquely named file for testing.
+ string path;
+ ASSERT_TRUE(utils::MakeTempFile("FileWriterTest-XXXXXX", &path, nullptr));
+ ScopedPathUnlinker path_unlinker(path);
+
+ DirectFileWriter file_writer;
+ EXPECT_EQ(0, file_writer.Open(path.c_str(),
+ O_CREAT | O_LARGEFILE | O_TRUNC | O_RDONLY,
+ 0644));
+ EXPECT_FALSE(file_writer.Write("x", 1));
+ EXPECT_EQ(0, file_writer.Close());
+}
+
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc
new file mode 100644
index 0000000..8530d37
--- /dev/null
+++ b/payload_consumer/filesystem_verifier_action.cc
@@ -0,0 +1,287 @@
+//
+// 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_consumer/filesystem_verifier_action.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <string>
+
+#include <base/bind.h>
+#include <brillo/streams/file_stream.h>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+const off_t kReadFileBufferSize = 128 * 1024;
+} // namespace
+
+FilesystemVerifierAction::FilesystemVerifierAction(
+ const BootControlInterface* boot_control,
+ VerifierMode verifier_mode)
+ : verifier_mode_(verifier_mode),
+ boot_control_(boot_control) {}
+
+void FilesystemVerifierAction::PerformAction() {
+ // Will tell the ActionProcessor we've failed if we return.
+ ScopedActionCompleter abort_action_completer(processor_, this);
+
+ if (!HasInputObject()) {
+ LOG(ERROR) << "FilesystemVerifierAction missing input object.";
+ return;
+ }
+ install_plan_ = GetInputObject();
+
+ // For delta updates (major version 1) we need to populate the source
+ // partition hash if not pre-populated.
+ if (!install_plan_.is_full_update && install_plan_.partitions.empty() &&
+ verifier_mode_ == VerifierMode::kComputeSourceHash &&
+ DeltaPerformer::kSupportedMinorPayloadVersion <
+ kOpSrcHashMinorPayloadVersion) {
+ LOG(INFO) << "Using legacy partition names.";
+ InstallPlan::Partition part;
+ string part_path;
+
+ part.name = kLegacyPartitionNameRoot;
+ if (!boot_control_->GetPartitionDevice(
+ part.name, install_plan_.source_slot, &part_path))
+ return;
+ int block_count = 0, block_size = 0;
+ if (utils::GetFilesystemSize(part_path, &block_count, &block_size)) {
+ part.source_size = static_cast<int64_t>(block_count) * block_size;
+ LOG(INFO) << "Partition " << part.name << " size: " << part.source_size
+ << " bytes (" << block_count << "x" << block_size << ").";
+ }
+ install_plan_.partitions.push_back(part);
+
+ part.name = kLegacyPartitionNameKernel;
+ if (!boot_control_->GetPartitionDevice(
+ part.name, install_plan_.source_slot, &part_path))
+ return;
+ off_t kernel_part_size = utils::FileSize(part_path);
+ if (kernel_part_size < 0)
+ return;
+ LOG(INFO) << "Partition " << part.name << " size: " << kernel_part_size
+ << " bytes.";
+ part.source_size = kernel_part_size;
+ install_plan_.partitions.push_back(part);
+ }
+
+ if (install_plan_.partitions.empty()) {
+ LOG(INFO) << "No partitions to verify.";
+ if (HasOutputPipe())
+ SetOutputObject(install_plan_);
+ abort_action_completer.set_code(ErrorCode::kSuccess);
+ return;
+ }
+
+ StartPartitionHashing();
+ abort_action_completer.set_should_complete(false);
+}
+
+void FilesystemVerifierAction::TerminateProcessing() {
+ cancelled_ = true;
+ Cleanup(ErrorCode::kSuccess); // error code is ignored if canceled_ is true.
+}
+
+bool FilesystemVerifierAction::IsCleanupPending() const {
+ return src_stream_ != nullptr;
+}
+
+void FilesystemVerifierAction::Cleanup(ErrorCode code) {
+ src_stream_.reset();
+ // This memory is not used anymore.
+ buffer_.clear();
+
+ if (cancelled_)
+ return;
+ if (code == ErrorCode::kSuccess && HasOutputPipe())
+ SetOutputObject(install_plan_);
+ processor_->ActionComplete(this, code);
+}
+
+void FilesystemVerifierAction::StartPartitionHashing() {
+ if (partition_index_ == install_plan_.partitions.size()) {
+ // We never called this action with kVerifySourceHash directly, if we are in
+ // this mode, it means the target partition verification has failed, so we
+ // should set the error code to reflect the error in target.
+ if (verifier_mode_ == VerifierMode::kVerifySourceHash)
+ Cleanup(ErrorCode::kNewRootfsVerificationError);
+ else
+ Cleanup(ErrorCode::kSuccess);
+ return;
+ }
+ InstallPlan::Partition& partition =
+ install_plan_.partitions[partition_index_];
+
+ string part_path;
+ switch (verifier_mode_) {
+ case VerifierMode::kComputeSourceHash:
+ case VerifierMode::kVerifySourceHash:
+ boot_control_->GetPartitionDevice(
+ partition.name, install_plan_.source_slot, &part_path);
+ remaining_size_ = partition.source_size;
+ break;
+ case VerifierMode::kVerifyTargetHash:
+ boot_control_->GetPartitionDevice(
+ partition.name, install_plan_.target_slot, &part_path);
+ remaining_size_ = partition.target_size;
+ break;
+ }
+ LOG(INFO) << "Hashing partition " << partition_index_ << " ("
+ << partition.name << ") on device " << part_path;
+ if (part_path.empty())
+ return Cleanup(ErrorCode::kFilesystemVerifierError);
+
+ 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";
+ return Cleanup(ErrorCode::kFilesystemVerifierError);
+ }
+
+ buffer_.resize(kReadFileBufferSize);
+ read_done_ = false;
+ hasher_.reset(new HashCalculator());
+
+ // Start the first read.
+ ScheduleRead();
+}
+
+void FilesystemVerifierAction::ScheduleRead() {
+ size_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()),
+ remaining_size_);
+ if (!bytes_to_read) {
+ OnReadDoneCallback(0);
+ 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);
+ }
+}
+
+void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) {
+ if (bytes_read == 0) {
+ read_done_ = true;
+ } else {
+ remaining_size_ -= bytes_read;
+ CHECK(!read_done_);
+ if (!hasher_->Update(buffer_.data(), bytes_read)) {
+ LOG(ERROR) << "Unable to update the hash.";
+ Cleanup(ErrorCode::kError);
+ return;
+ }
+ }
+
+ // We either terminate the current partition or have more data to read.
+ if (cancelled_)
+ return Cleanup(ErrorCode::kError);
+
+ if (read_done_ || remaining_size_ == 0) {
+ if (remaining_size_ != 0) {
+ LOG(ERROR) << "Failed to read the remaining " << remaining_size_
+ << " bytes from partition "
+ << install_plan_.partitions[partition_index_].name;
+ return Cleanup(ErrorCode::kFilesystemVerifierError);
+ }
+ return FinishPartitionHashing();
+ }
+ ScheduleRead();
+}
+
+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);
+}
+
+void FilesystemVerifierAction::FinishPartitionHashing() {
+ if (!hasher_->Finalize()) {
+ LOG(ERROR) << "Unable to finalize the hash.";
+ return Cleanup(ErrorCode::kError);
+ }
+ InstallPlan::Partition& partition =
+ install_plan_.partitions[partition_index_];
+ LOG(INFO) << "Hash of " << partition.name << ": " << hasher_->hash();
+
+ switch (verifier_mode_) {
+ case VerifierMode::kComputeSourceHash:
+ partition.source_hash = hasher_->raw_hash();
+ partition_index_++;
+ break;
+ case VerifierMode::kVerifyTargetHash:
+ if (partition.target_hash != hasher_->raw_hash()) {
+ LOG(ERROR) << "New '" << partition.name
+ << "' partition verification failed.";
+ if (DeltaPerformer::kSupportedMinorPayloadVersion <
+ kOpSrcHashMinorPayloadVersion)
+ return Cleanup(ErrorCode::kNewRootfsVerificationError);
+ // If we support per-operation source hash, then we skipped source
+ // filesystem verification, now that the target partition does not
+ // match, we need to switch to kVerifySourceHash mode to check if it's
+ // because the source partition does not match either.
+ verifier_mode_ = VerifierMode::kVerifySourceHash;
+ partition_index_ = 0;
+ } else {
+ partition_index_++;
+ }
+ break;
+ case VerifierMode::kVerifySourceHash:
+ if (partition.source_hash != hasher_->raw_hash()) {
+ LOG(ERROR) << "Old '" << partition.name
+ << "' partition verification failed.";
+ return Cleanup(ErrorCode::kDownloadStateInitializationError);
+ }
+ partition_index_++;
+ break;
+ }
+ // Start hashing the next partition, if any.
+ hasher_.reset();
+ buffer_.clear();
+ src_stream_->CloseBlocking(nullptr);
+ StartPartitionHashing();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/filesystem_verifier_action.h b/payload_consumer/filesystem_verifier_action.h
new file mode 100644
index 0000000..94f1b4e
--- /dev/null
+++ b/payload_consumer/filesystem_verifier_action.h
@@ -0,0 +1,130 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_FILESYSTEM_VERIFIER_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FILESYSTEM_VERIFIER_ACTION_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/streams/stream.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/payload_consumer/install_plan.h"
+
+// This action will hash all the partitions of a single slot involved in the
+// update (either source or target slot). The hashes are then either stored in
+// the InstallPlan (for source partitions) or verified against it (for target
+// partitions).
+
+namespace chromeos_update_engine {
+
+// The mode we are running the FilesystemVerifier on. On kComputeSourceHash mode
+// it computes the source_hash of all the partitions in the InstallPlan, based
+// on the already populated source_size values. On kVerifyTargetHash it computes
+// the hash on the target partitions based on the already populated size and
+// verifies it matches the one in the target_hash in the InstallPlan.
+enum class VerifierMode {
+ kComputeSourceHash,
+ kVerifyTargetHash,
+ kVerifySourceHash,
+};
+
+class FilesystemVerifierAction : public InstallPlanAction {
+ public:
+ FilesystemVerifierAction(const BootControlInterface* boot_control,
+ VerifierMode verifier_mode);
+
+ void PerformAction() override;
+ void TerminateProcessing() override;
+
+ // Used for testing. Return true if Cleanup() has not yet been called due
+ // to a callback upon the completion or cancellation of the verifier action.
+ // A test should wait until IsCleanupPending() returns false before
+ // terminating the main loop.
+ bool IsCleanupPending() const;
+
+ // Debugging/logging
+ static std::string StaticType() { return "FilesystemVerifierAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ private:
+ friend class FilesystemVerifierActionTest;
+ FRIEND_TEST(FilesystemVerifierActionTest,
+ RunAsRootDetermineFilesystemSizeTest);
+
+ // Starts the hashing of the current partition. If there aren't any partitions
+ // remaining to be hashed, if finishes the action.
+ void StartPartitionHashing();
+
+ // Schedules the asynchronous read of the filesystem.
+ void ScheduleRead();
+
+ // 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);
+
+ // When the read is done, finalize the hash checking of the current partition
+ // and continue checking the next one.
+ void FinishPartitionHashing();
+
+ // Cleans up all the variables we use for async operations and tells the
+ // ActionProcessor we're done w/ |code| as passed in. |cancelled_| should be
+ // true if TerminateProcessing() was called.
+ void Cleanup(ErrorCode code);
+
+ // The type of the partition that we are verifying.
+ VerifierMode verifier_mode_;
+
+ // The BootControlInterface used to get the partitions based on the slots.
+ const BootControlInterface* const boot_control_;
+
+ // The index in the install_plan_.partitions vector of the partition currently
+ // being hashed.
+ size_t partition_index_{0};
+
+ // If not null, the FileStream used to read from the device.
+ brillo::StreamPtr src_stream_;
+
+ // Buffer for storing data we read.
+ brillo::Blob buffer_;
+
+ bool read_done_{false}; // true if reached EOF on the input stream.
+ 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_;
+
+ // Reads and hashes this many bytes from the head of the input stream. This
+ // field is initialized from the corresponding InstallPlan::Partition size,
+ // when the partition starts to be hashed.
+ int64_t remaining_size_{0};
+
+ DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_FILESYSTEM_VERIFIER_ACTION_H_
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc
new file mode 100644
index 0000000..0b6232b
--- /dev/null
+++ b/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -0,0 +1,386 @@
+//
+// 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_consumer/filesystem_verifier_action.h"
+
+#include <fcntl.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.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/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"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using brillo::MessageLoop;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FilesystemVerifierActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1));
+ }
+
+ // Returns true iff test has completed successfully.
+ bool DoTest(bool terminate_early,
+ bool hash_fail,
+ VerifierMode verifier_mode);
+
+ brillo::FakeMessageLoop loop_{nullptr};
+ FakeBootControl fake_boot_control_;
+};
+
+class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
+ public:
+ explicit FilesystemVerifierActionTestDelegate(
+ FilesystemVerifierAction* action)
+ : action_(action), ran_(false), code_(ErrorCode::kError) {}
+ void ExitMainLoop() {
+ // We need to wait for the Action to call Cleanup.
+ if (action_->IsCleanupPending()) {
+ LOG(INFO) << "Waiting for Cleanup() to be called.";
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&FilesystemVerifierActionTestDelegate::ExitMainLoop,
+ base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(100));
+ } else {
+ MessageLoop::current()->BreakLoop();
+ }
+ }
+ void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+ ExitMainLoop();
+ }
+ void ProcessingStopped(const ActionProcessor* processor) {
+ ExitMainLoop();
+ }
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == FilesystemVerifierAction::StaticType()) {
+ ran_ = true;
+ code_ = code;
+ }
+ }
+ bool ran() const { return ran_; }
+ ErrorCode code() const { return code_; }
+
+ private:
+ FilesystemVerifierAction* action_;
+ bool ran_;
+ ErrorCode code_;
+};
+
+void StartProcessorInRunLoop(ActionProcessor* processor,
+ FilesystemVerifierAction* filesystem_copier_action,
+ bool terminate_early) {
+ processor->StartProcessing();
+ if (terminate_early) {
+ EXPECT_NE(nullptr, filesystem_copier_action);
+ processor->StopProcessing();
+ }
+}
+
+// TODO(garnold) Temporarily disabling this test, see chromium-os:31082 for
+// details; still trying to track down the root cause for these rare write
+// failures and whether or not they are due to the test setup or an inherent
+// issue with the chroot environment, library versions we use, etc.
+TEST_F(FilesystemVerifierActionTest, DISABLED_RunAsRootSimpleTest) {
+ ASSERT_EQ(0, getuid());
+ bool test = DoTest(false, false, VerifierMode::kComputeSourceHash);
+ EXPECT_TRUE(test);
+ if (!test)
+ return;
+ test = DoTest(false, false, VerifierMode::kVerifyTargetHash);
+ EXPECT_TRUE(test);
+}
+
+bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
+ bool hash_fail,
+ VerifierMode verifier_mode) {
+ string a_loop_file;
+
+ if (!(utils::MakeTempFile("a_loop_file.XXXXXX", &a_loop_file, nullptr))) {
+ ADD_FAILURE();
+ return false;
+ }
+ ScopedPathUnlinker a_loop_file_unlinker(a_loop_file);
+
+ // Make random data for a.
+ const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
+ brillo::Blob a_loop_data(kLoopFileSize);
+ test_utils::FillWithData(&a_loop_data);
+
+ // Write data to disk
+ if (!(test_utils::WriteFileVector(a_loop_file, a_loop_data))) {
+ ADD_FAILURE();
+ return false;
+ }
+
+ // Attach loop devices to the files
+ string a_dev;
+ test_utils::ScopedLoopbackDeviceBinder a_dev_releaser(a_loop_file, &a_dev);
+ if (!(a_dev_releaser.is_bound())) {
+ ADD_FAILURE();
+ return false;
+ }
+
+ LOG(INFO) << "verifying: " << a_loop_file << " (" << a_dev << ")";
+
+ bool success = true;
+
+ // Set up the action objects
+ InstallPlan install_plan;
+ install_plan.source_slot = 0;
+ install_plan.target_slot = 1;
+ InstallPlan::Partition part;
+ part.name = "part";
+ if (verifier_mode == VerifierMode::kVerifyTargetHash) {
+ part.target_size = kLoopFileSize - (hash_fail ? 1 : 0);
+ part.target_path = a_dev;
+ fake_boot_control_.SetPartitionDevice(
+ part.name, install_plan.target_slot, a_dev);
+ if (!HashCalculator::RawHashOfData(a_loop_data, &part.target_hash)) {
+ ADD_FAILURE();
+ success = false;
+ }
+ }
+ part.source_size = kLoopFileSize;
+ part.source_path = a_dev;
+ fake_boot_control_.SetPartitionDevice(
+ part.name, install_plan.source_slot, a_dev);
+ if (!HashCalculator::RawHashOfData(a_loop_data, &part.source_hash)) {
+ ADD_FAILURE();
+ success = false;
+ }
+ install_plan.partitions = {part};
+
+ ActionProcessor processor;
+
+ ObjectFeederAction<InstallPlan> feeder_action;
+ FilesystemVerifierAction copier_action(&fake_boot_control_, verifier_mode);
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&feeder_action, &copier_action);
+ BondActions(&copier_action, &collector_action);
+
+ FilesystemVerifierActionTestDelegate delegate(&copier_action);
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&copier_action);
+ processor.EnqueueAction(&collector_action);
+
+ feeder_action.set_obj(install_plan);
+
+ loop_.PostTask(FROM_HERE, base::Bind(&StartProcessorInRunLoop,
+ &processor,
+ &copier_action,
+ terminate_early));
+ loop_.Run();
+
+ if (!terminate_early) {
+ bool is_delegate_ran = delegate.ran();
+ EXPECT_TRUE(is_delegate_ran);
+ success = success && is_delegate_ran;
+ } else {
+ EXPECT_EQ(ErrorCode::kError, delegate.code());
+ return (ErrorCode::kError == delegate.code());
+ }
+ if (hash_fail) {
+ ErrorCode expected_exit_code = ErrorCode::kNewRootfsVerificationError;
+ EXPECT_EQ(expected_exit_code, delegate.code());
+ return (expected_exit_code == delegate.code());
+ }
+ EXPECT_EQ(ErrorCode::kSuccess, delegate.code());
+
+ // Make sure everything in the out_image is there
+ brillo::Blob a_out;
+ if (!utils::ReadFile(a_dev, &a_out)) {
+ ADD_FAILURE();
+ return false;
+ }
+ const bool is_a_file_reading_eq =
+ test_utils::ExpectVectorsEq(a_loop_data, a_out);
+ EXPECT_TRUE(is_a_file_reading_eq);
+ success = success && is_a_file_reading_eq;
+
+ bool is_install_plan_eq = (collector_action.object() == install_plan);
+ EXPECT_TRUE(is_install_plan_eq);
+ success = success && is_install_plan_eq;
+ return success;
+}
+
+class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate {
+ public:
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == FilesystemVerifierAction::StaticType()) {
+ ran_ = true;
+ code_ = code;
+ }
+ }
+ bool ran_;
+ ErrorCode code_;
+};
+
+TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) {
+ ActionProcessor processor;
+ FilesystemVerifierActionTest2Delegate delegate;
+
+ processor.set_delegate(&delegate);
+
+ FilesystemVerifierAction copier_action(&fake_boot_control_,
+ VerifierMode::kVerifyTargetHash);
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&copier_action, &collector_action);
+
+ processor.EnqueueAction(&copier_action);
+ processor.EnqueueAction(&collector_action);
+ processor.StartProcessing();
+ EXPECT_FALSE(processor.IsRunning());
+ EXPECT_TRUE(delegate.ran_);
+ EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
+ ActionProcessor processor;
+ FilesystemVerifierActionTest2Delegate delegate;
+
+ processor.set_delegate(&delegate);
+
+ ObjectFeederAction<InstallPlan> feeder_action;
+ InstallPlan install_plan(false,
+ false,
+ "",
+ 0,
+ "",
+ 0,
+ "",
+ "");
+ InstallPlan::Partition part;
+ part.name = "nope";
+ part.source_path = "/no/such/file";
+ part.target_path = "/no/such/file";
+ install_plan.partitions = {part};
+
+ feeder_action.set_obj(install_plan);
+ FilesystemVerifierAction verifier_action(&fake_boot_control_,
+ VerifierMode::kVerifyTargetHash);
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&verifier_action, &collector_action);
+
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&verifier_action);
+ processor.EnqueueAction(&collector_action);
+ processor.StartProcessing();
+ EXPECT_FALSE(processor.IsRunning());
+ EXPECT_TRUE(delegate.ran_);
+ EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashTest) {
+ ASSERT_EQ(0, getuid());
+ EXPECT_TRUE(DoTest(false, false, VerifierMode::kVerifyTargetHash));
+ EXPECT_TRUE(DoTest(false, false, VerifierMode::kComputeSourceHash));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) {
+ ASSERT_EQ(0, getuid());
+ EXPECT_TRUE(DoTest(false, true, VerifierMode::kVerifyTargetHash));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) {
+ ASSERT_EQ(0, getuid());
+ EXPECT_TRUE(DoTest(true, false, VerifierMode::kVerifyTargetHash));
+ // TerminateEarlyTest may leak some null callbacks from the Stream class.
+ while (loop_.RunOnce(false)) {}
+}
+
+// Disabled as we switched to minor version 3, so this test is obsolete, will be
+// deleted when we delete the corresponding code in PerformAction().
+// Test that the rootfs and kernel size used for hashing in delta payloads for
+// major version 1 is properly read.
+TEST_F(FilesystemVerifierActionTest,
+ DISABLED_RunAsRootDetermineLegacySizeTest) {
+ string img;
+ EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
+ ScopedPathUnlinker img_unlinker(img);
+ test_utils::CreateExtImageAtPath(img, nullptr);
+ // Extend the "partition" holding the file system from 10MiB to 20MiB.
+ EXPECT_EQ(0, truncate(img.c_str(), 20 * 1024 * 1024));
+
+ InstallPlan install_plan;
+ install_plan.source_slot = 1;
+
+ fake_boot_control_.SetPartitionDevice(
+ kLegacyPartitionNameRoot, install_plan.source_slot, img);
+ fake_boot_control_.SetPartitionDevice(
+ kLegacyPartitionNameKernel, install_plan.source_slot, img);
+ FilesystemVerifierAction action(&fake_boot_control_,
+ VerifierMode::kComputeSourceHash);
+
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&feeder_action, &action);
+ BondActions(&action, &collector_action);
+ ActionProcessor processor;
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&action);
+ processor.EnqueueAction(&collector_action);
+
+ loop_.PostTask(FROM_HERE,
+ base::Bind([&processor]{ processor.StartProcessing(); }));
+ loop_.Run();
+ install_plan = collector_action.object();
+
+ ASSERT_EQ(2, install_plan.partitions.size());
+ // When computing the size of the rootfs on legacy delta updates we use the
+ // size of the filesystem, but when updating the kernel we use the whole
+ // partition.
+ EXPECT_EQ(10 * 1024 * 1024, install_plan.partitions[0].source_size);
+ EXPECT_EQ(kLegacyPartitionNameRoot, install_plan.partitions[0].name);
+ EXPECT_EQ(20 * 1024 * 1024, install_plan.partitions[1].source_size);
+ EXPECT_EQ(kLegacyPartitionNameKernel, install_plan.partitions[1].name);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc
new file mode 100644
index 0000000..a2a1b7b
--- /dev/null
+++ b/payload_consumer/install_plan.cc
@@ -0,0 +1,124 @@
+//
+// 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.
+//
+
+#include "update_engine/payload_consumer/install_plan.h"
+
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+InstallPlan::InstallPlan(bool is_resume,
+ bool is_full_update,
+ const string& url,
+ uint64_t payload_size,
+ const string& payload_hash,
+ uint64_t metadata_size,
+ const string& metadata_signature,
+ const string& public_key_rsa)
+ : is_resume(is_resume),
+ is_full_update(is_full_update),
+ download_url(url),
+ payload_size(payload_size),
+ payload_hash(payload_hash),
+ metadata_size(metadata_size),
+ metadata_signature(metadata_signature),
+ hash_checks_mandatory(false),
+ powerwash_required(false),
+ public_key_rsa(public_key_rsa) {}
+
+
+bool InstallPlan::operator==(const InstallPlan& that) const {
+ return ((is_resume == that.is_resume) &&
+ (is_full_update == that.is_full_update) &&
+ (download_url == that.download_url) &&
+ (payload_size == that.payload_size) &&
+ (payload_hash == that.payload_hash) &&
+ (metadata_size == that.metadata_size) &&
+ (metadata_signature == that.metadata_signature) &&
+ (source_slot == that.source_slot) &&
+ (target_slot == that.target_slot) &&
+ (partitions == that.partitions));
+}
+
+bool InstallPlan::operator!=(const InstallPlan& that) const {
+ return !((*this) == that);
+}
+
+void InstallPlan::Dump() const {
+ string partitions_str;
+ for (const auto& partition : partitions) {
+ partitions_str += base::StringPrintf(
+ ", part: %s (source_size: %" PRIu64 ", target_size %" PRIu64 ")",
+ partition.name.c_str(), partition.source_size, partition.target_size);
+ }
+
+ LOG(INFO) << "InstallPlan: "
+ << (is_resume ? "resume" : "new_update")
+ << ", payload type: " << (is_full_update ? "full" : "delta")
+ << ", source_slot: " << BootControlInterface::SlotName(source_slot)
+ << ", target_slot: " << BootControlInterface::SlotName(target_slot)
+ << ", url: " << download_url
+ << ", payload size: " << payload_size
+ << ", payload hash: " << payload_hash
+ << ", metadata size: " << metadata_size
+ << ", metadata signature: " << metadata_signature
+ << partitions_str
+ << ", hash_checks_mandatory: " << utils::ToString(
+ hash_checks_mandatory)
+ << ", powerwash_required: " << utils::ToString(
+ powerwash_required);
+}
+
+bool InstallPlan::LoadPartitionsFromSlots(BootControlInterface* boot_control) {
+ bool result = true;
+ for (Partition& partition : partitions) {
+ if (source_slot != BootControlInterface::kInvalidSlot) {
+ result = boot_control->GetPartitionDevice(
+ partition.name, source_slot, &partition.source_path) && result;
+ } else {
+ partition.source_path.clear();
+ }
+
+ if (target_slot != BootControlInterface::kInvalidSlot) {
+ result = boot_control->GetPartitionDevice(
+ partition.name, target_slot, &partition.target_path) && result;
+ } else {
+ partition.target_path.clear();
+ }
+ }
+ return result;
+}
+
+bool InstallPlan::Partition::operator==(
+ const InstallPlan::Partition& that) const {
+ return (name == that.name &&
+ source_path == that.source_path &&
+ source_size == that.source_size &&
+ source_hash == that.source_hash &&
+ target_path == that.target_path &&
+ target_size == that.target_size &&
+ target_hash == that.target_hash &&
+ run_postinstall == that.run_postinstall);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
new file mode 100644
index 0000000..e69462d
--- /dev/null
+++ b/payload_consumer/install_plan.h
@@ -0,0 +1,156 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_INSTALL_PLAN_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_INSTALL_PLAN_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/boot_control_interface.h"
+
+// InstallPlan is a simple struct that contains relevant info for many
+// parts of the update system about the install that should happen.
+namespace chromeos_update_engine {
+
+struct InstallPlan {
+ InstallPlan(bool is_resume,
+ bool is_full_update,
+ const std::string& url,
+ uint64_t payload_size,
+ const std::string& payload_hash,
+ uint64_t metadata_size,
+ const std::string& metadata_signature,
+ const std::string& public_key_rsa);
+
+ // Default constructor.
+ InstallPlan() = default;
+
+ bool operator==(const InstallPlan& that) const;
+ bool operator!=(const InstallPlan& that) const;
+
+ void Dump() const;
+
+ // Load the |source_path| and |target_path| of all |partitions| based on the
+ // |source_slot| and |target_slot| if available. Returns whether it succeeded
+ // to load all the partitions for the valid slots.
+ bool LoadPartitionsFromSlots(BootControlInterface* boot_control);
+
+ bool is_resume{false};
+ bool is_full_update{false};
+ std::string download_url; // url to download from
+ std::string version; // version we are installing.
+
+ uint64_t payload_size{0}; // size of the payload
+ std::string payload_hash; // SHA256 hash of the payload
+ uint64_t metadata_size{0}; // size of the metadata
+ std::string metadata_signature; // signature of the metadata
+
+ // The partition slots used for the update.
+ BootControlInterface::Slot source_slot{BootControlInterface::kInvalidSlot};
+ BootControlInterface::Slot target_slot{BootControlInterface::kInvalidSlot};
+
+ // The vector below is used for partition verification. The flow is:
+ //
+ // 1. FilesystemVerifierAction computes and fills in the source partition
+ // hash based on the guessed source size for delta major version 1 updates.
+ //
+ // 2. DownloadAction verifies the source partition sizes and hashes against
+ // the expected values transmitted in the update manifest. It fills in the
+ // expected target partition sizes and hashes based on the manifest.
+ //
+ // 3. FilesystemVerifierAction computes and verifies the applied partition
+ // sizes and hashes against the expected values in target_partition_hashes.
+ struct Partition {
+ bool operator==(const Partition& that) const;
+
+ // The name of the partition.
+ std::string name;
+
+ std::string source_path;
+ uint64_t source_size{0};
+ brillo::Blob source_hash;
+
+ std::string target_path;
+ uint64_t target_size{0};
+ brillo::Blob target_hash;
+
+ // Whether we should run the postinstall script from this partition.
+ bool run_postinstall{false};
+ };
+ std::vector<Partition> partitions;
+
+ // True if payload hash checks are mandatory based on the system state and
+ // the Omaha response.
+ bool hash_checks_mandatory{false};
+
+ // True if Powerwash is required on reboot after applying the payload.
+ // False otherwise.
+ bool powerwash_required{false};
+
+ // If not blank, a base-64 encoded representation of the PEM-encoded
+ // public key in the response.
+ std::string public_key_rsa;
+};
+
+class InstallPlanAction;
+
+template<>
+class ActionTraits<InstallPlanAction> {
+ public:
+ // Takes the install plan as input
+ typedef InstallPlan InputObjectType;
+ // Passes the install plan as output
+ typedef InstallPlan OutputObjectType;
+};
+
+// Basic action that only receives and sends Install Plans.
+// Can be used to construct an Install Plan to send to any other Action that
+// accept an InstallPlan.
+class InstallPlanAction : public Action<InstallPlanAction> {
+ public:
+ InstallPlanAction() {}
+ explicit InstallPlanAction(const InstallPlan& install_plan):
+ install_plan_(install_plan) {}
+
+ void PerformAction() override {
+ if (HasOutputPipe()) {
+ SetOutputObject(install_plan_);
+ }
+ processor_->ActionComplete(this, ErrorCode::kSuccess);
+ }
+
+ InstallPlan* install_plan() { return &install_plan_; }
+
+ static std::string StaticType() { return "InstallPlanAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
+ typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType;
+
+ private:
+ InstallPlan install_plan_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstallPlanAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_INSTALL_PLAN_H_
diff --git a/payload_consumer/mock_download_action.h b/payload_consumer/mock_download_action.h
new file mode 100644
index 0000000..3abb809
--- /dev/null
+++ b/payload_consumer/mock_download_action.h
@@ -0,0 +1,41 @@
+//
+// 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_CONSUMER_MOCK_DOWNLOAD_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_MOCK_DOWNLOAD_ACTION_H_
+
+#include <stdint.h>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/payload_consumer/download_action.h"
+
+namespace chromeos_update_engine {
+
+class MockDownloadActionDelegate : public DownloadActionDelegate {
+ public:
+ MOCK_METHOD3(BytesReceived,
+ void(uint64_t bytes_progressed,
+ uint64_t bytes_received,
+ uint64_t total));
+ MOCK_METHOD1(ShouldCancel, bool(ErrorCode* cancel_reason));
+ MOCK_METHOD0(DownloadComplete, void());
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_MOCK_DOWNLOAD_ACTION_H_
diff --git a/payload_consumer/mtd_file_descriptor.cc b/payload_consumer/mtd_file_descriptor.cc
new file mode 100644
index 0000000..da8fd58
--- /dev/null
+++ b/payload_consumer/mtd_file_descriptor.cc
@@ -0,0 +1,265 @@
+//
+// 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 <string>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+
+#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;
+using std::vector;
+
+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::StartsWithASCII(ubi_name.MaybeAsASCII(), "ubi", true));
+
+ 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
new file mode 100644
index 0000000..9ac1ec1
--- /dev/null
+++ b/payload_consumer/mtd_file_descriptor.h
@@ -0,0 +1,102 @@
+//
+// 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 <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;
+ 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;
+ 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/payload_constants.cc b/payload_consumer/payload_constants.cc
new file mode 100644
index 0000000..72abf8c
--- /dev/null
+++ b/payload_consumer/payload_constants.cc
@@ -0,0 +1,59 @@
+//
+// 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/payload_constants.h"
+
+namespace chromeos_update_engine {
+
+const uint64_t kChromeOSMajorPayloadVersion = 1;
+const uint64_t kBrilloMajorPayloadVersion = 2;
+
+const uint32_t kFullPayloadMinorVersion = 0;
+const uint32_t kInPlaceMinorPayloadVersion = 1;
+const uint32_t kSourceMinorPayloadVersion = 2;
+const uint32_t kOpSrcHashMinorPayloadVersion = 3;
+
+const char kLegacyPartitionNameKernel[] = "boot";
+const char kLegacyPartitionNameRoot[] = "system";
+
+const char kDeltaMagic[4] = {'C', 'r', 'A', 'U'};
+const char kBspatchPath[] = "bspatch";
+
+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:
+ return "REPLACE_BZ";
+ case InstallOperation::SOURCE_COPY:
+ return "SOURCE_COPY";
+ case InstallOperation::SOURCE_BSDIFF:
+ return "SOURCE_BSDIFF";
+ case InstallOperation::ZERO:
+ return "ZERO";
+ case InstallOperation::DISCARD:
+ return "DISCARD";
+ case InstallOperation::REPLACE_XZ:
+ return "REPLACE_XZ";
+ }
+ return "<unknown_op>";
+}
+
+}; // namespace chromeos_update_engine
diff --git a/payload_consumer/payload_constants.h b/payload_consumer/payload_constants.h
new file mode 100644
index 0000000..c3cd363
--- /dev/null
+++ b/payload_consumer/payload_constants.h
@@ -0,0 +1,65 @@
+//
+// 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_PAYLOAD_CONSTANTS_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_CONSTANTS_H_
+
+#include <stdint.h>
+
+#include <limits>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// The major version used by Chrome OS.
+extern const uint64_t kChromeOSMajorPayloadVersion;
+
+// The major version used by Brillo.
+extern const uint64_t kBrilloMajorPayloadVersion;
+
+// The minor version used for all full payloads.
+extern const uint32_t kFullPayloadMinorVersion;
+
+// The minor version used by the in-place delta generator algorithm.
+extern const uint32_t kInPlaceMinorPayloadVersion;
+
+// The minor version used by the A to B delta generator algorithm.
+extern const uint32_t kSourceMinorPayloadVersion;
+
+// The minor version that allows per-operation source hash.
+extern const uint32_t kOpSrcHashMinorPayloadVersion;
+
+
+// The kernel and rootfs partition names used by the BootControlInterface when
+// handling update payloads with a major version 1. The names of the updated
+// partitions are include in the payload itself for major version 2.
+extern const char kLegacyPartitionNameKernel[];
+extern const char kLegacyPartitionNameRoot[];
+
+extern const char kBspatchPath[];
+extern const char kDeltaMagic[4];
+
+// A block number denoting a hole on a sparse file. Used on Extents to refer to
+// section of blocks not present on disk on a sparse file.
+const uint64_t kSparseHole = std::numeric_limits<uint64_t>::max();
+
+// Return the name of the operation type.
+const char* InstallOperationTypeName(InstallOperation_Type op_type);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_CONSTANTS_H_
diff --git a/payload_consumer/payload_verifier.cc b/payload_consumer/payload_verifier.cc
new file mode 100644
index 0000000..ab5238c
--- /dev/null
+++ b/payload_consumer/payload_verifier.cc
@@ -0,0 +1,183 @@
+//
+// 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/payload_verifier.h"
+
+#include <base/logging.h>
+#include <openssl/pem.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The following is a standard PKCS1-v1_5 padding for SHA256 signatures, as
+// defined in RFC3447. It is prepended to the actual signature (32 bytes) to
+// form a sequence of 256 bytes (2048 bits) that is amenable to RSA signing. The
+// padded hash will look as follows:
+//
+// 0x00 0x01 0xff ... 0xff 0x00 ASN1HEADER SHA256HASH
+// |--------------205-----------||----19----||----32----|
+//
+// where ASN1HEADER is the ASN.1 description of the signed data. The complete 51
+// bytes of actual data (i.e. the ASN.1 header complete with the hash) are
+// packed as follows:
+//
+// SEQUENCE(2+49) {
+// SEQUENCE(2+13) {
+// OBJECT(2+9) id-sha256
+// NULL(2+0)
+// }
+// OCTET STRING(2+32) <actual signature bytes...>
+// }
+const uint8_t kRSA2048SHA256Padding[] = {
+ // PKCS1-v1_5 padding
+ 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x00,
+ // ASN.1 header
+ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+ 0x00, 0x04, 0x20,
+};
+
+} // namespace
+
+bool PayloadVerifier::VerifySignature(const brillo::Blob& signature_blob,
+ const string& public_key_path,
+ const brillo::Blob& hash_data) {
+ TEST_AND_RETURN_FALSE(!public_key_path.empty());
+
+ Signatures signatures;
+ LOG(INFO) << "signature blob size = " << signature_blob.size();
+ TEST_AND_RETURN_FALSE(signatures.ParseFromArray(signature_blob.data(),
+ signature_blob.size()));
+
+ if (!signatures.signatures_size()) {
+ LOG(ERROR) << "No signatures stored in the blob.";
+ return false;
+ }
+
+ std::vector<brillo::Blob> tested_hashes;
+ // Tries every signature in the signature blob.
+ for (int i = 0; i < signatures.signatures_size(); i++) {
+ const Signatures_Signature& signature = signatures.signatures(i);
+ brillo::Blob sig_data(signature.data().begin(), signature.data().end());
+ brillo::Blob sig_hash_data;
+ if (!GetRawHashFromSignature(sig_data, public_key_path, &sig_hash_data))
+ continue;
+
+ if (hash_data == sig_hash_data) {
+ LOG(INFO) << "Verified correct signature " << i + 1 << " out of "
+ << signatures.signatures_size() << " signatures.";
+ return true;
+ }
+ tested_hashes.push_back(sig_hash_data);
+ }
+ LOG(ERROR) << "None of the " << signatures.signatures_size()
+ << " signatures is correct. Expected:";
+ utils::HexDumpVector(hash_data);
+ LOG(ERROR) << "But found decrypted hashes:";
+ for (const auto& sig_hash_data : tested_hashes) {
+ utils::HexDumpVector(sig_hash_data);
+ }
+ return false;
+}
+
+
+bool PayloadVerifier::GetRawHashFromSignature(
+ const brillo::Blob& sig_data,
+ const string& public_key_path,
+ brillo::Blob* out_hash_data) {
+ TEST_AND_RETURN_FALSE(!public_key_path.empty());
+
+ // The code below executes the equivalent of:
+ //
+ // openssl rsautl -verify -pubin -inkey |public_key_path|
+ // -in |sig_data| -out |out_hash_data|
+
+ // Loads the public key.
+ FILE* fpubkey = fopen(public_key_path.c_str(), "rb");
+ if (!fpubkey) {
+ LOG(ERROR) << "Unable to open public key file: " << public_key_path;
+ return false;
+ }
+
+ char dummy_password[] = { ' ', 0 }; // Ensure no password is read from stdin.
+ RSA* rsa = PEM_read_RSA_PUBKEY(fpubkey, nullptr, nullptr, dummy_password);
+ fclose(fpubkey);
+ TEST_AND_RETURN_FALSE(rsa != nullptr);
+ unsigned int keysize = RSA_size(rsa);
+ if (sig_data.size() > 2 * keysize) {
+ LOG(ERROR) << "Signature size is too big for public key size.";
+ RSA_free(rsa);
+ return false;
+ }
+
+ // Decrypts the signature.
+ brillo::Blob hash_data(keysize);
+ int decrypt_size = RSA_public_decrypt(sig_data.size(),
+ sig_data.data(),
+ hash_data.data(),
+ rsa,
+ RSA_NO_PADDING);
+ RSA_free(rsa);
+ TEST_AND_RETURN_FALSE(decrypt_size > 0 &&
+ decrypt_size <= static_cast<int>(hash_data.size()));
+ hash_data.resize(decrypt_size);
+ out_hash_data->swap(hash_data);
+ return true;
+}
+
+bool PayloadVerifier::PadRSA2048SHA256Hash(brillo::Blob* hash) {
+ TEST_AND_RETURN_FALSE(hash->size() == 32);
+ hash->insert(hash->begin(),
+ reinterpret_cast<const char*>(kRSA2048SHA256Padding),
+ reinterpret_cast<const char*>(kRSA2048SHA256Padding +
+ sizeof(kRSA2048SHA256Padding)));
+ TEST_AND_RETURN_FALSE(hash->size() == 256);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/payload_verifier.h b/payload_consumer/payload_verifier.h
new file mode 100644
index 0000000..22ced40
--- /dev/null
+++ b/payload_consumer/payload_verifier.h
@@ -0,0 +1,65 @@
+//
+// 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_PAYLOAD_VERIFIER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// This class encapsulates methods used for payload signature verification.
+// See payload_generator/payload_signer.h for payload signing.
+
+namespace chromeos_update_engine {
+
+class PayloadVerifier {
+ public:
+ // Interprets |signature_blob| as a protocol buffer containing the Signatures
+ // message and decrypts each signature data using the |public_key_path|.
+ // Returns whether *any* of the decrypted hashes matches the |hash_data|.
+ // In case of any error parsing the signatures or the public key, returns
+ // false.
+ static bool VerifySignature(const brillo::Blob& signature_blob,
+ const std::string& public_key_path,
+ const brillo::Blob& hash_data);
+
+ // Decrypts sig_data with the given public_key_path and populates
+ // out_hash_data with the decoded raw hash. Returns true if successful,
+ // false otherwise.
+ static bool GetRawHashFromSignature(const brillo::Blob& sig_data,
+ const std::string& public_key_path,
+ brillo::Blob* out_hash_data);
+
+ // Pads a SHA256 hash so that it may be encrypted/signed with RSA2048
+ // using the PKCS#1 v1.5 scheme.
+ // hash should be a pointer to vector of exactly 256 bits. The vector
+ // will be modified in place and will result in having a length of
+ // 2048 bits. Returns true on success, false otherwise.
+ static bool PadRSA2048SHA256Hash(brillo::Blob* hash);
+
+ private:
+ // This should never be constructed
+ DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadVerifier);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
new file mode 100644
index 0000000..84ca398
--- /dev/null
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -0,0 +1,187 @@
+//
+// 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/payload_consumer/postinstall_runner_action.h"
+
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+
+namespace {
+// The absolute path to the post install command.
+const char kPostinstallScript[] = "/postinst";
+
+// Path to the binary file used by kPostinstallScript. Used to get and log the
+// file format of the binary to debug issues when the ELF format on the update
+// doesn't match the one on the current system. This path is not executed.
+const char kDebugPostinstallBinaryPath[] = "/usr/bin/cros_installer";
+}
+
+void PostinstallRunnerAction::PerformAction() {
+ CHECK(HasInputObject());
+ install_plan_ = GetInputObject();
+
+ if (install_plan_.powerwash_required) {
+ if (utils::CreatePowerwashMarkerFile(powerwash_marker_file_)) {
+ powerwash_marker_created_ = true;
+ } else {
+ return CompletePostinstall(ErrorCode::kPostinstallPowerwashError);
+ }
+ }
+
+ PerformPartitionPostinstall();
+}
+
+void PostinstallRunnerAction::PerformPartitionPostinstall() {
+ // Skip all the partitions that don't have a post-install step.
+ while (current_partition_ < install_plan_.partitions.size() &&
+ !install_plan_.partitions[current_partition_].run_postinstall) {
+ VLOG(1) << "Skipping post-install on partition "
+ << install_plan_.partitions[current_partition_].name;
+ current_partition_++;
+ }
+ if (current_partition_ == install_plan_.partitions.size())
+ return CompletePostinstall(ErrorCode::kSuccess);
+
+ const InstallPlan::Partition& partition =
+ install_plan_.partitions[current_partition_];
+
+ const string mountable_device =
+ utils::MakePartitionNameForMount(partition.target_path);
+ if (mountable_device.empty()) {
+ LOG(ERROR) << "Cannot make mountable device from " << partition.target_path;
+ return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
+ }
+
+ // Perform post-install for the current_partition_ partition. At this point we
+ // need to call CompletePartitionPostinstall to complete the operation and
+ // cleanup.
+ TEST_AND_RETURN(
+ utils::MakeTempDirectory("au_postint_mount.XXXXXX", &temp_rootfs_dir_));
+
+ if (!utils::MountFilesystem(mountable_device, temp_rootfs_dir_, MS_RDONLY)) {
+ return CompletePartitionPostinstall(
+ 1, "Error mounting the device " + mountable_device);
+ }
+
+ LOG(INFO) << "Performing postinst (" << kPostinstallScript
+ << ") installed on device " << partition.target_path
+ << " and mountable device " << mountable_device;
+
+ // Logs the file format of the postinstall script we are about to run. This
+ // will help debug when the postinstall script doesn't match the architecture
+ // of our build.
+ LOG(INFO) << "Format file for new " << kPostinstallScript << " is: "
+ << utils::GetFileFormat(temp_rootfs_dir_ + kPostinstallScript);
+ LOG(INFO) << "Format file for new " << kDebugPostinstallBinaryPath << " is: "
+ << utils::GetFileFormat(
+ temp_rootfs_dir_ + kDebugPostinstallBinaryPath);
+
+ // Runs the postinstall script asynchronously to free up the main loop while
+ // it's running.
+ vector<string> command;
+ if (!install_plan_.download_url.empty()) {
+ command.push_back(temp_rootfs_dir_ + kPostinstallScript);
+ } else {
+ // TODO(sosa): crbug.com/366207.
+ // If we're doing a rollback, just run our own postinstall.
+ command.push_back(kPostinstallScript);
+ }
+ command.push_back(partition.target_path);
+ if (!Subprocess::Get().Exec(
+ command,
+ base::Bind(
+ &PostinstallRunnerAction::CompletePartitionPostinstall,
+ base::Unretained(this)))) {
+ CompletePartitionPostinstall(1, "Postinstall didn't launch");
+ }
+}
+
+void PostinstallRunnerAction::CompletePartitionPostinstall(
+ int return_code,
+ const string& output) {
+ utils::UnmountFilesystem(temp_rootfs_dir_);
+ if (!base::DeleteFile(base::FilePath(temp_rootfs_dir_), false)) {
+ PLOG(WARNING) << "Not removing mountpoint " << temp_rootfs_dir_;
+ }
+ temp_rootfs_dir_.clear();
+
+ if (return_code != 0) {
+ LOG(ERROR) << "Postinst command failed with code: " << return_code;
+ ErrorCode error_code = ErrorCode::kPostinstallRunnerError;
+
+ if (return_code == 3) {
+ // This special return code means that we tried to update firmware,
+ // but couldn't because we booted from FW B, and we need to reboot
+ // to get back to FW A.
+ error_code = ErrorCode::kPostinstallBootedFromFirmwareB;
+ }
+
+ if (return_code == 4) {
+ // This special return code means that we tried to update firmware,
+ // but couldn't because we booted from FW B, and we need to reboot
+ // to get back to FW A.
+ error_code = ErrorCode::kPostinstallFirmwareRONotUpdatable;
+ }
+ return CompletePostinstall(error_code);
+ }
+ current_partition_++;
+ PerformPartitionPostinstall();
+}
+
+void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) {
+ // We only attempt to mark the new slot as active if all the postinstall
+ // steps succeeded.
+ if (error_code == ErrorCode::kSuccess &&
+ !boot_control_->SetActiveBootSlot(install_plan_.target_slot)) {
+ error_code = ErrorCode::kPostinstallRunnerError;
+ }
+
+ ScopedActionCompleter completer(processor_, this);
+
+ if (error_code != ErrorCode::kSuccess) {
+ LOG(ERROR) << "Postinstall action failed.";
+
+ // Undo any changes done to trigger Powerwash using clobber-state.
+ if (powerwash_marker_created_)
+ utils::DeletePowerwashMarkerFile(powerwash_marker_file_);
+
+ return;
+ }
+
+ LOG(INFO) << "All post-install commands succeeded";
+ if (HasOutputPipe()) {
+ SetOutputObject(install_plan_);
+ }
+
+ completer.set_code(ErrorCode::kSuccess);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/postinstall_runner_action.h b/payload_consumer/postinstall_runner_action.h
new file mode 100644
index 0000000..ab267b8
--- /dev/null
+++ b/payload_consumer/postinstall_runner_action.h
@@ -0,0 +1,88 @@
+//
+// 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_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_
+
+#include <string>
+
+#include "update_engine/common/action.h"
+#include "update_engine/payload_consumer/install_plan.h"
+
+// The Postinstall Runner Action is responsible for running the postinstall
+// script of a successfully downloaded update.
+
+namespace chromeos_update_engine {
+
+class BootControlInterface;
+
+class PostinstallRunnerAction : public InstallPlanAction {
+ public:
+ explicit PostinstallRunnerAction(BootControlInterface* boot_control)
+ : PostinstallRunnerAction(boot_control, nullptr) {}
+
+ void PerformAction();
+
+ // Note that there's no support for terminating this action currently.
+ void TerminateProcessing() { CHECK(false); }
+
+ // Debugging/logging
+ static std::string StaticType() { return "PostinstallRunnerAction"; }
+ std::string Type() const { return StaticType(); }
+
+ private:
+ friend class PostinstallRunnerActionTest;
+
+ // Special constructor used for testing purposes.
+ PostinstallRunnerAction(BootControlInterface* boot_control,
+ const char* powerwash_marker_file)
+ : boot_control_(boot_control),
+ powerwash_marker_file_(powerwash_marker_file) {}
+
+ void PerformPartitionPostinstall();
+
+ // Subprocess::Exec callback.
+ void CompletePartitionPostinstall(int return_code,
+ const std::string& output);
+
+ // Complete the Action with the passed |error_code| and mark the new slot as
+ // ready. Called when the post-install script was run for all the partitions.
+ void CompletePostinstall(ErrorCode error_code);
+
+ InstallPlan install_plan_;
+ std::string temp_rootfs_dir_;
+
+ // The partition being processed on the list of partitions specified in the
+ // InstallPlan.
+ size_t current_partition_{0};
+
+ // The BootControlInerface used to mark the new slot as ready.
+ BootControlInterface* boot_control_;
+
+ // True if Powerwash Marker was created before invoking post-install script.
+ // False otherwise. Used for cleaning up if post-install fails.
+ bool powerwash_marker_created_{false};
+
+ // Non-null value will cause post-install to override the default marker
+ // file name; used for testing.
+ const char* powerwash_marker_file_;
+
+ DISALLOW_COPY_AND_ASSIGN(PostinstallRunnerAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
new file mode 100644
index 0000000..beed4f1
--- /dev/null
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -0,0 +1,260 @@
+//
+// 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_consumer/postinstall_runner_action.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using brillo::MessageLoop;
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class PostinstallRunnerActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ async_signal_handler_.Init();
+ subprocess_.Init(&async_signal_handler_);
+ }
+
+ // DoTest with various combinations of do_losetup, err_code and
+ // powerwash_required.
+ void DoTest(bool do_losetup, int err_code, bool powerwash_required);
+
+ protected:
+ static const char* kImageMountPointTemplate;
+
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop loop_{&base_loop_};
+ brillo::AsynchronousSignalHandler async_signal_handler_;
+ Subprocess subprocess_;
+ FakeBootControl fake_boot_control_;
+};
+
+class PostinstActionProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ PostinstActionProcessorDelegate()
+ : code_(ErrorCode::kError),
+ code_set_(false) {}
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) {
+ MessageLoop::current()->BreakLoop();
+ }
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == PostinstallRunnerAction::StaticType()) {
+ code_ = code;
+ code_set_ = true;
+ }
+ }
+ ErrorCode code_;
+ bool code_set_;
+};
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
+ DoTest(true, 0, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
+ DoTest(true, 0, true);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
+ DoTest(false, 0, true);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
+ DoTest(true, 1, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
+ DoTest(true, 3, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareROErrScriptTest) {
+ DoTest(true, 4, false);
+}
+
+const char* PostinstallRunnerActionTest::kImageMountPointTemplate =
+ "au_destination-XXXXXX";
+
+void PostinstallRunnerActionTest::DoTest(
+ bool do_losetup,
+ int err_code,
+ bool powerwash_required) {
+ ASSERT_EQ(0, getuid()) << "Run me as root. Ideally don't run other tests "
+ << "as root, tho.";
+ // True if the post-install action is expected to succeed.
+ bool should_succeed = do_losetup && !err_code;
+
+ string orig_cwd;
+ {
+ vector<char> buf(1000);
+ ASSERT_EQ(buf.data(), getcwd(buf.data(), buf.size()));
+ orig_cwd = string(buf.data(), strlen(buf.data()));
+ }
+
+ // Create a unique named working directory and chdir into it.
+ string cwd;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "postinstall_runner_action_unittest-XXXXXX",
+ &cwd));
+ ASSERT_EQ(0, test_utils::Chdir(cwd));
+
+ // Create a 10MiB sparse file to be used as image; format it as ext2.
+ ASSERT_EQ(0, System(
+ "dd if=/dev/zero of=image.dat seek=10485759 bs=1 count=1 "
+ "status=none"));
+ ASSERT_EQ(0, System("mkfs.ext2 -F image.dat"));
+
+ // Create a uniquely named image mount point, mount the image.
+ ASSERT_EQ(0, System(string("mkdir -p ") + kStatefulPartition));
+ string mountpoint;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ string(kStatefulPartition) + "/" + kImageMountPointTemplate,
+ &mountpoint));
+ ASSERT_EQ(0, System(string("mount -o loop image.dat ") + mountpoint));
+
+ // Generate a fake postinst script inside the image.
+ string script = (err_code ?
+ base::StringPrintf("#!/bin/bash\nexit %d", err_code) :
+ base::StringPrintf(
+ "#!/bin/bash\n"
+ "mount | grep au_postint_mount | grep ext2\n"
+ "if [ $? -eq 0 ]; then\n"
+ " touch %s/postinst_called\n"
+ "fi\n",
+ cwd.c_str()));
+ const string script_file_name = mountpoint + "/postinst";
+ ASSERT_TRUE(WriteFileString(script_file_name, script));
+ ASSERT_EQ(0, System(string("chmod a+x ") + script_file_name));
+
+ // Unmount image; do not remove the uniquely named directory as it will be
+ // reused during the test.
+ ASSERT_TRUE(utils::UnmountFilesystem(mountpoint));
+
+ // get a loop device we can use for the install device
+ string dev = "/dev/null";
+
+ unique_ptr<test_utils::ScopedLoopbackDeviceBinder> loop_releaser;
+ if (do_losetup) {
+ loop_releaser.reset(new test_utils::ScopedLoopbackDeviceBinder(
+ cwd + "/image.dat", &dev));
+ }
+
+ // We use a test-specific powerwash marker file, to avoid race conditions.
+ string powerwash_marker_file = mountpoint + "/factory_install_reset";
+ LOG(INFO) << ">>> powerwash_marker_file=" << powerwash_marker_file;
+
+ ActionProcessor processor;
+ ObjectFeederAction<InstallPlan> feeder_action;
+ InstallPlan::Partition part;
+ part.name = "part";
+ part.target_path = dev;
+ part.run_postinstall = true;
+ InstallPlan install_plan;
+ install_plan.partitions = {part};
+ install_plan.download_url = "http://devserver:8080/update";
+ install_plan.powerwash_required = powerwash_required;
+ feeder_action.set_obj(install_plan);
+ PostinstallRunnerAction runner_action(&fake_boot_control_,
+ powerwash_marker_file.c_str());
+ BondActions(&feeder_action, &runner_action);
+ ObjectCollectorAction<InstallPlan> collector_action;
+ BondActions(&runner_action, &collector_action);
+ PostinstActionProcessorDelegate delegate;
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&runner_action);
+ processor.EnqueueAction(&collector_action);
+ processor.set_delegate(&delegate);
+
+ loop_.PostTask(FROM_HERE,
+ base::Bind([&processor] { processor.StartProcessing(); }));
+ loop_.Run();
+ ASSERT_FALSE(processor.IsRunning());
+
+ EXPECT_TRUE(delegate.code_set_);
+ EXPECT_EQ(should_succeed, delegate.code_ == ErrorCode::kSuccess);
+ if (should_succeed)
+ EXPECT_TRUE(install_plan == collector_action.object());
+
+ const base::FilePath kPowerwashMarkerPath(powerwash_marker_file);
+ string actual_cmd;
+ if (should_succeed && powerwash_required) {
+ EXPECT_TRUE(base::ReadFileToString(kPowerwashMarkerPath, &actual_cmd));
+ EXPECT_EQ(kPowerwashCommand, actual_cmd);
+ } else {
+ EXPECT_FALSE(
+ base::ReadFileToString(kPowerwashMarkerPath, &actual_cmd));
+ }
+
+ if (err_code == 2)
+ EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB, delegate.code_);
+
+ struct stat stbuf;
+ int rc = lstat((string(cwd) + "/postinst_called").c_str(), &stbuf);
+ if (should_succeed)
+ ASSERT_EQ(0, rc);
+ else
+ ASSERT_LT(rc, 0);
+
+ if (do_losetup) {
+ loop_releaser.reset(nullptr);
+ }
+
+ // Remove unique stateful directory.
+ ASSERT_EQ(0, System(string("rm -fr ") + mountpoint));
+
+ // Remove the temporary work directory.
+ ASSERT_EQ(0, test_utils::Chdir(orig_cwd));
+ ASSERT_EQ(0, System(string("rm -fr ") + cwd));
+}
+
+// Death tests don't seem to be working on Hardy
+TEST_F(PostinstallRunnerActionTest, DISABLED_RunAsRootDeathTest) {
+ ASSERT_EQ(0, getuid());
+ PostinstallRunnerAction runner_action(&fake_boot_control_);
+ ASSERT_DEATH({ runner_action.TerminateProcessing(); },
+ "postinstall_runner_action.h:.*] Check failed");
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/xz_extent_writer.cc b/payload_consumer/xz_extent_writer.cc
new file mode 100644
index 0000000..4bd893d
--- /dev/null
+++ b/payload_consumer/xz_extent_writer.cc
@@ -0,0 +1,118 @@
+//
+// 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_consumer/xz_extent_writer.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const brillo::Blob::size_type kOutputBufferLength = 16 * 1024;
+
+// xz uses a variable dictionary size which impacts on the compression ratio
+// and is required to be reconstructed in RAM during decompression. While we
+// control the required memory from the compressor side, the decompressor allows
+// to set a limit on this dictionary size, rejecting compressed streams that
+// require more than that. "xz -9" requires up to 64 MiB, so a 64 MiB limit
+// will allow compressed streams up to -9, the maximum compression setting.
+const uint32_t kXzMaxDictSize = 64 * 1024 * 1024;
+
+const char* XzErrorString(enum xz_ret error) {
+ #define __XZ_ERROR_STRING_CASE(code) case code: return #code;
+ switch (error) {
+ __XZ_ERROR_STRING_CASE(XZ_OK)
+ __XZ_ERROR_STRING_CASE(XZ_STREAM_END)
+ __XZ_ERROR_STRING_CASE(XZ_UNSUPPORTED_CHECK)
+ __XZ_ERROR_STRING_CASE(XZ_MEM_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_MEMLIMIT_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_FORMAT_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_OPTIONS_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_DATA_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_BUF_ERROR)
+ default:
+ return "<unknown xz error>";
+ }
+ #undef __XZ_ERROR_STRING_CASE
+};
+} // namespace
+
+XzExtentWriter::~XzExtentWriter() {
+ xz_dec_end(stream_);
+}
+
+bool XzExtentWriter::Init(FileDescriptorPtr fd,
+ const vector<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);
+}
+
+bool XzExtentWriter::Write(const void* bytes, size_t count) {
+ // Copy the input data into |input_buffer_| only if |input_buffer_| already
+ // contains unconsumed data. Otherwise, process the data directly from the
+ // source.
+ const uint8_t* input = reinterpret_cast<const uint8_t*>(bytes);
+ if (!input_buffer_.empty()) {
+ input_buffer_.insert(input_buffer_.end(), input, input + count);
+ input = input_buffer_.data();
+ count = input_buffer_.size();
+ }
+
+ xz_buf request;
+ request.in = input;
+ request.in_pos = 0;
+ request.in_size = count;
+
+ brillo::Blob output_buffer(kOutputBufferLength);
+ request.out = output_buffer.data();
+ request.out_size = output_buffer.size();
+ for (;;) {
+ request.out_pos = 0;
+
+ xz_ret ret = xz_dec_run(stream_, &request);
+ if (ret != XZ_OK && ret != XZ_STREAM_END) {
+ LOG(ERROR) << "xz_dec_run returned " << XzErrorString(ret);
+ return false;
+ }
+
+ if (request.out_pos == 0)
+ break;
+
+ TEST_AND_RETURN_FALSE(
+ underlying_writer_->Write(output_buffer.data(), request.out_pos));
+ if (ret == XZ_STREAM_END)
+ CHECK_EQ(request.in_size, request.in_pos);
+ if (request.in_size == request.in_pos)
+ break; // No more input to process.
+ }
+ output_buffer.clear();
+
+ // Store unconsumed data (if any) in |input_buffer_|. Since |input| can point
+ // to the existing |input_buffer_| we create a new one before assigning it.
+ brillo::Blob new_input_buffer(request.in + request.in_pos,
+ request.in + request.in_size);
+ input_buffer_ = std::move(new_input_buffer);
+ return true;
+}
+
+bool XzExtentWriter::EndImpl() {
+ TEST_AND_RETURN_FALSE(input_buffer_.empty());
+ return underlying_writer_->End();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/xz_extent_writer.h b/payload_consumer/xz_extent_writer.h
new file mode 100644
index 0000000..a6b3257
--- /dev/null
+++ b/payload_consumer/xz_extent_writer.h
@@ -0,0 +1,60 @@
+//
+// 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_CONSUMER_XZ_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_XZ_EXTENT_WRITER_H_
+
+#include <xz.h>
+
+#include <memory>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/extent_writer.h"
+
+// XzExtentWriter is a concrete ExtentWriter subclass that xz-decompresses
+// what it's given in Write using xz-embedded. Note that xz-embedded only
+// supports files with either no CRC or CRC-32. It passes the decompressed data
+// to an underlying ExtentWriter.
+
+namespace chromeos_update_engine {
+
+class XzExtentWriter : public ExtentWriter {
+ public:
+ explicit XzExtentWriter(std::unique_ptr<ExtentWriter> underlying_writer)
+ : underlying_writer_(std::move(underlying_writer)) {}
+ ~XzExtentWriter() override;
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) override;
+ bool Write(const void* bytes, size_t count) override;
+ bool EndImpl() override;
+
+ private:
+ // The underlying ExtentWriter.
+ std::unique_ptr<ExtentWriter> underlying_writer_;
+ // The opaque xz decompressor struct.
+ xz_dec* stream_{nullptr};
+ brillo::Blob input_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(XzExtentWriter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_XZ_EXTENT_WRITER_H_
diff --git a/payload_consumer/xz_extent_writer_unittest.cc b/payload_consumer/xz_extent_writer_unittest.cc
new file mode 100644
index 0000000..fb8bb40
--- /dev/null
+++ b/payload_consumer/xz_extent_writer_unittest.cc
@@ -0,0 +1,165 @@
+//
+// 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_consumer/xz_extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <brillo/make_unique_ptr.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/fake_extent_writer.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const char kSampleData[] = "Redundaaaaaaaaaaaaaant\n";
+
+// Compressed data with CRC-32 check, generated with:
+// echo "Redundaaaaaaaaaaaaaant" | xz -9 --check=crc32 |
+// hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressedDataCRC32[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0xe0, 0x00, 0x16, 0x00, 0x10, 0x5d, 0x00, 0x29, 0x19, 0x48, 0x87, 0x88,
+ 0xec, 0x49, 0x88, 0x73, 0x8b, 0x5d, 0xa6, 0x46, 0xb4, 0x00, 0x00, 0x00,
+ 0x68, 0xfc, 0x7b, 0x25, 0x00, 0x01, 0x28, 0x17, 0x46, 0x9e, 0x08, 0xfe,
+ 0x90, 0x42, 0x99, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, 0x5a,
+};
+
+// Compressed data without checksum, generated with:
+// echo "Redundaaaaaaaaaaaaaant" | xz -9 --check=none |
+// hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressedDataNoCheck[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00, 0xff, 0x12, 0xd9, 0x41,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0xe0, 0x00, 0x16, 0x00, 0x10, 0x5d, 0x00, 0x29, 0x19, 0x48, 0x87, 0x88,
+ 0xec, 0x49, 0x88, 0x73, 0x8b, 0x5d, 0xa6, 0x46, 0xb4, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x24, 0x17, 0x4a, 0xd1, 0xbd, 0x52, 0x06, 0x72, 0x9e, 0x7a,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x5a,
+};
+
+// Highly redundant data bigger than the internal buffer, generated with:
+// dd if=/dev/zero bs=30K count=1 | tr '\0' 'a' | xz -9 --check=crc32 |
+// hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressed30KiBofA[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0xe0, 0x77, 0xff, 0x00, 0x41, 0x5d, 0x00, 0x30, 0xef, 0xfb, 0xbf, 0xfe,
+ 0xa3, 0xb1, 0x5e, 0xe5, 0xf8, 0x3f, 0xb2, 0xaa, 0x26, 0x55, 0xf8, 0x68,
+ 0x70, 0x41, 0x70, 0x15, 0x0f, 0x8d, 0xfd, 0x1e, 0x4c, 0x1b, 0x8a, 0x42,
+ 0xb7, 0x19, 0xf4, 0x69, 0x18, 0x71, 0xae, 0x66, 0x23, 0x8a, 0x8a, 0x4d,
+ 0x2f, 0xa3, 0x0d, 0xd9, 0x7f, 0xa6, 0xe3, 0x8c, 0x23, 0x11, 0x53, 0xe0,
+ 0x59, 0x18, 0xc5, 0x75, 0x8a, 0xe2, 0x76, 0x4c, 0xee, 0x30, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf9, 0x47, 0xb5, 0xee, 0x00, 0x01, 0x59, 0x80,
+ 0xf0, 0x01, 0x00, 0x00, 0xe0, 0x41, 0x96, 0xde, 0x3e, 0x30, 0x0d, 0x8b,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, 0x5a,
+};
+
+} // namespace
+
+class XzExtentWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ fake_extent_writer_ = new FakeExtentWriter();
+ xz_writer_.reset(
+ new XzExtentWriter(brillo::make_unique_ptr(fake_extent_writer_)));
+ }
+
+ void WriteAll(const brillo::Blob& compressed) {
+ EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ EXPECT_TRUE(xz_writer_->Write(compressed.data(), compressed.size()));
+ EXPECT_TRUE(xz_writer_->End());
+
+ EXPECT_TRUE(fake_extent_writer_->InitCalled());
+ EXPECT_TRUE(fake_extent_writer_->EndCalled());
+ }
+
+ // Owned by |xz_writer_|. This object is invalidated after |xz_writer_| is
+ // deleted.
+ FakeExtentWriter* fake_extent_writer_{nullptr};
+ std::unique_ptr<XzExtentWriter> xz_writer_;
+
+ const brillo::Blob sample_data_{
+ std::begin(kSampleData),
+ std::begin(kSampleData) + strlen(kSampleData)};
+ FileDescriptorPtr fd_;
+};
+
+TEST_F(XzExtentWriterTest, CreateAndDestroy) {
+ // Test that no Init() or End() called doesn't crash the program.
+ EXPECT_FALSE(fake_extent_writer_->InitCalled());
+ EXPECT_FALSE(fake_extent_writer_->EndCalled());
+}
+
+TEST_F(XzExtentWriterTest, CompressedSampleData) {
+ WriteAll(brillo::Blob(std::begin(kCompressedDataNoCheck),
+ std::end(kCompressedDataNoCheck)));
+ EXPECT_EQ(sample_data_, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, CompressedSampleDataWithCrc) {
+ WriteAll(brillo::Blob(std::begin(kCompressedDataCRC32),
+ std::end(kCompressedDataCRC32)));
+ EXPECT_EQ(sample_data_, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, CompressedDataBiggerThanTheBuffer) {
+ // Test that even if the output data is bigger than the internal buffer, all
+ // the data is written.
+ WriteAll(brillo::Blob(std::begin(kCompressed30KiBofA),
+ std::end(kCompressed30KiBofA)));
+ brillo::Blob expected_data(30 * 1024, 'a');
+ EXPECT_EQ(expected_data, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, GarbageDataRejected) {
+ EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ // The sample_data_ is an uncompressed string.
+ EXPECT_FALSE(xz_writer_->Write(sample_data_.data(), sample_data_.size()));
+ EXPECT_TRUE(xz_writer_->End());
+
+ EXPECT_TRUE(fake_extent_writer_->EndCalled());
+}
+
+TEST_F(XzExtentWriterTest, PartialDataIsKept) {
+ brillo::Blob compressed(std::begin(kCompressed30KiBofA),
+ std::end(kCompressed30KiBofA));
+ EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ for (uint8_t byte : compressed) {
+ EXPECT_TRUE(xz_writer_->Write(&byte, 1));
+ }
+ EXPECT_TRUE(xz_writer_->End());
+
+ // The sample_data_ is an uncompressed string.
+ brillo::Blob expected_data(30 * 1024, 'a');
+ EXPECT_EQ(expected_data, fake_extent_writer_->WrittenData());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/ab_generator.cc b/payload_generator/ab_generator.cc
new file mode 100644
index 0000000..7caf897
--- /dev/null
+++ b/payload_generator/ab_generator.cc
@@ -0,0 +1,334 @@
+//
+// 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/ab_generator.h"
+
+#include <algorithm>
+
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.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/delta_diff_utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+bool ABGenerator::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);
+
+ 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;
+
+ aops->clear();
+ TEST_AND_RETURN_FALSE(diff_utils::DeltaReadPartition(
+ aops,
+ old_part,
+ new_part,
+ hard_chunk_blocks,
+ soft_chunk_blocks,
+ blob_file,
+ true)); // src_ops_allowed
+ LOG(INFO) << "done reading " << new_part.name;
+
+ TEST_AND_RETURN_FALSE(FragmentOperations(aops,
+ new_part.path,
+ blob_file));
+ SortOperationsByDestination(aops);
+
+ // Use the soft_chunk_size when merging operations to prevent merging all
+ // the operations into a huge one if there's no hard limit.
+ size_t merge_chunk_blocks = soft_chunk_blocks;
+ if (hard_chunk_blocks != -1 &&
+ static_cast<size_t>(hard_chunk_blocks) < soft_chunk_blocks) {
+ merge_chunk_blocks = hard_chunk_blocks;
+ }
+
+ TEST_AND_RETURN_FALSE(MergeOperations(aops,
+ merge_chunk_blocks,
+ new_part.path,
+ blob_file));
+
+ if (config.minor_version == kOpSrcHashMinorPayloadVersion)
+ TEST_AND_RETURN_FALSE(AddSourceHash(aops, old_part.path));
+
+ return true;
+}
+
+void ABGenerator::SortOperationsByDestination(
+ vector<AnnotatedOperation>* aops) {
+ sort(aops->begin(), aops->end(), diff_utils::CompareAopsByDestination);
+}
+
+bool ABGenerator::FragmentOperations(
+ vector<AnnotatedOperation>* aops,
+ const string& target_part_path,
+ BlobFileWriter* blob_file) {
+ vector<AnnotatedOperation> fragmented_aops;
+ for (const AnnotatedOperation& aop : *aops) {
+ if (aop.op.type() == InstallOperation::SOURCE_COPY) {
+ TEST_AND_RETURN_FALSE(SplitSourceCopy(aop, &fragmented_aops));
+ } else if ((aop.op.type() == InstallOperation::REPLACE) ||
+ (aop.op.type() == InstallOperation::REPLACE_BZ)) {
+ TEST_AND_RETURN_FALSE(SplitReplaceOrReplaceBz(aop, &fragmented_aops,
+ target_part_path,
+ blob_file));
+ } else {
+ fragmented_aops.push_back(aop);
+ }
+ }
+ *aops = fragmented_aops;
+ return true;
+}
+
+bool ABGenerator::SplitSourceCopy(
+ const AnnotatedOperation& original_aop,
+ vector<AnnotatedOperation>* result_aops) {
+ InstallOperation original_op = original_aop.op;
+ TEST_AND_RETURN_FALSE(original_op.type() == InstallOperation::SOURCE_COPY);
+ // Keeps track of the index of curr_src_ext.
+ int curr_src_ext_index = 0;
+ Extent curr_src_ext = original_op.src_extents(curr_src_ext_index);
+ for (int i = 0; i < original_op.dst_extents_size(); i++) {
+ Extent dst_ext = original_op.dst_extents(i);
+ // The new operation which will have only one dst extent.
+ InstallOperation new_op;
+ uint64_t blocks_left = dst_ext.num_blocks();
+ while (blocks_left > 0) {
+ if (curr_src_ext.num_blocks() <= blocks_left) {
+ // If the curr_src_ext is smaller than dst_ext, add it.
+ blocks_left -= curr_src_ext.num_blocks();
+ *(new_op.add_src_extents()) = curr_src_ext;
+ if (curr_src_ext_index + 1 < original_op.src_extents().size()) {
+ curr_src_ext = original_op.src_extents(++curr_src_ext_index);
+ } else {
+ break;
+ }
+ } else {
+ // Split src_exts that are bigger than the dst_ext we're dealing with.
+ Extent first_ext;
+ first_ext.set_num_blocks(blocks_left);
+ first_ext.set_start_block(curr_src_ext.start_block());
+ *(new_op.add_src_extents()) = first_ext;
+ // Keep the second half of the split op.
+ curr_src_ext.set_num_blocks(curr_src_ext.num_blocks() - blocks_left);
+ curr_src_ext.set_start_block(curr_src_ext.start_block() + blocks_left);
+ blocks_left -= first_ext.num_blocks();
+ }
+ }
+ // Fix up our new operation and add it to the results.
+ new_op.set_type(InstallOperation::SOURCE_COPY);
+ *(new_op.add_dst_extents()) = dst_ext;
+ new_op.set_src_length(dst_ext.num_blocks() * kBlockSize);
+ new_op.set_dst_length(dst_ext.num_blocks() * kBlockSize);
+
+ AnnotatedOperation new_aop;
+ new_aop.op = new_op;
+ new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i);
+ result_aops->push_back(new_aop);
+ }
+ if (curr_src_ext_index != original_op.src_extents().size() - 1) {
+ LOG(FATAL) << "Incorrectly split SOURCE_COPY operation. Did not use all "
+ << "source extents.";
+ }
+ return true;
+}
+
+bool ABGenerator::SplitReplaceOrReplaceBz(
+ const AnnotatedOperation& original_aop,
+ vector<AnnotatedOperation>* result_aops,
+ const string& target_part_path,
+ BlobFileWriter* blob_file) {
+ InstallOperation original_op = original_aop.op;
+ const bool is_replace = original_op.type() == InstallOperation::REPLACE;
+ TEST_AND_RETURN_FALSE(is_replace ||
+ original_op.type() == InstallOperation::REPLACE_BZ);
+
+ uint32_t data_offset = original_op.data_offset();
+ for (int i = 0; i < original_op.dst_extents_size(); i++) {
+ Extent dst_ext = original_op.dst_extents(i);
+ // Make a new operation with only one dst extent.
+ InstallOperation new_op;
+ *(new_op.add_dst_extents()) = dst_ext;
+ uint32_t data_size = dst_ext.num_blocks() * kBlockSize;
+ new_op.set_dst_length(data_size);
+ // If this is a REPLACE, attempt to reuse portions of the existing blob.
+ if (is_replace) {
+ new_op.set_type(InstallOperation::REPLACE);
+ new_op.set_data_length(data_size);
+ new_op.set_data_offset(data_offset);
+ data_offset += data_size;
+ }
+
+ AnnotatedOperation new_aop;
+ new_aop.op = new_op;
+ new_aop.name = base::StringPrintf("%s:%d", original_aop.name.c_str(), i);
+ TEST_AND_RETURN_FALSE(AddDataAndSetType(&new_aop, target_part_path,
+ blob_file));
+
+ result_aops->push_back(new_aop);
+ }
+ return true;
+}
+
+bool ABGenerator::MergeOperations(vector<AnnotatedOperation>* aops,
+ size_t chunk_blocks,
+ const string& target_part_path,
+ BlobFileWriter* blob_file) {
+ vector<AnnotatedOperation> new_aops;
+ for (const AnnotatedOperation& curr_aop : *aops) {
+ if (new_aops.empty()) {
+ new_aops.push_back(curr_aop);
+ continue;
+ }
+ AnnotatedOperation& last_aop = new_aops.back();
+
+ if (last_aop.op.dst_extents_size() <= 0 ||
+ curr_aop.op.dst_extents_size() <= 0) {
+ new_aops.push_back(curr_aop);
+ continue;
+ }
+ uint32_t last_dst_idx = last_aop.op.dst_extents_size() - 1;
+ uint32_t last_end_block =
+ last_aop.op.dst_extents(last_dst_idx).start_block() +
+ last_aop.op.dst_extents(last_dst_idx).num_blocks();
+ uint32_t curr_start_block = curr_aop.op.dst_extents(0).start_block();
+ uint32_t combined_block_count =
+ last_aop.op.dst_extents(last_dst_idx).num_blocks() +
+ curr_aop.op.dst_extents(0).num_blocks();
+ bool good_op_type = curr_aop.op.type() == InstallOperation::SOURCE_COPY ||
+ curr_aop.op.type() == InstallOperation::REPLACE ||
+ curr_aop.op.type() == InstallOperation::REPLACE_BZ;
+ if (good_op_type &&
+ last_aop.op.type() == curr_aop.op.type() &&
+ last_end_block == curr_start_block &&
+ combined_block_count <= chunk_blocks) {
+ // If the operations have the same type (which is a type that we can
+ // merge), are contiguous, are fragmented to have one destination extent,
+ // and their combined block count would be less than chunk size, merge
+ // them.
+ last_aop.name = base::StringPrintf("%s,%s",
+ last_aop.name.c_str(),
+ curr_aop.name.c_str());
+
+ ExtendExtents(last_aop.op.mutable_src_extents(),
+ curr_aop.op.src_extents());
+ if (curr_aop.op.src_length() > 0)
+ last_aop.op.set_src_length(last_aop.op.src_length() +
+ curr_aop.op.src_length());
+ ExtendExtents(last_aop.op.mutable_dst_extents(),
+ curr_aop.op.dst_extents());
+ if (curr_aop.op.dst_length() > 0)
+ last_aop.op.set_dst_length(last_aop.op.dst_length() +
+ curr_aop.op.dst_length());
+ // Set the data length to zero so we know to add the blob later.
+ if (curr_aop.op.type() == InstallOperation::REPLACE ||
+ curr_aop.op.type() == InstallOperation::REPLACE_BZ) {
+ last_aop.op.set_data_length(0);
+ }
+ } else {
+ // Otherwise just include the extent as is.
+ new_aops.push_back(curr_aop);
+ }
+ }
+
+ // Set the blobs for REPLACE/REPLACE_BZ operations that have been merged.
+ for (AnnotatedOperation& curr_aop : new_aops) {
+ if (curr_aop.op.data_length() == 0 &&
+ (curr_aop.op.type() == InstallOperation::REPLACE ||
+ curr_aop.op.type() == InstallOperation::REPLACE_BZ)) {
+ TEST_AND_RETURN_FALSE(AddDataAndSetType(&curr_aop, target_part_path,
+ blob_file));
+ }
+ }
+
+ *aops = new_aops;
+ return true;
+}
+
+bool ABGenerator::AddDataAndSetType(AnnotatedOperation* aop,
+ const string& target_part_path,
+ BlobFileWriter* blob_file) {
+ TEST_AND_RETURN_FALSE(aop->op.type() == InstallOperation::REPLACE ||
+ aop->op.type() == InstallOperation::REPLACE_BZ);
+
+ brillo::Blob data(aop->op.dst_length());
+ vector<Extent> dst_extents;
+ ExtentsToVector(aop->op.dst_extents(), &dst_extents);
+ TEST_AND_RETURN_FALSE(utils::ReadExtents(target_part_path,
+ dst_extents,
+ &data,
+ data.size(),
+ kBlockSize));
+
+ brillo::Blob data_bz;
+ TEST_AND_RETURN_FALSE(BzipCompress(data, &data_bz));
+ CHECK(!data_bz.empty());
+
+ brillo::Blob* data_p = nullptr;
+ InstallOperation_Type new_op_type;
+ if (data_bz.size() < data.size()) {
+ new_op_type = InstallOperation::REPLACE_BZ;
+ data_p = &data_bz;
+ } else {
+ new_op_type = InstallOperation::REPLACE;
+ data_p = &data;
+ }
+
+ // If the operation doesn't point to a data blob, then we add it.
+ if (aop->op.type() != new_op_type ||
+ aop->op.data_length() != data_p->size()) {
+ aop->op.set_type(new_op_type);
+ aop->SetOperationBlob(data_p, blob_file);
+ }
+
+ return true;
+}
+
+bool ABGenerator::AddSourceHash(vector<AnnotatedOperation>* aops,
+ const string& source_part_path) {
+ for (AnnotatedOperation& aop : *aops) {
+ if (aop.op.src_extents_size() == 0)
+ continue;
+
+ vector<Extent> src_extents;
+ ExtentsToVector(aop.op.src_extents(), &src_extents);
+ brillo::Blob src_data, src_hash;
+ uint64_t src_length =
+ aop.op.has_src_length()
+ ? aop.op.src_length()
+ : BlocksInExtents(aop.op.src_extents()) * kBlockSize;
+ TEST_AND_RETURN_FALSE(utils::ReadExtents(
+ source_part_path, src_extents, &src_data, src_length, kBlockSize));
+ TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfData(src_data, &src_hash));
+ aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
+ }
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/ab_generator.h b/payload_generator/ab_generator.h
new file mode 100644
index 0000000..c2837c0
--- /dev/null
+++ b/payload_generator/ab_generator.h
@@ -0,0 +1,132 @@
+//
+// 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_AB_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_AB_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/payload_generator/filesystem_interface.h"
+#include "update_engine/payload_generator/operations_generator.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// The ABGenerator is an operations generator that generates payloads using the
+// A-to-B operations SOURCE_COPY and SOURCE_BSDIFF introduced in the payload
+// minor version 2 format.
+class ABGenerator : public OperationsGenerator {
+ public:
+ ABGenerator() = default;
+
+ // Generate the update payload operations for the given partition using
+ // SOURCE_* operations, used for generating deltas for the minor version
+ // kSourceMinorPayloadVersion. This function will generate operations in the
+ // partition that will read blocks from the source partition in random order
+ // and write the new image on the target partition, also possibly in random
+ // order. The operations are stored in |aops| and should be executed in that
+ // order. 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;
+
+ // Split the operations in the vector of AnnotatedOperations |aops|
+ // such that for every operation there is only one dst extent and updates
+ // |aops| with the new list of operations. All kinds of operations are
+ // fragmented except BSDIFF and SOURCE_BSDIFF operations.
+ // The |target_part_path| is the filename of the new image, where the
+ // destination extents refer to. The blobs of the operations in |aops| should
+ // reference |blob_file|. |blob_file| are updated if needed.
+ static bool FragmentOperations(std::vector<AnnotatedOperation>* aops,
+ const std::string& target_part_path,
+ BlobFileWriter* blob_file);
+
+ // Takes a vector of AnnotatedOperations |aops| and sorts them by the first
+ // start block in their destination extents. Sets |aops| to a vector of the
+ // sorted operations.
+ static void SortOperationsByDestination(
+ std::vector<AnnotatedOperation>* aops);
+
+ // Takes an SOURCE_COPY install operation, |aop|, and adds one operation for
+ // each dst extent in |aop| to |ops|. The new operations added to |ops| will
+ // have only one dst extent. The src extents are split so the number of blocks
+ // in the src and dst extents are equal.
+ // E.g. we have a SOURCE_COPY operation:
+ // src extents: [(1, 3), (5, 1), (7, 1)], dst extents: [(2, 2), (6, 3)]
+ // Then we will get 2 new operations:
+ // 1. src extents: [(1, 2)], dst extents: [(2, 2)]
+ // 2. src extents: [(3, 1),(5, 1),(7, 1)], dst extents: [(6, 3)]
+ static bool SplitSourceCopy(const AnnotatedOperation& original_aop,
+ std::vector<AnnotatedOperation>* result_aops);
+
+ // Takes a REPLACE/REPLACE_BZ operation |aop|, and adds one operation for each
+ // dst extent in |aop| to |ops|. The new operations added to |ops| will have
+ // only one dst extent each, and may be either a REPLACE or REPLACE_BZ
+ // depending on whether compression is advantageous.
+ static bool SplitReplaceOrReplaceBz(
+ const AnnotatedOperation& original_aop,
+ std::vector<AnnotatedOperation>* result_aops,
+ const std::string& target_part,
+ BlobFileWriter* blob_file);
+
+ // Takes a sorted (by first destination extent) vector of operations |aops|
+ // and merges SOURCE_COPY, REPLACE, and REPLACE_BZ operations in that vector.
+ // It will merge two operations if:
+ // - They are of the same type.
+ // - They are contiguous.
+ // - Their combined blocks do not exceed |chunk_blocks| blocks.
+ // Note that unlike other methods, you can't pass a negative number in
+ // |chunk_blocks|.
+ static bool MergeOperations(std::vector<AnnotatedOperation>* aops,
+ size_t chunk_blocks,
+ const std::string& target_part,
+ BlobFileWriter* blob_file);
+
+ // Takes a vector of AnnotatedOperations |aops|, adds source hash to all
+ // operations that have src_extents.
+ static bool AddSourceHash(std::vector<AnnotatedOperation>* aops,
+ const std::string& source_part_path);
+
+ private:
+ // Adds the data payload for a REPLACE/REPLACE_BZ operation |aop| by reading
+ // its output extents from |target_part_path| and appending a corresponding
+ // data blob to |data_fd|. The blob will be compressed if this is smaller than
+ // the uncompressed form, and the operation type will be set accordingly.
+ // |*blob_file| will be updated as well. If the operation happens to have
+ // the right type and already points to a data blob, nothing is written.
+ // Caller should only set type and data blob if it's valid.
+ static bool AddDataAndSetType(AnnotatedOperation* aop,
+ const std::string& target_part_path,
+ BlobFileWriter* blob_file);
+
+ DISALLOW_COPY_AND_ASSIGN(ABGenerator);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_AB_GENERATOR_H_
diff --git a/payload_generator/ab_generator_unittest.cc b/payload_generator/ab_generator_unittest.cc
new file mode 100644
index 0000000..632fc64
--- /dev/null
+++ b/payload_generator/ab_generator_unittest.cc
@@ -0,0 +1,590 @@
+//
+// 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/ab_generator.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#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_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"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+bool ExtentEquals(Extent ext, uint64_t start_block, uint64_t num_blocks) {
+ 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,
+ bool compressible) {
+ const size_t op_ex1_start_block = 2;
+ const size_t op_ex1_num_blocks = 2;
+ const size_t op_ex2_start_block = 6;
+ const size_t op_ex2_num_blocks = 1;
+ const size_t part_num_blocks = 7;
+
+ // Create the target partition data.
+ string part_path;
+ EXPECT_TRUE(utils::MakeTempFile(
+ "SplitReplaceOrReplaceBzTest_part.XXXXXX", &part_path, nullptr));
+ ScopedPathUnlinker part_path_unlinker(part_path);
+ const size_t part_size = part_num_blocks * kBlockSize;
+ brillo::Blob part_data;
+ if (compressible) {
+ part_data.resize(part_size);
+ test_utils::FillWithData(&part_data);
+ } else {
+ std::mt19937 gen(12345);
+ std::uniform_int_distribution<uint8_t> dis(0, 255);
+ for (uint32_t i = 0; i < part_size; i++)
+ part_data.push_back(dis(gen));
+ }
+ ASSERT_EQ(part_size, part_data.size());
+ ASSERT_TRUE(utils::WriteFile(part_path.c_str(), part_data.data(), part_size));
+
+ // Create original operation and blob data.
+ const size_t op_ex1_offset = op_ex1_start_block * kBlockSize;
+ const size_t op_ex1_size = op_ex1_num_blocks * kBlockSize;
+ const size_t op_ex2_offset = op_ex2_start_block * kBlockSize;
+ const size_t op_ex2_size = op_ex2_num_blocks * kBlockSize;
+ InstallOperation op;
+ op.set_type(orig_type);
+ *(op.add_dst_extents()) = ExtentForRange(op_ex1_start_block,
+ op_ex1_num_blocks);
+ *(op.add_dst_extents()) = ExtentForRange(op_ex2_start_block,
+ op_ex2_num_blocks);
+ op.set_dst_length(op_ex1_num_blocks + op_ex2_num_blocks);
+
+ brillo::Blob op_data;
+ op_data.insert(op_data.end(),
+ part_data.begin() + op_ex1_offset,
+ part_data.begin() + op_ex1_offset + op_ex1_size);
+ op_data.insert(op_data.end(),
+ part_data.begin() + op_ex2_offset,
+ part_data.begin() + op_ex2_offset + op_ex2_size);
+ brillo::Blob op_blob;
+ if (orig_type == InstallOperation::REPLACE) {
+ op_blob = op_data;
+ } else {
+ ASSERT_TRUE(BzipCompress(op_data, &op_blob));
+ }
+ op.set_data_offset(0);
+ op.set_data_length(op_blob.size());
+
+ AnnotatedOperation aop;
+ aop.op = op;
+ aop.name = "SplitTestOp";
+
+ // Create the data file.
+ string data_path;
+ EXPECT_TRUE(utils::MakeTempFile(
+ "SplitReplaceOrReplaceBzTest_data.XXXXXX", &data_path, nullptr));
+ ScopedPathUnlinker data_path_unlinker(data_path);
+ int data_fd = open(data_path.c_str(), O_RDWR, 000);
+ EXPECT_GE(data_fd, 0);
+ ScopedFdCloser data_fd_closer(&data_fd);
+ EXPECT_TRUE(utils::WriteFile(data_path.c_str(), op_blob.data(),
+ op_blob.size()));
+ off_t data_file_size = op_blob.size();
+ BlobFileWriter blob_file(data_fd, &data_file_size);
+
+ // Split the operation.
+ vector<AnnotatedOperation> result_ops;
+ ASSERT_TRUE(ABGenerator::SplitReplaceOrReplaceBz(
+ aop, &result_ops, part_path, &blob_file));
+
+ // Check the result.
+ InstallOperation_Type expected_type =
+ compressible ? InstallOperation::REPLACE_BZ : InstallOperation::REPLACE;
+
+ ASSERT_EQ(2, result_ops.size());
+
+ EXPECT_EQ("SplitTestOp:0", result_ops[0].name);
+ InstallOperation first_op = result_ops[0].op;
+ EXPECT_EQ(expected_type, first_op.type());
+ EXPECT_EQ(op_ex1_size, first_op.dst_length());
+ EXPECT_EQ(1, first_op.dst_extents().size());
+ EXPECT_TRUE(ExtentEquals(first_op.dst_extents(0), op_ex1_start_block,
+ op_ex1_num_blocks));
+ // Obtain the expected blob.
+ brillo::Blob first_expected_data(
+ part_data.begin() + op_ex1_offset,
+ 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));
+ } else {
+ first_expected_blob = first_expected_data;
+ }
+ EXPECT_EQ(first_expected_blob.size(), first_op.data_length());
+ // Check that the actual blob matches what's expected.
+ brillo::Blob first_data_blob(first_op.data_length());
+ ssize_t bytes_read;
+ ASSERT_TRUE(utils::PReadAll(data_fd,
+ first_data_blob.data(),
+ first_op.data_length(),
+ first_op.data_offset(),
+ &bytes_read));
+ ASSERT_EQ(bytes_read, first_op.data_length());
+ EXPECT_EQ(first_expected_blob, first_data_blob);
+
+ EXPECT_EQ("SplitTestOp:1", result_ops[1].name);
+ InstallOperation second_op = result_ops[1].op;
+ EXPECT_EQ(expected_type, second_op.type());
+ EXPECT_EQ(op_ex2_size, second_op.dst_length());
+ EXPECT_EQ(1, second_op.dst_extents().size());
+ EXPECT_TRUE(ExtentEquals(second_op.dst_extents(0), op_ex2_start_block,
+ op_ex2_num_blocks));
+ // Obtain the expected blob.
+ brillo::Blob second_expected_data(
+ part_data.begin() + op_ex2_offset,
+ 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));
+ } else {
+ second_expected_blob = second_expected_data;
+ }
+ EXPECT_EQ(second_expected_blob.size(), second_op.data_length());
+ // Check that the actual blob matches what's expected.
+ brillo::Blob second_data_blob(second_op.data_length());
+ ASSERT_TRUE(utils::PReadAll(data_fd,
+ second_data_blob.data(),
+ second_op.data_length(),
+ second_op.data_offset(),
+ &bytes_read));
+ ASSERT_EQ(bytes_read, second_op.data_length());
+ EXPECT_EQ(second_expected_blob, second_data_blob);
+
+ // Check relative layout of data blobs.
+ EXPECT_EQ(first_op.data_offset() + first_op.data_length(),
+ second_op.data_offset());
+ EXPECT_EQ(second_op.data_offset() + second_op.data_length(), data_file_size);
+ // If we split a REPLACE into multiple ones, ensure reuse of preexisting blob.
+ if (!compressible && orig_type == InstallOperation::REPLACE) {
+ EXPECT_EQ(0, first_op.data_offset());
+ }
+}
+
+// Tests merging of REPLACE/REPLACE_BZ operations.
+void TestMergeReplaceOrReplaceBzOperations(InstallOperation_Type orig_type,
+ bool compressible) {
+ const size_t first_op_num_blocks = 1;
+ const size_t second_op_num_blocks = 2;
+ const size_t total_op_num_blocks = first_op_num_blocks + second_op_num_blocks;
+ const size_t part_num_blocks = total_op_num_blocks + 2;
+
+ // Create the target partition data.
+ string part_path;
+ EXPECT_TRUE(utils::MakeTempFile(
+ "MergeReplaceOrReplaceBzTest_part.XXXXXX", &part_path, nullptr));
+ ScopedPathUnlinker part_path_unlinker(part_path);
+ const size_t part_size = part_num_blocks * kBlockSize;
+ brillo::Blob part_data;
+ if (compressible) {
+ part_data.resize(part_size);
+ test_utils::FillWithData(&part_data);
+ } else {
+ std::mt19937 gen(12345);
+ std::uniform_int_distribution<uint8_t> dis(0, 255);
+ for (uint32_t i = 0; i < part_size; i++)
+ part_data.push_back(dis(gen));
+ }
+ ASSERT_EQ(part_size, part_data.size());
+ ASSERT_TRUE(utils::WriteFile(part_path.c_str(), part_data.data(), part_size));
+
+ // Create original operations and blob data.
+ vector<AnnotatedOperation> aops;
+ brillo::Blob blob_data;
+ const size_t total_op_size = total_op_num_blocks * kBlockSize;
+
+ InstallOperation first_op;
+ first_op.set_type(orig_type);
+ const size_t first_op_size = first_op_num_blocks * kBlockSize;
+ first_op.set_dst_length(first_op_size);
+ *(first_op.add_dst_extents()) = ExtentForRange(0, first_op_num_blocks);
+ brillo::Blob first_op_data(part_data.begin(),
+ part_data.begin() + first_op_size);
+ brillo::Blob first_op_blob;
+ if (orig_type == InstallOperation::REPLACE) {
+ first_op_blob = first_op_data;
+ } else {
+ ASSERT_TRUE(BzipCompress(first_op_data, &first_op_blob));
+ }
+ first_op.set_data_offset(0);
+ first_op.set_data_length(first_op_blob.size());
+ blob_data.insert(blob_data.end(), first_op_blob.begin(), first_op_blob.end());
+ AnnotatedOperation first_aop;
+ first_aop.op = first_op;
+ first_aop.name = "first";
+ aops.push_back(first_aop);
+
+ InstallOperation second_op;
+ second_op.set_type(orig_type);
+ const size_t second_op_size = second_op_num_blocks * kBlockSize;
+ second_op.set_dst_length(second_op_size);
+ *(second_op.add_dst_extents()) = ExtentForRange(first_op_num_blocks,
+ second_op_num_blocks);
+ brillo::Blob second_op_data(part_data.begin() + first_op_size,
+ part_data.begin() + total_op_size);
+ brillo::Blob second_op_blob;
+ if (orig_type == InstallOperation::REPLACE) {
+ second_op_blob = second_op_data;
+ } else {
+ ASSERT_TRUE(BzipCompress(second_op_data, &second_op_blob));
+ }
+ second_op.set_data_offset(first_op_blob.size());
+ second_op.set_data_length(second_op_blob.size());
+ blob_data.insert(blob_data.end(), second_op_blob.begin(),
+ second_op_blob.end());
+ AnnotatedOperation second_aop;
+ second_aop.op = second_op;
+ second_aop.name = "second";
+ aops.push_back(second_aop);
+
+ // Create the data file.
+ string data_path;
+ EXPECT_TRUE(utils::MakeTempFile(
+ "MergeReplaceOrReplaceBzTest_data.XXXXXX", &data_path, nullptr));
+ ScopedPathUnlinker data_path_unlinker(data_path);
+ int data_fd = open(data_path.c_str(), O_RDWR, 000);
+ EXPECT_GE(data_fd, 0);
+ ScopedFdCloser data_fd_closer(&data_fd);
+ EXPECT_TRUE(utils::WriteFile(data_path.c_str(), blob_data.data(),
+ blob_data.size()));
+ off_t data_file_size = blob_data.size();
+ BlobFileWriter blob_file(data_fd, &data_file_size);
+
+ // Merge the operations.
+ EXPECT_TRUE(ABGenerator::MergeOperations(
+ &aops, 5, part_path, &blob_file));
+
+ // Check the result.
+ InstallOperation_Type expected_op_type =
+ compressible ? InstallOperation::REPLACE_BZ : InstallOperation::REPLACE;
+ EXPECT_EQ(1, aops.size());
+ InstallOperation new_op = aops[0].op;
+ EXPECT_EQ(expected_op_type, new_op.type());
+ EXPECT_FALSE(new_op.has_src_length());
+ EXPECT_EQ(total_op_num_blocks * kBlockSize, new_op.dst_length());
+ EXPECT_EQ(1, new_op.dst_extents().size());
+ EXPECT_TRUE(ExtentEquals(new_op.dst_extents(0), 0, total_op_num_blocks));
+ EXPECT_EQ("first,second", aops[0].name);
+
+ // Check to see if the blob pointed to in the new extent has what we expect.
+ brillo::Blob expected_data(part_data.begin(),
+ part_data.begin() + total_op_size);
+ brillo::Blob expected_blob;
+ if (compressible) {
+ ASSERT_TRUE(BzipCompress(expected_data, &expected_blob));
+ } else {
+ expected_blob = expected_data;
+ }
+ ASSERT_EQ(expected_blob.size(), new_op.data_length());
+ ASSERT_EQ(blob_data.size() + expected_blob.size(), data_file_size);
+ brillo::Blob new_op_blob(new_op.data_length());
+ ssize_t bytes_read;
+ ASSERT_TRUE(utils::PReadAll(data_fd,
+ new_op_blob.data(),
+ new_op.data_length(),
+ new_op.data_offset(),
+ &bytes_read));
+ ASSERT_EQ(new_op.data_length(), bytes_read);
+ EXPECT_EQ(expected_blob, new_op_blob);
+}
+
+} // namespace
+
+class ABGeneratorTest : public ::testing::Test {};
+
+TEST_F(ABGeneratorTest, SplitSourceCopyTest) {
+ 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, 2);
+ *(op.add_dst_extents()) = ExtentForRange(14, 3);
+ *(op.add_dst_extents()) = ExtentForRange(18, 3);
+
+ AnnotatedOperation aop;
+ aop.op = op;
+ aop.name = "SplitSourceCopyTestOp";
+ vector<AnnotatedOperation> result_ops;
+ EXPECT_TRUE(ABGenerator::SplitSourceCopy(aop, &result_ops));
+ EXPECT_EQ(result_ops.size(), 3);
+
+ EXPECT_EQ("SplitSourceCopyTestOp:0", result_ops[0].name);
+ InstallOperation first_op = result_ops[0].op;
+ EXPECT_EQ(InstallOperation::SOURCE_COPY, first_op.type());
+ EXPECT_EQ(kBlockSize * 2, first_op.src_length());
+ EXPECT_EQ(1, first_op.src_extents().size());
+ EXPECT_EQ(2, first_op.src_extents(0).start_block());
+ EXPECT_EQ(2, first_op.src_extents(0).num_blocks());
+ EXPECT_EQ(kBlockSize * 2, first_op.dst_length());
+ EXPECT_EQ(1, first_op.dst_extents().size());
+ EXPECT_EQ(10, first_op.dst_extents(0).start_block());
+ EXPECT_EQ(2, first_op.dst_extents(0).num_blocks());
+
+ EXPECT_EQ("SplitSourceCopyTestOp:1", result_ops[1].name);
+ InstallOperation second_op = result_ops[1].op;
+ EXPECT_EQ(InstallOperation::SOURCE_COPY, second_op.type());
+ EXPECT_EQ(kBlockSize * 3, second_op.src_length());
+ EXPECT_EQ(3, second_op.src_extents().size());
+ EXPECT_EQ(4, second_op.src_extents(0).start_block());
+ EXPECT_EQ(1, second_op.src_extents(0).num_blocks());
+ EXPECT_EQ(6, second_op.src_extents(1).start_block());
+ EXPECT_EQ(1, second_op.src_extents(1).num_blocks());
+ EXPECT_EQ(8, second_op.src_extents(2).start_block());
+ EXPECT_EQ(1, second_op.src_extents(2).num_blocks());
+ EXPECT_EQ(kBlockSize * 3, second_op.dst_length());
+ EXPECT_EQ(1, second_op.dst_extents().size());
+ EXPECT_EQ(14, second_op.dst_extents(0).start_block());
+ EXPECT_EQ(3, second_op.dst_extents(0).num_blocks());
+
+ EXPECT_EQ("SplitSourceCopyTestOp:2", result_ops[2].name);
+ InstallOperation third_op = result_ops[2].op;
+ EXPECT_EQ(InstallOperation::SOURCE_COPY, third_op.type());
+ EXPECT_EQ(kBlockSize * 3, third_op.src_length());
+ EXPECT_EQ(1, third_op.src_extents().size());
+ EXPECT_EQ(9, third_op.src_extents(0).start_block());
+ EXPECT_EQ(3, third_op.src_extents(0).num_blocks());
+ EXPECT_EQ(kBlockSize * 3, third_op.dst_length());
+ EXPECT_EQ(1, third_op.dst_extents().size());
+ EXPECT_EQ(18, third_op.dst_extents(0).start_block());
+ EXPECT_EQ(3, third_op.dst_extents(0).num_blocks());
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceTest) {
+ TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE, false);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceIntoReplaceBzTest) {
+ TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE, true);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceBzTest) {
+ TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE_BZ, true);
+}
+
+TEST_F(ABGeneratorTest, SplitReplaceBzIntoReplaceTest) {
+ TestSplitReplaceOrReplaceBzOperation(InstallOperation::REPLACE_BZ, false);
+}
+
+TEST_F(ABGeneratorTest, SortOperationsByDestinationTest) {
+ vector<AnnotatedOperation> aops;
+ // One operation with multiple destination extents.
+ InstallOperation first_op;
+ *(first_op.add_dst_extents()) = ExtentForRange(6, 1);
+ *(first_op.add_dst_extents()) = ExtentForRange(10, 2);
+ AnnotatedOperation first_aop;
+ first_aop.op = first_op;
+ first_aop.name = "first";
+ aops.push_back(first_aop);
+
+ // One with no destination extent. Should end up at the end of the vector.
+ InstallOperation second_op;
+ AnnotatedOperation second_aop;
+ second_aop.op = second_op;
+ second_aop.name = "second";
+ aops.push_back(second_aop);
+
+ // One with one destination extent.
+ InstallOperation third_op;
+ *(third_op.add_dst_extents()) = ExtentForRange(3, 2);
+ AnnotatedOperation third_aop;
+ third_aop.op = third_op;
+ third_aop.name = "third";
+ aops.push_back(third_aop);
+
+ ABGenerator::SortOperationsByDestination(&aops);
+ EXPECT_EQ(aops.size(), 3);
+ EXPECT_EQ(third_aop.name, aops[0].name);
+ EXPECT_EQ(first_aop.name, aops[1].name);
+ EXPECT_EQ(second_aop.name, aops[2].name);
+}
+
+TEST_F(ABGeneratorTest, MergeSourceCopyOperationsTest) {
+ vector<AnnotatedOperation> aops;
+ InstallOperation first_op;
+ first_op.set_type(InstallOperation::SOURCE_COPY);
+ first_op.set_src_length(kBlockSize);
+ first_op.set_dst_length(kBlockSize);
+ *(first_op.add_src_extents()) = ExtentForRange(1, 1);
+ *(first_op.add_dst_extents()) = ExtentForRange(6, 1);
+ AnnotatedOperation first_aop;
+ first_aop.op = first_op;
+ first_aop.name = "1";
+ aops.push_back(first_aop);
+
+ InstallOperation second_op;
+ second_op.set_type(InstallOperation::SOURCE_COPY);
+ second_op.set_src_length(3 * kBlockSize);
+ second_op.set_dst_length(3 * kBlockSize);
+ *(second_op.add_src_extents()) = ExtentForRange(2, 2);
+ *(second_op.add_src_extents()) = ExtentForRange(8, 2);
+ *(second_op.add_dst_extents()) = ExtentForRange(7, 3);
+ *(second_op.add_dst_extents()) = ExtentForRange(11, 1);
+ AnnotatedOperation second_aop;
+ second_aop.op = second_op;
+ second_aop.name = "2";
+ aops.push_back(second_aop);
+
+ InstallOperation third_op;
+ third_op.set_type(InstallOperation::SOURCE_COPY);
+ third_op.set_src_length(kBlockSize);
+ third_op.set_dst_length(kBlockSize);
+ *(third_op.add_src_extents()) = ExtentForRange(11, 1);
+ *(third_op.add_dst_extents()) = ExtentForRange(12, 1);
+ AnnotatedOperation third_aop;
+ third_aop.op = third_op;
+ third_aop.name = "3";
+ aops.push_back(third_aop);
+
+ BlobFileWriter blob_file(0, nullptr);
+ EXPECT_TRUE(ABGenerator::MergeOperations(&aops, 5, "", &blob_file));
+
+ EXPECT_EQ(aops.size(), 1);
+ InstallOperation first_result_op = aops[0].op;
+ EXPECT_EQ(InstallOperation::SOURCE_COPY, first_result_op.type());
+ EXPECT_EQ(kBlockSize * 5, first_result_op.src_length());
+ EXPECT_EQ(3, first_result_op.src_extents().size());
+ EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(0), 1, 3));
+ EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(1), 8, 2));
+ EXPECT_TRUE(ExtentEquals(first_result_op.src_extents(2), 11, 1));
+ EXPECT_EQ(kBlockSize * 5, first_result_op.dst_length());
+ EXPECT_EQ(2, first_result_op.dst_extents().size());
+ EXPECT_TRUE(ExtentEquals(first_result_op.dst_extents(0), 6, 4));
+ EXPECT_TRUE(ExtentEquals(first_result_op.dst_extents(1), 11, 2));
+ EXPECT_EQ(aops[0].name, "1,2,3");
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceOperationsTest) {
+ TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE, false);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceOperationsToReplaceBzTest) {
+ TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE, true);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceBzOperationsTest) {
+ TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE_BZ, true);
+}
+
+TEST_F(ABGeneratorTest, MergeReplaceBzOperationsToReplaceTest) {
+ TestMergeReplaceOrReplaceBzOperations(InstallOperation::REPLACE_BZ, false);
+}
+
+TEST_F(ABGeneratorTest, NoMergeOperationsTest) {
+ // Test to make sure we don't merge operations that shouldn't be merged.
+ vector<AnnotatedOperation> aops;
+ InstallOperation first_op;
+ first_op.set_type(InstallOperation::REPLACE_BZ);
+ *(first_op.add_dst_extents()) = ExtentForRange(0, 1);
+ first_op.set_data_length(kBlockSize);
+ AnnotatedOperation first_aop;
+ first_aop.op = first_op;
+ aops.push_back(first_aop);
+
+ // Should merge with first, except op types don't match...
+ InstallOperation second_op;
+ second_op.set_type(InstallOperation::REPLACE);
+ *(second_op.add_dst_extents()) = ExtentForRange(1, 2);
+ second_op.set_data_length(2 * kBlockSize);
+ AnnotatedOperation second_aop;
+ second_aop.op = second_op;
+ aops.push_back(second_aop);
+
+ // Should merge with second, except it would exceed chunk size...
+ InstallOperation third_op;
+ third_op.set_type(InstallOperation::REPLACE);
+ *(third_op.add_dst_extents()) = ExtentForRange(3, 3);
+ third_op.set_data_length(3 * kBlockSize);
+ AnnotatedOperation third_aop;
+ third_aop.op = third_op;
+ aops.push_back(third_aop);
+
+ // Should merge with third, except they aren't contiguous...
+ InstallOperation fourth_op;
+ fourth_op.set_type(InstallOperation::REPLACE);
+ *(fourth_op.add_dst_extents()) = ExtentForRange(7, 2);
+ fourth_op.set_data_length(2 * kBlockSize);
+ AnnotatedOperation fourth_aop;
+ fourth_aop.op = fourth_op;
+ aops.push_back(fourth_aop);
+
+ BlobFileWriter blob_file(0, nullptr);
+ EXPECT_TRUE(ABGenerator::MergeOperations(&aops, 4, "", &blob_file));
+
+ // No operations were merged, the number of ops is the same.
+ EXPECT_EQ(aops.size(), 4);
+}
+
+TEST_F(ABGeneratorTest, AddSourceHashTest) {
+ vector<AnnotatedOperation> aops;
+ InstallOperation first_op;
+ first_op.set_type(InstallOperation::SOURCE_COPY);
+ first_op.set_src_length(kBlockSize);
+ *(first_op.add_src_extents()) = ExtentForRange(0, 1);
+ AnnotatedOperation first_aop;
+ first_aop.op = first_op;
+ aops.push_back(first_aop);
+
+ InstallOperation second_op;
+ second_op.set_type(InstallOperation::REPLACE);
+ AnnotatedOperation second_aop;
+ second_aop.op = second_op;
+ aops.push_back(second_aop);
+
+ string src_part_path;
+ EXPECT_TRUE(utils::MakeTempFile("AddSourceHashTest_src_part.XXXXXX",
+ &src_part_path, nullptr));
+ ScopedPathUnlinker src_part_path_unlinker(src_part_path);
+ brillo::Blob src_data(kBlockSize);
+ test_utils::FillWithData(&src_data);
+ ASSERT_TRUE(utils::WriteFile(src_part_path.c_str(), src_data.data(),
+ src_data.size()));
+
+ EXPECT_TRUE(ABGenerator::AddSourceHash(&aops, src_part_path));
+
+ EXPECT_TRUE(aops[0].op.has_src_sha256_hash());
+ EXPECT_FALSE(aops[1].op.has_src_sha256_hash());
+ brillo::Blob expected_hash;
+ EXPECT_TRUE(HashCalculator::RawHashOfData(src_data, &expected_hash));
+ brillo::Blob result_hash(aops[0].op.src_sha256_hash().begin(),
+ aops[0].op.src_sha256_hash().end());
+ EXPECT_EQ(expected_hash, result_hash);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/annotated_operation.cc b/payload_generator/annotated_operation.cc
new file mode 100644
index 0000000..984f921
--- /dev/null
+++ b/payload_generator/annotated_operation.cc
@@ -0,0 +1,70 @@
+//
+// 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/annotated_operation.h"
+
+#include <base/format_macros.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+namespace chromeos_update_engine {
+
+namespace {
+// Output the list of extents as (start_block, num_blocks) in the passed output
+// stream.
+void OutputExtents(std::ostream* os,
+ const google::protobuf::RepeatedPtrField<Extent>& extents) {
+ for (const auto& extent : extents) {
+ *os << " (" << extent.start_block() << ", " << extent.num_blocks() << ")";
+ }
+}
+} // namespace
+
+bool AnnotatedOperation::SetOperationBlob(brillo::Blob* blob,
+ BlobFileWriter* blob_file) {
+ off_t data_offset = blob_file->StoreBlob(*blob);
+ TEST_AND_RETURN_FALSE(data_offset != -1);
+ op.set_data_offset(data_offset);
+ op.set_data_length(blob->size());
+ return true;
+}
+
+std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop) {
+ // For example, this prints:
+ // REPLACE_BZ 500 @3000
+ // name: /foo/bar
+ // dst: (123, 3) (127, 2)
+ os << InstallOperationTypeName(aop.op.type()) << " " << aop.op.data_length();
+ if (aop.op.data_length() > 0)
+ os << " @" << aop.op.data_offset();
+ if (!aop.name.empty()) {
+ os << std::endl << " name: " << aop.name;
+ }
+ if (aop.op.src_extents_size() != 0) {
+ os << std::endl << " src:";
+ OutputExtents(&os, aop.op.src_extents());
+ }
+ if (aop.op.dst_extents_size() != 0) {
+ os << std::endl << " dst:";
+ OutputExtents(&os, aop.op.dst_extents());
+ }
+ return os;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/annotated_operation.h b/payload_generator/annotated_operation.h
new file mode 100644
index 0000000..4076070
--- /dev/null
+++ b/payload_generator/annotated_operation.h
@@ -0,0 +1,49 @@
+//
+// 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_ANNOTATED_OPERATION_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
+
+#include <ostream> // NOLINT(readability/streams)
+#include <string>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+struct AnnotatedOperation {
+ // The name given to the operation, for logging and debugging purposes only.
+ // This normally includes the path to the file and the chunk used, if any.
+ std::string name;
+
+ // The InstallOperation, as defined by the protobuf.
+ InstallOperation op;
+
+ // Writes |blob| to the end of |data_fd|, and updates |data_file_size| to
+ // match the new size of |data_fd|. It sets the data_offset and data_length
+ // in AnnotatedOperation to match the offset and size of |blob| in |data_fd|.
+ bool SetOperationBlob(brillo::Blob* blob, BlobFileWriter* blob_file);
+};
+
+// For logging purposes.
+std::ostream& operator<<(std::ostream& os, const AnnotatedOperation& aop);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_ANNOTATED_OPERATION_H_
diff --git a/payload_generator/blob_file_writer.cc b/payload_generator/blob_file_writer.cc
new file mode 100644
index 0000000..8225df4
--- /dev/null
+++ b/payload_generator/blob_file_writer.cc
@@ -0,0 +1,47 @@
+//
+// 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/blob_file_writer.h"
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+off_t BlobFileWriter::StoreBlob(const brillo::Blob& blob) {
+ base::AutoLock auto_lock(blob_mutex_);
+ if (!utils::PWriteAll(blob_fd_, blob.data(), blob.size(), *blob_file_size_))
+ return -1;
+
+ off_t result = *blob_file_size_;
+ *blob_file_size_ += blob.size();
+
+ stored_blobs_++;
+ if (total_blobs_ > 0 &&
+ (10 * (stored_blobs_ - 1) / total_blobs_) !=
+ (10 * stored_blobs_ / total_blobs_)) {
+ LOG(INFO) << (100 * stored_blobs_ / total_blobs_)
+ << "% complete " << stored_blobs_ << "/" << total_blobs_
+ << " ops (output size: " << *blob_file_size_ << ")";
+ }
+ return result;
+}
+
+void BlobFileWriter::SetTotalBlobs(size_t total_blobs) {
+ total_blobs_ = total_blobs;
+ stored_blobs_ = 0;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/blob_file_writer.h b/payload_generator/blob_file_writer.h
new file mode 100644
index 0000000..cbc13ae
--- /dev/null
+++ b/payload_generator/blob_file_writer.h
@@ -0,0 +1,59 @@
+//
+// 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_BLOB_FILE_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_BLOB_FILE_WRITER_H_
+
+#include <base/macros.h>
+
+#include <base/synchronization/lock.h>
+#include <brillo/secure_blob.h>
+
+namespace chromeos_update_engine {
+
+class BlobFileWriter {
+ public:
+ // Create the BlobFileWriter object that will manage the blobs stored to
+ // |blob_fd| in a thread safe way.
+ BlobFileWriter(int blob_fd, off_t* blob_file_size)
+ : blob_fd_(blob_fd),
+ blob_file_size_(blob_file_size) {}
+
+ // Store the passed |blob| in the blob file. Returns the offset at which it
+ // 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);
+
+ private:
+ size_t total_blobs_{0};
+ size_t stored_blobs_{0};
+
+ // The file and its size are protected with the |blob_mutex_|.
+ int blob_fd_;
+ off_t* blob_file_size_;
+
+ base::Lock blob_mutex_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlobFileWriter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_BLOB_FILE_WRITER_H_
diff --git a/payload_generator/blob_file_writer_unittest.cc b/payload_generator/blob_file_writer_unittest.cc
new file mode 100644
index 0000000..5f94ef3
--- /dev/null
+++ b/payload_generator/blob_file_writer_unittest.cc
@@ -0,0 +1,59 @@
+//
+// 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/blob_file_writer.h"
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using chromeos_update_engine::test_utils::FillWithData;
+using std::string;
+
+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));
+ off_t blob_file_size = 0;
+ BlobFileWriter blob_file(blob_fd, &blob_file_size);
+
+ off_t blob_size = 1024;
+ brillo::Blob blob(blob_size);
+ FillWithData(&blob);
+ EXPECT_EQ(0, blob_file.StoreBlob(blob));
+ EXPECT_EQ(blob_size, blob_file.StoreBlob(blob));
+
+ brillo::Blob stored_blob(blob_size);
+ ssize_t bytes_read;
+ ASSERT_TRUE(utils::PReadAll(blob_fd,
+ stored_blob.data(),
+ blob_size,
+ 0,
+ &bytes_read));
+ EXPECT_EQ(bytes_read, blob_size);
+ EXPECT_EQ(blob, stored_blob);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/block_mapping.cc b/payload_generator/block_mapping.cc
new file mode 100644
index 0000000..ff10f0b
--- /dev/null
+++ b/payload_generator/block_mapping.cc
@@ -0,0 +1,161 @@
+//
+// 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/block_mapping.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+size_t HashValue(const brillo::Blob& blob) {
+ std::hash<string> hash_fn;
+ return hash_fn(string(blob.begin(), blob.end()));
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+BlockMapping::BlockId BlockMapping::AddBlock(const brillo::Blob& block_data) {
+ return AddBlock(-1, 0, block_data);
+}
+
+BlockMapping::BlockId BlockMapping::AddDiskBlock(int fd, off_t byte_offset) {
+ brillo::Blob blob(block_size_);
+ ssize_t bytes_read = 0;
+ if (!utils::PReadAll(fd, blob.data(), block_size_, byte_offset, &bytes_read))
+ return -1;
+ if (static_cast<size_t>(bytes_read) != block_size_)
+ return -1;
+ return AddBlock(fd, byte_offset, blob);
+}
+
+bool BlockMapping::AddManyDiskBlocks(int fd,
+ off_t initial_byte_offset,
+ size_t num_blocks,
+ vector<BlockId>* block_ids) {
+ bool ret = true;
+ block_ids->resize(num_blocks);
+ for (size_t block = 0; block < num_blocks; block++) {
+ (*block_ids)[block] = AddDiskBlock(
+ fd, initial_byte_offset + block * block_size_);
+ ret = ret && (*block_ids)[block] != -1;
+ }
+ return ret;
+}
+
+BlockMapping::BlockId BlockMapping::AddBlock(int fd,
+ off_t byte_offset,
+ const brillo::Blob& block_data) {
+ if (block_data.size() != block_size_)
+ return -1;
+ size_t h = HashValue(block_data);
+
+ // We either reuse a UniqueBlock or create a new one. If we need a new
+ // UniqueBlock it could also be part of a new or existing bucket (if there is
+ // a hash collision).
+ vector<UniqueBlock> *bucket = nullptr;
+
+ auto mapping_it = mapping_.find(h);
+ if (mapping_it == mapping_.end()) {
+ bucket = &mapping_[h];
+ } else {
+ for (UniqueBlock& existing_block : mapping_it->second) {
+ bool equals = false;
+ if (!existing_block.CompareData(block_data, &equals))
+ return -1;
+ if (equals)
+ return existing_block.block_id;
+ }
+ bucket = &mapping_it->second;
+ }
+
+ // No existing block was found at this point, so we create and fill in a new
+ // one.
+ bucket->emplace_back();
+ UniqueBlock *new_ublock = &bucket->back();
+
+ new_ublock->times_read = 1;
+ new_ublock->fd = fd;
+ new_ublock->byte_offset = byte_offset;
+ new_ublock->block_id = used_block_ids++;
+ // We need to cache blocks that are not referencing any disk location.
+ if (fd == -1)
+ new_ublock->block_data = block_data;
+
+ return new_ublock->block_id;
+}
+
+bool BlockMapping::UniqueBlock::CompareData(const brillo::Blob& other_block,
+ bool* equals) {
+ if (!block_data.empty()) {
+ *equals = block_data == other_block;
+ return true;
+ }
+ const size_t block_size = other_block.size();
+ brillo::Blob blob(block_size);
+ ssize_t bytes_read = 0;
+ if (!utils::PReadAll(fd, blob.data(), block_size, byte_offset, &bytes_read))
+ return false;
+ if (static_cast<size_t>(bytes_read) != block_size)
+ return false;
+ *equals = blob == other_block;
+
+ // We increase the number of times we had to read this block from disk and
+ // we cache this block based on that. This caching method is optimized for
+ // the common use case of having two partitions that share blocks between them
+ // but have few repeated blocks inside each partition, such as the block
+ // with all zeros or duplicated files.
+ times_read++;
+ if (times_read > 3)
+ block_data = std::move(blob);
+ return true;
+}
+
+bool MapPartitionBlocks(const string& old_part,
+ const string& new_part,
+ size_t old_size,
+ size_t new_size,
+ size_t block_size,
+ vector<BlockMapping::BlockId>* old_block_ids,
+ vector<BlockMapping::BlockId>* new_block_ids) {
+ BlockMapping mapping(block_size);
+ if (mapping.AddBlock(brillo::Blob(block_size, '\0')) != 0)
+ return false;
+ int old_fd = HANDLE_EINTR(open(old_part.c_str(), O_RDONLY));
+ int new_fd = HANDLE_EINTR(open(new_part.c_str(), O_RDONLY));
+ ScopedFdCloser old_fd_closer(&old_fd);
+ ScopedFdCloser new_fd_closer(&new_fd);
+
+ TEST_AND_RETURN_FALSE(mapping.AddManyDiskBlocks(
+ old_fd, 0, old_size / block_size, old_block_ids));
+ TEST_AND_RETURN_FALSE(mapping.AddManyDiskBlocks(
+ new_fd, 0, new_size / block_size, new_block_ids));
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/block_mapping.h b/payload_generator/block_mapping.h
new file mode 100644
index 0000000..3fe94ab
--- /dev/null
+++ b/payload_generator/block_mapping.h
@@ -0,0 +1,112 @@
+//
+// 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_BLOCK_MAPPING_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_BLOCK_MAPPING_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+// BlockMapping allows to map data blocks (brillo::Blobs of block_size size)
+// into unique integer values called "block ids". This mapping differs from a
+// hash function in that two blocks with the same data will have the same id but
+// also two blocks with the same id will have the same data. This is only valid
+// in the context of the same BlockMapping instance.
+class BlockMapping {
+ public:
+ using BlockId = int64_t;
+
+ explicit BlockMapping(size_t block_size) : block_size_(block_size) {}
+
+ // Add a single data block to the mapping. Returns its unique block id.
+ // In case of error returns -1.
+ BlockId AddBlock(const brillo::Blob& block_data);
+
+ // Add a block from disk reading it from the file descriptor |fd| from the
+ // offset in bytes |byte_offset|. The data block may or may not be cached, so
+ // the file descriptor must be available until the BlockMapping is destroyed.
+ // Returns the unique block id of the added block or -1 in case of error.
+ BlockId AddDiskBlock(int fd, off_t byte_offset);
+
+ // This is a helper method to add |num_blocks| contiguous blocks reading them
+ // from the file descriptor |fd| starting at offset |initial_byte_offset|.
+ // Returns whether it succeeded to add all the disk blocks and stores in
+ // |block_ids| the block id for each one of the added blocks.
+ bool AddManyDiskBlocks(int fd, off_t initial_byte_offset, size_t num_blocks,
+ std::vector<BlockId>* block_ids);
+
+ private:
+ FRIEND_TEST(BlockMappingTest, BlocksAreNotKeptInMemory);
+
+ // Add a single block passed in |block_data|. If |fd| is not -1, the block
+ // can be discarded to save RAM and retrieved later from |fd| at the position
+ // |byte_offset|.
+ BlockId AddBlock(int fd, off_t byte_offset, const brillo::Blob& block_data);
+
+ size_t block_size_;
+
+ BlockId used_block_ids{0};
+
+ // The UniqueBlock represents the data of a block associated to a unique
+ // block id.
+ struct UniqueBlock {
+ brillo::Blob block_data;
+
+ // The block id assigned to this unique block.
+ BlockId block_id;
+
+ // The location on this unique block on disk (if not cached in block_data).
+ int fd{-1};
+ off_t byte_offset{0};
+
+ // Number of times we have seen this data block. Used for caching.
+ uint32_t times_read{0};
+
+ // Compares the UniqueBlock data with the other_block data and stores if
+ // they are equal in |equals|. Returns whether there was an error reading
+ // the block from disk while comparing it.
+ bool CompareData(const brillo::Blob& other_block, bool* equals);
+ };
+
+ // A mapping from hash values to possible block ids.
+ std::map<size_t, std::vector<UniqueBlock>> mapping_;
+};
+
+// Maps the blocks of the old and new partitions |old_part| and |new_part| whose
+// size in bytes are |old_size| and |new_size| into block ids where two blocks
+// with the same data will have the same block id and vice versa, regardless of
+// the partition they are on.
+// The block ids number 0 corresponds to the block with all zeros, but any
+// other block id number is assigned randomly.
+bool MapPartitionBlocks(const std::string& old_part,
+ const std::string& new_part,
+ size_t old_size,
+ size_t new_size,
+ size_t block_size,
+ std::vector<BlockMapping::BlockId>* old_block_ids,
+ std::vector<BlockMapping::BlockId>* new_block_ids);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_BLOCK_MAPPING_H_
diff --git a/payload_generator/block_mapping_unittest.cc b/payload_generator/block_mapping_unittest.cc
new file mode 100644
index 0000000..18e48c4
--- /dev/null
+++ b/payload_generator/block_mapping_unittest.cc
@@ -0,0 +1,134 @@
+//
+// 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/block_mapping.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+} // namespace
+
+class BlockMappingTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ EXPECT_TRUE(utils::MakeTempFile("BlockMappingTest_old.XXXXXX",
+ &old_part_path_,
+ nullptr));
+ EXPECT_TRUE(utils::MakeTempFile("BlockMappingTest_new.XXXXXX",
+ &new_part_path_,
+ nullptr));
+
+ old_part_unlinker_.reset(new ScopedPathUnlinker(old_part_path_));
+ new_part_unlinker_.reset(new ScopedPathUnlinker(new_part_path_));
+ }
+
+ // Old new partition files used in testing.
+ string old_part_path_;
+ string new_part_path_;
+ std::unique_ptr<ScopedPathUnlinker> old_part_unlinker_;
+ std::unique_ptr<ScopedPathUnlinker> new_part_unlinker_;
+
+ size_t block_size_{1024};
+ BlockMapping bm_{block_size_}; // BlockMapping under test.
+};
+
+TEST_F(BlockMappingTest, FirstAddedBlockIsZero) {
+ brillo::Blob blob(block_size_);
+ // The BlockMapping just assigns the block ids in order, so it doesn't matter
+ // what are the contents of the first block.
+ blob[0] = 42;
+ EXPECT_EQ(0, bm_.AddBlock(blob));
+ blob[0] = 5;
+ EXPECT_EQ(1, bm_.AddBlock(blob));
+}
+
+TEST_F(BlockMappingTest, BlocksAreNotKeptInMemory) {
+ test_utils::WriteFileString(old_part_path_, string(block_size_, 'a'));
+ int old_fd = HANDLE_EINTR(open(old_part_path_.c_str(), O_RDONLY));
+ ScopedFdCloser old_fd_closer(&old_fd);
+
+ EXPECT_EQ(0, bm_.AddDiskBlock(old_fd, 0));
+
+ // Check that the block_data is not stored on memory if we just used the block
+ // once.
+ for (const auto& it : bm_.mapping_) {
+ for (const BlockMapping::UniqueBlock& ublock : it.second) {
+ EXPECT_TRUE(ublock.block_data.empty());
+ }
+ }
+
+ brillo::Blob block(block_size_, 'a');
+ for (int i = 0; i < 5; ++i) {
+ // Re-add the same block 5 times.
+ EXPECT_EQ(0, bm_.AddBlock(block));
+ }
+
+ for (const auto& it : bm_.mapping_) {
+ for (const BlockMapping::UniqueBlock& ublock : it.second) {
+ EXPECT_FALSE(ublock.block_data.empty());
+ // The block was loaded from disk only 4 times, and after that the counter
+ // is not updated anymore.
+ EXPECT_EQ(4, ublock.times_read);
+ }
+ }
+}
+
+TEST_F(BlockMappingTest, MapPartitionBlocks) {
+ // A string with 10 blocks where all the blocks are different.
+ string old_contents(10 * block_size_, '\0');
+ for (size_t i = 0; i < old_contents.size(); ++i)
+ old_contents[i] = 4 + i / block_size_;
+ test_utils::WriteFileString(old_part_path_, old_contents);
+
+ // A string including the block with all zeros and overlapping some of the
+ // other blocks in old_contents.
+ string new_contents(6 * block_size_, '\0');
+ for (size_t i = 0; i < new_contents.size(); ++i)
+ new_contents[i] = i / block_size_;
+ test_utils::WriteFileString(new_part_path_, new_contents);
+
+ vector<BlockMapping::BlockId> old_ids, new_ids;
+ EXPECT_TRUE(MapPartitionBlocks(old_part_path_,
+ new_part_path_,
+ old_contents.size(),
+ new_contents.size(),
+ block_size_,
+ &old_ids,
+ &new_ids));
+
+ EXPECT_EQ((vector<BlockMapping::BlockId>{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}),
+ old_ids);
+ EXPECT_EQ((vector<BlockMapping::BlockId>{0, 11, 12, 13, 1, 2}),
+ new_ids);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/bzip.cc b/payload_generator/bzip.cc
new file mode 100644
index 0000000..9040193
--- /dev/null
+++ b/payload_generator/bzip.cc
@@ -0,0 +1,130 @@
+//
+// 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/bzip.h"
+
+#include <bzlib.h>
+#include <stdlib.h>
+
+#include <algorithm>
+#include <limits>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// BzipData compresses or decompresses the input to the output.
+// Returns true on success.
+// Use one of BzipBuffToBuff*ompress as the template parameter to BzipData().
+int BzipBuffToBuffDecompress(uint8_t* out,
+ uint32_t* out_length,
+ const void* in,
+ uint32_t in_length) {
+ return BZ2_bzBuffToBuffDecompress(
+ reinterpret_cast<char*>(out),
+ out_length,
+ reinterpret_cast<char*>(const_cast<void*>(in)),
+ in_length,
+ 0, // Silent verbosity
+ 0); // Normal algorithm
+}
+
+int BzipBuffToBuffCompress(uint8_t* out,
+ uint32_t* out_length,
+ const void* in,
+ uint32_t in_length) {
+ return BZ2_bzBuffToBuffCompress(
+ reinterpret_cast<char*>(out),
+ out_length,
+ reinterpret_cast<char*>(const_cast<void*>(in)),
+ in_length,
+ 9, // Best compression
+ 0, // Silent verbosity
+ 0); // Default work factor
+}
+
+template<int F(uint8_t* out,
+ uint32_t* out_length,
+ const void* in,
+ uint32_t in_length)>
+bool BzipData(const void* const in,
+ const size_t in_size,
+ brillo::Blob* const out) {
+ TEST_AND_RETURN_FALSE(out);
+ out->clear();
+ if (in_size == 0) {
+ return true;
+ }
+ // Try increasing buffer size until it works
+ size_t buf_size = in_size;
+ out->resize(buf_size);
+
+ for (;;) {
+ if (buf_size > std::numeric_limits<uint32_t>::max())
+ return false;
+ uint32_t data_size = buf_size;
+ int rc = F(out->data(), &data_size, in, in_size);
+ TEST_AND_RETURN_FALSE(rc == BZ_OUTBUFF_FULL || rc == BZ_OK);
+ if (rc == BZ_OK) {
+ // we're done!
+ out->resize(data_size);
+ return true;
+ }
+
+ // Data didn't fit; double the buffer size.
+ buf_size *= 2;
+ out->resize(buf_size);
+ }
+}
+
+} // namespace
+
+bool BzipDecompress(const brillo::Blob& in, brillo::Blob* out) {
+ return BzipData<BzipBuffToBuffDecompress>(in.data(), in.size(), out);
+}
+
+bool BzipCompress(const brillo::Blob& in, brillo::Blob* out) {
+ return BzipData<BzipBuffToBuffCompress>(in.data(), in.size(), out);
+}
+
+namespace {
+template<bool F(const void* const in,
+ const size_t in_size,
+ brillo::Blob* const out)>
+bool BzipString(const string& str,
+ brillo::Blob* out) {
+ TEST_AND_RETURN_FALSE(out);
+ brillo::Blob temp;
+ TEST_AND_RETURN_FALSE(F(str.data(), str.size(), &temp));
+ out->clear();
+ out->insert(out->end(), temp.begin(), temp.end());
+ return true;
+}
+} // namespace
+
+bool BzipCompressString(const string& str, brillo::Blob* out) {
+ return BzipString<BzipData<BzipBuffToBuffCompress>>(str, out);
+}
+
+bool BzipDecompressString(const string& str, brillo::Blob* out) {
+ return BzipString<BzipData<BzipBuffToBuffDecompress>>(str, out);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/bzip.h b/payload_generator/bzip.h
new file mode 100644
index 0000000..ca9956e
--- /dev/null
+++ b/payload_generator/bzip.h
@@ -0,0 +1,35 @@
+//
+// 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_BZIP_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_BZIP_H_
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+namespace chromeos_update_engine {
+
+// Bzip2 compresses or decompresses str/in to out.
+bool BzipDecompress(const brillo::Blob& in, brillo::Blob* out);
+bool BzipCompress(const brillo::Blob& in, brillo::Blob* out);
+bool BzipCompressString(const std::string& str, brillo::Blob* out);
+bool BzipDecompressString(const std::string& str, brillo::Blob* out);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_BZIP_H_
diff --git a/payload_generator/cycle_breaker.cc b/payload_generator/cycle_breaker.cc
new file mode 100644
index 0000000..321732e
--- /dev/null
+++ b/payload_generator/cycle_breaker.cc
@@ -0,0 +1,211 @@
+//
+// 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 <set>
+#include <string>
+#include <utility>
+
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.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 (utils::MapContainsKey(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 = kuint64max;
+ 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 (utils::SetContainsKey(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
new file mode 100644
index 0000000..231d63a
--- /dev/null
+++ b/payload_generator/cycle_breaker.h
@@ -0,0 +1,71 @@
+//
+// 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
new file mode 100644
index 0000000..4053cf3
--- /dev/null
+++ b/payload_generator/cycle_breaker_unittest.cc
@@ -0,0 +1,278 @@
+//
+// 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 <gtest/gtest.h>
+
+#include "update_engine/common/utils.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(utils::SetContainsKey(broken_edges, make_pair(n_a, n_e)) ||
+ utils::SetContainsKey(broken_edges, make_pair(n_e, n_b)) ||
+ utils::SetContainsKey(broken_edges, make_pair(n_b, n_a)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_c, n_d)) ||
+ utils::SetContainsKey(broken_edges, make_pair(n_d, n_e)) ||
+ utils::SetContainsKey(broken_edges, make_pair(n_e, n_c)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_g, n_h)) ||
+ utils::SetContainsKey(broken_edges, make_pair(n_h, n_g)));
+ EXPECT_EQ(3, 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) {
+ int 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) {
+ 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);
+ 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(utils::SetContainsKey(broken_edges, make_pair(n_b, n_a)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_b, n_c)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_d, n_e)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_f, n_g)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_h, n_i)));
+}
+
+TEST(CycleBreakerTest, UnblockGraphTest) {
+ 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 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(utils::SetContainsKey(broken_edges, make_pair(n_a, n_b)));
+ EXPECT_TRUE(utils::SetContainsKey(broken_edges, make_pair(n_a, n_c)));
+}
+
+TEST(CycleBreakerTest, SkipOpsTest) {
+ int 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(2, breaker.skipped_ops());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_generator.cc b/payload_generator/delta_diff_generator.cc
new file mode 100644
index 0000000..ffe0fe9
--- /dev/null
+++ b/payload_generator/delta_diff_generator.cc
@@ -0,0 +1,148 @@
+//
+// 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/delta_diff_generator.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/ab_generator.h"
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/full_update_generator.h"
+#include "update_engine/payload_generator/inplace_generator.h"
+#include "update_engine/payload_generator/payload_file.h"
+
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+// bytes
+const size_t kRootFSPartitionSize = static_cast<size_t>(2) * 1024 * 1024 * 1024;
+const size_t kBlockSize = 4096; // bytes
+
+bool GenerateUpdatePayloadFile(
+ const PayloadGenerationConfig& config,
+ const string& output_path,
+ const string& private_key_path,
+ uint64_t* metadata_size) {
+
+ // Create empty payload file object.
+ 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);
+
+ {
+ off_t data_file_size = 0;
+ ScopedFdCloser data_file_fd_closer(&data_file_fd);
+ 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("");
+ 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];
+ LOG(INFO) << "Partition name: " << new_part.name;
+ LOG(INFO) << "Partition size: " << new_part.size;
+ LOG(INFO) << "Block count: " << new_part.size / config.block_size;
+
+ // Select payload generation strategy based on the config.
+ unique_ptr<OperationsGenerator> strategy;
+ // We don't efficiently support deltas on squashfs. For now, we will
+ // produce full operations in that case.
+ if (!old_part.path.empty() &&
+ !utils::IsSquashfsFilesystem(new_part.path)) {
+ // Delta update.
+ if (utils::IsExtFilesystem(new_part.path)) {
+ LOG_IF(WARNING, old_part.size != new_part.size)
+ << "Old and new filesystems have different size.";
+ // TODO(deymo): Our tools only support growing the filesystem size
+ // during an update. Remove this check when that's fixed.
+ // crbug.com/192136
+ LOG_IF(FATAL, old_part.size > new_part.size)
+ << "Shirking the filesystem size is not supported at the moment.";
+ }
+ if (config.minor_version == kInPlaceMinorPayloadVersion) {
+ LOG(INFO) << "Using generator InplaceGenerator().";
+ strategy.reset(new InplaceGenerator());
+ } else if (config.minor_version == kSourceMinorPayloadVersion ||
+ config.minor_version == kOpSrcHashMinorPayloadVersion) {
+ LOG(INFO) << "Using generator ABGenerator().";
+ strategy.reset(new ABGenerator());
+ } else {
+ LOG(ERROR) << "Unsupported minor version given for delta payload: "
+ << config.minor_version;
+ return false;
+ }
+ } else {
+ LOG(INFO) << "Using generator FullUpdateGenerator().";
+ 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);
+
+ TEST_AND_RETURN_FALSE(payload.AddPartition(old_part, new_part, aops));
+ }
+ }
+
+ 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));
+
+ LOG(INFO) << "All done. Successfully created delta file with "
+ << "metadata size = " << *metadata_size;
+ return true;
+}
+
+}; // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_generator.h b/payload_generator/delta_diff_generator.h
new file mode 100644
index 0000000..d8bdae2
--- /dev/null
+++ b/payload_generator/delta_diff_generator.h
@@ -0,0 +1,47 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
+
+#include <string>
+
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+extern const size_t kBlockSize;
+extern const size_t kRootFSPartitionSize;
+
+// The |config| describes the payload generation request, describing both
+// old and new images for delta payloads and only the new image for full
+// payloads.
+// For delta payloads, the images should be already mounted read-only at
+// the respective rootfs_mountpt.
+// |private_key_path| points to a private key used to sign the update.
+// Pass empty string to not sign the update.
+// |output_path| is the filename where the delta update should be written.
+// Returns true on success. Also writes the size of the metadata into
+// |metadata_size|.
+bool GenerateUpdatePayloadFile(const PayloadGenerationConfig& config,
+ const std::string& output_path,
+ const std::string& private_key_path,
+ uint64_t* metadata_size);
+
+
+}; // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_GENERATOR_H_
diff --git a/payload_generator/delta_diff_utils.cc b/payload_generator/delta_diff_utils.cc
new file mode 100644
index 0000000..ba1aac4
--- /dev/null
+++ b/payload_generator/delta_diff_utils.cc
@@ -0,0 +1,679 @@
+//
+// 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/delta_diff_utils.h"
+
+#include <algorithm>
+#include <map>
+
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/block_mapping.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"
+
+using std::map;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+namespace {
+
+const char* const kBsdiffPath = "bsdiff";
+
+// The maximum destination size allowed for bsdiff. In general, bsdiff should
+// work for arbitrary big files, but the payload generation and payload
+// application requires a significant amount of RAM. We put a hard-limit of
+// 200 MiB that should not affect any released board, but will limit the
+// Chrome binary in ASan builders.
+const uint64_t kMaxBsdiffDestinationSize = 200 * 1024 * 1024; // bytes
+
+// 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;
+ bool new_src = true, new_dst = true;
+ size_t removed_bytes = 0, nonfull_block_bytes;
+ bool do_remove = false;
+ while (src_idx < src_extents->size() && dst_idx < dst_extents->size()) {
+ if (new_src) {
+ src_offset = 0;
+ new_src = false;
+ }
+ if (new_dst) {
+ dst_offset = 0;
+ new_dst = false;
+ }
+
+ 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;
+
+ new_src = ProcessExtentBlockRange(src_extents, &src_idx, do_remove,
+ prev_src_offset, src_offset);
+ new_dst = ProcessExtentBlockRange(dst_extents, &dst_idx, do_remove,
+ prev_dst_offset, dst_offset);
+ 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;
+}
+
+} // namespace
+
+namespace diff_utils {
+
+bool DeltaReadPartition(
+ vector<AnnotatedOperation>* aops,
+ const PartitionConfig& old_part,
+ const PartitionConfig& new_part,
+ ssize_t hard_chunk_blocks,
+ size_t soft_chunk_blocks,
+ BlobFileWriter* blob_file,
+ bool src_ops_allowed) {
+ ExtentRanges old_visited_blocks;
+ ExtentRanges new_visited_blocks;
+
+ TEST_AND_RETURN_FALSE(DeltaMovedAndZeroBlocks(
+ aops,
+ old_part.path,
+ new_part.path,
+ old_part.fs_interface ? old_part.fs_interface->GetBlockCount() : 0,
+ new_part.fs_interface->GetBlockCount(),
+ soft_chunk_blocks,
+ src_ops_allowed,
+ blob_file,
+ &old_visited_blocks,
+ &new_visited_blocks));
+
+ map<string, vector<Extent>> old_files_map;
+ if (old_part.fs_interface) {
+ vector<FilesystemInterface::File> old_files;
+ old_part.fs_interface->GetFiles(&old_files);
+ for (const FilesystemInterface::File& file : old_files)
+ old_files_map[file.name] = file.extents;
+ }
+
+ TEST_AND_RETURN_FALSE(new_part.fs_interface);
+ vector<FilesystemInterface::File> new_files;
+ new_part.fs_interface->GetFiles(&new_files);
+
+ // The processing is very straightforward here, we generate operations for
+ // every file (and pseudo-file such as the metadata) in the new filesystem
+ // based on the file with the same name in the old filesystem, if any.
+ // Files with overlapping data blocks (like hardlinks or filesystems with tail
+ // packing or compression where the blocks store more than one file) are only
+ // generated once in the new image, but are also used only once from the old
+ // image due to some simplifications (see below).
+ for (const FilesystemInterface::File& new_file : new_files) {
+ // Ignore the files in the new filesystem without blocks. Symlinks with
+ // data blocks (for example, symlinks bigger than 60 bytes in ext2) are
+ // handled as normal files. We also ignore blocks that were already
+ // processed by a previous file.
+ vector<Extent> new_file_extents = FilterExtentRanges(
+ new_file.extents, new_visited_blocks);
+ new_visited_blocks.AddExtents(new_file_extents);
+
+ if (new_file_extents.empty())
+ continue;
+
+ LOG(INFO) << "Encoding file " << new_file.name << " ("
+ << BlocksInExtents(new_file_extents) << " blocks)";
+
+ // We can't visit each dst image inode more than once, as that would
+ // duplicate work. Here, we avoid visiting each source image inode
+ // more than once. Technically, we could have multiple operations
+ // that read the same blocks from the source image for diffing, but
+ // we choose not to avoid complexity. Eventually we will move away
+ // from using a graph/cycle detection/etc to generate diffs, and at that
+ // time, it will be easy (non-complex) to have many operations read
+ // from the same source blocks. At that time, this code can die. -adlr
+ vector<Extent> old_file_extents = FilterExtentRanges(
+ old_files_map[new_file.name], old_visited_blocks);
+ old_visited_blocks.AddExtents(old_file_extents);
+
+ TEST_AND_RETURN_FALSE(DeltaReadFile(
+ aops,
+ old_part.path,
+ new_part.path,
+ old_file_extents,
+ new_file_extents,
+ new_file.name, // operation name
+ hard_chunk_blocks,
+ blob_file,
+ src_ops_allowed));
+ }
+ // Process all the blocks not included in any file. We provided all the unused
+ // blocks in the old partition as available data.
+ vector<Extent> new_unvisited = {
+ ExtentForRange(0, new_part.size / kBlockSize)};
+ new_unvisited = FilterExtentRanges(new_unvisited, new_visited_blocks);
+ if (new_unvisited.empty())
+ return true;
+
+ vector<Extent> old_unvisited;
+ if (old_part.fs_interface) {
+ old_unvisited.push_back(ExtentForRange(0, old_part.size / kBlockSize));
+ old_unvisited = FilterExtentRanges(old_unvisited, old_visited_blocks);
+ }
+
+ LOG(INFO) << "Scanning " << BlocksInExtents(new_unvisited)
+ << " unwritten blocks using chunk size of "
+ << soft_chunk_blocks << " blocks.";
+ // We use the soft_chunk_blocks limit for the <non-file-data> as we don't
+ // really know the structure of this data and we should not expect it to have
+ // redundancy between partitions.
+ TEST_AND_RETURN_FALSE(DeltaReadFile(
+ aops,
+ old_part.path,
+ new_part.path,
+ old_unvisited,
+ new_unvisited,
+ "<non-file-data>", // operation name
+ soft_chunk_blocks,
+ blob_file,
+ src_ops_allowed));
+
+ return true;
+}
+
+bool DeltaMovedAndZeroBlocks(
+ vector<AnnotatedOperation>* aops,
+ const string& old_part,
+ const string& new_part,
+ size_t old_num_blocks,
+ size_t new_num_blocks,
+ ssize_t chunk_blocks,
+ bool src_ops_allowed,
+ BlobFileWriter* blob_file,
+ ExtentRanges* old_visited_blocks,
+ ExtentRanges* new_visited_blocks) {
+ vector<BlockMapping::BlockId> old_block_ids;
+ vector<BlockMapping::BlockId> new_block_ids;
+ TEST_AND_RETURN_FALSE(MapPartitionBlocks(old_part,
+ new_part,
+ old_num_blocks * kBlockSize,
+ new_num_blocks * kBlockSize,
+ kBlockSize,
+ &old_block_ids,
+ &new_block_ids));
+
+ // For minor-version=1, we map all the blocks that didn't move, regardless of
+ // the contents since they are already copied and no operation is required.
+ if (!src_ops_allowed) {
+ 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.
+ map<BlockMapping::BlockId, vector<uint64_t>> old_blocks_map;
+
+ for (uint64_t block = old_num_blocks; block-- > 0; ) {
+ if (old_block_ids[block] != 0 && !old_visited_blocks->ContainsBlock(block))
+ old_blocks_map[old_block_ids[block]].push_back(block);
+ }
+
+ // The collection of blocks in the new partition with just zeros. This is a
+ // common case for free-space that's also problematic for bsdiff, so we want
+ // to optimize it using REPLACE_BZ operations. The blob for a REPLACE_BZ of
+ // just zeros is so small that it doesn't make sense to spend the I/O reading
+ // zeros from the old partition.
+ vector<Extent> new_zeros;
+
+ vector<Extent> old_identical_blocks;
+ vector<Extent> new_identical_blocks;
+
+ for (uint64_t block = 0; block < new_num_blocks; block++) {
+ // Only produce operations for blocks that were not yet visited.
+ if (new_visited_blocks->ContainsBlock(block))
+ continue;
+ if (new_block_ids[block] == 0) {
+ AppendBlockToExtents(&new_zeros, block);
+ continue;
+ }
+
+ auto old_blocks_map_it = old_blocks_map.find(new_block_ids[block]);
+ // Check if the block exists in the old partition at all.
+ if (old_blocks_map_it == old_blocks_map.end() ||
+ old_blocks_map_it->second.empty())
+ continue;
+
+ 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 doesn't support that.
+ if (!src_ops_allowed)
+ old_blocks_map_it->second.pop_back();
+ }
+
+ // Produce operations for the zero blocks split per output extent.
+ size_t num_ops = aops->size();
+ new_visited_blocks->AddExtents(new_zeros);
+ for (Extent extent : new_zeros) {
+ TEST_AND_RETURN_FALSE(DeltaReadFile(
+ aops,
+ "",
+ new_part,
+ vector<Extent>(), // old_extents
+ vector<Extent>{extent}, // new_extents
+ "<zeros>",
+ chunk_blocks,
+ blob_file,
+ src_ops_allowed));
+ }
+ LOG(INFO) << "Produced " << (aops->size() - num_ops) << " operations for "
+ << BlocksInExtents(new_zeros) << " zeroed blocks";
+
+ // Produce MOVE/SOURCE_COPY operations for the moved blocks.
+ num_ops = aops->size();
+ if (chunk_blocks == -1)
+ chunk_blocks = new_num_blocks;
+ uint64_t used_blocks = 0;
+ old_visited_blocks->AddExtents(old_identical_blocks);
+ new_visited_blocks->AddExtents(new_identical_blocks);
+ for (Extent extent : new_identical_blocks) {
+ // We split the operation at the extent boundary or when bigger than
+ // chunk_blocks.
+ for (uint64_t op_block_offset = 0; op_block_offset < extent.num_blocks();
+ op_block_offset += chunk_blocks) {
+ aops->emplace_back();
+ AnnotatedOperation* aop = &aops->back();
+ aop->name = "<identical-blocks>";
+ aop->op.set_type(src_ops_allowed ? InstallOperation::SOURCE_COPY
+ : InstallOperation::MOVE);
+
+ uint64_t chunk_num_blocks =
+ std::min(extent.num_blocks() - op_block_offset,
+ static_cast<uint64_t>(chunk_blocks));
+
+ // The current operation represents the move/copy operation for the
+ // sublist starting at |used_blocks| of length |chunk_num_blocks| where
+ // the src and dst are from |old_identical_blocks| and
+ // |new_identical_blocks| respectively.
+ StoreExtents(
+ ExtentsSublist(old_identical_blocks, used_blocks, chunk_num_blocks),
+ aop->op.mutable_src_extents());
+
+ Extent* op_dst_extent = aop->op.add_dst_extents();
+ op_dst_extent->set_start_block(extent.start_block() + op_block_offset);
+ op_dst_extent->set_num_blocks(chunk_num_blocks);
+ CHECK(
+ vector<Extent>{*op_dst_extent} == // NOLINT(whitespace/braces)
+ ExtentsSublist(new_identical_blocks, used_blocks, chunk_num_blocks));
+
+ used_blocks += chunk_num_blocks;
+ }
+ }
+ LOG(INFO) << "Produced " << (aops->size() - num_ops) << " operations for "
+ << used_blocks << " identical blocks moved";
+
+ return true;
+}
+
+bool DeltaReadFile(
+ vector<AnnotatedOperation>* aops,
+ const string& old_part,
+ const string& new_part,
+ const vector<Extent>& old_extents,
+ const vector<Extent>& new_extents,
+ const string& name,
+ ssize_t chunk_blocks,
+ BlobFileWriter* blob_file,
+ bool src_ops_allowed) {
+ brillo::Blob data;
+ InstallOperation operation;
+
+ uint64_t total_blocks = BlocksInExtents(new_extents);
+ if (chunk_blocks == -1)
+ chunk_blocks = total_blocks;
+
+ // If bsdiff breaks again, blacklist the problem file by using:
+ // bsdiff_allowed = (name != "/foo/bar")
+ //
+ // TODO(dgarrett): chromium-os:15274 connect this test to the command line.
+ bool bsdiff_allowed = true;
+ if (static_cast<uint64_t>(chunk_blocks) * kBlockSize >
+ kMaxBsdiffDestinationSize) {
+ bsdiff_allowed = false;
+ }
+
+ if (!bsdiff_allowed) {
+ LOG(INFO) << "bsdiff blacklisting: " << name;
+ }
+
+ for (uint64_t block_offset = 0; block_offset < total_blocks;
+ block_offset += chunk_blocks) {
+ // Split the old/new file in the same chunks. Note that this could drop
+ // some information from the old file used for the new chunk. If the old
+ // file is smaller (or even empty when there's no old file) the chunk will
+ // also be empty.
+ vector<Extent> old_extents_chunk = ExtentsSublist(
+ old_extents, block_offset, chunk_blocks);
+ vector<Extent> new_extents_chunk = ExtentsSublist(
+ new_extents, block_offset, chunk_blocks);
+ NormalizeExtents(&old_extents_chunk);
+ NormalizeExtents(&new_extents_chunk);
+
+ TEST_AND_RETURN_FALSE(ReadExtentsToDiff(old_part,
+ new_part,
+ old_extents_chunk,
+ new_extents_chunk,
+ bsdiff_allowed,
+ &data,
+ &operation,
+ src_ops_allowed));
+
+ // 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;
+ }
+ }
+
+ // Now, insert into the list of operations.
+ AnnotatedOperation aop;
+ aop.name = name;
+ if (static_cast<uint64_t>(chunk_blocks) < total_blocks) {
+ aop.name = base::StringPrintf("%s:%" PRIu64,
+ name.c_str(), block_offset / chunk_blocks);
+ }
+ aop.op = operation;
+
+ // Write the data
+ if (operation.type() != InstallOperation::MOVE &&
+ operation.type() != InstallOperation::SOURCE_COPY) {
+ TEST_AND_RETURN_FALSE(aop.SetOperationBlob(&data, blob_file));
+ } else {
+ TEST_AND_RETURN_FALSE(blob_file->StoreBlob(data) != -1);
+ }
+ aops->emplace_back(aop);
+ }
+ return true;
+}
+
+bool ReadExtentsToDiff(const string& old_part,
+ const string& new_part,
+ const vector<Extent>& old_extents,
+ const vector<Extent>& new_extents,
+ bool bsdiff_allowed,
+ brillo::Blob* out_data,
+ InstallOperation* out_op,
+ bool src_ops_allowed) {
+ InstallOperation operation;
+ // Data blob that will be written to delta file.
+ const brillo::Blob* data_blob = nullptr;
+
+ // We read blocks from old_extents and write blocks to new_extents.
+ uint64_t blocks_to_read = BlocksInExtents(old_extents);
+ uint64_t blocks_to_write = BlocksInExtents(new_extents);
+
+ // Make copies of the extents so we can modify them.
+ vector<Extent> src_extents = old_extents;
+ vector<Extent> dst_extents = new_extents;
+
+ // Read in bytes from new data.
+ brillo::Blob new_data;
+ TEST_AND_RETURN_FALSE(utils::ReadExtents(new_part,
+ new_extents,
+ &new_data,
+ kBlockSize * blocks_to_write,
+ kBlockSize));
+ TEST_AND_RETURN_FALSE(!new_data.empty());
+
+
+ // Using a REPLACE is always an option.
+ operation.set_type(InstallOperation::REPLACE);
+ data_blob = &new_data;
+
+ // Try compressing it with bzip2.
+ brillo::Blob new_data_bz;
+ TEST_AND_RETURN_FALSE(BzipCompress(new_data, &new_data_bz));
+ CHECK(!new_data_bz.empty());
+ if (new_data_bz.size() < data_blob->size()) {
+ // A REPLACE_BZ is better.
+ operation.set_type(InstallOperation::REPLACE_BZ);
+ data_blob = &new_data_bz;
+ }
+
+ brillo::Blob old_data;
+ brillo::Blob empty_blob;
+ brillo::Blob bsdiff_delta;
+ if (blocks_to_read > 0) {
+ // Read old data.
+ TEST_AND_RETURN_FALSE(
+ utils::ReadExtents(old_part, src_extents, &old_data,
+ kBlockSize * blocks_to_read, kBlockSize));
+ if (old_data == new_data) {
+ // No change in data.
+ if (src_ops_allowed) {
+ operation.set_type(InstallOperation::SOURCE_COPY);
+ } else {
+ operation.set_type(InstallOperation::MOVE);
+ }
+ data_blob = &empty_blob;
+ } else if (bsdiff_allowed) {
+ // If the source file is considered bsdiff safe (no bsdiff bugs
+ // triggered), see if BSDIFF encoding is smaller.
+ base::FilePath old_chunk;
+ TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&old_chunk));
+ ScopedPathUnlinker old_unlinker(old_chunk.value());
+ TEST_AND_RETURN_FALSE(
+ utils::WriteFile(old_chunk.value().c_str(),
+ old_data.data(), old_data.size()));
+ base::FilePath new_chunk;
+ TEST_AND_RETURN_FALSE(base::CreateTemporaryFile(&new_chunk));
+ ScopedPathUnlinker new_unlinker(new_chunk.value());
+ TEST_AND_RETURN_FALSE(
+ utils::WriteFile(new_chunk.value().c_str(),
+ new_data.data(), new_data.size()));
+
+ TEST_AND_RETURN_FALSE(
+ BsdiffFiles(old_chunk.value(), new_chunk.value(), &bsdiff_delta));
+ CHECK_GT(bsdiff_delta.size(), static_cast<brillo::Blob::size_type>(0));
+ if (bsdiff_delta.size() < data_blob->size()) {
+ if (src_ops_allowed) {
+ operation.set_type(InstallOperation::SOURCE_BSDIFF);
+ } else {
+ operation.set_type(InstallOperation::BSDIFF);
+ }
+ data_blob = &bsdiff_delta;
+ }
+ }
+ }
+
+ size_t removed_bytes = 0;
+ // Remove identical src/dst block ranges in MOVE operations.
+ if (operation.type() == InstallOperation::MOVE) {
+ removed_bytes = RemoveIdenticalBlockRanges(
+ &src_extents, &dst_extents, new_data.size());
+ }
+ // Set legacy src_length and dst_length fields.
+ operation.set_src_length(old_data.size() - removed_bytes);
+ operation.set_dst_length(new_data.size() - removed_bytes);
+
+ // Embed extents in the operation.
+ StoreExtents(src_extents, operation.mutable_src_extents());
+ StoreExtents(dst_extents, operation.mutable_dst_extents());
+
+ // Replace operations should not have source extents.
+ if (operation.type() == InstallOperation::REPLACE ||
+ operation.type() == InstallOperation::REPLACE_BZ) {
+ operation.clear_src_extents();
+ operation.clear_src_length();
+ }
+
+ *out_data = std::move(*data_blob);
+ *out_op = operation;
+
+ return true;
+}
+
+// Runs the bsdiff tool on two files and returns the resulting delta in
+// 'out'. Returns true on success.
+bool BsdiffFiles(const string& old_file,
+ const string& new_file,
+ brillo::Blob* out) {
+ const string kPatchFile = "delta.patchXXXXXX";
+ string patch_file_path;
+
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile(kPatchFile, &patch_file_path, nullptr));
+
+ vector<string> cmd;
+ cmd.push_back(kBsdiffPath);
+ cmd.push_back(old_file);
+ cmd.push_back(new_file);
+ cmd.push_back(patch_file_path);
+
+ int rc = 1;
+ brillo::Blob patch_file;
+ TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &rc, nullptr));
+ TEST_AND_RETURN_FALSE(rc == 0);
+ TEST_AND_RETURN_FALSE(utils::ReadFile(patch_file_path, out));
+ unlink(patch_file_path.c_str());
+ return true;
+}
+
+// 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;
+ TEST_AND_RETURN_FALSE(hasher.UpdateFile(part.path, part.size) ==
+ static_cast<off_t>(part.size));
+ TEST_AND_RETURN_FALSE(hasher.Finalize());
+ const brillo::Blob& hash = hasher.raw_hash();
+ info->set_hash(hash.data(), hash.size());
+ LOG(INFO) << part.path << ": size=" << part.size << " hash=" << hasher.hash();
+ return true;
+}
+
+bool CompareAopsByDestination(AnnotatedOperation first_aop,
+ AnnotatedOperation second_aop) {
+ // We want empty operations to be at the end of the payload.
+ if (!first_aop.op.dst_extents().size() || !second_aop.op.dst_extents().size())
+ return ((!first_aop.op.dst_extents().size()) <
+ (!second_aop.op.dst_extents().size()));
+ uint32_t first_dst_start = first_aop.op.dst_extents(0).start_block();
+ uint32_t second_dst_start = second_aop.op.dst_extents(0).start_block();
+ return first_dst_start < second_dst_start;
+}
+
+} // namespace diff_utils
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/delta_diff_utils.h b/payload_generator/delta_diff_utils.h
new file mode 100644
index 0000000..5ba9bc7
--- /dev/null
+++ b/payload_generator/delta_diff_utils.h
@@ -0,0 +1,137 @@
+//
+// 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_DELTA_DIFF_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+namespace diff_utils {
+
+// Create operations in |aops| to produce all the blocks in the |new_part|
+// partition using the filesystem opened in that PartitionConfig.
+// It uses the files reported by the filesystem in |old_part| and the data
+// blocks in that partition (if available) to determine the best way to compress
+// the new files (REPLACE, REPLACE_BZ, COPY, BSDIFF) and writes any necessary
+// data to the end of |data_fd| updating |data_file_size| accordingly.
+// |hard_chunk_blocks| and |soft_chunk_blocks| are the hard and soft chunk
+// limits in number of blocks respectively. The soft chunk limit is used to
+// split MOVE and SOURCE_COPY operations and REPLACE_BZ of zeroed blocks, while
+// the hard limit is used to split a file when generating other operations. A
+// value of -1 in |hard_chunk_blocks| means whole files.
+bool DeltaReadPartition(std::vector<AnnotatedOperation>* aops,
+ const PartitionConfig& old_part,
+ const PartitionConfig& new_part,
+ ssize_t hard_chunk_blocks,
+ size_t soft_chunk_blocks,
+ BlobFileWriter* blob_file,
+ bool src_ops_allowed);
+
+// Create operations in |aops| for identical blocks that moved around in the old
+// and new partition and also handle zeroed blocks. The old and new partition
+// are stored in the |old_part| and |new_part| files and have |old_num_blocks|
+// and |new_num_blocks| respectively. The maximum operation size is
+// |chunk_blocks| blocks, or unlimited if |chunk_blocks| is -1. The blobs of the
+// produced operations are stored in the |data_fd| file whose size is updated
+// in the value pointed by |data_file_size|.
+// The collections |old_visited_blocks| and |new_visited_blocks| state what
+// blocks already have operations reading or writing them and only operations
+// for unvisited blocks are produced by this function updating both collections
+// with the used blocks.
+bool DeltaMovedAndZeroBlocks(std::vector<AnnotatedOperation>* aops,
+ const std::string& old_part,
+ const std::string& new_part,
+ size_t old_num_blocks,
+ size_t new_num_blocks,
+ ssize_t chunk_blocks,
+ bool src_ops_allowed,
+ BlobFileWriter* blob_file,
+ ExtentRanges* old_visited_blocks,
+ ExtentRanges* new_visited_blocks);
+
+// For a given file |name| append operations to |aops| to produce it in the
+// |new_part|. The file will be split in chunks of |chunk_blocks| blocks each
+// or treated as a single chunk if |chunk_blocks| is -1. The file data is
+// stored in |new_part| in the blocks described by |new_extents| and, if it
+// exists, the old version exists in |old_part| in the blocks described by
+// |old_extents|. The operations added to |aops| reference the data blob
+// in the file |data_fd|, which has length *data_file_size. *data_file_size is
+// updated appropriately. Returns true on success.
+bool DeltaReadFile(std::vector<AnnotatedOperation>* aops,
+ const std::string& old_part,
+ const std::string& new_part,
+ const std::vector<Extent>& old_extents,
+ const std::vector<Extent>& new_extents,
+ const std::string& name,
+ ssize_t chunk_blocks,
+ BlobFileWriter* blob_file,
+ bool src_ops_allowed);
+
+// Reads the blocks |old_extents| from |old_part| (if it exists) and the
+// |new_extents| from |new_part| and determines the smallest way to encode
+// this |new_extents| for the diff. It stores necessary data in |out_data| and
+// fills in |out_op|. If there's no change in old and new files, it creates a
+// MOVE operation. If there is a change, the smallest of REPLACE, REPLACE_BZ,
+// or BSDIFF wins. |new_extents| must not be empty.
+// If |src_ops_allowed| is true, it will emit SOURCE_COPY and SOURCE_BSDIFF
+// operations instead of MOVE and BSDIFF, respectively.
+// Returns true on success.
+bool ReadExtentsToDiff(const std::string& old_part,
+ const std::string& new_part,
+ const std::vector<Extent>& old_extents,
+ const std::vector<Extent>& new_extents,
+ bool bsdiff_allowed,
+ brillo::Blob* out_data,
+ InstallOperation* out_op,
+ bool src_ops_allowed);
+
+// Runs the bsdiff tool on two files and returns the resulting delta in
+// |out|. Returns true on success.
+bool BsdiffFiles(const std::string& old_file,
+ const std::string& new_file,
+ brillo::Blob* out);
+
+// 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);
+
+// Compare two AnnotatedOperations by the start block of the first Extent in
+// their destination extents.
+bool CompareAopsByDestination(AnnotatedOperation first_aop,
+ AnnotatedOperation second_aop);
+
+} // namespace diff_utils
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
diff --git a/payload_generator/delta_diff_utils_unittest.cc b/payload_generator/delta_diff_utils_unittest.cc
new file mode 100644
index 0000000..4dc5981
--- /dev/null
+++ b/payload_generator/delta_diff_utils_unittest.cc
@@ -0,0 +1,775 @@
+//
+// 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/delta_diff_utils.h"
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/scoped_file.h>
+#include <base/format_macros.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/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/fake_filesystem.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// Writes the |data| in the blocks specified by |extents| on the partition
+// |part_path|. The |data| size could be smaller than the size of the blocks
+// passed.
+bool WriteExtents(const string& part_path,
+ const vector<Extent>& extents,
+ off_t block_size,
+ const brillo::Blob& data) {
+ uint64_t offset = 0;
+ base::ScopedFILE fp(fopen(part_path.c_str(), "r+"));
+ TEST_AND_RETURN_FALSE(fp.get());
+
+ for (const Extent& extent : extents) {
+ if (offset >= data.size())
+ break;
+ TEST_AND_RETURN_FALSE(
+ fseek(fp.get(), extent.start_block() * block_size, SEEK_SET) == 0);
+ uint64_t to_write = std::min(extent.num_blocks() * block_size,
+ data.size() - offset);
+ TEST_AND_RETURN_FALSE(
+ fwrite(data.data() + offset, 1, to_write, fp.get()) == to_write);
+ offset += extent.num_blocks() * block_size;
+ }
+ return true;
+}
+
+// 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,
+ 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->fs_interface.reset(new FakeFilesystem(block_size, size / block_size));
+ part->size = size;
+}
+
+// Writes to the |partition| path blocks such they are all different and they
+// include the tag passed in |tag|, making them also different to any other
+// block on a partition initialized with this function with a different tag.
+// The |block_size| should be a divisor of the partition size.
+// Returns whether the function succeeded writing the partition.
+bool InitializePartitionWithUniqueBlocks(const PartitionConfig& part,
+ uint64_t block_size,
+ uint64_t tag) {
+ TEST_AND_RETURN_FALSE(part.size % block_size == 0);
+ size_t num_blocks = part.size / block_size;
+ brillo::Blob file_data(part.size);
+ for (size_t i = 0; i < num_blocks; ++i) {
+ string prefix = base::StringPrintf(
+ "block tag 0x%.16" PRIx64 ", block number %16" PRIuS " ", tag, i);
+ brillo::Blob block_data(prefix.begin(), prefix.end());
+ TEST_AND_RETURN_FALSE(prefix.size() <= block_size);
+ block_data.resize(block_size, 'X');
+ std::copy(block_data.begin(), block_data.end(),
+ file_data.begin() + i * block_size);
+ }
+ return test_utils::WriteFileVector(part.path, file_data);
+}
+
+} // namespace
+
+class DeltaDiffUtilsTest : public ::testing::Test {
+ protected:
+ const uint64_t kDefaultBlockCount = 128;
+
+ void SetUp() override {
+ CreatePartition(&old_part_, "DeltaDiffUtilsTest-old_part-XXXXXX",
+ block_size_, block_size_ * kDefaultBlockCount);
+ CreatePartition(&new_part_, "DeltaDiffUtilsTest-old_part-XXXXXX",
+ 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 avoid repeating all the arguments that never change.
+ bool RunDeltaMovedAndZeroBlocks(ssize_t chunk_blocks,
+ bool src_ops_allowed) {
+ BlobFileWriter blob_file(blob_fd_, &blob_size_);
+ return diff_utils::DeltaMovedAndZeroBlocks(
+ &aops_,
+ old_part_.path,
+ new_part_.path,
+ old_part_.size / block_size_,
+ new_part_.size / block_size_,
+ chunk_blocks,
+ src_ops_allowed,
+ &blob_file,
+ &old_visited_blocks_,
+ &new_visited_blocks_);
+ }
+
+ // Old and new temporary partitions used in the tests. These are initialized
+ // with
+ PartitionConfig old_part_{"part"};
+ PartitionConfig new_part_{"part"};
+
+ // The file holding the output blob from the various diff utils functions.
+ string blob_path_;
+ int blob_fd_{-1};
+ off_t blob_size_{0};
+
+ size_t block_size_{kBlockSize};
+
+ // Default input/output arguments used when calling DeltaMovedAndZeroBlocks().
+ vector<AnnotatedOperation> aops_;
+ ExtentRanges old_visited_blocks_;
+ ExtentRanges new_visited_blocks_;
+};
+
+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,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+ 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(BlocksInExtents(op.src_extents()),
+ BlocksInExtents(op.dst_extents()));
+ EXPECT_EQ(1, 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 = BlocksInExtents(old_extents);
+ EXPECT_EQ(num_blocks, 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,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+
+ 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 = BlocksInExtents(old_extents);
+
+ EXPECT_EQ(num_blocks * kBlockSize, op.src_length());
+ EXPECT_EQ(num_blocks * kBlockSize, op.dst_length());
+
+ EXPECT_EQ(old_extents.size(), 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(), 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,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+
+ 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(BlocksInExtents(op.src_extents()),
+ BlocksInExtents(op.dst_extents()));
+ EXPECT_EQ(1, BlocksInExtents(op.dst_extents()));
+}
+
+TEST_F(DeltaDiffUtilsTest, BsdiffNotAllowedTest) {
+ // Same setup as the previous test, but this time BSDIFF operations are not
+ // allowed.
+ 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,
+ false, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+
+ EXPECT_FALSE(data.empty());
+
+ // The point of this test is that we don't use BSDIFF the way the above
+ // did. The rest of the details are to be caught in other tests.
+ EXPECT_TRUE(op.has_type());
+ EXPECT_NE(InstallOperation::BSDIFF, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, BsdiffNotAllowedMoveTest) {
+ 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));
+ 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,
+ false, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+
+ EXPECT_TRUE(data.empty());
+
+ // The point of this test is that we can still use a MOVE for a file
+ // that is blacklisted.
+ EXPECT_TRUE(op.has_type());
+ EXPECT_EQ(InstallOperation::MOVE, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, ReplaceSmallTest) {
+ // 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) };
+
+ // Make a blob that's just 1's that will compress well.
+ brillo::Blob ones(kBlockSize, 1);
+
+ // Make a blob with random data that won't compress well.
+ brillo::Blob random_data;
+ std::mt19937 gen(12345);
+ std::uniform_int_distribution<uint8_t> dis(0, 255);
+ for (uint32_t i = 0; i < kBlockSize; i++) {
+ random_data.push_back(dis(gen));
+ }
+
+ for (int i = 0; i < 2; i++) {
+ brillo::Blob data_to_test = i == 0 ? random_data : ones;
+ // The old_extents will be initialized with 0.
+ EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize,
+ data_to_test));
+
+ brillo::Blob data;
+ InstallOperation op;
+ EXPECT_TRUE(diff_utils::ReadExtentsToDiff(
+ old_part_.path,
+ new_part_.path,
+ old_extents,
+ new_extents,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ false)); // src_ops_allowed
+ EXPECT_FALSE(data.empty());
+
+ EXPECT_TRUE(op.has_type());
+ const InstallOperation_Type expected_type =
+ (i == 0 ? InstallOperation::REPLACE : InstallOperation::REPLACE_BZ);
+ EXPECT_EQ(expected_type, op.type());
+ EXPECT_FALSE(op.has_data_offset());
+ EXPECT_FALSE(op.has_data_length());
+ EXPECT_EQ(0, op.src_extents_size());
+ EXPECT_FALSE(op.has_src_length());
+ EXPECT_EQ(1, op.dst_extents_size());
+ EXPECT_EQ(data_to_test.size(), op.dst_length());
+ EXPECT_EQ(1, BlocksInExtents(op.dst_extents()));
+ }
+}
+
+TEST_F(DeltaDiffUtilsTest, SourceCopyTest) {
+ // Makes sure SOURCE_COPY operations are emitted whenever src_ops_allowed
+ // is true. It is the same setup as MoveSmallTest, which checks that
+ // the operation is well-formed.
+ 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(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,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ true)); // src_ops_allowed
+ EXPECT_TRUE(data.empty());
+
+ EXPECT_TRUE(op.has_type());
+ EXPECT_EQ(InstallOperation::SOURCE_COPY, op.type());
+}
+
+TEST_F(DeltaDiffUtilsTest, SourceBsdiffTest) {
+ // Makes sure SOURCE_BSDIFF operations are emitted whenever src_ops_allowed
+ // is true. It is the same setup as BsdiffSmallTest, which checks
+ // that the operation is well-formed.
+ 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,
+ true, // bsdiff_allowed
+ &data,
+ &op,
+ true)); // src_ops_allowed
+
+ EXPECT_FALSE(data.empty());
+ EXPECT_TRUE(op.has_type());
+ EXPECT_EQ(InstallOperation::SOURCE_BSDIFF, 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) {
+ InitializePartitionWithUniqueBlocks(old_part_, block_size_, 5);
+ InitializePartitionWithUniqueBlocks(new_part_, block_size_, 42);
+
+ EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1, // chunk_blocks
+ false)); // src_ops_allowed
+
+ EXPECT_EQ(0, old_visited_blocks_.blocks());
+ EXPECT_EQ(0, 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 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
+ false)); // src_ops_allowed
+
+ 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) {
+ // We use a smaller partition for this test.
+ old_part_.size = kBlockSize * 50;
+ new_part_.size = kBlockSize * 50;
+
+ 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, 5),
+ ExtentForRange(25, 7)};
+ old_visited_blocks_.AddExtents(already_visited);
+ new_visited_blocks_.AddExtents(already_visited);
+
+ // Override some of the old blocks with different data.
+ vector<Extent> different_blocks = {ExtentForRange(40, 5)};
+ EXPECT_TRUE(WriteExtents(old_part_.path, different_blocks, kBlockSize,
+ brillo::Blob(5 * kBlockSize, 'a')));
+
+ EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(10, // chunk_blocks
+ true)); // src_ops_allowed
+
+ ExtentRanges expected_ranges;
+ expected_ranges.AddExtent(ExtentForRange(0, 50));
+ expected_ranges.SubtractExtents(different_blocks);
+
+ EXPECT_EQ(expected_ranges.extent_set(), old_visited_blocks_.extent_set());
+ EXPECT_EQ(expected_ranges.extent_set(), new_visited_blocks_.extent_set());
+ EXPECT_EQ(0, blob_size_);
+
+ // We expect all the blocks that we didn't override with |different_blocks|
+ // and that we didn't mark as visited in |already_visited| to match and have a
+ // SOURCE_COPY operation chunked at 10 blocks.
+ vector<Extent> expected_op_extents = {
+ ExtentForRange(0, 5),
+ ExtentForRange(10, 10),
+ ExtentForRange(20, 5),
+ ExtentForRange(32, 8),
+ ExtentForRange(45, 5),
+ };
+
+ EXPECT_EQ(expected_op_extents.size(), aops_.size());
+ for (size_t i = 0; i < aops_.size() && i < expected_op_extents.size(); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Failed on operation number %" PRIuS, i));
+ const AnnotatedOperation& aop = aops_[i];
+ EXPECT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
+ EXPECT_EQ(1, aop.op.src_extents_size());
+ EXPECT_EQ(expected_op_extents[i], aop.op.src_extents(0));
+ EXPECT_EQ(1, aop.op.dst_extents_size());
+ EXPECT_EQ(expected_op_extents[i], aop.op.dst_extents(0));
+ }
+}
+
+TEST_F(DeltaDiffUtilsTest, IdenticalBlocksAreCopiedInOder) {
+ // We use a smaller partition for this test.
+ old_part_.size = block_size_ * 50;
+ new_part_.size = block_size_ * 50;
+
+ // Create two identical partitions with 5 copies of the same unique "file".
+ brillo::Blob file_data(block_size_ * 10, 'a');
+ for (size_t offset = 0; offset < file_data.size(); offset += block_size_)
+ file_data[offset] = 'a' + offset / block_size_;
+
+ brillo::Blob partition_data(old_part_.size);
+ for (size_t offset = 0; offset < partition_data.size();
+ offset += file_data.size()) {
+ std::copy(file_data.begin(), file_data.end(),
+ partition_data.begin() + offset);
+ }
+ EXPECT_TRUE(test_utils::WriteFileVector(old_part_.path, partition_data));
+ EXPECT_TRUE(test_utils::WriteFileVector(new_part_.path, partition_data));
+
+ EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1, // chunk_blocks
+ true)); // src_ops_allowed
+
+ // There should be only one SOURCE_COPY, for the whole partition and the
+ // source extents should cover only the first copy of the source file since
+ // we prefer to re-read files (maybe cached) instead of continue reading the
+ // rest of the partition.
+ EXPECT_EQ(1, aops_.size());
+ const AnnotatedOperation& aop = aops_[0];
+ EXPECT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
+ EXPECT_EQ(5, aop.op.src_extents_size());
+ for (int i = 0; i < aop.op.src_extents_size(); ++i) {
+ EXPECT_EQ(ExtentForRange(0, 10), aop.op.src_extents(i));
+ }
+
+ EXPECT_EQ(1, aop.op.dst_extents_size());
+ EXPECT_EQ(ExtentForRange(0, 50), aop.op.dst_extents(0));
+
+ EXPECT_EQ(0, blob_size_);
+}
+
+// Test that all blocks with zeros are handled separately using REPLACE_BZ
+// operations unless they are not moved.
+TEST_F(DeltaDiffUtilsTest, ZeroBlocksUseReplaceBz) {
+ InitializePartitionWithUniqueBlocks(old_part_, block_size_, 42);
+ InitializePartitionWithUniqueBlocks(new_part_, block_size_, 5);
+
+ // We set four ranges of blocks with zeros: a single block, a range that fits
+ // in the chunk size, a range that doesn't and finally a range of zeros that
+ // was also zeros in the old image.
+ vector<Extent> new_zeros = {
+ ExtentForRange(10, 1),
+ ExtentForRange(20, 4),
+ // The last range is split since the old image has zeros in part of it.
+ ExtentForRange(30, 20),
+ };
+ brillo::Blob zeros_data(BlocksInExtents(new_zeros) * block_size_, '\0');
+ EXPECT_TRUE(WriteExtents(new_part_.path, new_zeros, block_size_, zeros_data));
+
+ vector<Extent> old_zeros = vector<Extent>{ExtentForRange(43, 7)};
+ EXPECT_TRUE(WriteExtents(old_part_.path, old_zeros, block_size_, zeros_data));
+
+ EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(5, // chunk_blocks
+ false)); // src_ops_allowed
+
+ // Zeroed blocks from old_visited_blocks_ were copied over, so me actually
+ // use them regardless of the trivial MOVE operation not being emitted.
+ 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).
+ EXPECT_EQ(new_zeros,
+ new_visited_blocks_.GetExtentsForBlockCount(
+ new_visited_blocks_.blocks()));
+
+ vector<Extent> expected_op_extents = {
+ ExtentForRange(10, 1),
+ ExtentForRange(20, 4),
+ // This range should be split.
+ ExtentForRange(30, 5),
+ ExtentForRange(35, 5),
+ ExtentForRange(40, 3),
+ };
+
+ EXPECT_EQ(expected_op_extents.size(), aops_.size());
+ for (size_t i = 0; i < aops_.size() && i < expected_op_extents.size(); ++i) {
+ SCOPED_TRACE(base::StringPrintf("Failed on operation number %" PRIuS, i));
+ const AnnotatedOperation& aop = aops_[i];
+ EXPECT_EQ(InstallOperation::REPLACE_BZ, aop.op.type());
+ EXPECT_EQ(0, aop.op.src_extents_size());
+ EXPECT_EQ(1, aop.op.dst_extents_size());
+ EXPECT_EQ(expected_op_extents[i], aop.op.dst_extents(0));
+ }
+ EXPECT_NE(0, blob_size_);
+}
+
+TEST_F(DeltaDiffUtilsTest, ShuffledBlocksAreTracked) {
+ vector<uint64_t> permutation = {0, 1, 5, 6, 7, 2, 3, 4, 9, 10, 11, 12, 8};
+ vector<Extent> perm_extents;
+ for (uint64_t x : permutation)
+ AppendBlockToExtents(&perm_extents, x);
+
+ // We use a smaller partition for this test.
+ old_part_.size = block_size_ * permutation.size();
+ new_part_.size = block_size_ * permutation.size();
+ InitializePartitionWithUniqueBlocks(new_part_, block_size_, 123);
+
+ // We initialize the old_part_ with the blocks from new_part but in the
+ // |permutation| order. Block i in the old_part_ will contain the same data
+ // as block permutation[i] in the new_part_.
+ brillo::Blob new_contents;
+ EXPECT_TRUE(utils::ReadFile(new_part_.path, &new_contents));
+ EXPECT_TRUE(WriteExtents(old_part_.path, perm_extents, block_size_,
+ new_contents));
+
+ EXPECT_TRUE(RunDeltaMovedAndZeroBlocks(-1, // chunk_blocks
+ true)); // src_ops_allowed
+
+ EXPECT_EQ(permutation.size(), old_visited_blocks_.blocks());
+ EXPECT_EQ(permutation.size(), new_visited_blocks_.blocks());
+
+ // There should be only one SOURCE_COPY, with a complicate list of extents.
+ EXPECT_EQ(1, aops_.size());
+ const AnnotatedOperation& aop = aops_[0];
+ EXPECT_EQ(InstallOperation::SOURCE_COPY, aop.op.type());
+ vector<Extent> aop_src_extents;
+ ExtentsToVector(aop.op.src_extents(), &aop_src_extents);
+ EXPECT_EQ(perm_extents, aop_src_extents);
+
+ EXPECT_EQ(1, aop.op.dst_extents_size());
+ EXPECT_EQ(ExtentForRange(0, permutation.size()), aop.op.dst_extents(0));
+
+ EXPECT_EQ(0, blob_size_);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/ext2_filesystem.cc b/payload_generator/ext2_filesystem.cc
new file mode 100644
index 0000000..b452b41
--- /dev/null
+++ b/payload_generator/ext2_filesystem.cc
@@ -0,0 +1,355 @@
+//
+// 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/ext2_filesystem.h"
+
+#include <et/com_err.h>
+#include <ext2fs/ext2_io.h>
+#include <ext2fs/ext2fs.h>
+
+#include <map>
+#include <set>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/extent_utils.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+// Processes all blocks belonging to an inode and adds them to the extent list.
+// This function should match the prototype expected by ext2fs_block_iterate2().
+int ProcessInodeAllBlocks(ext2_filsys fs,
+ blk_t* blocknr,
+ e2_blkcnt_t blockcnt,
+ blk_t ref_blk,
+ int ref_offset,
+ void* priv) {
+ vector<Extent>* extents = static_cast<vector<Extent>*>(priv);
+ AppendBlockToExtents(extents, *blocknr);
+ return 0;
+}
+
+// Processes only indirect, double indirect or triple indirect metadata
+// blocks belonging to an inode. This function should match the prototype of
+// ext2fs_block_iterate2().
+int AddMetadataBlocks(ext2_filsys fs,
+ blk_t* blocknr,
+ e2_blkcnt_t blockcnt,
+ blk_t ref_blk,
+ int ref_offset,
+ void* priv) {
+ set<uint64_t>* blocks = static_cast<set<uint64_t>*>(priv);
+ // If |blockcnt| is non-negative, |blocknr| points to the physical block
+ // number.
+ // If |blockcnt| is negative, it is one of the values: BLOCK_COUNT_IND,
+ // BLOCK_COUNT_DIND, BLOCK_COUNT_TIND or BLOCK_COUNT_TRANSLATOR and
+ // |blocknr| points to a block in the first three cases. The last case is
+ // only used by GNU Hurd, so we shouldn't see those cases here.
+ if (blockcnt == BLOCK_COUNT_IND || blockcnt == BLOCK_COUNT_DIND ||
+ blockcnt == BLOCK_COUNT_TIND) {
+ blocks->insert(*blocknr);
+ }
+ return 0;
+}
+
+struct UpdateFileAndAppendState {
+ std::map<ext2_ino_t, FilesystemInterface::File>* inodes = nullptr;
+ set<ext2_ino_t>* used_inodes = nullptr;
+ vector<FilesystemInterface::File>* files = nullptr;
+ ext2_filsys filsys;
+};
+
+int UpdateFileAndAppend(ext2_ino_t dir,
+ int entry,
+ struct ext2_dir_entry *dirent,
+ int offset,
+ int blocksize,
+ char *buf,
+ void *priv_data) {
+ UpdateFileAndAppendState* state =
+ static_cast<UpdateFileAndAppendState*>(priv_data);
+ uint32_t file_type = dirent->name_len >> 8;
+ // Directories can't have hard links, and they are added from the outer loop.
+ if (file_type == EXT2_FT_DIR)
+ return 0;
+
+ auto ino_file = state->inodes->find(dirent->inode);
+ if (ino_file == state->inodes->end())
+ return 0;
+ auto dir_file = state->inodes->find(dir);
+ if (dir_file == state->inodes->end())
+ return 0;
+ string basename(dirent->name, dirent->name_len & 0xff);
+ ino_file->second.name = dir_file->second.name;
+ if (dir_file->second.name != "/")
+ ino_file->second.name += "/";
+ ino_file->second.name += basename;
+
+ // Append this file to the output. If the file has a hard link, it will be
+ // added twice to the output, but with different names, which is ok. That will
+ // help identify all the versions of the same file.
+ state->files->push_back(ino_file->second);
+ state->used_inodes->insert(dirent->inode);
+ return 0;
+}
+
+} // namespace
+
+unique_ptr<Ext2Filesystem> Ext2Filesystem::CreateFromFile(
+ const string& filename) {
+ if (filename.empty())
+ return nullptr;
+ unique_ptr<Ext2Filesystem> result(new Ext2Filesystem());
+ result->filename_ = filename;
+
+ errcode_t err = ext2fs_open(filename.c_str(),
+ 0, // flags (read only)
+ 0, // superblock block number
+ 0, // block_size (autodetect)
+ unix_io_manager,
+ &result->filsys_);
+ if (err) {
+ LOG(ERROR) << "Opening ext2fs " << filename;
+ return nullptr;
+ }
+ return result;
+}
+
+Ext2Filesystem::~Ext2Filesystem() {
+ ext2fs_free(filsys_);
+}
+
+size_t Ext2Filesystem::GetBlockSize() const {
+ return filsys_->blocksize;
+}
+
+size_t Ext2Filesystem::GetBlockCount() const {
+ return ext2fs_blocks_count(filsys_->super);
+}
+
+bool Ext2Filesystem::GetFiles(vector<File>* files) const {
+ TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_read_inode_bitmap(filsys_));
+
+ ext2_inode_scan iscan;
+ TEST_AND_RETURN_FALSE_ERRCODE(
+ ext2fs_open_inode_scan(filsys_, 0 /* buffer_blocks */, &iscan));
+
+ std::map<ext2_ino_t, File> inodes;
+
+ // List of directories. We need to first parse all the files in a directory
+ // to later fix the absolute paths.
+ vector<ext2_ino_t> directories;
+
+ set<uint64_t> inode_blocks;
+
+ // Iterator
+ ext2_ino_t it_ino;
+ ext2_inode it_inode;
+
+ bool ok = true;
+ while (true) {
+ errcode_t error = ext2fs_get_next_inode(iscan, &it_ino, &it_inode);
+ if (error) {
+ LOG(ERROR) << "Failed to retrieve next inode (" << error << ")";
+ ok = false;
+ break;
+ }
+ if (it_ino == 0)
+ break;
+
+ // Skip inodes that are not in use.
+ if (!ext2fs_test_inode_bitmap(filsys_->inode_map, it_ino))
+ continue;
+
+ File& file = inodes[it_ino];
+ if (it_ino == EXT2_RESIZE_INO) {
+ file.name = "<group-descriptors>";
+ } else {
+ file.name = base::StringPrintf("<inode-%u>", it_ino);
+ }
+
+ memset(&file.file_stat, 0, sizeof(file.file_stat));
+ file.file_stat.st_ino = it_ino;
+ file.file_stat.st_mode = it_inode.i_mode;
+ file.file_stat.st_nlink = it_inode.i_links_count;
+ file.file_stat.st_uid = it_inode.i_uid;
+ file.file_stat.st_gid = it_inode.i_gid;
+ file.file_stat.st_size = it_inode.i_size;
+ file.file_stat.st_blksize = filsys_->blocksize;
+ file.file_stat.st_blocks = it_inode.i_blocks;
+ file.file_stat.st_atime = it_inode.i_atime;
+ file.file_stat.st_mtime = it_inode.i_mtime;
+ file.file_stat.st_ctime = it_inode.i_ctime;
+
+ bool is_dir = (ext2fs_check_directory(filsys_, it_ino) == 0);
+ if (is_dir)
+ directories.push_back(it_ino);
+
+ if (!ext2fs_inode_has_valid_blocks(&it_inode))
+ continue;
+
+ // Process the inode data and metadata blocks.
+ // For normal files, inode blocks are indirect, double indirect
+ // and triple indirect blocks (no data blocks). For directories and
+ // the journal, all blocks are considered metadata blocks.
+ int flags = it_ino < EXT2_GOOD_OLD_FIRST_INO ? 0 : BLOCK_FLAG_DATA_ONLY;
+ error = ext2fs_block_iterate2(filsys_, it_ino, flags,
+ nullptr, // block_buf
+ ProcessInodeAllBlocks,
+ &file.extents);
+
+ if (error) {
+ LOG(ERROR) << "Failed to enumerate inode " << it_ino
+ << " blocks (" << error << ")";
+ continue;
+ }
+ if (it_ino >= EXT2_GOOD_OLD_FIRST_INO) {
+ ext2fs_block_iterate2(filsys_, it_ino, 0, nullptr,
+ AddMetadataBlocks,
+ &inode_blocks);
+ }
+ }
+ ext2fs_close_inode_scan(iscan);
+ if (!ok)
+ return false;
+
+ // The set of inodes already added to the output. There can be less elements
+ // here than in files since the later can contain repeated inodes due to
+ // hardlink files.
+ set<ext2_ino_t> used_inodes;
+
+ UpdateFileAndAppendState priv_data;
+ priv_data.inodes = &inodes;
+ priv_data.used_inodes = &used_inodes;
+ priv_data.files = files;
+ priv_data.filsys = filsys_;
+
+ files->clear();
+ // Iterate over all the files of each directory to update the name and add it.
+ for (ext2_ino_t dir_ino : directories) {
+ char* dir_name = nullptr;
+ errcode_t error = ext2fs_get_pathname(filsys_, dir_ino, 0, &dir_name);
+ if (error) {
+ // Not being able to read a directory name is not a fatal error, it is
+ // just skiped.
+ LOG(WARNING) << "Reading directory name on inode " << dir_ino
+ << " (error " << error << ")";
+ inodes[dir_ino].name = base::StringPrintf("<dir-%u>", dir_ino);
+ } else {
+ inodes[dir_ino].name = dir_name;
+ files->push_back(inodes[dir_ino]);
+ used_inodes.insert(dir_ino);
+ }
+ ext2fs_free_mem(&dir_name);
+
+ error = ext2fs_dir_iterate2(
+ filsys_, dir_ino, 0, nullptr /* block_buf */,
+ UpdateFileAndAppend, &priv_data);
+ if (error) {
+ LOG(WARNING) << "Failed to enumerate files in directory "
+ << inodes[dir_ino].name << " (error " << error << ")";
+ }
+ }
+
+ // Add <inode-blocks> file with the blocks that hold inodes.
+ File inode_file;
+ inode_file.name = "<inode-blocks>";
+ for (uint64_t block : inode_blocks) {
+ AppendBlockToExtents(&inode_file.extents, block);
+ }
+ files->push_back(inode_file);
+
+ // Add <free-spacce> blocs.
+ errcode_t error = ext2fs_read_block_bitmap(filsys_);
+ if (error) {
+ LOG(ERROR) << "Reading the blocks bitmap (error " << error << ")";
+ } else {
+ File free_space;
+ free_space.name = "<free-space>";
+ blk64_t blk_start = ext2fs_get_block_bitmap_start2(filsys_->block_map);
+ blk64_t blk_end = ext2fs_get_block_bitmap_end2(filsys_->block_map);
+ for (blk64_t block = blk_start; block < blk_end; block++) {
+ if (!ext2fs_test_block_bitmap2(filsys_->block_map, block))
+ AppendBlockToExtents(&free_space.extents, block);
+ }
+ files->push_back(free_space);
+ }
+
+ // Add all the unreachable files plus the pseudo-files with an inode. Since
+ // these inodes aren't files in the filesystem, ignore the empty ones.
+ for (const auto& ino_file : inodes) {
+ if (used_inodes.find(ino_file.first) != used_inodes.end())
+ continue;
+ if (ino_file.second.extents.empty())
+ continue;
+
+ File file = ino_file.second;
+ ExtentRanges ranges;
+ ranges.AddExtents(file.extents);
+ file.extents = ranges.GetExtentsForBlockCount(ranges.blocks());
+
+ files->push_back(file);
+ }
+
+ return true;
+}
+
+bool Ext2Filesystem::LoadSettings(brillo::KeyValueStore* store) const {
+ // First search for the settings inode following symlinks if we find some.
+ ext2_ino_t ino_num = 0;
+ errcode_t err = ext2fs_namei_follow(
+ filsys_, EXT2_ROOT_INO /* root */, EXT2_ROOT_INO /* cwd */,
+ "/etc/update_engine.conf", &ino_num);
+ if (err != 0)
+ return false;
+
+ ext2_inode ino_data;
+ if (ext2fs_read_inode(filsys_, ino_num, &ino_data) != 0)
+ return false;
+
+ // Load the list of blocks and then the contents of the inodes.
+ vector<Extent> extents;
+ err = ext2fs_block_iterate2(filsys_, ino_num, BLOCK_FLAG_DATA_ONLY,
+ nullptr, // block_buf
+ ProcessInodeAllBlocks,
+ &extents);
+ if (err != 0)
+ return false;
+
+ brillo::Blob blob;
+ uint64_t physical_size = BlocksInExtents(extents) * filsys_->blocksize;
+ // Sparse holes in the settings file are not supported.
+ if (EXT2_I_SIZE(&ino_data) > physical_size)
+ return false;
+ if (!utils::ReadExtents(filename_, extents, &blob, physical_size,
+ filsys_->blocksize))
+ return false;
+
+ string text(blob.begin(), blob.begin() + EXT2_I_SIZE(&ino_data));
+ return store->LoadFromString(text);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/ext2_filesystem.h b/payload_generator/ext2_filesystem.h
new file mode 100644
index 0000000..248e208
--- /dev/null
+++ b/payload_generator/ext2_filesystem.h
@@ -0,0 +1,71 @@
+//
+// 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_EXT2_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXT2_FILESYSTEM_H_
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <ext2fs/ext2fs.h>
+
+namespace chromeos_update_engine {
+
+class Ext2Filesystem : public FilesystemInterface {
+ public:
+ // Creates an Ext2Filesystem from a ext2 formatted filesystem stored in a
+ // file. The file doesn't need to be loop-back mounted.
+ static std::unique_ptr<Ext2Filesystem> CreateFromFile(
+ const std::string& filename);
+ virtual ~Ext2Filesystem();
+
+ // FilesystemInterface overrides.
+ size_t GetBlockSize() const override;
+ size_t GetBlockCount() const override;
+
+ // GetFiles will return one FilesystemInterface::File for every file and every
+ // directory in the filesystem. Hard-linked files will appear in the list
+ // several times with the same list of blocks.
+ // On addition to actual files, it also returns these pseudo-files:
+ // <free-space>: With all the unallocated data-blocks.
+ // <inode-blocks>: Will all the data-blocks for second and third level inodes
+ // of all the files.
+ // <group-descriptors>: With the block group descriptor and their reserved
+ // space.
+ // <metadata>: With the rest of ext2 metadata blocks, such as superblocks
+ // and bitmap tables.
+ bool GetFiles(std::vector<File>* files) const override;
+
+ bool LoadSettings(brillo::KeyValueStore* store) const override;
+
+ private:
+ Ext2Filesystem() = default;
+
+ // The ext2 main data structure holding the filesystem.
+ ext2_filsys filsys_ = nullptr;
+
+ // The file where the filesystem is stored.
+ std::string filename_;
+
+ DISALLOW_COPY_AND_ASSIGN(Ext2Filesystem);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXT2_FILESYSTEM_H_
diff --git a/payload_generator/ext2_filesystem_unittest.cc b/payload_generator/ext2_filesystem_unittest.cc
new file mode 100644
index 0000000..df8b98f
--- /dev/null
+++ b/payload_generator/ext2_filesystem_unittest.cc
@@ -0,0 +1,214 @@
+//
+// 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/ext2_filesystem.h"
+
+#include <unistd.h>
+
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.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/extent_utils.h"
+
+using chromeos_update_engine::test_utils::System;
+using std::map;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+uint64_t kDefaultFilesystemSize = 4 * 1024 * 1024;
+size_t kDefaultFilesystemBlockCount = 1024;
+size_t kDefaultFilesystemBlockSize = 4096;
+
+// Checks that all the blocks in |extents| are in the range [0, total_blocks).
+void ExpectBlocksInRange(const vector<Extent>& extents, uint64_t total_blocks) {
+ for (const Extent& extent : extents) {
+ EXPECT_LE(0, extent.start_block());
+ EXPECT_LE(extent.start_block() + extent.num_blocks(), total_blocks);
+ }
+}
+
+} // namespace
+
+
+class Ext2FilesystemTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(utils::MakeTempFile("Ext2FilesystemTest-XXXXXX",
+ &fs_filename_, nullptr));
+ ASSERT_EQ(0, truncate(fs_filename_.c_str(), kDefaultFilesystemSize));
+ }
+
+ void TearDown() override {
+ unlink(fs_filename_.c_str());
+ }
+
+ string fs_filename_;
+};
+
+TEST_F(Ext2FilesystemTest, InvalidFilesystem) {
+ unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(fs_filename_);
+ ASSERT_EQ(nullptr, fs.get());
+
+ fs = Ext2Filesystem::CreateFromFile("/path/to/invalid/file");
+ ASSERT_EQ(nullptr, fs.get());
+}
+
+TEST_F(Ext2FilesystemTest, EmptyFilesystem) {
+ EXPECT_EQ(0, System(base::StringPrintf(
+ "/sbin/mkfs.ext2 -q -b %" PRIuS " -F %s",
+ kDefaultFilesystemBlockSize, fs_filename_.c_str())));
+ unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(fs_filename_);
+
+ ASSERT_NE(nullptr, fs.get());
+ EXPECT_EQ(kDefaultFilesystemBlockCount, fs->GetBlockCount());
+ EXPECT_EQ(kDefaultFilesystemBlockSize, fs->GetBlockSize());
+
+ vector<FilesystemInterface::File> files;
+ EXPECT_TRUE(fs->GetFiles(&files));
+
+ map<string, FilesystemInterface::File> map_files;
+ for (const auto& file : files) {
+ EXPECT_EQ(map_files.end(), map_files.find(file.name))
+ << "File " << file.name << " repeated in the list.";
+ map_files[file.name] = file;
+ ExpectBlocksInRange(file.extents, fs->GetBlockCount());
+ }
+ EXPECT_EQ(2, map_files["/"].file_stat.st_ino);
+ EXPECT_FALSE(map_files["<free-space>"].extents.empty());
+}
+
+// This test parses the sample images generated during build time with the
+// "generate_image.sh" script. The expected conditions of each file in these
+// images is encoded in the file name, as defined in the mentioned script.
+TEST_F(Ext2FilesystemTest, ParseGeneratedImages) {
+ const vector<string> kGeneratedImages = {
+ "disk_ext2_1k.img",
+ "disk_ext2_4k.img" };
+ base::FilePath build_path = test_utils::GetBuildArtifactsPath().Append("gen");
+ for (const string& fs_name : kGeneratedImages) {
+ LOG(INFO) << "Testing " << fs_name;
+ unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(
+ build_path.Append(fs_name).value());
+ ASSERT_NE(nullptr, fs.get());
+
+ vector<FilesystemInterface::File> files;
+ map<string, FilesystemInterface::File> map_files;
+ set<string> filenames;
+ EXPECT_TRUE(fs->GetFiles(&files));
+ for (const auto& file : files) {
+ // Check no repeated files. We should parse hard-links with two different
+ // names.
+ EXPECT_EQ(map_files.end(), map_files.find(file.name))
+ << "File " << file.name << " repeated in the list.";
+ map_files[file.name] = file;
+ filenames.insert(file.name);
+ ExpectBlocksInRange(file.extents, fs->GetBlockCount());
+ }
+
+ // Check that all the files are parsed, and the /removed file should not
+ // be included in the list.
+ set<string> kExpectedFiles = {
+ "/",
+ "/dir1",
+ "/dir1/file",
+ "/dir1/dir2",
+ "/dir1/dir2/file",
+ "/dir1/dir2/dir1",
+ "/empty-file",
+ "/link-hard-regular-16k",
+ "/link-long_symlink",
+ "/link-short_symlink",
+ "/lost+found",
+ "/regular-small",
+ "/regular-16k",
+ "/regular-32k-zeros",
+ "/regular-with_net_cap",
+ "/sparse_empty-10k",
+ "/sparse_empty-2blocks",
+ "/sparse-10000blocks",
+ "/sparse-16k-last_block",
+ "/sparse-16k-first_block",
+ "/sparse-16k-holes",
+ "<inode-blocks>",
+ "<free-space>",
+ "<group-descriptors>",
+ };
+ EXPECT_EQ(kExpectedFiles, filenames);
+
+ FilesystemInterface::File file;
+
+ // Small symlinks don't actually have data blocks.
+ EXPECT_TRUE(map_files["/link-short_symlink"].extents.empty());
+ EXPECT_EQ(1, BlocksInExtents(map_files["/link-long_symlink"].extents));
+
+ // Hard-links report the same list of blocks.
+ EXPECT_EQ(map_files["/link-hard-regular-16k"].extents,
+ map_files["/regular-16k"].extents);
+ EXPECT_FALSE(map_files["/regular-16k"].extents.empty());
+
+ // The number of blocks in these files doesn't depend on the
+ // block size.
+ EXPECT_TRUE(map_files["/empty-file"].extents.empty());
+ EXPECT_EQ(1, BlocksInExtents(map_files["/regular-small"].extents));
+ EXPECT_EQ(1, BlocksInExtents(map_files["/regular-with_net_cap"].extents));
+ EXPECT_TRUE(map_files["/sparse_empty-10k"].extents.empty());
+ EXPECT_TRUE(map_files["/sparse_empty-2blocks"].extents.empty());
+ EXPECT_EQ(1, BlocksInExtents(map_files["/sparse-16k-last_block"].extents));
+ EXPECT_EQ(1, BlocksInExtents(map_files["/sparse-16k-first_block"].extents));
+ EXPECT_EQ(2, BlocksInExtents(map_files["/sparse-16k-holes"].extents));
+ }
+}
+
+TEST_F(Ext2FilesystemTest, LoadSettingsFailsTest) {
+ base::FilePath path = test_utils::GetBuildArtifactsPath().Append(
+ "gen/disk_ext2_1k.img");
+ unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(path.value());
+
+ brillo::KeyValueStore store;
+ // disk_ext2_1k.img doesn't have the /etc/update_engine.conf file.
+ EXPECT_FALSE(fs->LoadSettings(&store));
+}
+
+TEST_F(Ext2FilesystemTest, LoadSettingsWorksTest) {
+ base::FilePath path = test_utils::GetBuildArtifactsPath().Append(
+ "gen/disk_ext2_ue_settings.img");
+ unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile(path.value());
+
+ brillo::KeyValueStore store;
+ EXPECT_TRUE(fs->LoadSettings(&store));
+ string minor_version;
+ EXPECT_TRUE(store.GetString("PAYLOAD_MINOR_VERSION", &minor_version));
+ EXPECT_EQ("1234", minor_version);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/extent_ranges.cc b/payload_generator/extent_ranges.cc
new file mode 100644
index 0000000..848fdc7
--- /dev/null
+++ b/payload_generator/extent_ranges.cc
@@ -0,0 +1,306 @@
+//
+// 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/extent_ranges.h"
+
+#include <algorithm>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::set;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+bool ExtentRanges::ExtentsOverlapOrTouch(const Extent& a, const Extent& b) {
+ if (a.start_block() == b.start_block())
+ return true;
+ if (a.start_block() == kSparseHole || b.start_block() == kSparseHole)
+ return false;
+ if (a.start_block() < b.start_block()) {
+ return a.start_block() + a.num_blocks() >= b.start_block();
+ } else {
+ return b.start_block() + b.num_blocks() >= a.start_block();
+ }
+}
+
+bool ExtentRanges::ExtentsOverlap(const Extent& a, const Extent& b) {
+ if (a.start_block() == b.start_block())
+ return true;
+ if (a.start_block() == kSparseHole || b.start_block() == kSparseHole)
+ return false;
+ if (a.start_block() < b.start_block()) {
+ return a.start_block() + a.num_blocks() > b.start_block();
+ } else {
+ return b.start_block() + b.num_blocks() > a.start_block();
+ }
+}
+
+void ExtentRanges::AddBlock(uint64_t block) {
+ AddExtent(ExtentForRange(block, 1));
+}
+
+void ExtentRanges::SubtractBlock(uint64_t block) {
+ SubtractExtent(ExtentForRange(block, 1));
+}
+
+namespace {
+
+Extent UnionOverlappingExtents(const Extent& first, const Extent& second) {
+ CHECK_NE(kSparseHole, first.start_block());
+ CHECK_NE(kSparseHole, second.start_block());
+ uint64_t start = std::min(first.start_block(), second.start_block());
+ uint64_t end = std::max(first.start_block() + first.num_blocks(),
+ second.start_block() + second.num_blocks());
+ return ExtentForRange(start, end - start);
+}
+
+} // namespace
+
+void ExtentRanges::AddExtent(Extent extent) {
+ if (extent.start_block() == kSparseHole || extent.num_blocks() == 0)
+ return;
+
+ ExtentSet::iterator begin_del = extent_set_.end();
+ ExtentSet::iterator end_del = extent_set_.end();
+ uint64_t del_blocks = 0;
+ for (ExtentSet::iterator it = extent_set_.begin(), e = extent_set_.end();
+ it != e; ++it) {
+ if (ExtentsOverlapOrTouch(*it, extent)) {
+ end_del = it;
+ ++end_del;
+ del_blocks += it->num_blocks();
+ if (begin_del == extent_set_.end())
+ begin_del = it;
+
+ extent = UnionOverlappingExtents(extent, *it);
+ }
+ }
+ extent_set_.erase(begin_del, end_del);
+ extent_set_.insert(extent);
+ blocks_ -= del_blocks;
+ blocks_ += extent.num_blocks();
+}
+
+namespace {
+// Returns base - subtractee (set subtraction).
+ExtentRanges::ExtentSet SubtractOverlappingExtents(const Extent& base,
+ const Extent& subtractee) {
+ ExtentRanges::ExtentSet ret;
+ if (subtractee.start_block() > base.start_block()) {
+ ret.insert(ExtentForRange(base.start_block(),
+ subtractee.start_block() - base.start_block()));
+ }
+ uint64_t base_end = base.start_block() + base.num_blocks();
+ uint64_t subtractee_end = subtractee.start_block() + subtractee.num_blocks();
+ if (base_end > subtractee_end) {
+ ret.insert(ExtentForRange(subtractee_end, base_end - subtractee_end));
+ }
+ return ret;
+}
+} // namespace
+
+void ExtentRanges::SubtractExtent(const Extent& extent) {
+ if (extent.start_block() == kSparseHole || extent.num_blocks() == 0)
+ return;
+
+ ExtentSet::iterator begin_del = extent_set_.end();
+ ExtentSet::iterator end_del = extent_set_.end();
+ uint64_t del_blocks = 0;
+ ExtentSet new_extents;
+ for (ExtentSet::iterator it = extent_set_.begin(), e = extent_set_.end();
+ it != e; ++it) {
+ if (!ExtentsOverlap(*it, extent))
+ continue;
+
+ if (begin_del == extent_set_.end())
+ begin_del = it;
+ end_del = it;
+ ++end_del;
+
+ del_blocks += it->num_blocks();
+
+ ExtentSet subtraction = SubtractOverlappingExtents(*it, extent);
+ for (ExtentSet::iterator jt = subtraction.begin(), je = subtraction.end();
+ jt != je; ++jt) {
+ new_extents.insert(*jt);
+ del_blocks -= jt->num_blocks();
+ }
+ }
+ extent_set_.erase(begin_del, end_del);
+ extent_set_.insert(new_extents.begin(), new_extents.end());
+ blocks_ -= del_blocks;
+}
+
+void ExtentRanges::AddRanges(const ExtentRanges& ranges) {
+ for (ExtentSet::const_iterator it = ranges.extent_set_.begin(),
+ e = ranges.extent_set_.end(); it != e; ++it) {
+ AddExtent(*it);
+ }
+}
+
+void ExtentRanges::SubtractRanges(const ExtentRanges& ranges) {
+ for (ExtentSet::const_iterator it = ranges.extent_set_.begin(),
+ e = ranges.extent_set_.end(); it != e; ++it) {
+ SubtractExtent(*it);
+ }
+}
+
+void ExtentRanges::AddExtents(const vector<Extent>& extents) {
+ for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+ it != e; ++it) {
+ AddExtent(*it);
+ }
+}
+
+void ExtentRanges::SubtractExtents(const vector<Extent>& extents) {
+ for (vector<Extent>::const_iterator it = extents.begin(), e = extents.end();
+ it != e; ++it) {
+ SubtractExtent(*it);
+ }
+}
+
+void ExtentRanges::AddRepeatedExtents(
+ const ::google::protobuf::RepeatedPtrField<Extent> &exts) {
+ for (int i = 0, e = exts.size(); i != e; ++i) {
+ AddExtent(exts.Get(i));
+ }
+}
+
+void ExtentRanges::SubtractRepeatedExtents(
+ const ::google::protobuf::RepeatedPtrField<Extent> &exts) {
+ for (int i = 0, e = exts.size(); i != e; ++i) {
+ SubtractExtent(exts.Get(i));
+ }
+}
+
+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|.
+ if (lower != extent_set_.begin())
+ lower--;
+ // Any extent starting at block+1 or later is not interesting, so this is the
+ // upper limit.
+ auto upper = extent_set_.lower_bound(ExtentForRange(block + 1, 0));
+ for (auto iter = lower; iter != upper; ++iter) {
+ if (iter->start_block() <= block &&
+ block < iter->start_block() + iter->num_blocks()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void ExtentRanges::Dump() const {
+ LOG(INFO) << "ExtentRanges Dump. blocks: " << blocks_;
+ for (ExtentSet::const_iterator it = extent_set_.begin(),
+ e = extent_set_.end();
+ it != e; ++it) {
+ LOG(INFO) << "{" << it->start_block() << ", " << it->num_blocks() << "}";
+ }
+}
+
+Extent ExtentForRange(uint64_t start_block, uint64_t num_blocks) {
+ Extent ret;
+ ret.set_start_block(start_block);
+ ret.set_num_blocks(num_blocks);
+ return ret;
+}
+
+vector<Extent> ExtentRanges::GetExtentsForBlockCount(
+ uint64_t count) const {
+ vector<Extent> out;
+ if (count == 0)
+ return out;
+ uint64_t out_blocks = 0;
+ CHECK(count <= blocks_);
+ for (ExtentSet::const_iterator it = extent_set_.begin(),
+ e = extent_set_.end();
+ it != e; ++it) {
+ const uint64_t blocks_needed = count - out_blocks;
+ const Extent& extent = *it;
+ out.push_back(extent);
+ out_blocks += extent.num_blocks();
+ if (extent.num_blocks() < blocks_needed)
+ continue;
+ if (extent.num_blocks() == blocks_needed)
+ break;
+ // If we get here, we just added the last extent needed, but it's too big
+ out_blocks -= extent.num_blocks();
+ out_blocks += blocks_needed;
+ out.back().set_num_blocks(blocks_needed);
+ break;
+ }
+ return out;
+}
+
+vector<Extent> FilterExtentRanges(const vector<Extent>& extents,
+ const ExtentRanges& ranges) {
+ vector<Extent> result;
+ const ExtentRanges::ExtentSet& extent_set = ranges.extent_set();
+ for (Extent extent : extents) {
+ // The extents are sorted by the start_block. We want to iterate all the
+ // Extents in the ExtentSet possibly overlapping the current |extent|. This
+ // is achieved by looking from the extent whose start_block is *lower* than
+ // the extent.start_block() up to the greatest extent whose start_block is
+ // lower than extent.start_block() + extent.num_blocks().
+ auto lower = extent_set.lower_bound(extent);
+ // We need to decrement the lower_bound to look at the extent that could
+ // overlap the beginning of the current |extent|.
+ if (lower != extent_set.begin())
+ lower--;
+ auto upper = extent_set.lower_bound(
+ ExtentForRange(extent.start_block() + extent.num_blocks(), 0));
+ for (auto iter = lower; iter != upper; ++iter) {
+ if (!ExtentRanges::ExtentsOverlap(extent, *iter))
+ continue;
+ if (iter->start_block() <= extent.start_block()) {
+ // We need to cut blocks from the beginning of the |extent|.
+ uint64_t cut_blocks = iter->start_block() + iter->num_blocks() -
+ extent.start_block();
+ if (cut_blocks >= extent.num_blocks()) {
+ extent.set_num_blocks(0);
+ break;
+ }
+ extent = ExtentForRange(extent.start_block() + cut_blocks,
+ extent.num_blocks() - cut_blocks);
+ } else {
+ // We need to cut blocks on the middle of the extent, possible up to the
+ // end of it.
+ result.push_back(
+ ExtentForRange(extent.start_block(),
+ iter->start_block() - extent.start_block()));
+ uint64_t new_start = iter->start_block() + iter->num_blocks();
+ uint64_t old_end = extent.start_block() + extent.num_blocks();
+ if (new_start >= old_end) {
+ extent.set_num_blocks(0);
+ break;
+ }
+ extent = ExtentForRange(new_start, old_end - new_start);
+ }
+ }
+ if (extent.num_blocks() > 0)
+ result.push_back(extent);
+ }
+ return result;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/extent_ranges.h b/payload_generator/extent_ranges.h
new file mode 100644
index 0000000..198c834
--- /dev/null
+++ b/payload_generator/extent_ranges.h
@@ -0,0 +1,94 @@
+//
+// 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_EXTENT_RANGES_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_RANGES_H_
+
+#include <map>
+#include <set>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// An ExtentRanges object represents an unordered collection of extents (and
+// therefore blocks). Such an object may be modified by adding or subtracting
+// blocks (think: set addition or set subtraction). Note that ExtentRanges
+// ignores sparse hole extents mostly to avoid confusion between extending a
+// sparse hole range vs. set addition but also to ensure that the delta
+// generator doesn't use sparse holes as scratch space.
+
+namespace chromeos_update_engine {
+
+struct ExtentLess {
+ bool operator()(const Extent& x, const Extent& y) const {
+ return x.start_block() < y.start_block();
+ }
+};
+
+Extent ExtentForRange(uint64_t start_block, uint64_t num_blocks);
+
+class ExtentRanges {
+ public:
+ typedef std::set<Extent, ExtentLess> ExtentSet;
+
+ ExtentRanges() : blocks_(0) {}
+ void AddBlock(uint64_t block);
+ void SubtractBlock(uint64_t block);
+ void AddExtent(Extent extent);
+ void SubtractExtent(const Extent& extent);
+ void AddExtents(const std::vector<Extent>& extents);
+ void SubtractExtents(const std::vector<Extent>& extents);
+ void AddRepeatedExtents(
+ const ::google::protobuf::RepeatedPtrField<Extent> &exts);
+ void SubtractRepeatedExtents(
+ const ::google::protobuf::RepeatedPtrField<Extent> &exts);
+ void AddRanges(const ExtentRanges& ranges);
+ void SubtractRanges(const ExtentRanges& ranges);
+
+ // Returns whether the block |block| is in this ExtentRange.
+ bool ContainsBlock(uint64_t block) const;
+
+ static bool ExtentsOverlapOrTouch(const Extent& a, const Extent& b);
+ static bool ExtentsOverlap(const Extent& a, const Extent& b);
+
+ // Dumps contents to the log file. Useful for debugging.
+ void Dump() const;
+
+ uint64_t blocks() const { return blocks_; }
+ const ExtentSet& extent_set() const { return extent_set_; }
+
+ // Returns an ordered vector of extents for |count| blocks,
+ // using extents in extent_set_. The returned extents are not
+ // removed from extent_set_. |count| must be less than or equal to
+ // the number of blocks in this extent set.
+ std::vector<Extent> GetExtentsForBlockCount(uint64_t count) const;
+
+ private:
+ ExtentSet extent_set_;
+ uint64_t blocks_;
+};
+
+// Filters out from the passed list of extents |extents| all the blocks in the
+// ExtentRanges set. Note that the order of the blocks in |extents| is preserved
+// omitting blocks present in the ExtentRanges |ranges|.
+std::vector<Extent> FilterExtentRanges(const std::vector<Extent>& extents,
+ const ExtentRanges& ranges);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_RANGES_H_
diff --git a/payload_generator/extent_ranges_unittest.cc b/payload_generator/extent_ranges_unittest.cc
new file mode 100644
index 0000000..8b39040
--- /dev/null
+++ b/payload_generator/extent_ranges_unittest.cc
@@ -0,0 +1,345 @@
+//
+// 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/extent_ranges.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class ExtentRangesTest : public ::testing::Test {};
+
+namespace {
+void ExpectRangeEq(const ExtentRanges& ranges,
+ const uint64_t* expected,
+ size_t sz,
+ int line) {
+ uint64_t blocks = 0;
+ for (size_t i = 1; i < sz; i += 2) {
+ blocks += expected[i];
+ }
+ EXPECT_EQ(blocks, ranges.blocks()) << "line: " << line;
+
+ const ExtentRanges::ExtentSet& result = ranges.extent_set();
+ ExtentRanges::ExtentSet::const_iterator it = result.begin();
+ for (size_t i = 0; i < sz; i += 2) {
+ EXPECT_FALSE(it == result.end()) << "line: " << line;
+ EXPECT_EQ(expected[i], it->start_block()) << "line: " << line;
+ EXPECT_EQ(expected[i + 1], it->num_blocks()) << "line: " << line;
+ ++it;
+ }
+}
+
+#define EXPECT_RANGE_EQ(ranges, var) \
+ do { \
+ ExpectRangeEq(ranges, var, arraysize(var), __LINE__); \
+ } while (0)
+
+void ExpectRangesOverlapOrTouch(uint64_t a_start, uint64_t a_num,
+ uint64_t b_start, uint64_t b_num) {
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+}
+
+void ExpectFalseRangesOverlapOrTouch(uint64_t a_start, uint64_t a_num,
+ uint64_t b_start, uint64_t b_num) {
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+}
+
+void ExpectRangesOverlap(uint64_t a_start, uint64_t a_num,
+ uint64_t b_start, uint64_t b_num) {
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_TRUE(ExtentRanges::ExtentsOverlapOrTouch(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+}
+
+void ExpectFalseRangesOverlap(uint64_t a_start, uint64_t a_num,
+ uint64_t b_start, uint64_t b_num) {
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(a_start,
+ a_num),
+ ExtentForRange(b_start,
+ b_num)));
+ EXPECT_FALSE(ExtentRanges::ExtentsOverlap(ExtentForRange(b_start,
+ b_num),
+ ExtentForRange(a_start,
+ a_num)));
+}
+
+} // namespace
+
+TEST(ExtentRangesTest, ExtentsOverlapTest) {
+ ExpectRangesOverlapOrTouch(10, 20, 30, 10);
+ ExpectRangesOverlap(10, 20, 25, 10);
+ ExpectFalseRangesOverlapOrTouch(10, 20, 35, 10);
+ ExpectFalseRangesOverlap(10, 20, 30, 10);
+ ExpectRangesOverlap(12, 4, 12, 3);
+
+ ExpectRangesOverlapOrTouch(kSparseHole, 2, kSparseHole, 3);
+ ExpectRangesOverlap(kSparseHole, 2, kSparseHole, 3);
+ ExpectFalseRangesOverlapOrTouch(kSparseHole, 2, 10, 3);
+ ExpectFalseRangesOverlapOrTouch(10, 2, kSparseHole, 3);
+ ExpectFalseRangesOverlap(kSparseHole, 2, 10, 3);
+ ExpectFalseRangesOverlap(10, 2, kSparseHole, 3);
+}
+
+TEST(ExtentRangesTest, SimpleTest) {
+ ExtentRanges ranges;
+ {
+ static const uint64_t expected[] = {};
+ // Can't use arraysize() on 0-length arrays:
+ ExpectRangeEq(ranges, expected, 0, __LINE__);
+ }
+ ranges.SubtractBlock(2);
+ {
+ static const uint64_t expected[] = {};
+ // Can't use arraysize() on 0-length arrays:
+ ExpectRangeEq(ranges, expected, 0, __LINE__);
+ }
+
+ ranges.AddBlock(0);
+ ranges.Dump();
+ ranges.AddBlock(1);
+ ranges.AddBlock(3);
+
+ {
+ static const uint64_t expected[] = {0, 2, 3, 1};
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+ ranges.AddBlock(2);
+ {
+ static const uint64_t expected[] = {0, 4};
+ EXPECT_RANGE_EQ(ranges, expected);
+ ranges.AddBlock(kSparseHole);
+ EXPECT_RANGE_EQ(ranges, expected);
+ ranges.SubtractBlock(kSparseHole);
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+ ranges.SubtractBlock(2);
+ {
+ static const uint64_t expected[] = {0, 2, 3, 1};
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+
+ for (uint64_t i = 100; i < 1000; i += 100) {
+ ranges.AddExtent(ExtentForRange(i, 50));
+ }
+ {
+ static const uint64_t expected[] = {
+ 0, 2, 3, 1, 100, 50, 200, 50, 300, 50, 400, 50,
+ 500, 50, 600, 50, 700, 50, 800, 50, 900, 50
+ };
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+
+ ranges.SubtractExtent(ExtentForRange(210, 410 - 210));
+ {
+ static const uint64_t expected[] = {
+ 0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+ 600, 50, 700, 50, 800, 50, 900, 50
+ };
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+ ranges.AddExtent(ExtentForRange(100000, 0));
+ {
+ static const uint64_t expected[] = {
+ 0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+ 600, 50, 700, 50, 800, 50, 900, 50
+ };
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+ ranges.SubtractExtent(ExtentForRange(3, 0));
+ {
+ static const uint64_t expected[] = {
+ 0, 2, 3, 1, 100, 50, 200, 10, 410, 40, 500, 50,
+ 600, 50, 700, 50, 800, 50, 900, 50
+ };
+ EXPECT_RANGE_EQ(ranges, expected);
+ }
+}
+
+TEST(ExtentRangesTest, MultipleRanges) {
+ ExtentRanges ranges_a, ranges_b;
+ ranges_a.AddBlock(0);
+ ranges_b.AddBlock(4);
+ ranges_b.AddBlock(3);
+ {
+ uint64_t expected[] = {3, 2};
+ EXPECT_RANGE_EQ(ranges_b, expected);
+ }
+ ranges_a.AddRanges(ranges_b);
+ {
+ uint64_t expected[] = {0, 1, 3, 2};
+ EXPECT_RANGE_EQ(ranges_a, expected);
+ }
+ ranges_a.SubtractRanges(ranges_b);
+ {
+ uint64_t expected[] = {0, 1};
+ EXPECT_RANGE_EQ(ranges_a, expected);
+ }
+ {
+ uint64_t expected[] = {3, 2};
+ EXPECT_RANGE_EQ(ranges_b, expected);
+ }
+}
+
+TEST(ExtentRangesTest, GetExtentsForBlockCountTest) {
+ ExtentRanges ranges;
+ ranges.AddExtents(vector<Extent>(1, ExtentForRange(10, 30)));
+ {
+ vector<Extent> zero_extents = ranges.GetExtentsForBlockCount(0);
+ EXPECT_TRUE(zero_extents.empty());
+ }
+ ::google::protobuf::RepeatedPtrField<Extent> rep_field;
+ *rep_field.Add() = ExtentForRange(30, 40);
+ ranges.AddRepeatedExtents(rep_field);
+ ranges.SubtractExtents(vector<Extent>(1, ExtentForRange(20, 10)));
+ *rep_field.Mutable(0) = ExtentForRange(50, 10);
+ ranges.SubtractRepeatedExtents(rep_field);
+ EXPECT_EQ(40, ranges.blocks());
+
+ for (int i = 0; i < 2; i++) {
+ vector<Extent> expected(2);
+ expected[0] = ExtentForRange(10, 10);
+ expected[1] = ExtentForRange(30, i == 0 ? 10 : 20);
+ vector<Extent> actual =
+ ranges.GetExtentsForBlockCount(10 + expected[1].num_blocks());
+ EXPECT_EQ(expected.size(), actual.size());
+ for (vector<Extent>::size_type j = 0, e = expected.size(); j != e; ++j) {
+ EXPECT_EQ(expected[j].start_block(), actual[j].start_block())
+ << "j = " << j;
+ EXPECT_EQ(expected[j].num_blocks(), actual[j].num_blocks())
+ << "j = " << j;
+ }
+ }
+}
+
+TEST(ExtentRangesTest, ContainsBlockTest) {
+ ExtentRanges ranges;
+ EXPECT_FALSE(ranges.ContainsBlock(123));
+
+ ranges.AddExtent(ExtentForRange(10, 10));
+ ranges.AddExtent(ExtentForRange(100, 1));
+
+ EXPECT_FALSE(ranges.ContainsBlock(9));
+ EXPECT_TRUE(ranges.ContainsBlock(10));
+ EXPECT_TRUE(ranges.ContainsBlock(15));
+ EXPECT_TRUE(ranges.ContainsBlock(19));
+ EXPECT_FALSE(ranges.ContainsBlock(20));
+
+ // Test for an extent with just the block we are requesting.
+ EXPECT_FALSE(ranges.ContainsBlock(99));
+ EXPECT_TRUE(ranges.ContainsBlock(100));
+ EXPECT_FALSE(ranges.ContainsBlock(101));
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesEmptyRanges) {
+ ExtentRanges ranges;
+ EXPECT_EQ(vector<Extent>(),
+ FilterExtentRanges(vector<Extent>(), ranges));
+ EXPECT_EQ(
+ vector<Extent>{ ExtentForRange(50, 10) },
+ FilterExtentRanges(vector<Extent>{ ExtentForRange(50, 10) }, ranges));
+ // Check that the empty Extents are ignored.
+ EXPECT_EQ(
+ (vector<Extent>{ ExtentForRange(10, 10), ExtentForRange(20, 10) }),
+ FilterExtentRanges(vector<Extent>{
+ ExtentForRange(10, 10),
+ ExtentForRange(3, 0),
+ ExtentForRange(20, 10) }, ranges));
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesMultipleRanges) {
+ // Two overlaping extents, with three ranges to remove.
+ vector<Extent> extents {
+ ExtentForRange(10, 100),
+ ExtentForRange(30, 100) };
+ ExtentRanges ranges;
+ // This overlaps the beginning of the second extent.
+ ranges.AddExtent(ExtentForRange(28, 3));
+ ranges.AddExtent(ExtentForRange(50, 10));
+ ranges.AddExtent(ExtentForRange(70, 10));
+ // This overlaps the end of the second extent.
+ ranges.AddExtent(ExtentForRange(108, 6));
+ EXPECT_EQ(
+ (vector<Extent>{
+ // For the first extent:
+ ExtentForRange(10, 18),
+ ExtentForRange(31, 19),
+ ExtentForRange(60, 10),
+ ExtentForRange(80, 28),
+ // For the second extent:
+ ExtentForRange(31, 19),
+ ExtentForRange(60, 10),
+ ExtentForRange(80, 28),
+ ExtentForRange(114, 16)}),
+ FilterExtentRanges(extents, ranges));
+}
+
+TEST(ExtentRangesTest, FilterExtentRangesOvelapping) {
+ ExtentRanges ranges;
+ ranges.AddExtent(ExtentForRange(10, 3));
+ ranges.AddExtent(ExtentForRange(20, 5));
+ // Requested extent overlaps with one of the ranges.
+ EXPECT_EQ(vector<Extent>(),
+ FilterExtentRanges(vector<Extent>{
+ ExtentForRange(10, 1),
+ ExtentForRange(22, 1) },
+ ranges));
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/extent_utils.cc b/payload_generator/extent_utils.cc
new file mode 100644
index 0000000..1093445
--- /dev/null
+++ b/payload_generator/extent_utils.cc
@@ -0,0 +1,154 @@
+//
+// 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/extent_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_ranges.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+void AppendBlockToExtents(vector<Extent>* extents, uint64_t block) {
+ // First try to extend the last extent in |extents|, if any.
+ if (!extents->empty()) {
+ Extent& extent = extents->back();
+ uint64_t next_block = extent.start_block() == kSparseHole ?
+ kSparseHole : extent.start_block() + extent.num_blocks();
+ if (next_block == block) {
+ extent.set_num_blocks(extent.num_blocks() + 1);
+ return;
+ }
+ }
+ // If unable to extend the last extent, append a new single-block extent.
+ Extent new_extent;
+ new_extent.set_start_block(block);
+ new_extent.set_num_blocks(1);
+ extents->push_back(new_extent);
+}
+
+Extent GetElement(const vector<Extent>& collection, size_t index) {
+ return collection[index];
+}
+
+Extent GetElement(
+ const google::protobuf::RepeatedPtrField<Extent>& collection,
+ size_t index) {
+ return collection.Get(index);
+}
+
+void ExtendExtents(
+ google::protobuf::RepeatedPtrField<Extent>* extents,
+ const google::protobuf::RepeatedPtrField<Extent>& extents_to_add) {
+ vector<Extent> extents_vector;
+ vector<Extent> extents_to_add_vector;
+ ExtentsToVector(*extents, &extents_vector);
+ ExtentsToVector(extents_to_add, &extents_to_add_vector);
+ extents_vector.insert(extents_vector.end(),
+ extents_to_add_vector.begin(),
+ extents_to_add_vector.end());
+ NormalizeExtents(&extents_vector);
+ extents->Clear();
+ StoreExtents(extents_vector, extents);
+}
+
+// Stores all Extents in 'extents' into 'out'.
+void StoreExtents(const vector<Extent>& extents,
+ google::protobuf::RepeatedPtrField<Extent>* out) {
+ for (const Extent& extent : extents) {
+ Extent* new_extent = out->Add();
+ *new_extent = extent;
+ }
+}
+
+// Stores all extents in |extents| into |out_vector|.
+void ExtentsToVector(const google::protobuf::RepeatedPtrField<Extent>& extents,
+ vector<Extent>* out_vector) {
+ out_vector->clear();
+ for (int i = 0; i < extents.size(); i++) {
+ out_vector->push_back(extents.Get(i));
+ }
+}
+
+void NormalizeExtents(vector<Extent>* extents) {
+ vector<Extent> new_extents;
+ for (const Extent& curr_ext : *extents) {
+ if (new_extents.empty()) {
+ new_extents.push_back(curr_ext);
+ continue;
+ }
+ Extent& last_ext = new_extents.back();
+ if (last_ext.start_block() + last_ext.num_blocks() ==
+ curr_ext.start_block()) {
+ // If the extents are touching, we want to combine them.
+ last_ext.set_num_blocks(last_ext.num_blocks() + curr_ext.num_blocks());
+ } else {
+ // Otherwise just include the extent as is.
+ new_extents.push_back(curr_ext);
+ }
+ }
+ *extents = new_extents;
+}
+
+vector<Extent> ExtentsSublist(const vector<Extent>& extents,
+ uint64_t block_offset, uint64_t block_count) {
+ vector<Extent> result;
+ uint64_t scanned_blocks = 0;
+ if (block_count == 0)
+ return result;
+ uint64_t end_block_offset = block_offset + block_count;
+ for (const Extent& extent : extents) {
+ // The loop invariant is that if |extents| has enough blocks, there's
+ // still some extent to add to |result|. This implies that at the beginning
+ // of the loop scanned_blocks < block_offset + block_count.
+ if (scanned_blocks + extent.num_blocks() > block_offset) {
+ // This case implies that |extent| has some overlapping with the requested
+ // subsequence.
+ uint64_t new_start = extent.start_block();
+ uint64_t new_num_blocks = extent.num_blocks();
+ if (scanned_blocks + new_num_blocks > end_block_offset) {
+ // Cut the end part of the extent.
+ new_num_blocks = end_block_offset - scanned_blocks;
+ }
+ if (block_offset > scanned_blocks) {
+ // Cut the begin part of the extent.
+ new_num_blocks -= block_offset - scanned_blocks;
+ new_start += block_offset - scanned_blocks;
+ }
+ result.push_back(ExtentForRange(new_start, new_num_blocks));
+ }
+ scanned_blocks += extent.num_blocks();
+ if (scanned_blocks >= end_block_offset)
+ break;
+ }
+ return result;
+}
+
+bool operator==(const Extent& a, const Extent& b) {
+ return a.start_block() == b.start_block() && a.num_blocks() == b.num_blocks();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/extent_utils.h b/payload_generator/extent_utils.h
new file mode 100644
index 0000000..91729d5
--- /dev/null
+++ b/payload_generator/extent_utils.h
@@ -0,0 +1,101 @@
+//
+// 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_EXTENT_UTILS_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
+
+#include <vector>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/update_metadata.pb.h"
+
+// Utility functions for manipulating Extents and lists of blocks.
+
+namespace chromeos_update_engine {
+
+// |block| must either be the next block in the last extent or a block
+// in the next extent. This function will not handle inserting block
+// into an arbitrary place in the extents.
+void AppendBlockToExtents(std::vector<Extent>* extents, uint64_t block);
+
+// Get/SetElement are intentionally overloaded so that templated functions
+// can accept either type of collection of Extents.
+Extent GetElement(const std::vector<Extent>& collection, size_t index);
+Extent GetElement(
+ const google::protobuf::RepeatedPtrField<Extent>& collection,
+ size_t index);
+
+// Return the total number of blocks in a collection (vector or
+// RepeatedPtrField) of Extents.
+template<typename T>
+uint64_t BlocksInExtents(const T& collection) {
+ uint64_t ret = 0;
+ for (size_t i = 0; i < static_cast<size_t>(collection.size()); ++i) {
+ ret += GetElement(collection, i).num_blocks();
+ }
+ return ret;
+}
+
+// Takes a collection (vector or RepeatedPtrField) of Extent and
+// returns a vector of the blocks referenced, in order.
+template<typename T>
+std::vector<uint64_t> ExpandExtents(const T& extents) {
+ std::vector<uint64_t> ret;
+ for (size_t i = 0, e = static_cast<size_t>(extents.size()); i != e; ++i) {
+ const Extent extent = GetElement(extents, i);
+ if (extent.start_block() == kSparseHole) {
+ ret.resize(ret.size() + extent.num_blocks(), kSparseHole);
+ } else {
+ for (uint64_t block = extent.start_block();
+ block < (extent.start_block() + extent.num_blocks()); block++) {
+ ret.push_back(block);
+ }
+ }
+ }
+ return ret;
+}
+
+// Stores all Extents in 'extents' into 'out'.
+void StoreExtents(const std::vector<Extent>& extents,
+ google::protobuf::RepeatedPtrField<Extent>* out);
+
+// Stores all extents in |extents| into |out_vector|.
+void ExtentsToVector(const google::protobuf::RepeatedPtrField<Extent>& extents,
+ std::vector<Extent>* out_vector);
+
+// Takes a pointer to extents |extents| and extents |extents_to_add|, and
+// merges them by adding |extents_to_add| to |extents| and normalizing.
+void ExtendExtents(
+ google::protobuf::RepeatedPtrField<Extent>* extents,
+ const google::protobuf::RepeatedPtrField<Extent>& extents_to_add);
+
+// Takes a vector of extents and normalizes those extents. Expects the extents
+// to be sorted by start block. E.g. if |extents| is [(1, 2), (3, 5), (10, 2)]
+// then |extents| will be changed to [(1, 7), (10, 2)].
+void NormalizeExtents(std::vector<Extent>* extents);
+
+// Return a subsequence of the list of blocks passed. Both the passed list of
+// blocks |extents| and the return value are expressed as a list of Extent, not
+// blocks. The returned list skips the first |block_offset| blocks from the
+// |extents| and cotains |block_count| blocks (or less if |extents| is shorter).
+std::vector<Extent> ExtentsSublist(const std::vector<Extent>& extents,
+ uint64_t block_offset, uint64_t block_count);
+
+bool operator==(const Extent& a, const Extent& b);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_
diff --git a/payload_generator/extent_utils_unittest.cc b/payload_generator/extent_utils_unittest.cc
new file mode 100644
index 0000000..62c8ff1
--- /dev/null
+++ b/payload_generator/extent_utils_unittest.cc
@@ -0,0 +1,164 @@
+//
+// 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/extent_utils.h"
+
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class ExtentUtilsTest : public ::testing::Test {};
+
+TEST(ExtentUtilsTest, AppendSparseToExtentsTest) {
+ vector<Extent> extents;
+
+ EXPECT_EQ(0, extents.size());
+ AppendBlockToExtents(&extents, kSparseHole);
+ EXPECT_EQ(1, extents.size());
+ AppendBlockToExtents(&extents, 0);
+ EXPECT_EQ(2, extents.size());
+ AppendBlockToExtents(&extents, kSparseHole);
+ AppendBlockToExtents(&extents, kSparseHole);
+
+ ASSERT_EQ(3, extents.size());
+ EXPECT_EQ(kSparseHole, extents[0].start_block());
+ EXPECT_EQ(1, extents[0].num_blocks());
+ EXPECT_EQ(0, extents[1].start_block());
+ EXPECT_EQ(1, extents[1].num_blocks());
+ EXPECT_EQ(kSparseHole, extents[2].start_block());
+ EXPECT_EQ(2, extents[2].num_blocks());
+}
+
+TEST(ExtentUtilsTest, BlocksInExtentsTest) {
+ {
+ vector<Extent> extents;
+ EXPECT_EQ(0, BlocksInExtents(extents));
+ extents.push_back(ExtentForRange(0, 1));
+ EXPECT_EQ(1, BlocksInExtents(extents));
+ extents.push_back(ExtentForRange(23, 55));
+ EXPECT_EQ(56, BlocksInExtents(extents));
+ extents.push_back(ExtentForRange(1, 2));
+ EXPECT_EQ(58, BlocksInExtents(extents));
+ }
+ {
+ google::protobuf::RepeatedPtrField<Extent> extents;
+ EXPECT_EQ(0, BlocksInExtents(extents));
+ *extents.Add() = ExtentForRange(0, 1);
+ EXPECT_EQ(1, BlocksInExtents(extents));
+ *extents.Add() = ExtentForRange(23, 55);
+ EXPECT_EQ(56, BlocksInExtents(extents));
+ *extents.Add() = ExtentForRange(1, 2);
+ EXPECT_EQ(58, BlocksInExtents(extents));
+ }
+}
+
+TEST(ExtentUtilsTest, ExtendExtentsTest) {
+ InstallOperation first_op;
+ *(first_op.add_src_extents()) = ExtentForRange(1, 1);
+ *(first_op.add_src_extents()) = ExtentForRange(3, 1);
+
+ InstallOperation second_op;
+ *(second_op.add_src_extents()) = ExtentForRange(4, 2);
+ *(second_op.add_src_extents()) = ExtentForRange(8, 2);
+
+ ExtendExtents(first_op.mutable_src_extents(), second_op.src_extents());
+ vector<Extent> first_op_vec;
+ ExtentsToVector(first_op.src_extents(), &first_op_vec);
+ EXPECT_EQ((vector<Extent>{
+ ExtentForRange(1, 1),
+ ExtentForRange(3, 3),
+ ExtentForRange(8, 2)}), first_op_vec);
+}
+
+TEST(ExtentUtilsTest, NormalizeExtentsSimpleList) {
+ // Make sure it works when there's just one extent.
+ vector<Extent> extents;
+ NormalizeExtents(&extents);
+ EXPECT_EQ(0, extents.size());
+
+ extents = { ExtentForRange(0, 3) };
+ NormalizeExtents(&extents);
+ EXPECT_EQ(1, extents.size());
+ EXPECT_EQ(ExtentForRange(0, 3), extents[0]);
+}
+
+TEST(ExtentUtilsTest, NormalizeExtentsTest) {
+ vector<Extent> extents = {
+ ExtentForRange(0, 3),
+ ExtentForRange(3, 2),
+ ExtentForRange(5, 1),
+ ExtentForRange(8, 4),
+ ExtentForRange(13, 1),
+ ExtentForRange(14, 2)
+ };
+ NormalizeExtents(&extents);
+ EXPECT_EQ(3, extents.size());
+ EXPECT_EQ(ExtentForRange(0, 6), extents[0]);
+ EXPECT_EQ(ExtentForRange(8, 4), extents[1]);
+ EXPECT_EQ(ExtentForRange(13, 3), extents[2]);
+}
+
+TEST(ExtentUtilsTest, ExtentsSublistTest) {
+ vector<Extent> extents = {
+ ExtentForRange(10, 10),
+ ExtentForRange(30, 10),
+ ExtentForRange(50, 10)
+ };
+
+ // Simple empty result cases.
+ EXPECT_EQ(vector<Extent>(),
+ ExtentsSublist(extents, 1000, 20));
+ EXPECT_EQ(vector<Extent>(),
+ ExtentsSublist(extents, 5, 0));
+ EXPECT_EQ(vector<Extent>(),
+ ExtentsSublist(extents, 30, 1));
+
+ // Normal test cases.
+ EXPECT_EQ(vector<Extent>{ ExtentForRange(13, 2) },
+ ExtentsSublist(extents, 3, 2));
+ EXPECT_EQ(vector<Extent>{ ExtentForRange(15, 5) },
+ ExtentsSublist(extents, 5, 5));
+ EXPECT_EQ((vector<Extent>{ ExtentForRange(15, 5), ExtentForRange(30, 5) }),
+ ExtentsSublist(extents, 5, 10));
+ EXPECT_EQ((vector<Extent>{
+ ExtentForRange(13, 7),
+ ExtentForRange(30, 10),
+ ExtentForRange(50, 3), }),
+ ExtentsSublist(extents, 3, 20));
+
+ // Extact match case.
+ EXPECT_EQ(vector<Extent>{ ExtentForRange(30, 10) },
+ ExtentsSublist(extents, 10, 10));
+ EXPECT_EQ(vector<Extent>{ ExtentForRange(50, 10) },
+ ExtentsSublist(extents, 20, 10));
+
+ // Cases where the requested num_blocks is too big.
+ EXPECT_EQ(vector<Extent>{ ExtentForRange(53, 7) },
+ ExtentsSublist(extents, 23, 100));
+ EXPECT_EQ((vector<Extent>{ ExtentForRange(34, 6), ExtentForRange(50, 10) }),
+ ExtentsSublist(extents, 14, 100));
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/fake_filesystem.cc b/payload_generator/fake_filesystem.cc
new file mode 100644
index 0000000..3a6458d
--- /dev/null
+++ b/payload_generator/fake_filesystem.cc
@@ -0,0 +1,60 @@
+//
+// 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/fake_filesystem.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+FakeFilesystem::FakeFilesystem(uint64_t block_size, uint64_t block_count) :
+ block_size_(block_size),
+ block_count_(block_count) {
+}
+
+size_t FakeFilesystem::GetBlockSize() const {
+ return block_size_;
+}
+
+size_t FakeFilesystem::GetBlockCount() const {
+ return block_count_;
+}
+
+bool FakeFilesystem::GetFiles(std::vector<File>* files) const {
+ *files = files_;
+ return true;
+}
+
+void FakeFilesystem::AddFile(const std::string& filename,
+ const std::vector<Extent> extents) {
+ File file;
+ file.name = filename;
+ file.extents = extents;
+ for (const Extent& extent : extents) {
+ EXPECT_LE(0, extent.start_block());
+ EXPECT_LE(extent.start_block() + extent.num_blocks(), block_count_);
+ }
+ files_.push_back(file);
+}
+
+bool FakeFilesystem::LoadSettings(brillo::KeyValueStore* store) const {
+ if (minor_version_ < 0)
+ return false;
+ store->SetString("PAYLOAD_MINOR_VERSION", std::to_string(minor_version_));
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/fake_filesystem.h b/payload_generator/fake_filesystem.h
new file mode 100644
index 0000000..a14b8d3
--- /dev/null
+++ b/payload_generator/fake_filesystem.h
@@ -0,0 +1,68 @@
+//
+// 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_FAKE_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FAKE_FILESYSTEM_H_
+
+// A fake filesystem interface implementation allowing the user to add arbitrary
+// files/metadata.
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <string>
+#include <vector>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class FakeFilesystem : public FilesystemInterface {
+ public:
+ FakeFilesystem(uint64_t block_size, uint64_t block_count);
+ virtual ~FakeFilesystem() = default;
+
+ // FilesystemInterface overrides.
+ size_t GetBlockSize() const override;
+ size_t GetBlockCount() const override;
+ bool GetFiles(std::vector<File>* files) const override;
+ bool LoadSettings(brillo::KeyValueStore* store) const override;
+
+ // Fake methods.
+
+ // Add a file to the list of fake files.
+ void AddFile(const std::string& filename, const std::vector<Extent> extents);
+
+ // Sets the PAYLOAD_MINOR_VERSION key stored by LoadSettings(). Use a negative
+ // value to produce an error in LoadSettings().
+ void SetMinorVersion(int minor_version) {
+ minor_version_ = minor_version;
+ }
+
+ private:
+ FakeFilesystem() = default;
+
+ uint64_t block_size_;
+ uint64_t block_count_;
+ int minor_version_{-1};
+
+ std::vector<File> files_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeFilesystem);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_FAKE_FILESYSTEM_H_
diff --git a/payload_generator/filesystem_interface.h b/payload_generator/filesystem_interface.h
new file mode 100644
index 0000000..866c46b
--- /dev/null
+++ b/payload_generator/filesystem_interface.h
@@ -0,0 +1,95 @@
+//
+// 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_FILESYSTEM_INTERFACE_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FILESYSTEM_INTERFACE_H_
+
+// This class is used to abstract a filesystem and iterate the blocks
+// associated with the files and filesystem structures.
+// For the purposes of the update payload generation, a filesystem is a formated
+// partition composed by fixed-size blocks, since that's the interface used in
+// the update payload.
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/key_value_store.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class FilesystemInterface {
+ public:
+ // This represents a file or pseudo-file in the filesystem. It can include
+ // all sort of files, like symlinks, hardlinks, directories and even a file
+ // entry representing the metadata, free space, journaling data, etc.
+ struct File {
+ File() {
+ memset(&file_stat, 0, sizeof(file_stat));
+ }
+
+ // The stat struct for the file. This is invalid (inode 0) for some
+ // pseudo-files.
+ struct stat file_stat;
+
+ // The absolute path to the file inside the filesystem, for example,
+ // "/usr/bin/bash". For pseudo-files, like blocks associated to internal
+ // filesystem tables or free space, the path doesn't start with a /.
+ std::string name;
+
+ // The list of all physical blocks holding the data of this file in
+ // the same order as the logical data. All the block numbers shall be
+ // between 0 and GetBlockCount() - 1. The blocks are encoded in extents,
+ // indicating the starting block, and the number of consecutive blocks.
+ std::vector<Extent> extents;
+ };
+
+ virtual ~FilesystemInterface() = default;
+
+ // Returns the size of a block in the filesystem.
+ virtual size_t GetBlockSize() const = 0;
+
+ // Returns the number of blocks in the filesystem.
+ virtual size_t GetBlockCount() const = 0;
+
+ // Stores in |files| the list of files and pseudo-files in the filesystem. See
+ // FileInterface for details. The paths returned by this method shall not
+ // be repeated; but the same block could be present in more than one file as
+ // happens for example with hard-linked files, but not limited to those cases.
+ // Returns whether the function succeeded.
+ virtual bool GetFiles(std::vector<File>* files) const = 0;
+
+ // Load the image settings stored in the filesystem in the
+ // /etc/update_engine.conf file. Returns whether the settings were found.
+ virtual bool LoadSettings(brillo::KeyValueStore* store) const = 0;
+
+ protected:
+ FilesystemInterface() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FilesystemInterface);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_FILESYSTEM_INTERFACE_H_
diff --git a/payload_generator/full_update_generator.cc b/payload_generator/full_update_generator.cc
new file mode 100644
index 0000000..56a3ca8
--- /dev/null
+++ b/payload_generator/full_update_generator.cc
@@ -0,0 +1,203 @@
+//
+// 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/full_update_generator.h"
+
+#include <fcntl.h>
+#include <inttypes.h>
+
+#include <algorithm>
+#include <deque>
+#include <memory>
+
+#include <base/format_macros.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <base/synchronization/lock.h>
+#include <base/threading/simple_thread.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/bzip.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const size_t kDefaultFullChunkSize = 1024 * 1024; // 1 MiB
+
+// This class encapsulates a full update chunk processing thread work. The
+// processor reads a chunk of data from the input file descriptor and compresses
+// it. The processor will destroy itself when the work is done.
+class ChunkProcessor : public base::DelegateSimpleThread::Delegate {
+ public:
+ // Read a chunk of |size| bytes from |fd| starting at offset |offset|.
+ ChunkProcessor(int fd, off_t offset, size_t size,
+ BlobFileWriter* blob_file, AnnotatedOperation* aop)
+ : fd_(fd),
+ offset_(offset),
+ size_(size),
+ blob_file_(blob_file),
+ aop_(aop) {}
+ // We use a default move constructor since all the data members are POD types.
+ ChunkProcessor(ChunkProcessor&&) = default;
+ ~ChunkProcessor() override = default;
+
+ // Overrides DelegateSimpleThread::Delegate.
+ // Run() handles the read from |fd| in a thread-safe way, and stores the
+ // new operation to generate the region starting at |offset| of size |size|
+ // in the output operation |aop|. The associated blob data is stored in
+ // |blob_fd| and |blob_file_size| is updated.
+ void Run() override;
+
+ private:
+ bool ProcessChunk();
+
+ // Work parameters.
+ int fd_;
+ off_t offset_;
+ size_t size_;
+ BlobFileWriter* blob_file_;
+ AnnotatedOperation* aop_;
+
+ DISALLOW_COPY_AND_ASSIGN(ChunkProcessor);
+};
+
+void ChunkProcessor::Run() {
+ if (!ProcessChunk()) {
+ LOG(ERROR) << "Error processing region at " << offset_ << " of size "
+ << size_;
+ }
+}
+
+bool ChunkProcessor::ProcessChunk() {
+ brillo::Blob buffer_in_(size_);
+ brillo::Blob op_blob;
+ ssize_t bytes_read = -1;
+ TEST_AND_RETURN_FALSE(utils::PReadAll(fd_,
+ buffer_in_.data(),
+ buffer_in_.size(),
+ offset_,
+ &bytes_read));
+ TEST_AND_RETURN_FALSE(bytes_read == static_cast<ssize_t>(size_));
+ TEST_AND_RETURN_FALSE(BzipCompress(buffer_in_, &op_blob));
+
+ InstallOperation_Type op_type = InstallOperation::REPLACE_BZ;
+
+ if (op_blob.size() >= buffer_in_.size()) {
+ // A REPLACE is cheaper than a REPLACE_BZ in this case.
+ op_blob = std::move(buffer_in_);
+ op_type = InstallOperation::REPLACE;
+ }
+
+ TEST_AND_RETURN_FALSE(aop_->SetOperationBlob(&op_blob, blob_file_));
+ aop_->op.set_type(op_type);
+ return true;
+}
+
+} // namespace
+
+bool FullUpdateGenerator::GenerateOperations(
+ const PayloadGenerationConfig& config,
+ const PartitionConfig& old_part,
+ const PartitionConfig& new_part,
+ BlobFileWriter* blob_file,
+ vector<AnnotatedOperation>* aops) {
+ TEST_AND_RETURN_FALSE(new_part.ValidateExists());
+
+ // FullUpdateGenerator requires a positive chunk_size, otherwise there will
+ // be only one operation with the whole partition which should not be allowed.
+ // For performance reasons, we force a small default hard limit of 1 MiB. This
+ // limit can be changed in the config, and we will use the smaller of the two
+ // soft/hard limits.
+ size_t full_chunk_size;
+ if (config.hard_chunk_size >= 0) {
+ full_chunk_size = std::min(static_cast<size_t>(config.hard_chunk_size),
+ config.soft_chunk_size);
+ } else {
+ full_chunk_size = std::min(kDefaultFullChunkSize, config.soft_chunk_size);
+ LOG(INFO) << "No chunk_size provided, using the default chunk_size for the "
+ << "full operations: " << full_chunk_size << " bytes.";
+ }
+ TEST_AND_RETURN_FALSE(full_chunk_size > 0);
+ TEST_AND_RETURN_FALSE(full_chunk_size % config.block_size == 0);
+
+ size_t chunk_blocks = full_chunk_size / config.block_size;
+ size_t max_threads = std::max(sysconf(_SC_NPROCESSORS_ONLN), 4L);
+ LOG(INFO) << "Compressing partition " << new_part.name
+ << " from " << new_part.path << " splitting in chunks of "
+ << chunk_blocks << " blocks (" << config.block_size
+ << " bytes each) using " << max_threads << " threads";
+
+ int in_fd = open(new_part.path.c_str(), O_RDONLY, 0);
+ TEST_AND_RETURN_FALSE(in_fd >= 0);
+ ScopedFdCloser in_fd_closer(&in_fd);
+
+ // We potentially have all the ChunkProcessors in memory but only
+ // |max_threads| will actually hold a block in memory while we process.
+ size_t partition_blocks = new_part.size / config.block_size;
+ size_t num_chunks = (partition_blocks + chunk_blocks - 1) / chunk_blocks;
+ aops->resize(num_chunks);
+ vector<ChunkProcessor> chunk_processors;
+ chunk_processors.reserve(num_chunks);
+ blob_file->SetTotalBlobs(num_chunks);
+
+ for (size_t i = 0; i < num_chunks; ++i) {
+ size_t start_block = i * chunk_blocks;
+ // The last chunk could be smaller.
+ size_t num_blocks = std::min(chunk_blocks,
+ partition_blocks - i * chunk_blocks);
+
+ // Preset all the static information about the operations. The
+ // ChunkProcessor will set the rest.
+ AnnotatedOperation* aop = aops->data() + i;
+ aop->name = base::StringPrintf("<%s-operation-%" PRIuS ">",
+ new_part.name.c_str(), i);
+ Extent* dst_extent = aop->op.add_dst_extents();
+ dst_extent->set_start_block(start_block);
+ dst_extent->set_num_blocks(num_blocks);
+
+ chunk_processors.emplace_back(
+ in_fd,
+ static_cast<off_t>(start_block) * config.block_size,
+ num_blocks * config.block_size,
+ blob_file,
+ aop);
+ }
+
+ // Thread pool used for worker threads.
+ base::DelegateSimpleThreadPool thread_pool("full-update-generator",
+ max_threads);
+ thread_pool.Start();
+ for (ChunkProcessor& processor : chunk_processors)
+ 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) {
+ if (!aop.op.has_type())
+ return false;
+ }
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/full_update_generator.h b/payload_generator/full_update_generator.h
new file mode 100644
index 0000000..d722028
--- /dev/null
+++ b/payload_generator/full_update_generator.h
@@ -0,0 +1,53 @@
+//
+// 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_FULL_UPDATE_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_FULL_UPDATE_GENERATOR_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/payload_generator/operations_generator.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+class FullUpdateGenerator : public OperationsGenerator {
+ public:
+ FullUpdateGenerator() = default;
+
+ // OperationsGenerator override.
+ // Creates a full update for the target image defined in |config|. |config|
+ // must be a valid payload generation configuration for a full payload.
+ // Populates |aops|, with data about the update operations, and writes
+ // relevant data 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(FullUpdateGenerator);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_FULL_UPDATE_GENERATOR_H_
diff --git a/payload_generator/full_update_generator_unittest.cc b/payload_generator/full_update_generator_unittest.cc
new file mode 100644
index 0000000..d46346d
--- /dev/null
+++ b/payload_generator/full_update_generator_unittest.cc
@@ -0,0 +1,142 @@
+//
+// 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/full_update_generator.h"
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/extent_utils.h"
+
+using chromeos_update_engine::test_utils::FillWithData;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FullUpdateGeneratorTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ config_.is_delta = false;
+ config_.minor_version = kFullPayloadMinorVersion;
+ config_.hard_chunk_size = 128 * 1024;
+ config_.block_size = 4096;
+
+ EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_partition.XXXXXX",
+ &new_part_conf.path,
+ nullptr));
+ EXPECT_TRUE(utils::MakeTempFile("FullUpdateTest_blobs.XXXXXX",
+ &out_blobs_path_,
+ &out_blobs_fd_));
+
+ blob_file_.reset(new BlobFileWriter(out_blobs_fd_, &out_blobs_length_));
+ part_path_unlinker_.reset(new ScopedPathUnlinker(new_part_conf.path));
+ out_blobs_unlinker_.reset(new ScopedPathUnlinker(out_blobs_path_));
+ }
+
+ PayloadGenerationConfig config_;
+ PartitionConfig new_part_conf{"part"};
+
+ 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_};
+
+ std::unique_ptr<BlobFileWriter> blob_file_;
+ std::unique_ptr<ScopedPathUnlinker> part_path_unlinker_;
+ std::unique_ptr<ScopedPathUnlinker> out_blobs_unlinker_;
+
+ // FullUpdateGenerator under test.
+ FullUpdateGenerator generator_;
+};
+
+TEST_F(FullUpdateGeneratorTest, RunTest) {
+ brillo::Blob new_part(9 * 1024 * 1024);
+ FillWithData(&new_part);
+ new_part_conf.size = new_part.size();
+
+ EXPECT_TRUE(test_utils::WriteFileVector(new_part_conf.path, new_part));
+
+ EXPECT_TRUE(generator_.GenerateOperations(config_,
+ new_part_conf, // this is ignored
+ new_part_conf,
+ blob_file_.get(),
+ &aops));
+ int64_t new_part_chunks = new_part_conf.size / config_.hard_chunk_size;
+ EXPECT_EQ(new_part_chunks, aops.size());
+ for (off_t i = 0; i < new_part_chunks; ++i) {
+ EXPECT_EQ(1, aops[i].op.dst_extents_size());
+ EXPECT_EQ(i * config_.hard_chunk_size / config_.block_size,
+ aops[i].op.dst_extents(0).start_block()) << "i = " << i;
+ 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());
+ }
+ }
+}
+
+// Test that if the chunk size is not a divisor of the image size, it handles
+// correctly the last chunk of the partition.
+TEST_F(FullUpdateGeneratorTest, ChunkSizeTooBig) {
+ config_.hard_chunk_size = 1024 * 1024;
+ config_.soft_chunk_size = config_.hard_chunk_size;
+ brillo::Blob new_part(1536 * 1024); // 1.5 MiB
+ new_part_conf.size = new_part.size();
+
+ EXPECT_TRUE(test_utils::WriteFileVector(new_part_conf.path, new_part));
+
+ EXPECT_TRUE(generator_.GenerateOperations(config_,
+ new_part_conf, // this is ignored
+ new_part_conf,
+ blob_file_.get(),
+ &aops));
+ // new_part has one chunk and a half.
+ EXPECT_EQ(2, aops.size());
+ EXPECT_EQ(config_.hard_chunk_size / config_.block_size,
+ BlocksInExtents(aops[0].op.dst_extents()));
+ EXPECT_EQ((new_part.size() - config_.hard_chunk_size) / config_.block_size,
+ BlocksInExtents(aops[1].op.dst_extents()));
+}
+
+// Test that if the image size is much smaller than the chunk size, it handles
+// correctly the only chunk of the partition.
+TEST_F(FullUpdateGeneratorTest, ImageSizeTooSmall) {
+ brillo::Blob new_part(16 * 1024);
+ new_part_conf.size = new_part.size();
+
+ EXPECT_TRUE(test_utils::WriteFileVector(new_part_conf.path, new_part));
+
+ EXPECT_TRUE(generator_.GenerateOperations(config_,
+ new_part_conf, // this is ignored
+ new_part_conf,
+ blob_file_.get(),
+ &aops));
+
+ // new_part has less than one chunk.
+ EXPECT_EQ(1, aops.size());
+ EXPECT_EQ(new_part.size() / config_.block_size,
+ BlocksInExtents(aops[0].op.dst_extents()));
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
new file mode 100644
index 0000000..f462347
--- /dev/null
+++ b/payload_generator/generate_delta_main.cc
@@ -0,0 +1,546 @@
+//
+// 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 <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_split.h>
+#include <brillo/flag_helper.h>
+#include <brillo/key_value_store.h>
+
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/terminator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/update_metadata.pb.h"
+
+// This file contains a simple program that takes an old path, a new path,
+// 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::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+void ParseSignatureSizes(const string& signature_sizes_flag,
+ vector<int>* signature_sizes) {
+ signature_sizes->clear();
+ vector<string> split_strings;
+
+ base::SplitString(signature_sizes_flag, ':', &split_strings);
+ for (const string& str : split_strings) {
+ int size = 0;
+ bool parsing_successful = base::StringToInt(str, &size);
+ LOG_IF(FATAL, !parsing_successful)
+ << "Invalid signature size: " << str;
+
+ LOG_IF(FATAL, size != (2048 / 8)) <<
+ "Only signature sizes of 256 bytes are supported.";
+
+ signature_sizes->push_back(size);
+ }
+}
+
+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<int> &sizes,
+ const string& out_hash_file,
+ const string& out_metadata_hash_file,
+ const string& in_file) {
+ LOG(INFO) << "Calculating hash for signing.";
+ LOG_IF(FATAL, in_file.empty())
+ << "Must pass --in_file to calculate hash for signing.";
+ LOG_IF(FATAL, out_hash_file.empty())
+ << "Must pass --out_hash_file to calculate hash for signing.";
+
+ brillo::Blob payload_hash, metadata_hash;
+ CHECK(PayloadSigner::HashPayloadForSigning(in_file, sizes, &payload_hash,
+ &metadata_hash));
+ CHECK(utils::WriteFile(out_hash_file.c_str(), payload_hash.data(),
+ payload_hash.size()));
+ if (!out_metadata_hash_file.empty())
+ CHECK(utils::WriteFile(out_metadata_hash_file.c_str(), metadata_hash.data(),
+ metadata_hash.size()));
+
+ LOG(INFO) << "Done calculating hash for signing.";
+}
+
+void SignatureFileFlagToBlobs(const string& signature_file_flag,
+ vector<brillo::Blob>* signatures) {
+ vector<string> signature_files;
+ base::SplitString(signature_file_flag, ':', &signature_files);
+ for (const string& signature_file : signature_files) {
+ brillo::Blob signature;
+ CHECK(utils::ReadFile(signature_file, &signature));
+ signatures->push_back(signature);
+ }
+}
+
+void SignPayload(const string& in_file,
+ const string& out_file,
+ const string& payload_signature_file,
+ const string& metadata_signature_file,
+ const string& out_metadata_size_file) {
+ LOG(INFO) << "Signing payload.";
+ LOG_IF(FATAL, in_file.empty())
+ << "Must pass --in_file to sign payload.";
+ LOG_IF(FATAL, out_file.empty())
+ << "Must pass --out_file to sign payload.";
+ LOG_IF(FATAL, payload_signature_file.empty())
+ << "Must pass --signature_file to sign payload.";
+ vector<brillo::Blob> signatures, metadata_signatures;
+ SignatureFileFlagToBlobs(payload_signature_file, &signatures);
+ SignatureFileFlagToBlobs(metadata_signature_file, &metadata_signatures);
+ uint64_t final_metadata_size;
+ CHECK(PayloadSigner::AddSignatureToPayload(in_file, signatures,
+ metadata_signatures, out_file, &final_metadata_size));
+ LOG(INFO) << "Done signing payload. Final metadata size = "
+ << final_metadata_size;
+ if (!out_metadata_size_file.empty()) {
+ string metadata_size_string = std::to_string(final_metadata_size);
+ CHECK(utils::WriteFile(out_metadata_size_file.c_str(),
+ metadata_size_string.data(),
+ metadata_size_string.size()));
+ }
+}
+
+void VerifySignedPayload(const string& in_file,
+ const string& public_key) {
+ LOG(INFO) << "Verifying signed payload.";
+ LOG_IF(FATAL, in_file.empty())
+ << "Must pass --in_file to verify signed payload.";
+ LOG_IF(FATAL, public_key.empty())
+ << "Must pass --public_key to verify signed payload.";
+ CHECK(PayloadSigner::VerifySignedPayload(in_file, public_key));
+ LOG(INFO) << "Done verifying signed payload.";
+}
+
+// TODO(deymo): This function is likely broken for deltas minor version 2 or
+// newer. Move this function to a new file and make the delta_performer
+// integration tests use this instead.
+void ApplyDelta(const string& in_file,
+ const string& old_kernel,
+ const string& old_rootfs,
+ const string& prefs_dir) {
+ LOG(INFO) << "Applying delta.";
+ LOG_IF(FATAL, old_rootfs.empty())
+ << "Must pass --old_image to apply delta.";
+ Prefs prefs;
+ InstallPlan install_plan;
+ LOG(INFO) << "Setting up preferences under: " << prefs_dir;
+ LOG_IF(ERROR, !prefs.Init(base::FilePath(prefs_dir)))
+ << "Failed to initialize preferences.";
+ // Get original checksums
+ LOG(INFO) << "Calculating original checksums";
+ ImageConfig old_image;
+ old_image.partitions.emplace_back(kLegacyPartitionNameRoot);
+ old_image.partitions.back().path = old_rootfs;
+ old_image.partitions.emplace_back(kLegacyPartitionNameKernel);
+ old_image.partitions.back().path = old_kernel;
+ CHECK(old_image.LoadImageSize());
+ for (const auto& old_part : old_image.partitions) {
+ PartitionInfo part_info;
+ CHECK(diff_utils::InitializePartitionInfo(old_part, &part_info));
+ InstallPlan::Partition part;
+ part.name = old_part.name;
+ part.source_hash.assign(part_info.hash().begin(),
+ part_info.hash().end());
+ part.source_path = old_part.path;
+ // Apply the delta in-place to the old_part.
+ part.target_path = old_part.path;
+ install_plan.partitions.push_back(part);
+ }
+
+ DeltaPerformer performer(&prefs, nullptr, nullptr, nullptr, &install_plan);
+ brillo::Blob buf(1024 * 1024);
+ int fd = open(in_file.c_str(), O_RDONLY, 0);
+ CHECK_GE(fd, 0);
+ ScopedFdCloser fd_closer(&fd);
+ for (off_t offset = 0;; offset += buf.size()) {
+ ssize_t bytes_read;
+ CHECK(utils::PReadAll(fd, buf.data(), buf.size(), offset, &bytes_read));
+ if (bytes_read == 0)
+ break;
+ CHECK_EQ(performer.Write(buf.data(), bytes_read), bytes_read);
+ }
+ CHECK_EQ(performer.Close(), 0);
+ DeltaPerformer::ResetUpdateProgress(&prefs, false);
+ LOG(INFO) << "Done applying delta.";
+}
+
+int Main(int argc, char** argv) {
+ DEFINE_string(old_image, "", "Path to the old rootfs");
+ DEFINE_string(new_image, "", "Path to the new rootfs");
+ DEFINE_string(old_kernel, "", "Path to the old kernel partition image");
+ DEFINE_string(new_kernel, "", "Path to the new kernel partition image");
+ DEFINE_string(old_partitions, "",
+ "Path to the old partitions. To pass multiple partitions, use "
+ "a single argument with a colon between paths, e.g. "
+ "/path/to/part:/path/to/part2::/path/to/last_part . Path can "
+ "be empty, but it has to match the order of partition_names.");
+ DEFINE_string(new_partitions, "",
+ "Path to the new partitions. To pass multiple partitions, use "
+ "a single argument with a colon between paths, e.g. "
+ "/path/to/part:/path/to/part2:/path/to/last_part . Path has "
+ "to match the order of partition_names.");
+ DEFINE_string(partition_names,
+ string(kLegacyPartitionNameRoot) + ":" +
+ kLegacyPartitionNameKernel,
+ "Names of the partitions. To pass multiple names, use a single "
+ "argument with a colon between names, e.g. "
+ "name:name2:name3:last_name . Name can not be empty, and it "
+ "has to match the order of partitions.");
+ DEFINE_string(in_file, "",
+ "Path to input delta payload file used to hash/sign payloads "
+ "and apply delta over old_image (for debugging)");
+ DEFINE_string(out_file, "", "Path to output delta payload file");
+ DEFINE_string(out_hash_file, "", "Path to output hash file");
+ DEFINE_string(out_metadata_hash_file, "",
+ "Path to output metadata hash file");
+ DEFINE_string(out_metadata_size_file, "",
+ "Path to output metadata size file");
+ DEFINE_string(private_key, "", "Path to private key in .pem format");
+ DEFINE_string(public_key, "", "Path to public key in .pem format");
+ DEFINE_int32(public_key_version, -1,
+ "DEPRECATED. Key-check version # of client");
+ DEFINE_string(prefs_dir, "/tmp/update_engine_prefs",
+ "Preferences directory, used with apply_delta");
+ DEFINE_string(signature_size, "",
+ "Raw signature size used for hash calculation. "
+ "You may pass in multiple sizes by colon separating them. E.g. "
+ "2048:2048:4096 will assume 3 signatures, the first two with "
+ "2048 size and the last 4096.");
+ DEFINE_string(signature_file, "",
+ "Raw signature file to sign payload with. To pass multiple "
+ "signatures, use a single argument with a colon between paths, "
+ "e.g. /path/to/sig:/path/to/next:/path/to/last_sig . Each "
+ "signature will be assigned a client version, starting from "
+ "kSignatureOriginalVersion.");
+ DEFINE_string(metadata_signature_file, "",
+ "Raw signature file with the signature of the metadata hash. "
+ "To pass multiple signatures, use a single argument with a "
+ "colon between paths, "
+ "e.g. /path/to/sig:/path/to/next:/path/to/last_sig .");
+ DEFINE_int32(chunk_size, 200 * 1024 * 1024,
+ "Payload chunk size (-1 for whole files)");
+ DEFINE_uint64(rootfs_partition_size,
+ chromeos_update_engine::kRootFSPartitionSize,
+ "RootFS partition size for the image once installed");
+ DEFINE_uint64(major_version, 1,
+ "The major version of the payload being generated.");
+ DEFINE_int32(minor_version, -1,
+ "The minor version of the payload being generated "
+ "(-1 means autodetect).");
+
+ 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.");
+
+ 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. "
+ "Only allowed in major version 2 or newer.");
+
+ brillo::FlagHelper::Init(argc, argv,
+ "Generates a payload to provide to ChromeOS' update_engine.\n\n"
+ "This tool can create full payloads and also delta payloads if the src\n"
+ "image is provided. It also provides debugging options to apply, sign\n"
+ "and verify payloads.");
+ Terminator::Init();
+
+ logging::LoggingSettings log_settings;
+ log_settings.log_file = "delta_generator.log";
+ 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;
+
+ logging::InitLogging(log_settings);
+
+ vector<int> signature_sizes;
+ ParseSignatureSizes(FLAGS_signature_size, &signature_sizes);
+
+ if (!FLAGS_out_hash_file.empty() || !FLAGS_out_metadata_hash_file.empty()) {
+ CHECK(FLAGS_out_metadata_size_file.empty());
+ CalculateHashForSigning(signature_sizes, FLAGS_out_hash_file,
+ FLAGS_out_metadata_hash_file, FLAGS_in_file);
+ return 0;
+ }
+ if (!FLAGS_signature_file.empty()) {
+ SignPayload(FLAGS_in_file, FLAGS_out_file, FLAGS_signature_file,
+ FLAGS_metadata_signature_file, FLAGS_out_metadata_size_file);
+ return 0;
+ }
+ if (!FLAGS_public_key.empty()) {
+ LOG_IF(WARNING, FLAGS_public_key_version != -1)
+ << "--public_key_version is deprecated and ignored.";
+ VerifySignedPayload(FLAGS_in_file, FLAGS_public_key);
+ return 0;
+ }
+ if (!FLAGS_in_file.empty()) {
+ ApplyDelta(FLAGS_in_file, FLAGS_old_kernel, FLAGS_old_image,
+ FLAGS_prefs_dir);
+ return 0;
+ }
+
+ // A payload generation was requested. Convert the flags to a
+ // PayloadGenerationConfig.
+ PayloadGenerationConfig payload_config;
+ vector<string> partition_names, old_partitions, new_partitions;
+
+ base::SplitString(FLAGS_partition_names, ':', &partition_names);
+ 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] != kLegacyPartitionNameRoot ||
+ partition_names[1] != kLegacyPartitionNameKernel)
+ << "To support non-default partition name, please use the "
+ << "--new_partitions flag and major version 2.";
+ }
+
+ if (!FLAGS_new_partitions.empty()) {
+ LOG_IF(FATAL, !FLAGS_new_image.empty() || !FLAGS_new_kernel.empty())
+ << "--new_image and --new_kernel are deprecated, please use "
+ << "--new_partitions for all partitions.";
+ base::SplitString(FLAGS_new_partitions, ':', &new_partitions);
+ CHECK(partition_names.size() == new_partitions.size());
+
+ payload_config.is_delta = !FLAGS_old_partitions.empty();
+ LOG_IF(FATAL, !FLAGS_old_image.empty() || !FLAGS_old_kernel.empty())
+ << "--old_image and --old_kernel are deprecated, please use "
+ << "--old_partitions if you are using --new_partitions.";
+ } else {
+ new_partitions = {FLAGS_new_image, FLAGS_new_kernel};
+ LOG(WARNING) << "--new_partitions is empty, using deprecated --new_image "
+ << "and --new_kernel flags.";
+
+ payload_config.is_delta = !FLAGS_old_image.empty() ||
+ !FLAGS_old_kernel.empty();
+ LOG_IF(FATAL, !FLAGS_old_partitions.empty())
+ << "Please use --new_partitions if you are using --old_partitions.";
+ }
+ for (size_t i = 0; i < partition_names.size(); i++) {
+ LOG_IF(FATAL, partition_names[i].empty())
+ << "Partition name can't be empty, see --partition_names.";
+ payload_config.target.partitions.emplace_back(partition_names[i]);
+ payload_config.target.partitions.back().path = new_partitions[i];
+ }
+
+ if (payload_config.is_delta) {
+ if (!FLAGS_old_partitions.empty()) {
+ base::SplitString(FLAGS_old_partitions, ':', &old_partitions);
+ CHECK(old_partitions.size() == new_partitions.size());
+ } else {
+ old_partitions = {FLAGS_old_image, FLAGS_old_kernel};
+ LOG(WARNING) << "--old_partitions is empty, using deprecated --old_image "
+ << "and --old_kernel flags.";
+ }
+ for (size_t i = 0; i < partition_names.size(); i++) {
+ payload_config.source.partitions.emplace_back(partition_names[i]);
+ payload_config.source.partitions.back().path = old_partitions[i];
+ }
+ }
+
+ 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));
+ }
+
+ // Use the default soft_chunk_size defined in the config.
+ payload_config.hard_chunk_size = FLAGS_chunk_size;
+ payload_config.block_size = kBlockSize;
+
+ // The partition size is never passed to the delta_generator, so we
+ // need to detect those from the provided files.
+ if (payload_config.is_delta) {
+ CHECK(payload_config.source.LoadImageSize());
+ }
+ CHECK(payload_config.target.LoadImageSize());
+
+ 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) {
+ // Avoid opening the filesystem interface for full payloads.
+ for (PartitionConfig& part : payload_config.target.partitions)
+ CHECK(part.OpenFilesystem());
+ for (PartitionConfig& part : payload_config.source.partitions)
+ CHECK(part.OpenFilesystem());
+ }
+
+ payload_config.major_version = FLAGS_major_version;
+ LOG(INFO) << "Using provided major_version=" << FLAGS_major_version;
+
+ if (FLAGS_minor_version == -1) {
+ // Autodetect minor_version by looking at the update_engine.conf in the old
+ // image.
+ if (payload_config.is_delta) {
+ payload_config.minor_version = kInPlaceMinorPayloadVersion;
+ brillo::KeyValueStore store;
+ uint32_t minor_version;
+ for (const PartitionConfig& part : payload_config.source.partitions) {
+ if (part.fs_interface && part.fs_interface->LoadSettings(&store) &&
+ utils::GetMinorVersion(store, &minor_version)) {
+ payload_config.minor_version = minor_version;
+ break;
+ }
+ }
+ } else {
+ payload_config.minor_version = kFullPayloadMinorVersion;
+ }
+ LOG(INFO) << "Auto-detected minor_version=" << payload_config.minor_version;
+ } else {
+ payload_config.minor_version = FLAGS_minor_version;
+ LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version;
+ }
+
+ if (payload_config.is_delta) {
+ LOG(INFO) << "Generating delta update";
+ } else {
+ LOG(INFO) << "Generating full update";
+ }
+
+ // From this point, all the options have been parsed.
+ if (!payload_config.Validate()) {
+ LOG(ERROR) << "Invalid options passed. See errors above.";
+ return 1;
+ }
+
+ uint64_t metadata_size;
+ if (!GenerateUpdatePayloadFile(payload_config,
+ FLAGS_out_file,
+ FLAGS_private_key,
+ &metadata_size)) {
+ return 1;
+ }
+ if (!FLAGS_out_metadata_size_file.empty()) {
+ string metadata_size_string = std::to_string(metadata_size);
+ CHECK(utils::WriteFile(FLAGS_out_metadata_size_file.c_str(),
+ metadata_size_string.data(),
+ metadata_size_string.size()));
+ }
+ return 0;
+}
+
+} // namespace
+
+} // namespace chromeos_update_engine
+
+int main(int argc, char** argv) {
+ return chromeos_update_engine::Main(argc, argv);
+}
diff --git a/payload_generator/graph_types.cc b/payload_generator/graph_types.cc
new file mode 100644
index 0000000..7da76f7
--- /dev/null
+++ b/payload_generator/graph_types.cc
@@ -0,0 +1,23 @@
+//
+// 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/graph_types.h"
+
+namespace chromeos_update_engine {
+
+const Vertex::Index Vertex::kInvalidIndex = static_cast<Vertex::Index>(-1);
+
+} // chromeos_update_engine
diff --git a/payload_generator/graph_types.h b/payload_generator/graph_types.h
new file mode 100644
index 0000000..beee4eb
--- /dev/null
+++ b/payload_generator/graph_types.h
@@ -0,0 +1,90 @@
+//
+// 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;
+COMPILE_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
new file mode 100644
index 0000000..2d5fb63
--- /dev/null
+++ b/payload_generator/graph_utils.cc
@@ -0,0 +1,141 @@
+//
+// 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 (int i = 0, e = field.size(); i != e; ++i) {
+ LOG(INFO) << header << "(" << GetElement(field, i).start_block() << ", "
+ << GetElement(field, i).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
new file mode 100644
index 0000000..b32e666
--- /dev/null
+++ b/payload_generator/graph_utils.h
@@ -0,0 +1,56 @@
+//
+// 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
new file mode 100644
index 0000000..7d3dbe3
--- /dev/null
+++ b/payload_generator/graph_utils_unittest.cc
@@ -0,0 +1,95 @@
+//
+// 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(0, extents.size());
+ AppendBlockToExtents(&extents, 0);
+ EXPECT_EQ(1, extents.size());
+ AppendBlockToExtents(&extents, 1);
+ AppendBlockToExtents(&extents, 2);
+ EXPECT_EQ(1, extents.size());
+ AppendBlockToExtents(&extents, 4);
+
+ EXPECT_EQ(2, extents.size());
+ EXPECT_EQ(0, extents[0].start_block());
+ EXPECT_EQ(3, extents[0].num_blocks());
+ EXPECT_EQ(4, extents[1].start_block());
+ EXPECT_EQ(1, extents[1].num_blocks());
+
+ EXPECT_EQ(4, graph_utils::EdgeWeight(graph, make_pair(0, 1)));
+}
+
+
+TEST(GraphUtilsTest, DepsTest) {
+ Graph graph(3);
+
+ graph_utils::AddReadBeforeDep(&graph[0], 1, 3);
+ EXPECT_EQ(1, graph[0].out_edges.size());
+ {
+ Extent& extent = graph[0].out_edges[1].extents[0];
+ EXPECT_EQ(3, extent.start_block());
+ EXPECT_EQ(1, extent.num_blocks());
+ }
+ graph_utils::AddReadBeforeDep(&graph[0], 1, 4);
+ EXPECT_EQ(1, graph[0].out_edges.size());
+ {
+ Extent& extent = graph[0].out_edges[1].extents[0];
+ EXPECT_EQ(3, extent.start_block());
+ EXPECT_EQ(2, extent.num_blocks());
+ }
+ graph_utils::AddReadBeforeDepExtents(&graph[2], 1,
+ vector<Extent>(1, ExtentForRange(5, 2)));
+ EXPECT_EQ(1, graph[2].out_edges.size());
+ {
+ Extent& extent = graph[2].out_edges[1].extents[0];
+ EXPECT_EQ(5, extent.start_block());
+ EXPECT_EQ(2, 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(0, graph[2].out_edges.size());
+
+ EXPECT_EQ(1, graph[0].out_edges.size());
+ graph_utils::DropIncomingEdgesTo(&graph, 1);
+ EXPECT_EQ(0, graph[0].out_edges.size());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/inplace_generator.cc b/payload_generator/inplace_generator.cc
new file mode 100644
index 0000000..2111748
--- /dev/null
+++ b/payload_generator/inplace_generator.cc
@@ -0,0 +1,823 @@
+//
+// 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 "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 {
+
+// 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 (int i = 0, e = extents.size(); i < e; ++i) {
+ Extent extent = GetElement(extents, i);
+ 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 (utils::SetContainsKey(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,
+ (*graph)[cut.old_dst].aop.name,
+ -1, // chunk_blocks, forces to have a single operation.
+ blob_file,
+ false)); // src_ops_allowed
+ 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 int extents_size =
+ (field == READER) ? operation.src_extents_size() :
+ operation.dst_extents_size();
+ 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 (int i = 0; i < extents_size; i++) {
+ const Extent& extent = extents.Get(i);
+ 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& 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(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);
+
+ 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 == kLegacyPartitionNameRoot)
+ 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,
+ blob_file,
+ false)); // src_ops_allowed
+ LOG(INFO) << "Done reading " << new_part.name;
+
+ TEST_AND_RETURN_FALSE(
+ ResolveReadAfterWriteDependencies(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
new file mode 100644
index 0000000..4839824
--- /dev/null
+++ b/payload_generator/inplace_generator.h
@@ -0,0 +1,242 @@
+//
+// 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& 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
new file mode 100644
index 0000000..9932ef9
--- /dev/null
+++ b/payload_generator/inplace_generator_unittest.cc
@@ -0,0 +1,658 @@
+//
+// 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 <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/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_));
+ }
+
+ // 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(11, op.src_extents(0).start_block());
+ EXPECT_EQ(1, op.src_extents(0).num_blocks());
+ EXPECT_EQ(13, op.src_extents(1).start_block());
+ EXPECT_EQ(1, op.src_extents(1).num_blocks());
+ EXPECT_EQ(6, op.src_extents(2).start_block());
+ EXPECT_EQ(1, op.src_extents(2).num_blocks());
+ EXPECT_EQ(kSparseHole, op.src_extents(3).start_block());
+ EXPECT_EQ(4, op.src_extents(3).num_blocks());
+ EXPECT_EQ(10, op.src_extents(4).start_block());
+ EXPECT_EQ(1, op.src_extents(4).num_blocks());
+ EXPECT_EQ(14, op.src_extents(5).start_block());
+ EXPECT_EQ(1, op.src_extents(5).num_blocks());
+ EXPECT_EQ(8, op.src_extents(6).start_block());
+ EXPECT_EQ(2, 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(1, 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(3, 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(2, 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(2, graph[0].aop.op.src_extents(0).num_blocks());
+ EXPECT_EQ(7, graph[0].aop.op.src_extents(1).start_block());
+ EXPECT_EQ(1, 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(1, graph[0].aop.op.dst_extents(0).start_block());
+ EXPECT_EQ(2, graph[0].aop.op.dst_extents(0).num_blocks());
+ EXPECT_EQ(4, graph[0].aop.op.dst_extents(1).start_block());
+ EXPECT_EQ(1, graph[0].aop.op.dst_extents(1).num_blocks());
+
+ // Ensure it only depends on the next node and the new temp node
+ EXPECT_EQ(2, 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(1, graph[1].aop.op.src_extents(0).start_block());
+ EXPECT_EQ(2, graph[1].aop.op.src_extents(0).num_blocks());
+ EXPECT_EQ(4, graph[1].aop.op.src_extents(1).start_block());
+ EXPECT_EQ(1, graph[1].aop.op.src_extents(1).num_blocks());
+
+ EXPECT_EQ(2, graph[1].aop.op.dst_extents_size());
+ EXPECT_EQ(3, graph[1].aop.op.dst_extents(0).start_block());
+ EXPECT_EQ(1, graph[1].aop.op.dst_extents(0).num_blocks());
+ EXPECT_EQ(5, graph[1].aop.op.dst_extents(1).start_block());
+ EXPECT_EQ(2, graph[1].aop.op.dst_extents(1).num_blocks());
+
+ // Ensure it only depends on the next node
+ EXPECT_EQ(1, 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(2, graph[1].aop.op.src_extents(0).start_block());
+ EXPECT_EQ(1, 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(12, 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(0, vertex.aop.op.data_offset());
+ EXPECT_EQ(0, vertex.aop.op.data_length());
+ EXPECT_EQ(1, vertex.aop.op.dst_extents_size());
+ EXPECT_EQ(12, vertex.aop.op.dst_extents(0).start_block());
+ EXPECT_EQ(34, 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_blocs=%" PRIu64,
+ part_blocks));
+ vector<AnnotatedOperation> result_aops = aops;
+ EXPECT_TRUE(InplaceGenerator::ResolveReadAfterWriteDependencies(
+ part, part_blocks * block_size, block_size, blob_file_.get(),
+ &result_aops));
+
+ size_t full_ops = 0;
+ for (const auto& aop : result_aops) {
+ if (aop.op.type() == InstallOperation::REPLACE ||
+ aop.op.type() == InstallOperation::REPLACE_BZ) {
+ full_ops++;
+ }
+
+ if (aop.op.type() != InstallOperation::MOVE)
+ continue;
+ for (const Extent& extent : aop.op.src_extents()) {
+ EXPECT_NE(0, extent.start_block()) << "On src extents for aop: " << aop;
+ }
+ for (const Extent& extent : aop.op.dst_extents()) {
+ EXPECT_NE(0, 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 ? 2 : 1, full_ops);
+
+ if (HasNonfatalFailure()) {
+ LOG(INFO) << "Result operation list:";
+ for (const auto& aop : result_aops) {
+ LOG(INFO) << aop;
+ }
+ }
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/operations_generator.h b/payload_generator/operations_generator.h
new file mode 100644
index 0000000..9127d7b
--- /dev/null
+++ b/payload_generator/operations_generator.h
@@ -0,0 +1,59 @@
+//
+// 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_OPERATIONS_GENERATOR_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
+
+#include <vector>
+
+#include <base/macros.h>
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/blob_file_writer.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+
+namespace chromeos_update_engine {
+
+class OperationsGenerator {
+ public:
+ virtual ~OperationsGenerator() = default;
+
+ // This method generates a list of operations to update from the partition
+ // |old_part| to |new_part| and stores the generated operations in |aops|.
+ // These operations are generated based on the given |config|.
+ // The operations should be applied in the order specified in the list, and
+ // they respect the payload version and type (delta or full) specified in
+ // |config|.
+ // The operations generated will refer to offsets in the file |blob_file|,
+ // where this function stores the output, but not necessarily in the same
+ // order as they appear in the |aops|.
+ virtual bool GenerateOperations(
+ const PayloadGenerationConfig& config,
+ const PartitionConfig& old_part,
+ const PartitionConfig& new_part,
+ BlobFileWriter* blob_file,
+ std::vector<AnnotatedOperation>* aops) = 0;
+
+ protected:
+ OperationsGenerator() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(OperationsGenerator);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_OPERATIONS_GENERATOR_H_
diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
new file mode 100644
index 0000000..d268ab8
--- /dev/null
+++ b/payload_generator/payload_file.cc
@@ -0,0 +1,356 @@
+//
+// 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/payload_file.h"
+
+#include <endian.h>
+
+#include <algorithm>
+
+#include "update_engine/common/hash_calculator.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"
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/delta_diff_utils.h"
+#include "update_engine/payload_generator/payload_signer.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+struct DeltaObject {
+ DeltaObject(const string& in_name, const int in_type, const off_t in_size)
+ : name(in_name),
+ type(in_type),
+ size(in_size) {}
+ bool operator <(const DeltaObject& object) const {
+ return (size != object.size) ? (size < object.size) : (name < object.name);
+ }
+ string name;
+ int type;
+ off_t size;
+};
+
+// Writes the uint64_t passed in in host-endian to the file as big-endian.
+// Returns true on success.
+bool WriteUint64AsBigEndian(FileWriter* writer, const uint64_t value) {
+ uint64_t value_be = htobe64(value);
+ TEST_AND_RETURN_FALSE(writer->Write(&value_be, sizeof(value_be)));
+ return true;
+}
+
+} // namespace
+
+bool PayloadFile::Init(const PayloadGenerationConfig& config) {
+ major_version_ = config.major_version;
+ TEST_AND_RETURN_FALSE(major_version_ == kChromeOSMajorPayloadVersion ||
+ major_version_ == kBrilloMajorPayloadVersion);
+ manifest_.set_minor_version(config.minor_version);
+
+ 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);
+ 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 = { kLegacyPartitionNameRoot,
+ kLegacyPartitionNameKernel };
+ TEST_AND_RETURN_FALSE(part_vec_.size() < part_order.size());
+ TEST_AND_RETURN_FALSE(new_conf.name == part_order[part_vec_.size()]);
+ }
+ Partition part;
+ part.name = new_conf.name;
+ part.aops = aops;
+ part.postinstall = new_conf.postinstall;
+ // Initialize the PartitionInfo objects if present.
+ if (!old_conf.path.empty())
+ TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(old_conf,
+ &part.old_info));
+ TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(new_conf,
+ &part.new_info));
+ part_vec_.push_back(std::move(part));
+ return true;
+}
+
+bool PayloadFile::WritePayload(const string& payload_file,
+ const string& data_blobs_path,
+ 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));
+
+ // Check that install op blobs are in order.
+ uint64_t next_blob_offset = 0;
+ for (const auto& part : part_vec_) {
+ for (const auto& aop : part.aops) {
+ if (!aop.op.has_data_offset())
+ continue;
+ if (aop.op.data_offset() != next_blob_offset) {
+ LOG(FATAL) << "bad blob offset! " << aop.op.data_offset() << " != "
+ << next_blob_offset;
+ }
+ next_blob_offset += aop.op.data_length();
+ }
+ }
+
+ // 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);
+ }
+ for (const AnnotatedOperation& aop : part.aops) {
+ *partition->add_operations() = aop.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;
+ } else {
+ // major_version_ == kChromeOSMajorPayloadVersion
+ if (part.name == kLegacyPartitionNameKernel) {
+ 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;
+ }
+ }
+ }
+
+ // Signatures appear at the end of the blobs. Note the offset in the
+ // manifest_.
+ uint64_t signature_blob_length = 0;
+ if (!private_key_path.empty()) {
+ TEST_AND_RETURN_FALSE(
+ PayloadSigner::SignatureBlobLength(vector<string>(1, private_key_path),
+ &signature_blob_length));
+ PayloadSigner::AddSignatureToManifest(
+ next_blob_offset, signature_blob_length,
+ major_version_ == kChromeOSMajorPayloadVersion, &manifest_);
+ }
+
+ // Serialize protobuf
+ string serialized_manifest;
+ TEST_AND_RETURN_FALSE(manifest_.AppendToString(&serialized_manifest));
+
+ uint64_t metadata_size =
+ sizeof(kDeltaMagic) + 2 * sizeof(uint64_t) + serialized_manifest.size();
+
+ LOG(INFO) << "Writing final delta file header...";
+ DirectFileWriter writer;
+ TEST_AND_RETURN_FALSE_ERRNO(writer.Open(payload_file.c_str(),
+ O_WRONLY | O_CREAT | O_TRUNC,
+ 0644) == 0);
+ ScopedFileWriterCloser writer_closer(&writer);
+
+ // Write header
+ TEST_AND_RETURN_FALSE(writer.Write(kDeltaMagic, sizeof(kDeltaMagic)));
+
+ // Write major version number
+ TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer, major_version_));
+
+ // Write protobuf length
+ 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(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... "
+ << serialized_manifest.size();
+ TEST_AND_RETURN_FALSE(writer.Write(serialized_manifest.data(),
+ serialized_manifest.size()));
+
+ // Write metadata signature blob.
+ if (major_version_ == kBrilloMajorPayloadVersion &&
+ !private_key_path.empty()) {
+ brillo::Blob metadata_hash, metadata_signature;
+ TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfFile(payload_file,
+ metadata_size,
+ &metadata_hash));
+ TEST_AND_RETURN_FALSE(
+ PayloadSigner::SignHashWithKeys(metadata_hash,
+ vector<string>(1, private_key_path),
+ &metadata_signature));
+ TEST_AND_RETURN_FALSE(writer.Write(metadata_signature.data(),
+ metadata_signature.size()));
+ }
+
+ // Append the data blobs
+ LOG(INFO) << "Writing final delta file data blobs...";
+ int blobs_fd = open(ordered_blobs_path.c_str(), O_RDONLY, 0);
+ ScopedFdCloser blobs_fd_closer(&blobs_fd);
+ TEST_AND_RETURN_FALSE(blobs_fd >= 0);
+ for (;;) {
+ vector<char> buf(1024 * 1024);
+ ssize_t rc = read(blobs_fd, buf.data(), buf.size());
+ if (0 == rc) {
+ // EOF
+ break;
+ }
+ TEST_AND_RETURN_FALSE_ERRNO(rc > 0);
+ TEST_AND_RETURN_FALSE(writer.Write(buf.data(), rc));
+ }
+
+ // Write payload signature blob.
+ if (!private_key_path.empty()) {
+ LOG(INFO) << "Signing the update...";
+ brillo::Blob signature_blob;
+ TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(
+ payload_file,
+ vector<string>(1, private_key_path),
+ metadata_size,
+ metadata_signature_size,
+ metadata_size + metadata_signature_size + manifest_.signatures_offset(),
+ &signature_blob));
+ TEST_AND_RETURN_FALSE(writer.Write(signature_blob.data(),
+ signature_blob.size()));
+ }
+
+ ReportPayloadUsage(metadata_size);
+ *metadata_size_out = metadata_size;
+ return true;
+}
+
+bool PayloadFile::ReorderDataBlobs(
+ const string& data_blobs_path,
+ const string& new_data_blobs_path) {
+ int in_fd = open(data_blobs_path.c_str(), O_RDONLY, 0);
+ TEST_AND_RETURN_FALSE_ERRNO(in_fd >= 0);
+ ScopedFdCloser in_fd_closer(&in_fd);
+
+ DirectFileWriter writer;
+ TEST_AND_RETURN_FALSE(
+ writer.Open(new_data_blobs_path.c_str(),
+ O_WRONLY | O_TRUNC | O_CREAT,
+ 0644) == 0);
+ ScopedFileWriterCloser writer_closer(&writer);
+ uint64_t out_file_size = 0;
+
+ for (auto& part: part_vec_) {
+ for (AnnotatedOperation& aop : part.aops) {
+ if (!aop.op.has_data_offset())
+ continue;
+ CHECK(aop.op.has_data_length());
+ brillo::Blob buf(aop.op.data_length());
+ ssize_t rc = pread(in_fd, buf.data(), buf.size(), aop.op.data_offset());
+ TEST_AND_RETURN_FALSE(rc == static_cast<ssize_t>(buf.size()));
+
+ // Add the hash of the data blobs for this operation
+ TEST_AND_RETURN_FALSE(AddOperationHash(&aop.op, buf));
+
+ aop.op.set_data_offset(out_file_size);
+ TEST_AND_RETURN_FALSE(writer.Write(buf.data(), buf.size()));
+ out_file_size += buf.size();
+ }
+ }
+ return true;
+}
+
+bool PayloadFile::AddOperationHash(InstallOperation* op,
+ const brillo::Blob& buf) {
+ HashCalculator hasher;
+ TEST_AND_RETURN_FALSE(hasher.Update(buf.data(), buf.size()));
+ TEST_AND_RETURN_FALSE(hasher.Finalize());
+ const brillo::Blob& hash = hasher.raw_hash();
+ op->set_data_sha256_hash(hash.data(), hash.size());
+ return true;
+}
+
+void PayloadFile::ReportPayloadUsage(uint64_t metadata_size) const {
+ vector<DeltaObject> objects;
+ off_t total_size = 0;
+
+ for (const auto& part : part_vec_) {
+ for (const AnnotatedOperation& aop : part.aops) {
+ objects.push_back(DeltaObject(aop.name,
+ aop.op.type(),
+ aop.op.data_length()));
+ total_size += aop.op.data_length();
+ }
+ }
+
+ objects.push_back(DeltaObject("<manifest-metadata>",
+ -1,
+ metadata_size));
+ total_size += metadata_size;
+
+ std::sort(objects.begin(), objects.end());
+
+ static const char kFormatString[] = "%6.2f%% %10jd %-10s %s\n";
+ for (const DeltaObject& object : objects) {
+ fprintf(
+ stderr, kFormatString,
+ object.size * 100.0 / total_size,
+ static_cast<intmax_t>(object.size),
+ (object.type >= 0 ? InstallOperationTypeName(
+ static_cast<InstallOperation_Type>(object.type))
+ : "-"),
+ object.name.c_str());
+ }
+ fprintf(stderr, kFormatString,
+ 100.0, static_cast<intmax_t>(total_size), "", "<total>");
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/payload_file.h b/payload_generator/payload_file.h
new file mode 100644
index 0000000..7cc792a
--- /dev/null
+++ b/payload_generator/payload_file.h
@@ -0,0 +1,105 @@
+//
+// 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_PAYLOAD_FILE_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_FILE_H_
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/payload_generator/annotated_operation.h"
+#include "update_engine/payload_generator/payload_generation_config.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// Class to handle the creation of a payload file. This class is the only one
+// dealing with writing the payload and its format, but has no logic about what
+// should be on it.
+class PayloadFile {
+ public:
+ // Initialize the payload file with the payload generation config. It computes
+ // required hashes of the requested partitions.
+ bool Init(const PayloadGenerationConfig& config);
+
+ // Add a partition to the payload manifest. Including partition name, list of
+ // operations and partition info. The operations in |aops|
+ // 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);
+
+ // 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
+ // payload file to match the order of the operations. The size of the metadata
+ // section of the payload is stored in |metadata_size_out|.
+ bool WritePayload(const std::string& payload_file,
+ const std::string& data_blobs_path,
+ const std::string& private_key_path,
+ uint64_t* metadata_size_out);
+
+ private:
+ FRIEND_TEST(PayloadFileTest, ReorderBlobsTest);
+
+ // 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
+ // blob will not be available at payload creation time. So, update_engine will
+ // gracefully ignore the dummy signature operation.
+ static bool AddOperationHash(InstallOperation* op, const brillo::Blob& buf);
+
+ // Install operations in the manifest may reference data blobs, which
+ // are in data_blobs_path. This function creates a new data blobs file
+ // with the data blobs in the same order as the referencing install
+ // operations in the manifest. E.g. if manifest[0] has a data blob
+ // "X" at offset 1, manifest[1] has a data blob "Y" at offset 0,
+ // and data_blobs_path's file contains "YX", new_data_blobs_path
+ // will set to be a file that contains "XY".
+ bool ReorderDataBlobs(const std::string& data_blobs_path,
+ const std::string& new_data_blobs_path);
+
+ // Print in stderr the Payload usage report.
+ void ReportPayloadUsage(uint64_t metadata_size) const;
+
+ // The major_version of the requested payload.
+ uint64_t major_version_;
+
+ DeltaArchiveManifest manifest_;
+
+ // Struct has necessary information to write PartitionUpdate in protobuf.
+ struct Partition {
+ // The name of the partition.
+ std::string name;
+
+ // The operations to be performed to this partition.
+ std::vector<AnnotatedOperation> aops;
+
+ PartitionInfo old_info;
+ PartitionInfo new_info;
+
+ PostInstallConfig postinstall;
+ };
+
+ std::vector<Partition> part_vec_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_FILE_H_
diff --git a/payload_generator/payload_file_unittest.cc b/payload_generator/payload_file_unittest.cc
new file mode 100644
index 0000000..4d18adf
--- /dev/null
+++ b/payload_generator/payload_file_unittest.cc
@@ -0,0 +1,94 @@
+//
+// 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/payload_file.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class PayloadFileTest : public ::testing::Test {
+ protected:
+ PayloadFile payload_;
+};
+
+TEST_F(PayloadFileTest, ReorderBlobsTest) {
+ string orig_blobs;
+ EXPECT_TRUE(utils::MakeTempFile("ReorderBlobsTest.orig.XXXXXX", &orig_blobs,
+ nullptr));
+ ScopedPathUnlinker orig_blobs_unlinker(orig_blobs);
+
+ // The operations have three blob and one gap (the whitespace):
+ // Rootfs operation 1: [8, 3] bcd
+ // Rootfs operation 2: [7, 1] a
+ // Kernel operation 1: [0, 6] kernel
+ string orig_data = "kernel abcd";
+ EXPECT_TRUE(
+ utils::WriteFile(orig_blobs.c_str(), orig_data.data(), orig_data.size()));
+
+ string new_blobs;
+ EXPECT_TRUE(
+ utils::MakeTempFile("ReorderBlobsTest.new.XXXXXX", &new_blobs, nullptr));
+ ScopedPathUnlinker new_blobs_unlinker(new_blobs);
+
+ payload_.part_vec_.resize(2);
+
+ vector<AnnotatedOperation> aops;
+ AnnotatedOperation aop;
+ aop.op.set_data_offset(8);
+ aop.op.set_data_length(3);
+ aops.push_back(aop);
+
+ aop.op.set_data_offset(7);
+ aop.op.set_data_length(1);
+ aops.push_back(aop);
+ payload_.part_vec_[0].aops = aops;
+
+ aop.op.set_data_offset(0);
+ aop.op.set_data_length(6);
+ payload_.part_vec_[1].aops = {aop};
+
+ EXPECT_TRUE(payload_.ReorderDataBlobs(orig_blobs, new_blobs));
+
+ const vector<AnnotatedOperation>& part0_aops = payload_.part_vec_[0].aops;
+ const vector<AnnotatedOperation>& part1_aops = payload_.part_vec_[1].aops;
+ string new_data;
+ EXPECT_TRUE(utils::ReadFile(new_blobs, &new_data));
+ // Kernel blobs should appear at the end.
+ EXPECT_EQ("bcdakernel", new_data);
+
+ EXPECT_EQ(2, part0_aops.size());
+ EXPECT_EQ(0, part0_aops[0].op.data_offset());
+ EXPECT_EQ(3, part0_aops[0].op.data_length());
+ EXPECT_EQ(3, part0_aops[1].op.data_offset());
+ EXPECT_EQ(1, part0_aops[1].op.data_length());
+
+ EXPECT_EQ(1, part1_aops.size());
+ EXPECT_EQ(4, part1_aops[0].op.data_offset());
+ EXPECT_EQ(6, part1_aops[0].op.data_length());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
new file mode 100644
index 0000000..813e33c
--- /dev/null
+++ b/payload_generator/payload_generation_config.cc
@@ -0,0 +1,160 @@
+//
+// 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/payload_generation_config.h"
+
+#include <base/logging.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/ext2_filesystem.h"
+#include "update_engine/payload_generator/raw_filesystem.h"
+
+namespace chromeos_update_engine {
+
+bool PostInstallConfig::IsEmpty() const {
+ return run == false && path.empty() && filesystem_type.empty();
+}
+
+bool PartitionConfig::ValidateExists() const {
+ TEST_AND_RETURN_FALSE(!path.empty());
+ TEST_AND_RETURN_FALSE(utils::FileExists(path.c_str()));
+ TEST_AND_RETURN_FALSE(size > 0);
+ // The requested size is within the limits of the file.
+ TEST_AND_RETURN_FALSE(static_cast<off_t>(size) <=
+ utils::FileSize(path.c_str()));
+ // TODO(deymo): The delta generator algorithm doesn't support a block size
+ // different than 4 KiB. Remove this check once that's fixed. crbug.com/455045
+ int block_count, block_size;
+ if (utils::GetFilesystemSize(path, &block_count, &block_size) &&
+ block_size != 4096) {
+ LOG(ERROR) << "The filesystem provided in " << path
+ << " has a block size of " << block_size
+ << " but delta_generator only supports 4096.";
+ return false;
+ }
+ return true;
+}
+
+bool PartitionConfig::OpenFilesystem() {
+ if (path.empty())
+ return true;
+ fs_interface.reset();
+ if (utils::IsExtFilesystem(path)) {
+ fs_interface = Ext2Filesystem::CreateFromFile(path);
+ }
+
+ if (!fs_interface) {
+ // Fall back to a RAW filesystem.
+ TEST_AND_RETURN_FALSE(size % kBlockSize == 0);
+ fs_interface = RawFilesystem::Create(
+ "<" + name + "-partition>",
+ kBlockSize,
+ size / kBlockSize);
+ }
+ return true;
+}
+
+bool ImageConfig::ValidateIsEmpty() const {
+ TEST_AND_RETURN_FALSE(ImageInfoIsEmpty());
+ return partitions.empty();
+}
+
+bool ImageConfig::LoadImageSize() {
+ for (PartitionConfig& part : partitions) {
+ if (part.path.empty())
+ continue;
+ part.size = utils::FileSize(part.path);
+ }
+ return true;
+}
+
+bool ImageConfig::LoadPostInstallConfig(const brillo::KeyValueStore& store) {
+ bool found_postinstall = false;
+ for (PartitionConfig& part : partitions) {
+ bool run_postinstall;
+ if (!store.GetBoolean("RUN_POSTINSTALL_" + part.name, &run_postinstall) ||
+ !run_postinstall)
+ continue;
+ found_postinstall = true;
+ part.postinstall.run = true;
+ store.GetString("POSTINSTALL_PATH_" + part.name, &part.postinstall.path);
+ store.GetString("FILESYSTEM_TYPE_" + part.name,
+ &part.postinstall.filesystem_type);
+ }
+ if (!found_postinstall) {
+ LOG(ERROR) << "No valid postinstall config found.";
+ return false;
+ }
+ 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();
+}
+
+bool PayloadGenerationConfig::Validate() const {
+ if (is_delta) {
+ for (const PartitionConfig& part : source.partitions) {
+ if (!part.path.empty()) {
+ TEST_AND_RETURN_FALSE(part.ValidateExists());
+ TEST_AND_RETURN_FALSE(part.size % block_size == 0);
+ }
+ // Source partition should not have postinstall.
+ TEST_AND_RETURN_FALSE(part.postinstall.IsEmpty());
+ }
+
+ // Check for the supported minor_version values.
+ TEST_AND_RETURN_FALSE(minor_version == kInPlaceMinorPayloadVersion ||
+ minor_version == kSourceMinorPayloadVersion ||
+ minor_version == kOpSrcHashMinorPayloadVersion);
+
+ // 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());
+ TEST_AND_RETURN_FALSE(minor_version == kFullPayloadMinorVersion);
+ }
+
+ // In all cases, the target image must exists.
+ for (const PartitionConfig& part : target.partitions) {
+ TEST_AND_RETURN_FALSE(part.ValidateExists());
+ TEST_AND_RETURN_FALSE(part.size % block_size == 0);
+ if (minor_version == kInPlaceMinorPayloadVersion &&
+ part.name == kLegacyPartitionNameRoot)
+ TEST_AND_RETURN_FALSE(rootfs_partition_size >= part.size);
+ if (major_version == kChromeOSMajorPayloadVersion)
+ TEST_AND_RETURN_FALSE(part.postinstall.IsEmpty());
+ }
+
+ TEST_AND_RETURN_FALSE(hard_chunk_size == -1 ||
+ hard_chunk_size % block_size == 0);
+ TEST_AND_RETURN_FALSE(soft_chunk_size % block_size == 0);
+
+ TEST_AND_RETURN_FALSE(rootfs_partition_size % block_size == 0);
+
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
new file mode 100644
index 0000000..21bb89b
--- /dev/null
+++ b/payload_generator/payload_generation_config.h
@@ -0,0 +1,164 @@
+//
+// 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_PAYLOAD_GENERATION_CONFIG_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
+
+#include <cstddef>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <brillo/key_value_store.h>
+
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/filesystem_interface.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+struct PostInstallConfig {
+ // Whether the postinstall config is empty.
+ bool IsEmpty() const;
+
+ // Whether this partition carries a filesystem with post-install program that
+ // must be run to finalize the update process.
+ bool run = false;
+
+ // The path to the post-install program relative to the root of this
+ // filesystem.
+ std::string path;
+
+ // The filesystem type used to mount the partition in order to run the
+ // post-install program.
+ std::string filesystem_type;
+};
+
+struct PartitionConfig {
+ explicit PartitionConfig(std::string name) : name(name) {}
+
+ // Returns whether the PartitionConfig is not an empty image and all the
+ // fields are set correctly to a valid image file.
+ bool ValidateExists() const;
+
+ // Open then filesystem stored in this partition and stores it in
+ // |fs_interface|. Returns whether opening the filesystem worked.
+ bool OpenFilesystem();
+
+ // The path to the partition file. This can be a regular file or a block
+ // device such as a loop device.
+ std::string path;
+
+ // The size of the data in |path|. If rootfs verification is used (verity)
+ // this value should match the size of the verity device for the rootfs, and
+ // the size of the whole kernel. This value could be smaller than the
+ // partition and is the size of the data update_engine assumes verified for
+ // the source image, and the size of that data it should generate for the
+ // target image.
+ uint64_t size = 0;
+
+ // The FilesystemInterface implementation used to access this partition's
+ // files.
+ std::unique_ptr<FilesystemInterface> fs_interface;
+
+ std::string name;
+
+ PostInstallConfig postinstall;
+};
+
+// The ImageConfig struct describes a pair of binaries kernel and rootfs and the
+// metadata associated with the image they are part of, like build number, size,
+// etc.
+struct ImageConfig {
+ // Returns whether the ImageConfig is an empty image.
+ bool ValidateIsEmpty() const;
+
+ // Load |rootfs_size| and |kernel.size| from the respective image files. For
+ // the kernel, the whole |kernel.path| file is assumed. For the rootfs, the
+ // size is detected from the filesystem.
+ // Returns whether the image size was properly detected.
+ bool LoadImageSize();
+
+ // Load postinstall config from a key value store.
+ bool LoadPostInstallConfig(const brillo::KeyValueStore& store);
+
+ // 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;
+};
+
+// The PayloadGenerationConfig struct encapsulates all the configuration to
+// build the requested payload. This includes information about the old and new
+// image as well as the restrictions applied to the payload (like minor-version
+// and full/delta payload).
+struct PayloadGenerationConfig {
+ // Returns whether the PayloadGenerationConfig is valid.
+ bool Validate() const;
+
+ // Image information about the new image that's the target of this payload.
+ ImageConfig target;
+
+ // Image information pertaining the old image, if any. This is only valid
+ // if is_full is false, so we are requested a delta payload.
+ ImageConfig source;
+
+ // Wheter the requested payload is a delta payload.
+ bool is_delta = false;
+
+ // The major_version of the requested payload.
+ uint64_t major_version;
+
+ // The minor_version of the requested payload.
+ uint32_t minor_version;
+
+ // The size of the rootfs partition, that not necessarily is the same as the
+ // filesystem in either source or target version, since there is some space
+ // after the partition used to store the verity hashes and or the bootcache.
+ uint64_t rootfs_partition_size = 0;
+
+ // The |hard_chunk_size| is the maximum size that a single operation should
+ // write in the destination. Operations bigger than chunk_size should be
+ // split. A value of -1 means no hard chunk size limit. A very low limit
+ // means more operations, and less of a chance to reuse the data.
+ ssize_t hard_chunk_size = -1;
+
+ // The |soft_chunk_size| is the preferred chunk size to use when there's no
+ // significant impact to the operations. For example, REPLACE, MOVE and
+ // SOURCE_COPY operations are not significantly impacted by the chunk size,
+ // except for a few bytes overhead in the manifest to describe extra
+ // operations. On the other hand, splitting BSDIFF operations impacts the
+ // payload size since it is not possible to use the redundancy *between*
+ // chunks.
+ size_t soft_chunk_size = 2 * 1024 * 1024;
+
+ // TODO(deymo): Remove the block_size member and maybe replace it with a
+ // minimum alignment size for blocks (if needed). Algorithms should be able to
+ // pick the block_size they want, but for now only 4 KiB is supported.
+
+ // The block size used for all the operations in the manifest.
+ size_t block_size = 4096;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_GENERATION_CONFIG_H_
diff --git a/payload_generator/payload_generation_config_unittest.cc b/payload_generator/payload_generation_config_unittest.cc
new file mode 100644
index 0000000..122d94a
--- /dev/null
+++ b/payload_generator/payload_generation_config_unittest.cc
@@ -0,0 +1,52 @@
+//
+// 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/payload_generation_config.h"
+
+#include <gtest/gtest.h>
+
+namespace chromeos_update_engine {
+
+class PayloadGenerationConfigTest : public ::testing::Test {};
+
+TEST_F(PayloadGenerationConfigTest, SimpleLoadPostInstallConfigTest) {
+ ImageConfig image_config;
+ image_config.partitions.emplace_back("root");
+ brillo::KeyValueStore store;
+ EXPECT_TRUE(
+ store.LoadFromString("RUN_POSTINSTALL_root=true\n"
+ "POSTINSTALL_PATH_root=postinstall\n"
+ "FILESYSTEM_TYPE_root=ext4"));
+ EXPECT_TRUE(image_config.LoadPostInstallConfig(store));
+ EXPECT_FALSE(image_config.partitions[0].postinstall.IsEmpty());
+ EXPECT_EQ(true, image_config.partitions[0].postinstall.run);
+ EXPECT_EQ("postinstall", image_config.partitions[0].postinstall.path);
+ EXPECT_EQ("ext4", image_config.partitions[0].postinstall.filesystem_type);
+}
+
+TEST_F(PayloadGenerationConfigTest, LoadPostInstallConfigNameMismatchTest) {
+ ImageConfig image_config;
+ image_config.partitions.emplace_back("system");
+ brillo::KeyValueStore store;
+ EXPECT_TRUE(
+ store.LoadFromString("RUN_POSTINSTALL_root=true\n"
+ "POSTINSTALL_PATH_root=postinstall\n"
+ "FILESYSTEM_TYPE_root=ext4"));
+ EXPECT_FALSE(image_config.LoadPostInstallConfig(store));
+ EXPECT_TRUE(image_config.partitions[0].postinstall.IsEmpty());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/payload_signer.cc b/payload_generator/payload_signer.cc
new file mode 100644
index 0000000..a0c61b1
--- /dev/null
+++ b/payload_generator/payload_signer.cc
@@ -0,0 +1,484 @@
+//
+// 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/payload_generator/payload_signer.h"
+
+#include <endian.h>
+
+#include <base/logging.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <brillo/data_encoding.h>
+#include <openssl/pem.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The payload verifier will check all the signatures included in the payload
+// regardless of the version field. Old version of the verifier require the
+// version field to be included and be 1.
+const uint32_t kSignatureMessageLegacyVersion = 1;
+
+// Given raw |signatures|, packs them into a protobuf and serializes it into a
+// binary blob. Returns true on success, false otherwise.
+bool ConvertSignatureToProtobufBlob(const vector<brillo::Blob>& signatures,
+ brillo::Blob* out_signature_blob) {
+ // Pack it into a protobuf
+ Signatures out_message;
+ for (const brillo::Blob& signature : signatures) {
+ Signatures_Signature* sig_message = out_message.add_signatures();
+ // Set all the signatures with the same version number.
+ sig_message->set_version(kSignatureMessageLegacyVersion);
+ sig_message->set_data(signature.data(), signature.size());
+ }
+
+ // Serialize protobuf
+ string serialized;
+ TEST_AND_RETURN_FALSE(out_message.AppendToString(&serialized));
+ out_signature_blob->insert(out_signature_blob->end(),
+ serialized.begin(),
+ serialized.end());
+ LOG(INFO) << "Signature blob size: " << out_signature_blob->size();
+ return true;
+}
+
+// Given an unsigned payload under |payload_path| and the |signature_blob| and
+// |metadata_signature_blob| 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
+// |out_signatures_offset| with the expected offset for the new blob, and
+// |out_metadata_signature_size| which will be size of |metadata_signature_blob|
+// if the payload major version supports metadata signature, 0 otherwise.
+// Returns true on success, false otherwise.
+bool AddSignatureBlobToPayload(const string& payload_path,
+ const brillo::Blob& signature_blob,
+ const brillo::Blob& metadata_signature_blob,
+ brillo::Blob* out_payload,
+ uint64_t* out_metadata_size,
+ uint32_t* out_metadata_signature_size,
+ uint64_t* out_signatures_offset) {
+ uint64_t manifest_offset = 20;
+ const int kProtobufSizeOffset = 12;
+
+ // Loads the payload.
+ brillo::Blob payload;
+ DeltaArchiveManifest manifest;
+ uint64_t metadata_size, major_version;
+ uint32_t metadata_signature_size;
+ TEST_AND_RETURN_FALSE(PayloadSigner::LoadPayload(payload_path, &payload,
+ &manifest, &major_version, &metadata_size, &metadata_signature_size));
+
+ if (major_version == kBrilloMajorPayloadVersion) {
+ // Write metadata signature size in header.
+ uint32_t metadata_signature_size_be =
+ htobe32(metadata_signature_blob.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_blob.begin(),
+ metadata_signature_blob.end());
+ metadata_signature_size = metadata_signature_blob.size();
+ LOG(INFO) << "Metadata signature size: " << metadata_signature_size;
+ }
+
+ // Is there already a signature op in place?
+ if (manifest.has_signatures_size()) {
+ // The signature op is tied to the size of the signature blob, but not it's
+ // contents. We don't allow the manifest to change if there is already an op
+ // present, because that might invalidate previously generated
+ // hashes/signatures.
+ if (manifest.signatures_size() != signature_blob.size()) {
+ LOG(ERROR) << "Attempt to insert different signature sized blob. "
+ << "(current:" << manifest.signatures_size()
+ << "new:" << signature_blob.size() << ")";
+ return false;
+ }
+
+ LOG(INFO) << "Matching signature sizes already present.";
+ } else {
+ // Updates the manifest to include the signature operation.
+ PayloadSigner::AddSignatureToManifest(
+ payload.size() - metadata_size - metadata_signature_size,
+ signature_blob.size(),
+ major_version == kChromeOSMajorPayloadVersion,
+ &manifest);
+
+ // Updates the payload to include the new manifest.
+ string serialized_manifest;
+ TEST_AND_RETURN_FALSE(manifest.AppendToString(&serialized_manifest));
+ LOG(INFO) << "Updated protobuf size: " << serialized_manifest.size();
+ payload.erase(payload.begin() + manifest_offset,
+ payload.begin() + metadata_size);
+ payload.insert(payload.begin() + manifest_offset,
+ serialized_manifest.begin(),
+ serialized_manifest.end());
+
+ // Updates the protobuf size.
+ uint64_t size_be = htobe64(serialized_manifest.size());
+ memcpy(&payload[kProtobufSizeOffset], &size_be, sizeof(size_be));
+ metadata_size = serialized_manifest.size() + manifest_offset;
+
+ LOG(INFO) << "Updated payload size: " << payload.size();
+ LOG(INFO) << "Updated metadata size: " << metadata_size;
+ }
+ uint64_t signatures_offset = metadata_size + metadata_signature_size +
+ manifest.signatures_offset();
+ LOG(INFO) << "Signature Blob Offset: " << signatures_offset;
+ payload.resize(signatures_offset);
+ payload.insert(payload.begin() + signatures_offset,
+ signature_blob.begin(),
+ signature_blob.end());
+
+ *out_payload = std::move(payload);
+ *out_metadata_size = metadata_size;
+ *out_metadata_signature_size = metadata_signature_size;
+ *out_signatures_offset = signatures_offset;
+ return true;
+}
+
+// Given a |payload| with correct signature op and metadata signature size in
+// header and |metadata_size|, |metadata_signature_size|, |signatures_offset|,
+// calculate hash for payload and metadata, save it to |out_hash_data| and
+// |out_metadata_hash|.
+bool CalculateHashFromPayload(const brillo::Blob& payload,
+ const uint64_t metadata_size,
+ const uint32_t metadata_signature_size,
+ const uint64_t signatures_offset,
+ brillo::Blob* out_hash_data,
+ brillo::Blob* out_metadata_hash) {
+ if (out_metadata_hash) {
+ // Calculates the hash on the manifest.
+ TEST_AND_RETURN_FALSE(
+ HashCalculator::RawHashOfBytes(payload.data(), metadata_size,
+ out_metadata_hash));
+ }
+ if (out_hash_data) {
+ // Calculates the hash on the updated payload. Note that we skip metadata
+ // signature and payload signature.
+ HashCalculator calc;
+ TEST_AND_RETURN_FALSE(calc.Update(payload.data(), metadata_size));
+ TEST_AND_RETURN_FALSE(calc.Update(
+ payload.data() + metadata_size + metadata_signature_size,
+ signatures_offset - metadata_size - metadata_signature_size));
+ TEST_AND_RETURN_FALSE(calc.Finalize());
+ *out_hash_data = calc.raw_hash();
+ }
+ return true;
+}
+
+} // namespace
+
+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((signature_blob_length + kBlockSize - 1) /
+ kBlockSize);
+ }
+}
+
+bool PayloadSigner::LoadPayload(const string& payload_path,
+ brillo::Blob* out_payload,
+ DeltaArchiveManifest* out_manifest,
+ uint64_t* out_major_version,
+ uint64_t* out_metadata_size,
+ uint32_t* out_metadata_signature_size) {
+ brillo::Blob payload;
+ TEST_AND_RETURN_FALSE(utils::ReadFile(payload_path, &payload));
+ TEST_AND_RETURN_FALSE(payload.size() >=
+ DeltaPerformer::kMaxPayloadHeaderSize);
+ const uint8_t* read_pointer = payload.data();
+ TEST_AND_RETURN_FALSE(
+ memcmp(read_pointer, kDeltaMagic, sizeof(kDeltaMagic)) == 0);
+ read_pointer += sizeof(kDeltaMagic);
+
+ uint64_t major_version;
+ memcpy(&major_version, read_pointer, sizeof(major_version));
+ read_pointer += sizeof(major_version);
+ major_version = be64toh(major_version);
+ TEST_AND_RETURN_FALSE(major_version == kChromeOSMajorPayloadVersion ||
+ major_version == kBrilloMajorPayloadVersion);
+ if (out_major_version)
+ *out_major_version = major_version;
+
+ uint64_t manifest_size = 0;
+ memcpy(&manifest_size, read_pointer, sizeof(manifest_size));
+ read_pointer += sizeof(manifest_size);
+ manifest_size = be64toh(manifest_size);
+
+ uint32_t metadata_signature_size = 0;
+ if (major_version == kBrilloMajorPayloadVersion) {
+ memcpy(&metadata_signature_size, read_pointer,
+ sizeof(metadata_signature_size));
+ read_pointer += sizeof(metadata_signature_size);
+ metadata_signature_size = be32toh(metadata_signature_size);
+ }
+ if (out_metadata_signature_size)
+ *out_metadata_signature_size = metadata_signature_size;
+
+ *out_metadata_size = read_pointer - payload.data() + manifest_size;
+ TEST_AND_RETURN_FALSE(payload.size() >= *out_metadata_size);
+ if (out_manifest)
+ TEST_AND_RETURN_FALSE(
+ out_manifest->ParseFromArray(read_pointer, manifest_size));
+ *out_payload = std::move(payload);
+ return true;
+}
+
+bool PayloadSigner::VerifySignedPayload(const string& payload_path,
+ const string& public_key_path) {
+ brillo::Blob payload;
+ DeltaArchiveManifest manifest;
+ uint64_t metadata_size;
+ uint32_t metadata_signature_size;
+ TEST_AND_RETURN_FALSE(LoadPayload(payload_path, &payload, &manifest, nullptr,
+ &metadata_size, &metadata_signature_size));
+ TEST_AND_RETURN_FALSE(manifest.has_signatures_offset() &&
+ manifest.has_signatures_size());
+ uint64_t signatures_offset = metadata_size + metadata_signature_size +
+ manifest.signatures_offset();
+ CHECK_EQ(payload.size(), signatures_offset + manifest.signatures_size());
+ brillo::Blob payload_hash, metadata_hash;
+ TEST_AND_RETURN_FALSE(CalculateHashFromPayload(payload,
+ metadata_size,
+ metadata_signature_size,
+ signatures_offset,
+ &payload_hash,
+ &metadata_hash));
+ brillo::Blob signature_blob(payload.begin() + signatures_offset,
+ payload.end());
+ TEST_AND_RETURN_FALSE(PayloadVerifier::PadRSA2048SHA256Hash(&payload_hash));
+ TEST_AND_RETURN_FALSE(PayloadVerifier::VerifySignature(
+ signature_blob, public_key_path, payload_hash));
+ if (metadata_signature_size) {
+ signature_blob.assign(payload.begin() + metadata_size,
+ payload.begin() + metadata_size +
+ metadata_signature_size);
+ TEST_AND_RETURN_FALSE(
+ PayloadVerifier::PadRSA2048SHA256Hash(&metadata_hash));
+ TEST_AND_RETURN_FALSE(PayloadVerifier::VerifySignature(
+ signature_blob, public_key_path, metadata_hash));
+ }
+ return true;
+}
+
+bool PayloadSigner::SignHash(const brillo::Blob& hash,
+ const string& private_key_path,
+ brillo::Blob* out_signature) {
+ LOG(INFO) << "Signing hash with private key: " << private_key_path;
+ string sig_path;
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile("signature.XXXXXX", &sig_path, nullptr));
+ ScopedPathUnlinker sig_path_unlinker(sig_path);
+
+ string hash_path;
+ TEST_AND_RETURN_FALSE(
+ utils::MakeTempFile("hash.XXXXXX", &hash_path, nullptr));
+ ScopedPathUnlinker hash_path_unlinker(hash_path);
+ // We expect unpadded SHA256 hash coming in
+ TEST_AND_RETURN_FALSE(hash.size() == 32);
+ brillo::Blob padded_hash(hash);
+ PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash);
+ TEST_AND_RETURN_FALSE(utils::WriteFile(hash_path.c_str(),
+ padded_hash.data(),
+ padded_hash.size()));
+
+ // This runs on the server, so it's okay to copy out and call openssl
+ // executable rather than properly use the library.
+ vector<string> cmd = {"openssl", "rsautl", "-raw", "-sign", "-inkey",
+ private_key_path, "-in", hash_path, "-out", sig_path};
+ int return_code = 0;
+ TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &return_code,
+ nullptr));
+ TEST_AND_RETURN_FALSE(return_code == 0);
+
+ brillo::Blob signature;
+ TEST_AND_RETURN_FALSE(utils::ReadFile(sig_path, &signature));
+ out_signature->swap(signature);
+ return true;
+}
+
+bool PayloadSigner::SignHashWithKeys(const brillo::Blob& hash_data,
+ const vector<string>& private_key_paths,
+ brillo::Blob* out_signature_blob) {
+ vector<brillo::Blob> signatures;
+ for (const string& path : private_key_paths) {
+ brillo::Blob signature;
+ TEST_AND_RETURN_FALSE(SignHash(hash_data, path, &signature));
+ signatures.push_back(signature);
+ }
+ TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
+ out_signature_blob));
+ return true;
+}
+
+bool PayloadSigner::SignPayload(const string& unsigned_payload_path,
+ const vector<string>& private_key_paths,
+ const uint64_t metadata_size,
+ const uint32_t metadata_signature_size,
+ const uint64_t signatures_offset,
+ brillo::Blob* out_signature_blob) {
+ brillo::Blob payload;
+ TEST_AND_RETURN_FALSE(utils::ReadFile(unsigned_payload_path, &payload));
+ brillo::Blob hash_data;
+ TEST_AND_RETURN_FALSE(CalculateHashFromPayload(payload,
+ metadata_size,
+ metadata_signature_size,
+ signatures_offset,
+ &hash_data,
+ nullptr));
+ TEST_AND_RETURN_FALSE(SignHashWithKeys(hash_data,
+ private_key_paths,
+ out_signature_blob));
+ return true;
+}
+
+bool PayloadSigner::SignatureBlobLength(const vector<string>& private_key_paths,
+ uint64_t* out_length) {
+ DCHECK(out_length);
+ brillo::Blob x_blob(1, 'x'), hash_blob, sig_blob;
+ TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfBytes(x_blob.data(),
+ x_blob.size(),
+ &hash_blob));
+ TEST_AND_RETURN_FALSE(
+ SignHashWithKeys(hash_blob, private_key_paths, &sig_blob));
+ *out_length = sig_blob.size();
+ return true;
+}
+
+bool PayloadSigner::HashPayloadForSigning(const string& payload_path,
+ const vector<int>& signature_sizes,
+ brillo::Blob* out_payload_hash_data,
+ brillo::Blob* out_metadata_hash) {
+ // Create a signature blob with signatures filled with 0.
+ // Will be used for both payload signature and metadata signature.
+ vector<brillo::Blob> signatures;
+ for (int signature_size : signature_sizes) {
+ signatures.emplace_back(signature_size, 0);
+ }
+ brillo::Blob signature_blob;
+ TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(signatures,
+ &signature_blob));
+
+ brillo::Blob payload;
+ uint64_t metadata_size, signatures_offset;
+ uint32_t metadata_signature_size;
+ // Prepare payload for hashing.
+ TEST_AND_RETURN_FALSE(AddSignatureBlobToPayload(payload_path,
+ signature_blob,
+ signature_blob,
+ &payload,
+ &metadata_size,
+ &metadata_signature_size,
+ &signatures_offset));
+ TEST_AND_RETURN_FALSE(CalculateHashFromPayload(payload,
+ metadata_size,
+ metadata_signature_size,
+ signatures_offset,
+ out_payload_hash_data,
+ out_metadata_hash));
+ return true;
+}
+
+bool PayloadSigner::AddSignatureToPayload(
+ const string& payload_path,
+ const vector<brillo::Blob>& payload_signatures,
+ const vector<brillo::Blob>& metadata_signatures,
+ const string& signed_payload_path,
+ uint64_t *out_metadata_size) {
+ // TODO(petkov): Reduce memory usage -- the payload is manipulated in memory.
+
+ // Loads the payload and adds the signature op to it.
+ brillo::Blob signature_blob, metadata_signature_blob;
+ TEST_AND_RETURN_FALSE(ConvertSignatureToProtobufBlob(payload_signatures,
+ &signature_blob));
+ if (!metadata_signatures.empty()) {
+ TEST_AND_RETURN_FALSE(
+ ConvertSignatureToProtobufBlob(metadata_signatures,
+ &metadata_signature_blob));
+ }
+ brillo::Blob payload;
+ uint64_t signatures_offset;
+ uint32_t metadata_signature_size;
+ TEST_AND_RETURN_FALSE(AddSignatureBlobToPayload(payload_path,
+ signature_blob,
+ metadata_signature_blob,
+ &payload,
+ out_metadata_size,
+ &metadata_signature_size,
+ &signatures_offset));
+
+ LOG(INFO) << "Signed payload size: " << payload.size();
+ TEST_AND_RETURN_FALSE(utils::WriteFile(signed_payload_path.c_str(),
+ payload.data(),
+ payload.size()));
+ return true;
+}
+
+bool PayloadSigner::GetMetadataSignature(const void* const metadata,
+ size_t metadata_size,
+ const string& private_key_path,
+ string* out_signature) {
+ // Calculates the hash on the updated payload. Note that the payload includes
+ // the signature op but doesn't include the signature blob at the end.
+ brillo::Blob metadata_hash;
+ TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfBytes(metadata,
+ metadata_size,
+ &metadata_hash));
+
+ brillo::Blob signature;
+ TEST_AND_RETURN_FALSE(SignHash(metadata_hash,
+ private_key_path,
+ &signature));
+
+ *out_signature = brillo::data_encoding::Base64Encode(signature);
+ return true;
+}
+
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/payload_signer.h b/payload_generator/payload_signer.h
new file mode 100644
index 0000000..e7351dd
--- /dev/null
+++ b/payload_generator/payload_signer.h
@@ -0,0 +1,142 @@
+//
+// 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_PAYLOAD_SIGNER_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// This class encapsulates methods used for payload signing.
+// See update_metadata.proto for more info.
+
+namespace chromeos_update_engine {
+
+class PayloadSigner {
+ public:
+ // Reads the payload from the given |payload_path| into the |out_payload|
+ // vector. It also parses the manifest protobuf in the payload and returns it
+ // in |out_manifest| if not null, along with the major version of the payload
+ // in |out_major_version| if not null, the size of the entire metadata in
+ // |out_metadata_size| and the size of metadata signature in
+ // |out_metadata_signature_size| if not null.
+ static bool LoadPayload(const std::string& payload_path,
+ brillo::Blob* out_payload,
+ DeltaArchiveManifest* out_manifest,
+ uint64_t* out_major_version,
+ uint64_t* out_metadata_size,
+ uint32_t* out_metadata_signature_size);
+
+ // Returns true if the payload in |payload_path| is signed and its hash can be
+ // verified using the public key in |public_key_path| with the signature
+ // of a given version in the signature blob. Returns false otherwise.
+ 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.
+ 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
+ // raw signature in |out_signature|. Returns true on success, false otherwise.
+ static bool SignHash(const brillo::Blob& hash,
+ const std::string& private_key_path,
+ brillo::Blob* out_signature);
+
+ // Sign |hash_data| blob with all private keys in |private_key_paths|, then
+ // convert the signatures to protobuf blob.
+ static bool SignHashWithKeys(
+ const brillo::Blob& hash_data,
+ const std::vector<std::string>& private_key_paths,
+ brillo::Blob* out_signature_blob);
+
+ // Given an unsigned payload in |unsigned_payload_path|, private keys in
+ // |private_key_path|, metadata size in |metadata_size|, metadata signature
+ // size in |metadata_signature_size| and signatures offset in
+ // |signatures_offset|, calculates the payload signature blob into
+ // |out_signature_blob|. Note that the payload must already have an
+ // updated manifest that includes the dummy signature op and correct metadata
+ // signature size in header. Returns true on success, false otherwise.
+ static bool SignPayload(const std::string& unsigned_payload_path,
+ const std::vector<std::string>& private_key_paths,
+ const uint64_t metadata_size,
+ const uint32_t metadata_signature_size,
+ const uint64_t signatures_offset,
+ brillo::Blob* out_signature_blob);
+
+ // Returns the length of out_signature_blob that will result in a call
+ // to SignPayload with the given private keys. Returns true on success.
+ static bool SignatureBlobLength(
+ const std::vector<std::string>& private_key_paths,
+ uint64_t* out_length);
+
+ // Given an unsigned payload in |payload_path|,
+ // this method does two things:
+ // 1. It loads the payload into memory, and inserts placeholder signature
+ // operations and placeholder metadata signature to make the header and
+ // the manifest match what the final signed payload will look like based
+ // on |signatures_sizes|, if needed.
+ // 2. It calculates the raw SHA256 hash of the payload and the metadata in
+ // |payload_path| (except signatures) and returns the result in
+ // |out_hash_data| and |out_metadata_hash| respectively.
+ //
+ // The changes to payload are not preserved or written to disk.
+ static bool HashPayloadForSigning(const std::string& payload_path,
+ const std::vector<int>& signature_sizes,
+ brillo::Blob* out_payload_hash_data,
+ brillo::Blob* out_metadata_hash);
+
+ // Given an unsigned payload in |payload_path| (with no dummy 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
+ // |signed_payload_path| can point to the same file. Populates
+ // |out_metadata_size| with the size of the metadata after adding the
+ // signature operation in the manifest. Returns true on success, false
+ // otherwise.
+ static bool AddSignatureToPayload(
+ const std::string& payload_path,
+ const std::vector<brillo::Blob>& payload_signatures,
+ const std::vector<brillo::Blob>& metadata_signatures,
+ const std::string& signed_payload_path,
+ uint64_t* out_metadata_size);
+
+ // Computes the SHA256 hash of the first metadata_size bytes of |metadata|
+ // and signs the hash with the given private_key_path and writes the signed
+ // hash in |out_signature|. Returns true if successful or false if there was
+ // any error in the computations.
+ static bool GetMetadataSignature(const void* const metadata,
+ size_t metadata_size,
+ const std::string& private_key_path,
+ std::string* out_signature);
+
+ private:
+ // This should never be constructed
+ DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadSigner);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_PAYLOAD_SIGNER_H_
diff --git a/payload_generator/payload_signer_unittest.cc b/payload_generator/payload_signer_unittest.cc
new file mode 100644
index 0000000..b302f52
--- /dev/null
+++ b/payload_generator/payload_signer_unittest.cc
@@ -0,0 +1,245 @@
+//
+// 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/payload_signer.h"
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::string;
+using std::vector;
+
+// Note: the test key was generated with the following command:
+// openssl genrsa -out unittest_key.pem 2048
+// The public-key version is created by the build system.
+
+namespace chromeos_update_engine {
+
+const char* kUnittestPrivateKeyPath = "unittest_key.pem";
+const char* kUnittestPublicKeyPath = "unittest_key.pub.pem";
+const char* kUnittestPrivateKey2Path = "unittest_key2.pem";
+const char* kUnittestPublicKey2Path = "unittest_key2.pub.pem";
+
+// Some data and its corresponding hash and signature:
+const char kDataToSign[] = "This is some data to sign.";
+
+// Generated by:
+// echo -n 'This is some data to sign.' | openssl dgst -sha256 -binary |
+// hexdump -v -e '" " 8/1 "0x%02x, " "\n"'
+const uint8_t kDataHash[] = {
+ 0x7a, 0x07, 0xa6, 0x44, 0x08, 0x86, 0x20, 0xa6,
+ 0xc1, 0xf8, 0xd9, 0x02, 0x05, 0x63, 0x0d, 0xb7,
+ 0xfc, 0x2b, 0xa0, 0xa9, 0x7c, 0x9d, 0x1d, 0x8c,
+ 0x01, 0xf5, 0x78, 0x6d, 0xc5, 0x11, 0xb4, 0x06
+};
+
+// Generated with openssl 1.0, which at the time of this writing, you need
+// to download and install yourself. Here's my command:
+// echo -n 'This is some data to sign.' | openssl dgst -sha256 -binary |
+// ~/local/bin/openssl pkeyutl -sign -inkey unittest_key.pem -pkeyopt
+// digest:sha256 | hexdump -v -e '" " 8/1 "0x%02x, " "\n"'
+const uint8_t kDataSignature[] = {
+ 0x9f, 0x86, 0x25, 0x8b, 0xf3, 0xcc, 0xe3, 0x95,
+ 0x5f, 0x45, 0x83, 0xb2, 0x66, 0xf0, 0x2a, 0xcf,
+ 0xb7, 0xaa, 0x52, 0x25, 0x7a, 0xdd, 0x9d, 0x65,
+ 0xe5, 0xd6, 0x02, 0x4b, 0x37, 0x99, 0x53, 0x06,
+ 0xc2, 0xc9, 0x37, 0x36, 0x25, 0x62, 0x09, 0x4f,
+ 0x6b, 0x22, 0xf8, 0xb3, 0x89, 0x14, 0x98, 0x1a,
+ 0xbc, 0x30, 0x90, 0x4a, 0x43, 0xf5, 0xea, 0x2e,
+ 0xf0, 0xa4, 0xba, 0xc3, 0xa7, 0xa3, 0x44, 0x70,
+ 0xd6, 0xc4, 0x89, 0xd8, 0x45, 0x71, 0xbb, 0xee,
+ 0x59, 0x87, 0x3d, 0xd5, 0xe5, 0x40, 0x22, 0x3d,
+ 0x73, 0x7e, 0x2a, 0x58, 0x93, 0x8e, 0xcb, 0x9c,
+ 0xf2, 0xbb, 0x4a, 0xc9, 0xd2, 0x2c, 0x52, 0x42,
+ 0xb0, 0xd1, 0x13, 0x22, 0xa4, 0x78, 0xc7, 0xc6,
+ 0x3e, 0xf1, 0xdc, 0x4c, 0x7b, 0x2d, 0x40, 0xda,
+ 0x58, 0xac, 0x4a, 0x11, 0x96, 0x3d, 0xa0, 0x01,
+ 0xf6, 0x96, 0x74, 0xf6, 0x6c, 0x0c, 0x49, 0x69,
+ 0x4e, 0xc1, 0x7e, 0x9f, 0x2a, 0x42, 0xdd, 0x15,
+ 0x6b, 0x37, 0x2e, 0x3a, 0xa7, 0xa7, 0x6d, 0x91,
+ 0x13, 0xe8, 0x59, 0xde, 0xfe, 0x99, 0x07, 0xd9,
+ 0x34, 0x0f, 0x17, 0xb3, 0x05, 0x4c, 0xd2, 0xc6,
+ 0x82, 0xb7, 0x38, 0x36, 0x63, 0x1d, 0x9e, 0x21,
+ 0xa6, 0x32, 0xef, 0xf1, 0x65, 0xe6, 0xed, 0x95,
+ 0x25, 0x9b, 0x61, 0xe0, 0xba, 0x86, 0xa1, 0x7f,
+ 0xf8, 0xa5, 0x4a, 0x32, 0x1f, 0x15, 0x20, 0x8a,
+ 0x41, 0xc5, 0xb0, 0xd9, 0x4a, 0xda, 0x85, 0xf3,
+ 0xdc, 0xa0, 0x98, 0x5d, 0x1d, 0x18, 0x9d, 0x2e,
+ 0x42, 0xea, 0x69, 0x13, 0x74, 0x3c, 0x74, 0xf7,
+ 0x6d, 0x43, 0xb0, 0x63, 0x90, 0xdb, 0x04, 0xd5,
+ 0x05, 0xc9, 0x73, 0x1f, 0x6c, 0xd6, 0xfa, 0x46,
+ 0x4e, 0x0f, 0x33, 0x58, 0x5b, 0x0d, 0x1b, 0x55,
+ 0x39, 0xb9, 0x0f, 0x43, 0x37, 0xc0, 0x06, 0x0c,
+ 0x29, 0x93, 0x43, 0xc7, 0x43, 0xb9, 0xab, 0x7d
+};
+
+namespace {
+void SignSampleData(brillo::Blob* out_signature_blob,
+ const vector<string>& private_keys) {
+ brillo::Blob data_blob(std::begin(kDataToSign),
+ std::begin(kDataToSign) + strlen(kDataToSign));
+ uint64_t length = 0;
+ EXPECT_TRUE(PayloadSigner::SignatureBlobLength(private_keys, &length));
+ EXPECT_GT(length, 0);
+ brillo::Blob hash_blob;
+ EXPECT_TRUE(HashCalculator::RawHashOfBytes(data_blob.data(),
+ data_blob.size(),
+ &hash_blob));
+ EXPECT_TRUE(PayloadSigner::SignHashWithKeys(
+ hash_blob,
+ private_keys,
+ out_signature_blob));
+ EXPECT_EQ(length, out_signature_blob->size());
+}
+} // namespace
+
+class PayloadSignerTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash_data_);
+ }
+
+ void DoWriteAndLoadPayloadTest(const PayloadGenerationConfig& config) {
+ PayloadFile payload;
+ payload.Init(config);
+ string payload_path;
+ EXPECT_TRUE(utils::MakeTempFile("payload.XXXXXX", &payload_path, nullptr));
+ ScopedPathUnlinker payload_path_unlinker(payload_path);
+ uint64_t metadata_size;
+ EXPECT_TRUE(
+ payload.WritePayload(payload_path, "/dev/null", "", &metadata_size));
+ brillo::Blob payload_blob;
+ DeltaArchiveManifest manifest;
+ uint64_t load_metadata_size, load_major_version;
+ EXPECT_TRUE(PayloadSigner::LoadPayload(payload_path, &payload_blob,
+ &manifest, &load_major_version, &load_metadata_size, nullptr));
+ EXPECT_EQ(utils::FileSize(payload_path), payload_blob.size());
+ EXPECT_EQ(config.major_version, load_major_version);
+ EXPECT_EQ(metadata_size, load_metadata_size);
+ }
+
+ brillo::Blob padded_hash_data_{std::begin(kDataHash), std::end(kDataHash)};
+};
+
+TEST_F(PayloadSignerTest, LoadPayloadV1Test) {
+ PayloadGenerationConfig config;
+ config.major_version = kChromeOSMajorPayloadVersion;
+ DoWriteAndLoadPayloadTest(config);
+}
+
+TEST_F(PayloadSignerTest, LoadPayloadV2Test) {
+ PayloadGenerationConfig config;
+ config.major_version = kBrilloMajorPayloadVersion;
+ DoWriteAndLoadPayloadTest(config);
+}
+
+TEST_F(PayloadSignerTest, SignSimpleTextTest) {
+ brillo::Blob signature_blob;
+ SignSampleData(&signature_blob, {kUnittestPrivateKeyPath});
+
+ // Check the signature itself
+ Signatures signatures;
+ EXPECT_TRUE(signatures.ParseFromArray(signature_blob.data(),
+ signature_blob.size()));
+ EXPECT_EQ(1, signatures.signatures_size());
+ const Signatures_Signature& signature = signatures.signatures(0);
+ EXPECT_EQ(1, signature.version());
+ const string sig_data = signature.data();
+ ASSERT_EQ(arraysize(kDataSignature), sig_data.size());
+ for (size_t i = 0; i < arraysize(kDataSignature); i++) {
+ EXPECT_EQ(kDataSignature[i], static_cast<uint8_t>(sig_data[i]));
+ }
+}
+
+TEST_F(PayloadSignerTest, VerifyAllSignatureTest) {
+ brillo::Blob signature_blob;
+ SignSampleData(&signature_blob,
+ {kUnittestPrivateKeyPath, kUnittestPrivateKey2Path});
+
+ // Either public key should pass the verification.
+ EXPECT_TRUE(PayloadVerifier::VerifySignature(signature_blob,
+ kUnittestPublicKeyPath,
+ padded_hash_data_));
+ EXPECT_TRUE(PayloadVerifier::VerifySignature(signature_blob,
+ kUnittestPublicKey2Path,
+ padded_hash_data_));
+}
+
+TEST_F(PayloadSignerTest, VerifySignatureTest) {
+ brillo::Blob signature_blob;
+ SignSampleData(&signature_blob, {kUnittestPrivateKeyPath});
+
+ EXPECT_TRUE(PayloadVerifier::VerifySignature(signature_blob,
+ kUnittestPublicKeyPath,
+ padded_hash_data_));
+ // Passing the invalid key should fail the verification.
+ EXPECT_FALSE(PayloadVerifier::VerifySignature(signature_blob,
+ kUnittestPublicKey2Path,
+ padded_hash_data_));
+}
+
+TEST_F(PayloadSignerTest, SkipMetadataSignatureTest) {
+ string payload_path;
+ EXPECT_TRUE(utils::MakeTempFile("payload.XXXXXX", &payload_path, nullptr));
+ ScopedPathUnlinker payload_path_unlinker(payload_path);
+
+ PayloadGenerationConfig config;
+ config.major_version = kBrilloMajorPayloadVersion;
+ PayloadFile payload;
+ EXPECT_TRUE(payload.Init(config));
+ uint64_t metadata_size;
+ EXPECT_TRUE(
+ payload.WritePayload(payload_path, "/dev/null", "", &metadata_size));
+ const vector<int> sizes = {256};
+ brillo::Blob unsigned_payload_hash, unsigned_metadata_hash;
+ EXPECT_TRUE(PayloadSigner::HashPayloadForSigning(
+ payload_path, sizes, &unsigned_payload_hash, &unsigned_metadata_hash));
+ EXPECT_TRUE(payload.WritePayload(
+ payload_path, "/dev/null", kUnittestPrivateKeyPath, &metadata_size));
+ brillo::Blob signed_payload_hash, signed_metadata_hash;
+ EXPECT_TRUE(PayloadSigner::HashPayloadForSigning(
+ payload_path, sizes, &signed_payload_hash, &signed_metadata_hash));
+ EXPECT_EQ(unsigned_payload_hash, signed_payload_hash);
+ EXPECT_EQ(unsigned_metadata_hash, signed_metadata_hash);
+}
+
+TEST_F(PayloadSignerTest, VerifySignedPayloadTest) {
+ string payload_path;
+ EXPECT_TRUE(utils::MakeTempFile("payload.XXXXXX", &payload_path, nullptr));
+ ScopedPathUnlinker payload_path_unlinker(payload_path);
+
+ PayloadGenerationConfig config;
+ config.major_version = kBrilloMajorPayloadVersion;
+ PayloadFile payload;
+ EXPECT_TRUE(payload.Init(config));
+ uint64_t metadata_size;
+ EXPECT_TRUE(payload.WritePayload(
+ payload_path, "/dev/null", kUnittestPrivateKeyPath, &metadata_size));
+ EXPECT_TRUE(PayloadSigner::VerifySignedPayload(payload_path,
+ kUnittestPublicKeyPath));
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/raw_filesystem.cc b/payload_generator/raw_filesystem.cc
new file mode 100644
index 0000000..2fb1400
--- /dev/null
+++ b/payload_generator/raw_filesystem.cc
@@ -0,0 +1,53 @@
+//
+// 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/raw_filesystem.h"
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_engine {
+
+unique_ptr<RawFilesystem> RawFilesystem::Create(
+ const std::string& filename, uint64_t block_size, uint64_t block_count) {
+ unique_ptr<RawFilesystem> result(new RawFilesystem());
+ result->filename_ = filename;
+ result->block_size_ = block_size;
+ result->block_count_ = block_count;
+ return result;
+}
+
+size_t RawFilesystem::GetBlockSize() const {
+ return block_size_;
+}
+
+size_t RawFilesystem::GetBlockCount() const {
+ return block_count_;
+}
+
+bool RawFilesystem::GetFiles(std::vector<File>* files) const {
+ files->clear();
+ File file;
+ file.name = filename_;
+ file.extents = { ExtentForRange(0, block_count_) };
+ files->push_back(file);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/raw_filesystem.h b/payload_generator/raw_filesystem.h
new file mode 100644
index 0000000..0aecd81
--- /dev/null
+++ b/payload_generator/raw_filesystem.h
@@ -0,0 +1,60 @@
+//
+// 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_RAW_FILESYSTEM_H_
+#define UPDATE_ENGINE_PAYLOAD_GENERATOR_RAW_FILESYSTEM_H_
+
+// A simple filesystem interface implementation used for unknown filesystem
+// format such as the kernel.
+
+#include "update_engine/payload_generator/filesystem_interface.h"
+
+#include <string>
+#include <vector>
+
+namespace chromeos_update_engine {
+
+class RawFilesystem : public FilesystemInterface {
+ public:
+ static std::unique_ptr<RawFilesystem> Create(
+ const std::string& filename, uint64_t block_size, uint64_t block_count);
+ virtual ~RawFilesystem() = default;
+
+ // FilesystemInterface overrides.
+ size_t GetBlockSize() const override;
+ size_t GetBlockCount() const override;
+
+ // GetFiles will return only one file with all the blocks of the filesystem
+ // with the name passed during construction.
+ bool GetFiles(std::vector<File>* files) const override;
+
+ bool LoadSettings(brillo::KeyValueStore* store) const override {
+ return false;
+ }
+
+ private:
+ RawFilesystem() = default;
+
+ std::string filename_;
+ uint64_t block_count_;
+ uint64_t block_size_;
+
+ DISALLOW_COPY_AND_ASSIGN(RawFilesystem);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_RAW_FILESYSTEM_H_
diff --git a/payload_generator/tarjan.cc b/payload_generator/tarjan.cc
new file mode 100644
index 0000000..98e29f9
--- /dev/null
+++ b/payload_generator/tarjan.cc
@@ -0,0 +1,83 @@
+//
+// 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 "update_engine/common/utils.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 (utils::VectorContainsValue(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 (utils::VectorContainsValue(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
new file mode 100644
index 0000000..50cf563
--- /dev/null
+++ b/payload_generator/tarjan.h
@@ -0,0 +1,52 @@
+//
+// 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
new file mode 100644
index 0000000..e40a7ff
--- /dev/null
+++ b/payload_generator/tarjan_unittest.cc
@@ -0,0 +1,94 @@
+//
+// 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 <gtest/gtest.h>
+
+#include "update_engine/common/utils.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(5, vertex_indexes.size());
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_a));
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_b));
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_c));
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_d));
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_e));
+ }
+
+ {
+ vector<Vertex::Index> vertex_indexes;
+ tarjan.Execute(n_f, &graph, &vertex_indexes);
+
+ EXPECT_EQ(1, vertex_indexes.size());
+ EXPECT_TRUE(utils::VectorContainsValue(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(2, vertex_indexes.size());
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_g));
+ EXPECT_TRUE(utils::VectorContainsValue(vertex_indexes, n_h));
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_generator/topological_sort.cc b/payload_generator/topological_sort.cc
new file mode 100644
index 0000000..f164336
--- /dev/null
+++ b/payload_generator/topological_sort.cc
@@ -0,0 +1,56 @@
+//
+// 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
new file mode 100644
index 0000000..461cbe1
--- /dev/null
+++ b/payload_generator/topological_sort.h
@@ -0,0 +1,42 @@
+//
+// 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
new file mode 100644
index 0000000..1d866a7
--- /dev/null
+++ b/payload_generator/topological_sort_unittest.cc
@@ -0,0 +1,95 @@
+//
+// 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
new file mode 100644
index 0000000..49b08b0
--- /dev/null
+++ b/payload_generator/zip_unittest.cc
@@ -0,0 +1,114 @@
+//
+// 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 <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/payload_generator/bzip.h"
+
+using chromeos_update_engine::test_utils::kRandomString;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+template <typename T>
+class ZipTest : public ::testing::Test {
+ public:
+ bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const = 0;
+ bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const = 0;
+ bool ZipCompressString(const string& str, brillo::Blob* out) const = 0;
+ bool ZipDecompressString(const string& str, brillo::Blob* out) const = 0;
+};
+
+class BzipTest {};
+
+template <>
+class ZipTest<BzipTest> : public ::testing::Test {
+ public:
+ bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const {
+ return BzipDecompress(in, out);
+ }
+ bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const {
+ return BzipCompress(in, out);
+ }
+ bool ZipCompressString(const string& str, brillo::Blob* out) const {
+ return BzipCompressString(str, out);
+ }
+ bool ZipDecompressString(const string& str, brillo::Blob* out) const {
+ return BzipDecompressString(str, out);
+ }
+};
+
+typedef ::testing::Types<BzipTest> ZipTestTypes;
+TYPED_TEST_CASE(ZipTest, ZipTestTypes);
+
+
+
+TYPED_TEST(ZipTest, SimpleTest) {
+ string in("this should compress well xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
+ brillo::Blob out;
+ EXPECT_TRUE(this->ZipCompressString(in, &out));
+ EXPECT_LT(out.size(), in.size());
+ EXPECT_GT(out.size(), 0);
+ brillo::Blob decompressed;
+ EXPECT_TRUE(this->ZipDecompress(out, &decompressed));
+ EXPECT_EQ(in.size(), decompressed.size());
+ EXPECT_TRUE(!memcmp(in.data(), decompressed.data(), in.size()));
+}
+
+TYPED_TEST(ZipTest, PoorCompressionTest) {
+ string in(reinterpret_cast<const char*>(kRandomString),
+ sizeof(kRandomString));
+ brillo::Blob out;
+ EXPECT_TRUE(this->ZipCompressString(in, &out));
+ EXPECT_GT(out.size(), in.size());
+ string out_string(out.begin(), out.end());
+ brillo::Blob decompressed;
+ EXPECT_TRUE(this->ZipDecompressString(out_string, &decompressed));
+ EXPECT_EQ(in.size(), decompressed.size());
+ EXPECT_TRUE(!memcmp(in.data(), decompressed.data(), in.size()));
+}
+
+TYPED_TEST(ZipTest, MalformedZipTest) {
+ string in(reinterpret_cast<const char*>(kRandomString),
+ sizeof(kRandomString));
+ brillo::Blob out;
+ EXPECT_FALSE(this->ZipDecompressString(in, &out));
+}
+
+TYPED_TEST(ZipTest, EmptyInputsTest) {
+ string in;
+ brillo::Blob out;
+ EXPECT_TRUE(this->ZipDecompressString(in, &out));
+ EXPECT_EQ(0, out.size());
+
+ EXPECT_TRUE(this->ZipCompressString(in, &out));
+ EXPECT_EQ(0, out.size());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_state.cc b/payload_state.cc
new file mode 100644
index 0000000..8594f28
--- /dev/null
+++ b/payload_state.cc
@@ -0,0 +1,1382 @@
+//
+// 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_state.h"
+
+#include <algorithm>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <metrics/metrics_library.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/common/clock.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/common/utils.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;
+using std::min;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const TimeDelta PayloadState::kDurationSlack = TimeDelta::FromSeconds(600);
+
+// We want to upperbound backoffs to 16 days
+static const int kMaxBackoffDays = 16;
+
+// We want to randomize retry attempts after the backoff by +/- 6 hours.
+static const uint32_t kMaxBackoffFuzzMinutes = 12 * 60;
+
+PayloadState::PayloadState()
+ : prefs_(nullptr),
+ using_p2p_for_downloading_(false),
+ p2p_num_attempts_(0),
+ payload_attempt_number_(0),
+ full_payload_attempt_number_(0),
+ url_index_(0),
+ url_failure_count_(0),
+ url_switch_count_(0),
+ attempt_num_bytes_downloaded_(0),
+ attempt_connection_type_(metrics::ConnectionType::kUnknown),
+ attempt_type_(AttemptType::kUpdate) {
+ for (int i = 0; i <= kNumDownloadSources; i++)
+ 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();
+ LoadResponseSignature();
+ LoadPayloadAttemptNumber();
+ LoadFullPayloadAttemptNumber();
+ LoadUrlIndex();
+ LoadUrlFailureCount();
+ LoadUrlSwitchCount();
+ LoadBackoffExpiryTime();
+ LoadUpdateTimestampStart();
+ // The LoadUpdateDurationUptime() method relies on LoadUpdateTimestampStart()
+ // being called before it. Don't reorder.
+ LoadUpdateDurationUptime();
+ for (int i = 0; i < kNumDownloadSources; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+ LoadCurrentBytesDownloaded(source);
+ LoadTotalBytesDownloaded(source);
+ }
+ LoadNumReboots();
+ LoadNumResponsesSeen();
+ LoadRollbackVersion();
+ LoadP2PFirstAttemptTimestamp();
+ LoadP2PNumAttempts();
+ return true;
+}
+
+void PayloadState::SetResponse(const OmahaResponse& omaha_response) {
+ // Always store the latest response.
+ response_ = omaha_response;
+
+ // Compute the candidate URLs first as they are used to calculate the
+ // response signature so that a change in enterprise policy for
+ // HTTP downloads being enabled or not could be honored as soon as the
+ // next update check happens.
+ ComputeCandidateUrls();
+
+ // Check if the "signature" of this response (i.e. the fields we care about)
+ // has changed.
+ string new_response_signature = CalculateResponseSignature();
+ bool has_response_changed = (response_signature_ != new_response_signature);
+
+ // If the response has changed, we should persist the new signature and
+ // clear away all the existing state.
+ if (has_response_changed) {
+ LOG(INFO) << "Resetting all persisted state as this is a new response";
+ SetNumResponsesSeen(num_responses_seen_ + 1);
+ SetResponseSignature(new_response_signature);
+ ResetPersistedState();
+ return;
+ }
+
+ // This is the earliest point at which we can validate whether the URL index
+ // we loaded from the persisted state is a valid value. If the response
+ // hasn't changed but the URL index is invalid, it's indicative of some
+ // tampering of the persisted state.
+ if (static_cast<uint32_t>(url_index_) >= candidate_urls_.size()) {
+ LOG(INFO) << "Resetting all payload state as the url index seems to have "
+ "been tampered with";
+ ResetPersistedState();
+ return;
+ }
+
+ // Update the current download source which depends on the latest value of
+ // the response.
+ UpdateCurrentDownloadSource();
+}
+
+void PayloadState::SetUsingP2PForDownloading(bool value) {
+ using_p2p_for_downloading_ = value;
+ // Update the current download source which depends on whether we are
+ // using p2p or not.
+ UpdateCurrentDownloadSource();
+}
+
+void PayloadState::DownloadComplete() {
+ LOG(INFO) << "Payload downloaded successfully";
+ IncrementPayloadAttemptNumber();
+ IncrementFullPayloadAttemptNumber();
+}
+
+void PayloadState::DownloadProgress(size_t count) {
+ if (count == 0)
+ return;
+
+ CalculateUpdateDurationUptime();
+ UpdateBytesDownloaded(count);
+
+ // We've received non-zero bytes from a recent download operation. Since our
+ // URL failure count is meant to penalize a URL only for consecutive
+ // failures, downloading bytes successfully means we should reset the failure
+ // count (as we know at least that the URL is working). In future, we can
+ // design this to be more sophisticated to check for more intelligent failure
+ // patterns, but right now, even 1 byte downloaded will mark the URL to be
+ // good unless it hits 10 (or configured number of) consecutive failures
+ // again.
+
+ if (GetUrlFailureCount() == 0)
+ return;
+
+ LOG(INFO) << "Resetting failure count of Url" << GetUrlIndex()
+ << " to 0 as we received " << count << " bytes successfully";
+ SetUrlFailureCount(0);
+}
+
+void PayloadState::AttemptStarted(AttemptType attempt_type) {
+ // Flush previous state from abnormal attempt failure, if any.
+ ReportAndClearPersistedAttemptMetrics();
+
+ attempt_type_ = attempt_type;
+
+ ClockInterface *clock = system_state_->clock();
+ attempt_start_time_boot_ = clock->GetBootTime();
+ attempt_start_time_monotonic_ = clock->GetMonotonicTime();
+ attempt_num_bytes_downloaded_ = 0;
+
+ metrics::ConnectionType type;
+ NetworkConnectionType network_connection_type;
+ NetworkTethering tethering;
+ ConnectionManagerInterface* connection_manager =
+ system_state_->connection_manager();
+ if (!connection_manager->GetConnectionProperties(&network_connection_type,
+ &tethering)) {
+ LOG(ERROR) << "Failed to determine connection type.";
+ type = metrics::ConnectionType::kUnknown;
+ } else {
+ type = metrics_utils::GetConnectionType(network_connection_type, tethering);
+ }
+ attempt_connection_type_ = type;
+
+ if (attempt_type == AttemptType::kUpdate)
+ PersistAttemptMetrics();
+}
+
+void PayloadState::UpdateResumed() {
+ LOG(INFO) << "Resuming an update that was previously started.";
+ UpdateNumReboots();
+ AttemptStarted(AttemptType::kUpdate);
+}
+
+void PayloadState::UpdateRestarted() {
+ LOG(INFO) << "Starting a new update";
+ ResetDownloadSourcesOnNewUpdate();
+ SetNumReboots(0);
+ AttemptStarted(AttemptType::kUpdate);
+}
+
+void PayloadState::UpdateSucceeded() {
+ // Send the relevant metrics that are tracked in this class to UMA.
+ CalculateUpdateDurationUptime();
+ SetUpdateTimestampEnd(system_state_->clock()->GetWallclockTime());
+
+ switch (attempt_type_) {
+ case AttemptType::kUpdate:
+ CollectAndReportAttemptMetrics(ErrorCode::kSuccess);
+ CollectAndReportSuccessfulUpdateMetrics();
+ ClearPersistedAttemptMetrics();
+ break;
+
+ case AttemptType::kRollback:
+ metrics::ReportRollbackMetrics(system_state_,
+ metrics::RollbackResult::kSuccess);
+ break;
+ }
+
+ // Reset the number of responses seen since it counts from the last
+ // successful update, e.g. now.
+ SetNumResponsesSeen(0);
+
+ CreateSystemUpdatedMarkerFile();
+}
+
+void PayloadState::UpdateFailed(ErrorCode error) {
+ ErrorCode base_error = utils::GetBaseErrorCode(error);
+ LOG(INFO) << "Updating payload state for error code: " << base_error
+ << " (" << utils::CodeToString(base_error) << ")";
+
+ if (candidate_urls_.size() == 0) {
+ // This means we got this error even before we got a valid Omaha response
+ // or don't have any valid candidates in the Omaha response.
+ // So we should not advance the url_index_ in such cases.
+ LOG(INFO) << "Ignoring failures until we get a valid Omaha response.";
+ return;
+ }
+
+ switch (attempt_type_) {
+ case AttemptType::kUpdate:
+ CollectAndReportAttemptMetrics(base_error);
+ ClearPersistedAttemptMetrics();
+ break;
+
+ case AttemptType::kRollback:
+ metrics::ReportRollbackMetrics(system_state_,
+ metrics::RollbackResult::kFailed);
+ break;
+ }
+
+ switch (base_error) {
+ // Errors which are good indicators of a problem with a particular URL or
+ // the protocol used in the URL or entities in the communication channel
+ // (e.g. proxies). We should try the next available URL in the next update
+ // check to quickly recover from these errors.
+ case ErrorCode::kPayloadHashMismatchError:
+ case ErrorCode::kPayloadSizeMismatchError:
+ case ErrorCode::kDownloadPayloadVerificationError:
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ case ErrorCode::kDownloadManifestParseError:
+ case ErrorCode::kDownloadMetadataSignatureError:
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ case ErrorCode::kDownloadOperationExecutionError:
+ case ErrorCode::kDownloadOperationHashMismatch:
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ case ErrorCode::kDownloadOperationHashMissingError:
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ case ErrorCode::kPayloadMismatchedType:
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ IncrementUrlIndex();
+ break;
+
+ // Errors which seem to be just transient network/communication related
+ // failures and do not indicate any inherent problem with the URL itself.
+ // So, we should keep the current URL but just increment the
+ // failure count to give it more chances. This way, while we maximize our
+ // chances of downloading from the URLs that appear earlier in the response
+ // (because download from a local server URL that appears earlier in a
+ // response is preferable than downloading from the next URL which could be
+ // a internet URL and thus could be more expensive).
+
+ case ErrorCode::kError:
+ case ErrorCode::kDownloadTransferError:
+ case ErrorCode::kDownloadWriteError:
+ case ErrorCode::kDownloadStateInitializationError:
+ case ErrorCode::kOmahaErrorInHTTPResponse: // Aggregate for HTTP errors.
+ IncrementFailureCount();
+ break;
+
+ // Errors which are not specific to a URL and hence shouldn't result in
+ // the URL being penalized. This can happen in two cases:
+ // 1. We haven't started downloading anything: These errors don't cost us
+ // anything in terms of actual payload bytes, so we should just do the
+ // regular retries at the next update check.
+ // 2. We have successfully downloaded the payload: In this case, the
+ // payload attempt number would have been incremented and would take care
+ // of the backoff at the next update check.
+ // In either case, there's no need to update URL index or failure count.
+ case ErrorCode::kOmahaRequestError:
+ case ErrorCode::kOmahaResponseHandlerError:
+ case ErrorCode::kPostinstallRunnerError:
+ case ErrorCode::kFilesystemCopierError:
+ case ErrorCode::kInstallDeviceOpenError:
+ case ErrorCode::kKernelDeviceOpenError:
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ case ErrorCode::kNewRootfsVerificationError:
+ case ErrorCode::kNewKernelVerificationError:
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ case ErrorCode::kOmahaRequestXMLParseError:
+ case ErrorCode::kOmahaResponseInvalid:
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ case ErrorCode::kPostinstallPowerwashError:
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ case ErrorCode::kFilesystemVerifierError:
+ LOG(INFO) << "Not incrementing URL index or failure count for this error";
+ break;
+
+ case ErrorCode::kSuccess: // success code
+ case ErrorCode::kUmaReportedMax: // not an error code
+ case ErrorCode::kOmahaRequestHTTPResponseBase: // aggregated already
+ case ErrorCode::kDevModeFlag: // not an error code
+ case ErrorCode::kResumedFlag: // not an error code
+ case ErrorCode::kTestImageFlag: // not an error code
+ case ErrorCode::kTestOmahaUrlFlag: // not an error code
+ case ErrorCode::kSpecialFlags: // not an error code
+ // These shouldn't happen. Enumerating these explicitly here so that we
+ // can let the compiler warn about new error codes that are added to
+ // action_processor.h but not added here.
+ LOG(WARNING) << "Unexpected error code for UpdateFailed";
+ break;
+
+ // Note: Not adding a default here so as to let the compiler warn us of
+ // any new enums that were added in the .h but not listed in this switch.
+ }
+}
+
+bool PayloadState::ShouldBackoffDownload() {
+ if (response_.disable_payload_backoff) {
+ LOG(INFO) << "Payload backoff logic is disabled. "
+ "Can proceed with the download";
+ return false;
+ }
+ if (GetUsingP2PForDownloading() && !GetP2PUrl().empty()) {
+ LOG(INFO) << "Payload backoff logic is disabled because download "
+ << "will happen from local peer (via p2p).";
+ return false;
+ }
+ if (system_state_->request_params()->interactive()) {
+ LOG(INFO) << "Payload backoff disabled for interactive update checks.";
+ return false;
+ }
+ if (response_.is_delta_payload) {
+ // If delta payloads fail, we want to fallback quickly to full payloads as
+ // they are more likely to succeed. Exponential backoffs would greatly
+ // slow down the fallback to full payloads. So we don't backoff for delta
+ // payloads.
+ LOG(INFO) << "No backoffs for delta payloads. "
+ << "Can proceed with the download";
+ return false;
+ }
+
+ if (!system_state_->hardware()->IsOfficialBuild()) {
+ // Backoffs are needed only for official builds. We do not want any delays
+ // or update failures due to backoffs during testing or development.
+ LOG(INFO) << "No backoffs for test/dev images. "
+ << "Can proceed with the download";
+ return false;
+ }
+
+ if (backoff_expiry_time_.is_null()) {
+ LOG(INFO) << "No backoff expiry time has been set. "
+ << "Can proceed with the download";
+ return false;
+ }
+
+ if (backoff_expiry_time_ < Time::Now()) {
+ LOG(INFO) << "The backoff expiry time ("
+ << utils::ToString(backoff_expiry_time_)
+ << ") has elapsed. Can proceed with the download";
+ return false;
+ }
+
+ LOG(INFO) << "Cannot proceed with downloads as we need to backoff until "
+ << utils::ToString(backoff_expiry_time_);
+ return true;
+}
+
+void PayloadState::Rollback() {
+ SetRollbackVersion(system_state_->request_params()->app_version());
+ AttemptStarted(AttemptType::kRollback);
+}
+
+void PayloadState::IncrementPayloadAttemptNumber() {
+ // Update the payload attempt number for both payload types: full and delta.
+ SetPayloadAttemptNumber(GetPayloadAttemptNumber() + 1);
+}
+
+void PayloadState::IncrementFullPayloadAttemptNumber() {
+ // Update the payload attempt number for full payloads and the backoff time.
+ if (response_.is_delta_payload) {
+ LOG(INFO) << "Not incrementing payload attempt number for delta payloads";
+ return;
+ }
+
+ LOG(INFO) << "Incrementing the full payload attempt number";
+ SetFullPayloadAttemptNumber(GetFullPayloadAttemptNumber() + 1);
+ UpdateBackoffExpiryTime();
+}
+
+void PayloadState::IncrementUrlIndex() {
+ uint32_t next_url_index = GetUrlIndex() + 1;
+ if (next_url_index < candidate_urls_.size()) {
+ LOG(INFO) << "Incrementing the URL index for next attempt";
+ SetUrlIndex(next_url_index);
+ } else {
+ LOG(INFO) << "Resetting the current URL index (" << GetUrlIndex() << ") to "
+ << "0 as we only have " << candidate_urls_.size()
+ << " candidate URL(s)";
+ SetUrlIndex(0);
+ IncrementPayloadAttemptNumber();
+ IncrementFullPayloadAttemptNumber();
+ }
+
+ // If we have multiple URLs, record that we just switched to another one
+ if (candidate_urls_.size() > 1)
+ SetUrlSwitchCount(url_switch_count_ + 1);
+
+ // Whenever we update the URL index, we should also clear the URL failure
+ // count so we can start over fresh for the new URL.
+ SetUrlFailureCount(0);
+}
+
+void PayloadState::IncrementFailureCount() {
+ uint32_t next_url_failure_count = GetUrlFailureCount() + 1;
+ if (next_url_failure_count < response_.max_failure_count_per_url) {
+ LOG(INFO) << "Incrementing the URL failure count";
+ SetUrlFailureCount(next_url_failure_count);
+ } else {
+ LOG(INFO) << "Reached max number of failures for Url" << GetUrlIndex()
+ << ". Trying next available URL";
+ IncrementUrlIndex();
+ }
+}
+
+void PayloadState::UpdateBackoffExpiryTime() {
+ if (response_.disable_payload_backoff) {
+ LOG(INFO) << "Resetting backoff expiry time as payload backoff is disabled";
+ SetBackoffExpiryTime(Time());
+ return;
+ }
+
+ if (GetFullPayloadAttemptNumber() == 0) {
+ SetBackoffExpiryTime(Time());
+ return;
+ }
+
+ // Since we're doing left-shift below, make sure we don't shift more
+ // than this. E.g. if int is 4-bytes, don't left-shift more than 30 bits,
+ // since we don't expect value of kMaxBackoffDays to be more than 100 anyway.
+ int num_days = 1; // the value to be shifted.
+ const int kMaxShifts = (sizeof(num_days) * 8) - 2;
+
+ // Normal backoff days is 2 raised to (payload_attempt_number - 1).
+ // E.g. if payload_attempt_number is over 30, limit power to 30.
+ int power = min(GetFullPayloadAttemptNumber() - 1, kMaxShifts);
+
+ // The number of days is the minimum of 2 raised to (payload_attempt_number
+ // - 1) or kMaxBackoffDays.
+ num_days = min(num_days << power, kMaxBackoffDays);
+
+ // We don't want all retries to happen exactly at the same time when
+ // retrying after backoff. So add some random minutes to fuzz.
+ int fuzz_minutes = utils::FuzzInt(0, kMaxBackoffFuzzMinutes);
+ TimeDelta next_backoff_interval = TimeDelta::FromDays(num_days) +
+ TimeDelta::FromMinutes(fuzz_minutes);
+ LOG(INFO) << "Incrementing the backoff expiry time by "
+ << utils::FormatTimeDelta(next_backoff_interval);
+ SetBackoffExpiryTime(Time::Now() + next_backoff_interval);
+}
+
+void PayloadState::UpdateCurrentDownloadSource() {
+ current_download_source_ = kNumDownloadSources;
+
+ if (using_p2p_for_downloading_) {
+ current_download_source_ = kDownloadSourceHttpPeer;
+ } else if (GetUrlIndex() < candidate_urls_.size()) {
+ string current_url = candidate_urls_[GetUrlIndex()];
+ if (base::StartsWithASCII(current_url, "https://", false))
+ current_download_source_ = kDownloadSourceHttpsServer;
+ else if (base::StartsWithASCII(current_url, "http://", false))
+ current_download_source_ = kDownloadSourceHttpServer;
+ }
+
+ LOG(INFO) << "Current download source: "
+ << utils::ToString(current_download_source_);
+}
+
+void PayloadState::UpdateBytesDownloaded(size_t count) {
+ SetCurrentBytesDownloaded(
+ current_download_source_,
+ GetCurrentBytesDownloaded(current_download_source_) + count,
+ false);
+ SetTotalBytesDownloaded(
+ current_download_source_,
+ GetTotalBytesDownloaded(current_download_source_) + count,
+ false);
+
+ attempt_num_bytes_downloaded_ += count;
+}
+
+PayloadType PayloadState::CalculatePayloadType() {
+ PayloadType payload_type;
+ OmahaRequestParams* params = system_state_->request_params();
+ if (response_.is_delta_payload) {
+ payload_type = kPayloadTypeDelta;
+ } else if (params->delta_okay()) {
+ payload_type = kPayloadTypeFull;
+ } else { // Full payload, delta was not allowed by request.
+ payload_type = kPayloadTypeForcedFull;
+ }
+ return payload_type;
+}
+
+// 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();
+
+ PayloadType payload_type = CalculatePayloadType();
+
+ int64_t payload_size = response_.size;
+
+ int64_t payload_bytes_downloaded = attempt_num_bytes_downloaded_;
+
+ ClockInterface *clock = system_state_->clock();
+ TimeDelta duration = clock->GetBootTime() - attempt_start_time_boot_;
+ TimeDelta duration_uptime = clock->GetMonotonicTime() -
+ attempt_start_time_monotonic_;
+
+ int64_t payload_download_speed_bps = 0;
+ int64_t usec = duration_uptime.InMicroseconds();
+ if (usec > 0) {
+ double sec = static_cast<double>(usec) / Time::kMicrosecondsPerSecond;
+ double bps = static_cast<double>(payload_bytes_downloaded) / sec;
+ payload_download_speed_bps = static_cast<int64_t>(bps);
+ }
+
+ DownloadSource download_source = current_download_source_;
+
+ metrics::DownloadErrorCode payload_download_error_code =
+ metrics::DownloadErrorCode::kUnset;
+ ErrorCode internal_error_code = ErrorCode::kSuccess;
+ metrics::AttemptResult attempt_result = metrics_utils::GetAttemptResult(code);
+
+ // Add additional detail to AttemptResult
+ switch (attempt_result) {
+ case metrics::AttemptResult::kPayloadDownloadError:
+ payload_download_error_code = metrics_utils::GetDownloadErrorCode(code);
+ break;
+
+ case metrics::AttemptResult::kInternalError:
+ internal_error_code = code;
+ break;
+
+ // Explicit fall-through for cases where we do not have additional
+ // detail. We avoid the default keyword to force people adding new
+ // AttemptResult values to visit this code and examine whether
+ // additional detail is needed.
+ case metrics::AttemptResult::kUpdateSucceeded:
+ case metrics::AttemptResult::kMetadataMalformed:
+ case metrics::AttemptResult::kOperationMalformed:
+ case metrics::AttemptResult::kOperationExecutionError:
+ case metrics::AttemptResult::kMetadataVerificationFailed:
+ case metrics::AttemptResult::kPayloadVerificationFailed:
+ case metrics::AttemptResult::kVerificationFailed:
+ case metrics::AttemptResult::kPostInstallFailed:
+ case metrics::AttemptResult::kAbnormalTermination:
+ case metrics::AttemptResult::kNumConstants:
+ case metrics::AttemptResult::kUnset:
+ break;
+ }
+
+ metrics::ReportUpdateAttemptMetrics(system_state_,
+ attempt_number,
+ payload_type,
+ duration,
+ duration_uptime,
+ payload_size,
+ payload_bytes_downloaded,
+ payload_download_speed_bps,
+ download_source,
+ attempt_result,
+ internal_error_code,
+ payload_download_error_code,
+ attempt_connection_type_);
+}
+
+void PayloadState::PersistAttemptMetrics() {
+ // TODO(zeuthen): For now we only persist whether an attempt was in
+ // progress and not values/metrics related to the attempt. This
+ // means that when this happens, of all the UpdateEngine.Attempt.*
+ // metrics, only UpdateEngine.Attempt.Result is reported (with the
+ // value |kAbnormalTermination|). In the future we might want to
+ // persist more data so we can report other metrics in the
+ // UpdateEngine.Attempt.* namespace when this happens.
+ prefs_->SetBoolean(kPrefsAttemptInProgress, true);
+}
+
+void PayloadState::ClearPersistedAttemptMetrics() {
+ prefs_->Delete(kPrefsAttemptInProgress);
+}
+
+void PayloadState::ReportAndClearPersistedAttemptMetrics() {
+ bool attempt_in_progress = false;
+ if (!prefs_->GetBoolean(kPrefsAttemptInProgress, &attempt_in_progress))
+ return;
+ if (!attempt_in_progress)
+ return;
+
+ metrics::ReportAbnormallyTerminatedUpdateAttemptMetrics(system_state_);
+
+ ClearPersistedAttemptMetrics();
+}
+
+void PayloadState::CollectAndReportSuccessfulUpdateMetrics() {
+ string metric;
+
+ // Report metrics collected from all known download sources to UMA.
+ int64_t total_bytes_by_source[kNumDownloadSources];
+ int64_t successful_bytes = 0;
+ int64_t total_bytes = 0;
+ int64_t successful_mbs = 0;
+ int64_t total_mbs = 0;
+
+ for (int i = 0; i < kNumDownloadSources; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+ int64_t bytes;
+
+ // Only consider this download source (and send byte counts) as
+ // having been used if we downloaded a non-trivial amount of bytes
+ // (e.g. at least 1 MiB) that contributed to the final success of
+ // the update. Otherwise we're going to end up with a lot of
+ // zero-byte events in the histogram.
+
+ bytes = GetCurrentBytesDownloaded(source);
+ successful_bytes += bytes;
+ successful_mbs += bytes / kNumBytesInOneMiB;
+ SetCurrentBytesDownloaded(source, 0, true);
+
+ bytes = GetTotalBytesDownloaded(source);
+ total_bytes_by_source[i] = bytes;
+ total_bytes += bytes;
+ total_mbs += bytes / kNumBytesInOneMiB;
+ SetTotalBytesDownloaded(source, 0, true);
+ }
+
+ int download_overhead_percentage = 0;
+ if (successful_bytes > 0) {
+ download_overhead_percentage = (total_bytes - successful_bytes) * 100ULL /
+ successful_bytes;
+ }
+
+ int url_switch_count = static_cast<int>(url_switch_count_);
+
+ int reboot_count = GetNumReboots();
+
+ SetNumReboots(0);
+
+ TimeDelta duration = GetUpdateDuration();
+
+ prefs_->Delete(kPrefsUpdateTimestampStart);
+ prefs_->Delete(kPrefsUpdateDurationUptime);
+
+ PayloadType payload_type = CalculatePayloadType();
+
+ int64_t payload_size = response_.size;
+
+ int attempt_count = GetPayloadAttemptNumber();
+
+ int updates_abandoned_count = num_responses_seen_ - 1;
+
+ metrics::ReportSuccessfulUpdateMetrics(system_state_,
+ attempt_count,
+ updates_abandoned_count,
+ payload_type,
+ payload_size,
+ total_bytes_by_source,
+ download_overhead_percentage,
+ duration,
+ reboot_count,
+ url_switch_count);
+}
+
+void PayloadState::UpdateNumReboots() {
+ // We only update the reboot count when the system has been detected to have
+ // been rebooted.
+ if (!system_state_->system_rebooted()) {
+ return;
+ }
+
+ SetNumReboots(GetNumReboots() + 1);
+}
+
+void PayloadState::SetNumReboots(uint32_t num_reboots) {
+ CHECK(prefs_);
+ num_reboots_ = num_reboots;
+ prefs_->SetInt64(kPrefsNumReboots, num_reboots);
+ LOG(INFO) << "Number of Reboots during current update attempt = "
+ << num_reboots_;
+}
+
+void PayloadState::ResetPersistedState() {
+ SetPayloadAttemptNumber(0);
+ SetFullPayloadAttemptNumber(0);
+ SetUrlIndex(0);
+ SetUrlFailureCount(0);
+ SetUrlSwitchCount(0);
+ UpdateBackoffExpiryTime(); // This will reset the backoff expiry time.
+ SetUpdateTimestampStart(system_state_->clock()->GetWallclockTime());
+ SetUpdateTimestampEnd(Time()); // Set to null time
+ SetUpdateDurationUptime(TimeDelta::FromSeconds(0));
+ ResetDownloadSourcesOnNewUpdate();
+ ResetRollbackVersion();
+ SetP2PNumAttempts(0);
+ SetP2PFirstAttemptTimestamp(Time()); // Set to null time
+ SetScatteringWaitPeriod(TimeDelta());
+}
+
+void PayloadState::ResetRollbackVersion() {
+ CHECK(powerwash_safe_prefs_);
+ rollback_version_ = "";
+ powerwash_safe_prefs_->Delete(kPrefsRollbackVersion);
+}
+
+void PayloadState::ResetDownloadSourcesOnNewUpdate() {
+ for (int i = 0; i < kNumDownloadSources; i++) {
+ DownloadSource source = static_cast<DownloadSource>(i);
+ SetCurrentBytesDownloaded(source, 0, true);
+ // Note: Not resetting the TotalBytesDownloaded as we want that metric
+ // to count the bytes downloaded across various update attempts until
+ // we have successfully applied the update.
+ }
+}
+
+int64_t PayloadState::GetPersistedValue(const string& key) {
+ CHECK(prefs_);
+ if (!prefs_->Exists(key))
+ return 0;
+
+ int64_t stored_value;
+ if (!prefs_->GetInt64(key, &stored_value))
+ return 0;
+
+ if (stored_value < 0) {
+ LOG(ERROR) << key << ": Invalid value (" << stored_value
+ << ") in persisted state. Defaulting to 0";
+ return 0;
+ }
+
+ return stored_value;
+}
+
+string PayloadState::CalculateResponseSignature() {
+ string response_sign = base::StringPrintf(
+ "NumURLs = %d\n", static_cast<int>(candidate_urls_.size()));
+
+ for (size_t i = 0; i < candidate_urls_.size(); i++)
+ response_sign += base::StringPrintf("Candidate Url%d = %s\n",
+ static_cast<int>(i),
+ candidate_urls_[i].c_str());
+
+ response_sign += base::StringPrintf(
+ "Payload Size = %ju\n"
+ "Payload Sha256 Hash = %s\n"
+ "Metadata Size = %ju\n"
+ "Metadata Signature = %s\n"
+ "Is Delta Payload = %d\n"
+ "Max Failure Count Per Url = %d\n"
+ "Disable Payload Backoff = %d\n",
+ static_cast<uintmax_t>(response_.size),
+ response_.hash.c_str(),
+ static_cast<uintmax_t>(response_.metadata_size),
+ response_.metadata_signature.c_str(),
+ response_.is_delta_payload,
+ response_.max_failure_count_per_url,
+ response_.disable_payload_backoff);
+ return response_sign;
+}
+
+void PayloadState::LoadResponseSignature() {
+ CHECK(prefs_);
+ string stored_value;
+ if (prefs_->Exists(kPrefsCurrentResponseSignature) &&
+ prefs_->GetString(kPrefsCurrentResponseSignature, &stored_value)) {
+ SetResponseSignature(stored_value);
+ }
+}
+
+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_);
+}
+
+void PayloadState::LoadPayloadAttemptNumber() {
+ SetPayloadAttemptNumber(GetPersistedValue(kPrefsPayloadAttemptNumber));
+}
+
+void PayloadState::LoadFullPayloadAttemptNumber() {
+ SetFullPayloadAttemptNumber(GetPersistedValue(
+ kPrefsFullPayloadAttemptNumber));
+}
+
+void PayloadState::SetPayloadAttemptNumber(int payload_attempt_number) {
+ CHECK(prefs_);
+ payload_attempt_number_ = payload_attempt_number;
+ LOG(INFO) << "Payload Attempt Number = " << payload_attempt_number_;
+ prefs_->SetInt64(kPrefsPayloadAttemptNumber, 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,
+ full_payload_attempt_number_);
+}
+
+void PayloadState::LoadUrlIndex() {
+ SetUrlIndex(GetPersistedValue(kPrefsCurrentUrlIndex));
+}
+
+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_);
+
+ // Also update the download source, which is purely dependent on the
+ // current URL index alone.
+ UpdateCurrentDownloadSource();
+}
+
+void PayloadState::LoadScatteringWaitPeriod() {
+ SetScatteringWaitPeriod(
+ TimeDelta::FromSeconds(GetPersistedValue(kPrefsWallClockWaitPeriod)));
+}
+
+void PayloadState::SetScatteringWaitPeriod(TimeDelta wait_period) {
+ CHECK(prefs_);
+ scattering_wait_period_ = wait_period;
+ LOG(INFO) << "Scattering Wait Period (seconds) = "
+ << scattering_wait_period_.InSeconds();
+ if (scattering_wait_period_.InSeconds() > 0) {
+ prefs_->SetInt64(kPrefsWallClockWaitPeriod,
+ scattering_wait_period_.InSeconds());
+ } else {
+ prefs_->Delete(kPrefsWallClockWaitPeriod);
+ }
+}
+
+void PayloadState::LoadUrlSwitchCount() {
+ SetUrlSwitchCount(GetPersistedValue(kPrefsUrlSwitchCount));
+}
+
+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_);
+}
+
+void PayloadState::LoadUrlFailureCount() {
+ SetUrlFailureCount(GetPersistedValue(kPrefsCurrentUrlFailureCount));
+}
+
+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_;
+ prefs_->SetInt64(kPrefsCurrentUrlFailureCount, url_failure_count_);
+}
+
+void PayloadState::LoadBackoffExpiryTime() {
+ CHECK(prefs_);
+ int64_t stored_value;
+ if (!prefs_->Exists(kPrefsBackoffExpiryTime))
+ return;
+
+ if (!prefs_->GetInt64(kPrefsBackoffExpiryTime, &stored_value))
+ return;
+
+ Time stored_time = Time::FromInternalValue(stored_value);
+ if (stored_time > Time::Now() + TimeDelta::FromDays(kMaxBackoffDays)) {
+ LOG(ERROR) << "Invalid backoff expiry time ("
+ << utils::ToString(stored_time)
+ << ") in persisted state. Resetting.";
+ stored_time = Time();
+ }
+ SetBackoffExpiryTime(stored_time);
+}
+
+void PayloadState::SetBackoffExpiryTime(const Time& new_time) {
+ CHECK(prefs_);
+ backoff_expiry_time_ = new_time;
+ LOG(INFO) << "Backoff Expiry Time = "
+ << utils::ToString(backoff_expiry_time_);
+ prefs_->SetInt64(kPrefsBackoffExpiryTime,
+ backoff_expiry_time_.ToInternalValue());
+}
+
+TimeDelta PayloadState::GetUpdateDuration() {
+ Time end_time = update_timestamp_end_.is_null()
+ ? system_state_->clock()->GetWallclockTime() :
+ update_timestamp_end_;
+ return end_time - update_timestamp_start_;
+}
+
+void PayloadState::LoadUpdateTimestampStart() {
+ int64_t stored_value;
+ Time stored_time;
+
+ CHECK(prefs_);
+
+ Time now = system_state_->clock()->GetWallclockTime();
+
+ if (!prefs_->Exists(kPrefsUpdateTimestampStart)) {
+ // The preference missing is not unexpected - in that case, just
+ // use the current time as start time
+ stored_time = now;
+ } else if (!prefs_->GetInt64(kPrefsUpdateTimestampStart, &stored_value)) {
+ LOG(ERROR) << "Invalid UpdateTimestampStart value. Resetting.";
+ stored_time = now;
+ } else {
+ stored_time = Time::FromInternalValue(stored_value);
+ }
+
+ // Sanity 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.
+ TimeDelta duration_according_to_stored_time = now - stored_time;
+ if (duration_according_to_stored_time < -kDurationSlack) {
+ LOG(ERROR) << "The UpdateTimestampStart value ("
+ << utils::ToString(stored_time)
+ << ") in persisted state is "
+ << utils::FormatTimeDelta(duration_according_to_stored_time)
+ << " in the future. Resetting.";
+ stored_time = now;
+ }
+
+ SetUpdateTimestampStart(stored_time);
+}
+
+void PayloadState::SetUpdateTimestampStart(const Time& value) {
+ CHECK(prefs_);
+ update_timestamp_start_ = value;
+ prefs_->SetInt64(kPrefsUpdateTimestampStart,
+ update_timestamp_start_.ToInternalValue());
+ LOG(INFO) << "Update Timestamp Start = "
+ << utils::ToString(update_timestamp_start_);
+}
+
+void PayloadState::SetUpdateTimestampEnd(const Time& value) {
+ update_timestamp_end_ = value;
+ LOG(INFO) << "Update Timestamp End = "
+ << utils::ToString(update_timestamp_end_);
+}
+
+TimeDelta PayloadState::GetUpdateDurationUptime() {
+ return update_duration_uptime_;
+}
+
+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
+ } else if (!prefs_->GetInt64(kPrefsUpdateDurationUptime, &stored_value)) {
+ LOG(ERROR) << "Invalid UpdateDurationUptime value. Resetting.";
+ stored_delta = TimeDelta::FromSeconds(0);
+ } else {
+ stored_delta = TimeDelta::FromInternalValue(stored_value);
+ }
+
+ // Sanity-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;
+ if (diff < -kDurationSlack) {
+ LOG(ERROR) << "The UpdateDurationUptime value ("
+ << utils::FormatTimeDelta(stored_delta)
+ << ") in persisted state is "
+ << utils::FormatTimeDelta(diff)
+ << " larger than the wall-clock delta. Resetting.";
+ stored_delta = update_duration_current_;
+ }
+
+ SetUpdateDurationUptime(stored_delta);
+}
+
+void PayloadState::LoadNumReboots() {
+ SetNumReboots(GetPersistedValue(kPrefsNumReboots));
+}
+
+void PayloadState::LoadRollbackVersion() {
+ CHECK(powerwash_safe_prefs_);
+ string rollback_version;
+ if (powerwash_safe_prefs_->GetString(kPrefsRollbackVersion,
+ &rollback_version)) {
+ SetRollbackVersion(rollback_version);
+ }
+}
+
+void PayloadState::SetRollbackVersion(const string& rollback_version) {
+ CHECK(powerwash_safe_prefs_);
+ LOG(INFO) << "Blacklisting version "<< rollback_version;
+ rollback_version_ = rollback_version;
+ powerwash_safe_prefs_->SetString(kPrefsRollbackVersion, 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,
+ update_duration_uptime_.ToInternalValue());
+ if (use_logging) {
+ LOG(INFO) << "Update Duration Uptime = "
+ << utils::FormatTimeDelta(update_duration_uptime_);
+ }
+}
+
+void PayloadState::SetUpdateDurationUptime(const TimeDelta& value) {
+ Time now = system_state_->clock()->GetMonotonicTime();
+ SetUpdateDurationUptimeExtended(value, now, true);
+}
+
+void PayloadState::CalculateUpdateDurationUptime() {
+ Time now = system_state_->clock()->GetMonotonicTime();
+ TimeDelta uptime_since_last_update = now - update_duration_uptime_timestamp_;
+ TimeDelta new_uptime = update_duration_uptime_ + uptime_since_last_update;
+ // We're frequently called so avoid logging this write
+ SetUpdateDurationUptimeExtended(new_uptime, now, false);
+}
+
+string PayloadState::GetPrefsKey(const string& prefix, DownloadSource source) {
+ return prefix + "-from-" + utils::ToString(source);
+}
+
+void PayloadState::LoadCurrentBytesDownloaded(DownloadSource source) {
+ string key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source);
+ SetCurrentBytesDownloaded(source, GetPersistedValue(key), true);
+}
+
+void PayloadState::SetCurrentBytesDownloaded(
+ DownloadSource source,
+ uint64_t current_bytes_downloaded,
+ bool log) {
+ CHECK(prefs_);
+
+ if (source >= kNumDownloadSources)
+ return;
+
+ // Update the in-memory value.
+ current_bytes_downloaded_[source] = current_bytes_downloaded;
+
+ string prefs_key = GetPrefsKey(kPrefsCurrentBytesDownloaded, source);
+ prefs_->SetInt64(prefs_key, current_bytes_downloaded);
+ LOG_IF(INFO, log) << "Current bytes downloaded for "
+ << utils::ToString(source) << " = "
+ << GetCurrentBytesDownloaded(source);
+}
+
+void PayloadState::LoadTotalBytesDownloaded(DownloadSource source) {
+ string key = GetPrefsKey(kPrefsTotalBytesDownloaded, source);
+ SetTotalBytesDownloaded(source, GetPersistedValue(key), true);
+}
+
+void PayloadState::SetTotalBytesDownloaded(
+ DownloadSource source,
+ uint64_t total_bytes_downloaded,
+ bool log) {
+ CHECK(prefs_);
+
+ if (source >= kNumDownloadSources)
+ return;
+
+ // Update the in-memory value.
+ total_bytes_downloaded_[source] = total_bytes_downloaded;
+
+ // Persist.
+ string prefs_key = GetPrefsKey(kPrefsTotalBytesDownloaded, source);
+ prefs_->SetInt64(prefs_key, total_bytes_downloaded);
+ LOG_IF(INFO, log) << "Total bytes downloaded for "
+ << utils::ToString(source) << " = "
+ << GetTotalBytesDownloaded(source);
+}
+
+void PayloadState::LoadNumResponsesSeen() {
+ SetNumResponsesSeen(GetPersistedValue(kPrefsNumResponsesSeen));
+}
+
+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_);
+}
+
+void PayloadState::ComputeCandidateUrls() {
+ bool http_url_ok = true;
+
+ if (system_state_->hardware()->IsOfficialBuild()) {
+ const policy::DevicePolicy* policy = system_state_->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 {
+ LOG(INFO) << "Allowing HTTP downloads for unofficial builds";
+ http_url_ok = true;
+ }
+
+ candidate_urls_.clear();
+ for (size_t i = 0; i < response_.payload_urls.size(); i++) {
+ string candidate_url = response_.payload_urls[i];
+ if (base::StartsWithASCII(candidate_url, "http://", false) && !http_url_ok)
+ continue;
+ candidate_urls_.push_back(candidate_url);
+ LOG(INFO) << "Candidate Url" << (candidate_urls_.size() - 1)
+ << ": " << candidate_url;
+ }
+
+ LOG(INFO) << "Found " << candidate_urls_.size() << " candidate URLs "
+ << "out of " << response_.payload_urls.size() << " URLs supplied";
+}
+
+void PayloadState::CreateSystemUpdatedMarkerFile() {
+ CHECK(prefs_);
+ int64_t value = system_state_->clock()->GetWallclockTime().ToInternalValue();
+ prefs_->SetInt64(kPrefsSystemUpdatedMarker, value);
+}
+
+void PayloadState::BootedIntoUpdate(TimeDelta time_to_reboot) {
+ // Send |time_to_reboot| as a UMA stat.
+ string metric = metrics::kMetricTimeToRebootMinutes;
+ system_state_->metrics_lib()->SendToUMA(metric,
+ time_to_reboot.InMinutes(),
+ 0, // min: 0 minute
+ 30*24*60, // max: 1 month (approx)
+ kNumDefaultUmaBuckets);
+ LOG(INFO) << "Uploading " << utils::FormatTimeDelta(time_to_reboot)
+ << " for metric " << metric;
+}
+
+void PayloadState::UpdateEngineStarted() {
+ // Flush previous state from abnormal attempt failure, if any.
+ ReportAndClearPersistedAttemptMetrics();
+
+ // Avoid the UpdateEngineStarted actions if this is not the first time we
+ // run the update engine since reboot.
+ if (!system_state_->system_rebooted())
+ return;
+
+ // Figure out if we just booted into a new update
+ if (prefs_->Exists(kPrefsSystemUpdatedMarker)) {
+ int64_t stored_value;
+ if (prefs_->GetInt64(kPrefsSystemUpdatedMarker, &stored_value)) {
+ Time system_updated_at = Time::FromInternalValue(stored_value);
+ if (!system_updated_at.is_null()) {
+ TimeDelta time_to_reboot =
+ system_state_->clock()->GetWallclockTime() - system_updated_at;
+ if (time_to_reboot.ToInternalValue() < 0) {
+ LOG(ERROR) << "time_to_reboot is negative - system_updated_at: "
+ << utils::ToString(system_updated_at);
+ } else {
+ BootedIntoUpdate(time_to_reboot);
+ }
+ }
+ }
+ prefs_->Delete(kPrefsSystemUpdatedMarker);
+ }
+ // Check if it is needed to send metrics about a failed reboot into a new
+ // version.
+ ReportFailedBootIfNeeded();
+}
+
+void PayloadState::ReportFailedBootIfNeeded() {
+ // If the kPrefsTargetVersionInstalledFrom is present, a successfully applied
+ // payload was marked as ready immediately before the last reboot, and we
+ // need to check if such payload successfully rebooted or not.
+ if (prefs_->Exists(kPrefsTargetVersionInstalledFrom)) {
+ int64_t installed_from = 0;
+ if (!prefs_->GetInt64(kPrefsTargetVersionInstalledFrom, &installed_from)) {
+ LOG(ERROR) << "Error reading TargetVersionInstalledFrom on reboot.";
+ return;
+ }
+ // Old Chrome OS devices will write 2 or 4 in this setting, with the
+ // partition number. We are now using slot numbers (0 or 1) instead, so
+ // the following comparison will not match if we are comparing an old
+ // partition number against a new slot number, which is the correct outcome
+ // 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()) {
+ // A reboot was pending, but the chromebook is again in the same
+ // BootDevice where the update was installed from.
+ int64_t target_attempt;
+ if (!prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt)) {
+ LOG(ERROR) << "Error reading TargetVersionAttempt when "
+ "TargetVersionInstalledFrom was present.";
+ target_attempt = 1;
+ }
+
+ // Report the UMA metric of the current boot failure.
+ string metric = metrics::kMetricFailedUpdateCount;
+ LOG(INFO) << "Uploading " << target_attempt
+ << " (count) for metric " << metric;
+ system_state_->metrics_lib()->SendToUMA(
+ metric,
+ target_attempt,
+ 1, // min value
+ 50, // max value
+ kNumDefaultUmaBuckets);
+ } else {
+ prefs_->Delete(kPrefsTargetVersionAttempt);
+ prefs_->Delete(kPrefsTargetVersionUniqueId);
+ }
+ prefs_->Delete(kPrefsTargetVersionInstalledFrom);
+ }
+}
+
+void PayloadState::ExpectRebootInNewVersion(const string& target_version_uid) {
+ // Expect to boot into the new partition in the next reboot setting the
+ // TargetVersion* flags in the Prefs.
+ string stored_target_version_uid;
+ string target_version_id;
+ string target_partition;
+ int64_t target_attempt;
+
+ if (prefs_->Exists(kPrefsTargetVersionUniqueId) &&
+ prefs_->GetString(kPrefsTargetVersionUniqueId,
+ &stored_target_version_uid) &&
+ stored_target_version_uid == target_version_uid) {
+ if (!prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt))
+ target_attempt = 0;
+ } else {
+ prefs_->SetString(kPrefsTargetVersionUniqueId, target_version_uid);
+ target_attempt = 0;
+ }
+ prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt + 1);
+
+ prefs_->SetInt64(kPrefsTargetVersionInstalledFrom,
+ system_state_->boot_control()->GetCurrentSlot());
+}
+
+void PayloadState::ResetUpdateStatus() {
+ // Remove the TargetVersionInstalledFrom pref so that if the machine is
+ // rebooted the next boot is not flagged as failed to rebooted into the
+ // new applied payload.
+ prefs_->Delete(kPrefsTargetVersionInstalledFrom);
+
+ // Also decrement the attempt number if it exists.
+ int64_t target_attempt;
+ if (prefs_->GetInt64(kPrefsTargetVersionAttempt, &target_attempt))
+ prefs_->SetInt64(kPrefsTargetVersionAttempt, target_attempt - 1);
+}
+
+int PayloadState::GetP2PNumAttempts() {
+ return p2p_num_attempts_;
+}
+
+void PayloadState::SetP2PNumAttempts(int value) {
+ p2p_num_attempts_ = value;
+ LOG(INFO) << "p2p Num Attempts = " << p2p_num_attempts_;
+ CHECK(prefs_);
+ prefs_->SetInt64(kPrefsP2PNumAttempts, value);
+}
+
+void PayloadState::LoadP2PNumAttempts() {
+ SetP2PNumAttempts(GetPersistedValue(kPrefsP2PNumAttempts));
+}
+
+Time PayloadState::GetP2PFirstAttemptTimestamp() {
+ return p2p_first_attempt_timestamp_;
+}
+
+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);
+}
+
+void PayloadState::LoadP2PFirstAttemptTimestamp() {
+ int64_t stored_value = GetPersistedValue(kPrefsP2PFirstAttemptTimestamp);
+ Time stored_time = Time::FromInternalValue(stored_value);
+ SetP2PFirstAttemptTimestamp(stored_time);
+}
+
+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());
+ }
+ // Increase number of attempts
+ SetP2PNumAttempts(GetP2PNumAttempts() + 1);
+}
+
+bool PayloadState::P2PAttemptAllowed() {
+ if (p2p_num_attempts_ > kMaxP2PAttempts) {
+ LOG(INFO) << "Number of p2p attempts is " << p2p_num_attempts_
+ << " which is greater than "
+ << kMaxP2PAttempts
+ << " - disallowing p2p.";
+ return false;
+ }
+
+ if (!p2p_first_attempt_timestamp_.is_null()) {
+ Time now = system_state_->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"
+ << " - disallowing p2p.";
+ return false;
+ }
+ if (time_spent_attempting_p2p.InSeconds() > kMaxP2PAttemptTimeSeconds) {
+ LOG(INFO) << "Time spent attempting p2p is "
+ << utils::FormatTimeDelta(time_spent_attempting_p2p)
+ << " which is greater than "
+ << utils::FormatTimeDelta(TimeDelta::FromSeconds(
+ kMaxP2PAttemptTimeSeconds))
+ << " - disallowing p2p.";
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_state.h b/payload_state.h
new file mode 100644
index 0000000..bec5823
--- /dev/null
+++ b/payload_state.h
@@ -0,0 +1,573 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_STATE_H_
+#define UPDATE_ENGINE_PAYLOAD_STATE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/metrics.h"
+#include "update_engine/payload_state_interface.h"
+
+namespace chromeos_update_engine {
+
+class SystemState;
+
+// Encapsulates all the payload state required for download. This includes the
+// state necessary for handling multiple URLs in Omaha response, the backoff
+// state, etc. All state is persisted so that we use the most recently saved
+// value when resuming the update_engine process. All state is also cached in
+// memory so that we ensure we always make progress based on last known good
+// state even when there's any issue in reading/writing from the file system.
+class PayloadState : public PayloadStateInterface {
+ public:
+ PayloadState();
+ ~PayloadState() override {}
+
+ // Initializes a payload state object using the given global system state.
+ // 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);
+
+ // Implementation of PayloadStateInterface methods.
+ void SetResponse(const OmahaResponse& response) override;
+ void DownloadComplete() override;
+ void DownloadProgress(size_t count) override;
+ void UpdateResumed() override;
+ void UpdateRestarted() override;
+ void UpdateSucceeded() override;
+ void UpdateFailed(ErrorCode error) override;
+ void ResetUpdateStatus() override;
+ bool ShouldBackoffDownload() override;
+ void Rollback() override;
+ void ExpectRebootInNewVersion(const std::string& target_version_uid) override;
+ void SetUsingP2PForDownloading(bool value) override;
+
+ void SetUsingP2PForSharing(bool value) override {
+ using_p2p_for_sharing_ = value;
+ }
+
+ inline std::string GetResponseSignature() override {
+ return response_signature_;
+ }
+
+ inline int GetFullPayloadAttemptNumber() override {
+ return full_payload_attempt_number_;
+ }
+
+ inline int GetPayloadAttemptNumber() override {
+ return payload_attempt_number_;
+ }
+
+ inline std::string GetCurrentUrl() override {
+ return candidate_urls_.size() ? candidate_urls_[url_index_] : "";
+ }
+
+ inline uint32_t GetUrlFailureCount() override {
+ return url_failure_count_;
+ }
+
+ inline uint32_t GetUrlSwitchCount() override {
+ return url_switch_count_;
+ }
+
+ inline int GetNumResponsesSeen() override {
+ return num_responses_seen_;
+ }
+
+ inline base::Time GetBackoffExpiryTime() override {
+ return backoff_expiry_time_;
+ }
+
+ base::TimeDelta GetUpdateDuration() override;
+
+ base::TimeDelta GetUpdateDurationUptime() override;
+
+ inline uint64_t GetCurrentBytesDownloaded(DownloadSource source) override {
+ return source < kNumDownloadSources ? current_bytes_downloaded_[source] : 0;
+ }
+
+ inline uint64_t GetTotalBytesDownloaded(DownloadSource source) override {
+ return source < kNumDownloadSources ? total_bytes_downloaded_[source] : 0;
+ }
+
+ inline uint32_t GetNumReboots() override {
+ return num_reboots_;
+ }
+
+ void UpdateEngineStarted() override;
+
+ inline std::string GetRollbackVersion() override {
+ return rollback_version_;
+ }
+
+ int GetP2PNumAttempts() override;
+ base::Time GetP2PFirstAttemptTimestamp() override;
+ void P2PNewAttempt() override;
+ bool P2PAttemptAllowed() override;
+
+ bool GetUsingP2PForDownloading() const override {
+ return using_p2p_for_downloading_;
+ }
+
+ bool GetUsingP2PForSharing() const override {
+ return using_p2p_for_sharing_;
+ }
+
+ base::TimeDelta GetScatteringWaitPeriod() override {
+ return scattering_wait_period_;
+ }
+
+ void SetScatteringWaitPeriod(base::TimeDelta wait_period) override;
+
+ void SetP2PUrl(const std::string& url) override {
+ p2p_url_ = url;
+ }
+
+ std::string GetP2PUrl() const override {
+ return p2p_url_;
+ }
+
+ private:
+ enum class AttemptType {
+ kUpdate,
+ kRollback,
+ };
+
+ friend class PayloadStateTest;
+ FRIEND_TEST(PayloadStateTest, RebootAfterUpdateFailedMetric);
+ FRIEND_TEST(PayloadStateTest, RebootAfterUpdateSucceed);
+ FRIEND_TEST(PayloadStateTest, RebootAfterCanceledUpdate);
+ FRIEND_TEST(PayloadStateTest, RollbackVersion);
+ FRIEND_TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs);
+
+ // Helper called when an attempt has begun, is called by
+ // UpdateResumed(), UpdateRestarted() and Rollback().
+ void AttemptStarted(AttemptType attempt_type);
+
+ // Increments the payload attempt number used for metrics.
+ void IncrementPayloadAttemptNumber();
+
+ // Increments the payload attempt number which governs the backoff behavior
+ // at the time of the next update check.
+ void IncrementFullPayloadAttemptNumber();
+
+ // Advances the current URL index to the next available one. If all URLs have
+ // been exhausted during the current payload download attempt (as indicated
+ // by the payload attempt number), then it will increment the payload attempt
+ // number and wrap around again with the first URL in the list. This also
+ // updates the URL switch count, if needed.
+ void IncrementUrlIndex();
+
+ // Increments the failure count of the current URL. If the configured max
+ // failure count is reached for this URL, it advances the current URL index
+ // to the next URL and resets the failure count for that URL.
+ void IncrementFailureCount();
+
+ // Updates the backoff expiry time exponentially based on the current
+ // payload attempt number.
+ void UpdateBackoffExpiryTime();
+
+ // Updates the value of current download source based on the current URL
+ // index. If the download source is not one of the known sources, it's set
+ // to kNumDownloadSources.
+ void UpdateCurrentDownloadSource();
+
+ // Updates the various metrics corresponding with the given number of bytes
+ // that were downloaded recently.
+ void UpdateBytesDownloaded(size_t count);
+
+ // Calculates the PayloadType we're using.
+ PayloadType CalculatePayloadType();
+
+ // Collects and reports the various metrics related to an update attempt.
+ void CollectAndReportAttemptMetrics(ErrorCode code);
+
+ // Persists values related to the UpdateEngine.Attempt.* metrics so
+ // we can identify later if an update attempt ends abnormally.
+ void PersistAttemptMetrics();
+
+ // Clears persistent state previously set using AttemptMetricsPersist().
+ void ClearPersistedAttemptMetrics();
+
+ // Checks if persistent state previously set using AttemptMetricsPersist()
+ // exists and, if so, emits it with |attempt_result| set to
+ // metrics::AttemptResult::kAbnormalTermination.
+ void ReportAndClearPersistedAttemptMetrics();
+
+ // Collects and reports the various metrics related to a successful update.
+ void CollectAndReportSuccessfulUpdateMetrics();
+
+ // Checks if we were expecting to be running in the new version but the
+ // boot into the new version failed for some reason. If that's the case, an
+ // UMA metric is sent reporting the number of attempts the same applied
+ // payload was attempted to reboot. This function is called by UpdateAttempter
+ // every time the update engine starts and there's no reboot pending.
+ void ReportFailedBootIfNeeded();
+
+ // Resets all the persisted state values which are maintained relative to the
+ // current response signature. The response signature itself is not reset.
+ void ResetPersistedState();
+
+ // Resets the appropriate state related to download sources that need to be
+ // reset on a new update.
+ void ResetDownloadSourcesOnNewUpdate();
+
+ // 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);
+
+ // Calculates the response "signature", which is basically a string composed
+ // of the subset of the fields in the current response that affect the
+ // behavior of the PayloadState.
+ std::string CalculateResponseSignature();
+
+ // Initializes the current response signature from the persisted state.
+ void LoadResponseSignature();
+
+ // Sets the response signature to the given value. Also persists the value
+ // being set so that we resume from the save value in case of a process
+ // restart.
+ void SetResponseSignature(const std::string& response_signature);
+
+ // Initializes the payload attempt number from the persisted state.
+ void LoadPayloadAttemptNumber();
+
+ // Initializes the payload attempt number for full payloads from the persisted
+ // state.
+ void LoadFullPayloadAttemptNumber();
+
+ // Sets the payload attempt number to the given value. Also persists the
+ // value being set so that we resume from the same value in case of a process
+ // restart.
+ void SetPayloadAttemptNumber(int payload_attempt_number);
+
+ // Sets the payload attempt number for full updates to the given value. Also
+ // persists the value being set so that we resume from the same value in case
+ // of a process restart.
+ void SetFullPayloadAttemptNumber(int payload_attempt_number);
+
+ // Initializes the current URL index from the persisted state.
+ void LoadUrlIndex();
+
+ // Sets the current URL index to the given value. Also persists the value
+ // being set so that we resume from the same value in case of a process
+ // restart.
+ void SetUrlIndex(uint32_t url_index);
+
+ // Initializes the current URL's failure count from the persisted stae.
+ void LoadUrlFailureCount();
+
+ // Sets the current URL's failure count to the given value. Also persists the
+ // value being set so that we resume from the same value in case of a process
+ // restart.
+ void SetUrlFailureCount(uint32_t url_failure_count);
+
+ // Sets |url_switch_count_| to the given value and persists the value.
+ void SetUrlSwitchCount(uint32_t url_switch_count);
+
+ // Initializes |url_switch_count_| from the persisted stae.
+ void LoadUrlSwitchCount();
+
+ // Initializes the backoff expiry time from the persisted state.
+ void LoadBackoffExpiryTime();
+
+ // Sets the backoff expiry time to the given value. Also persists the value
+ // being set so that we resume from the same value in case of a process
+ // restart.
+ void SetBackoffExpiryTime(const base::Time& new_time);
+
+ // Initializes |update_timestamp_start_| from the persisted state.
+ void LoadUpdateTimestampStart();
+
+ // Sets |update_timestamp_start_| to the given value and persists the value.
+ void SetUpdateTimestampStart(const base::Time& value);
+
+ // Sets |update_timestamp_end_| to the given value. This is not persisted
+ // as it happens at the end of the update process where state is deleted
+ // anyway.
+ void SetUpdateTimestampEnd(const base::Time& value);
+
+ // Initializes |update_duration_uptime_| from the persisted state.
+ void LoadUpdateDurationUptime();
+
+ // Helper method used in SetUpdateDurationUptime() and
+ // CalculateUpdateDurationUptime().
+ void SetUpdateDurationUptimeExtended(const base::TimeDelta& value,
+ const base::Time& timestamp,
+ bool use_logging);
+
+ // Sets |update_duration_uptime_| to the given value and persists
+ // the value and sets |update_duration_uptime_timestamp_| to the
+ // current monotonic time.
+ void SetUpdateDurationUptime(const base::TimeDelta& value);
+
+ // Adds the difference between current monotonic time and
+ // |update_duration_uptime_timestamp_| to |update_duration_uptime_| and
+ // sets |update_duration_uptime_timestamp_| to current monotonic time.
+ void CalculateUpdateDurationUptime();
+
+ // Returns the full key for a download source given the prefix.
+ std::string GetPrefsKey(const std::string& prefix, DownloadSource source);
+
+ // Loads the number of bytes that have been currently downloaded through the
+ // previous attempts from the persisted state for the given source. It's
+ // reset to 0 everytime we begin a full update and is continued from previous
+ // attempt if we're resuming the update.
+ void LoadCurrentBytesDownloaded(DownloadSource source);
+
+ // Sets the number of bytes that have been currently downloaded for the
+ // given source. This value is also persisted.
+ void SetCurrentBytesDownloaded(DownloadSource source,
+ uint64_t current_bytes_downloaded,
+ bool log);
+
+ // Loads the total number of bytes that have been downloaded (since the last
+ // successful update) from the persisted state for the given source. It's
+ // reset to 0 everytime we successfully apply an update and counts the bytes
+ // downloaded for both successful and failed attempts since then.
+ void LoadTotalBytesDownloaded(DownloadSource source);
+
+ // Sets the total number of bytes that have been downloaded so far for the
+ // given source. This value is also persisted.
+ void SetTotalBytesDownloaded(DownloadSource source,
+ uint64_t total_bytes_downloaded,
+ bool log);
+
+ // Loads the blacklisted version from our prefs file.
+ void LoadRollbackVersion();
+
+ // Blacklists 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.
+ void ResetRollbackVersion();
+
+ inline uint32_t GetUrlIndex() {
+ return url_index_;
+ }
+
+ // Computes the list of candidate URLs from the total list of payload URLs in
+ // the Omaha response.
+ void ComputeCandidateUrls();
+
+ // Sets |num_responses_seen_| and persist it to disk.
+ void SetNumResponsesSeen(int num_responses_seen);
+
+ // Initializes |num_responses_seen_| from persisted state.
+ void LoadNumResponsesSeen();
+
+ // Initializes |num_reboots_| from the persisted state.
+ void LoadNumReboots();
+
+ // Sets |num_reboots| for the update attempt. Also persists the
+ // value being set so that we resume from the same value in case of a process
+ // restart.
+ void SetNumReboots(uint32_t num_reboots);
+
+ // Checks to see if the device rebooted since the last call and if so
+ // increments num_reboots.
+ void UpdateNumReboots();
+
+ // Writes the current wall-clock time to the kPrefsSystemUpdatedMarker
+ // state variable.
+ void CreateSystemUpdatedMarkerFile();
+
+ // Called at program startup if the device booted into a new update.
+ // The |time_to_reboot| parameter contains the (wall-clock) duration
+ // from when the update successfully completed (the value written
+ // into the kPrefsSystemUpdatedMarker state variable) until the device
+ // was booted into the update (current wall-clock time).
+ void BootedIntoUpdate(base::TimeDelta time_to_reboot);
+
+ // Loads the |kPrefsP2PFirstAttemptTimestamp| state variable from disk
+ // into |p2p_first_attempt_timestamp_|.
+ void LoadP2PFirstAttemptTimestamp();
+
+ // Loads the |kPrefsP2PNumAttempts| state variable into |p2p_num_attempts_|.
+ void LoadP2PNumAttempts();
+
+ // Sets the |kPrefsP2PNumAttempts| state variable to |value|.
+ void SetP2PNumAttempts(int value);
+
+ // Sets the |kPrefsP2PFirstAttemptTimestamp| state variable to |time|.
+ void SetP2PFirstAttemptTimestamp(const base::Time& time);
+
+ // Loads the persisted scattering wallclock-based wait period.
+ void LoadScatteringWaitPeriod();
+
+ // 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_;
+
+ // Interface object with which we read/write persisted state. This must
+ // be set by calling the Initialize method before calling any other method.
+ // This object persists across powerwashes.
+ PrefsInterface* powerwash_safe_prefs_;
+
+ // This is the current response object from Omaha.
+ OmahaResponse response_;
+
+ // Whether P2P is being used for downloading and sharing.
+ bool using_p2p_for_downloading_;
+ bool using_p2p_for_sharing_;
+
+ // Stores the P2P download URL, if one is used.
+ std::string p2p_url_;
+
+ // The cached value of |kPrefsP2PFirstAttemptTimestamp|.
+ base::Time p2p_first_attempt_timestamp_;
+
+ // The cached value of |kPrefsP2PNumAttempts|.
+ int p2p_num_attempts_;
+
+ // This stores a "signature" of the current response. The signature here
+ // refers to a subset of the current response from Omaha. Each update to
+ // this value is persisted so we resume from the same value in case of a
+ // process restart.
+ std::string response_signature_;
+
+ // The number of times we've tried to download the payload. This is
+ // incremented each time we download the payload successsfully or when we
+ // exhaust all failure limits for all URLs and are about to wrap around back
+ // to the first URL. Each update to this value is persisted so we resume from
+ // the same value in case of a process restart.
+ int payload_attempt_number_;
+
+ // The number of times we've tried to download the payload in full. This is
+ // incremented each time we download the payload in full successsfully or
+ // when we exhaust all failure limits for all URLs and are about to wrap
+ // around back to the first URL. Each update to this value is persisted so
+ // we resume from the same value in case of a process restart.
+ int full_payload_attempt_number_;
+
+ // 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
+ // 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.
+ int64_t url_index_;
+
+ // The count of failures encountered in the current attempt to download using
+ // the current URL (specified by url_index_). Each update to this value is
+ // persisted so we resume from the same value in case of a process restart.
+ int64_t url_failure_count_;
+
+ // The number of times we've switched URLs.
+ int32_t url_switch_count_;
+
+ // The current download source based on the current URL. This value is
+ // not persisted as it can be recomputed everytime we update the URL.
+ // We're storing this so as not to recompute this on every few bytes of
+ // data we read from the socket.
+ DownloadSource current_download_source_;
+
+ // The number of different Omaha responses seen. Increases every time
+ // a new response is seen. Resets to 0 only when the system has been
+ // successfully updated.
+ int num_responses_seen_;
+
+ // The number of system reboots during an update attempt. Technically since
+ // we don't go out of our way to not update it when not attempting an update,
+ // also records the number of reboots before the next update attempt starts.
+ uint32_t num_reboots_;
+
+ // The timestamp until which we've to wait before attempting to download the
+ // payload again, so as to backoff repeated downloads.
+ base::Time backoff_expiry_time_;
+
+ // The most recently calculated value of the update duration.
+ base::TimeDelta update_duration_current_;
+
+ // The point in time (wall-clock) that the update was started.
+ base::Time update_timestamp_start_;
+
+ // The point in time (wall-clock) that the update ended. If the update
+ // is still in progress, this is set to the Epoch (e.g. 0).
+ base::Time update_timestamp_end_;
+
+ // The update duration uptime
+ base::TimeDelta update_duration_uptime_;
+
+ // The monotonic time when |update_duration_uptime_| was last set
+ base::Time update_duration_uptime_timestamp_;
+
+ // The number of bytes that have been downloaded for each source for each new
+ // update attempt. If we resume an update, we'll continue from the previous
+ // value, but if we get a new response or if the previous attempt failed,
+ // we'll reset this to 0 to start afresh. Each update to this value is
+ // persisted so we resume from the same value in case of a process restart.
+ // The extra index in the array is to no-op accidental access in case the
+ // return value from GetCurrentDownloadSource is used without validation.
+ uint64_t current_bytes_downloaded_[kNumDownloadSources + 1];
+
+ // The number of bytes that have been downloaded for each source since the
+ // the last successful update. This is used to compute the overhead we incur.
+ // Each update to this value is persisted so we resume from the same value in
+ // case of a process restart.
+ // The extra index in the array is to no-op accidental access in case the
+ // return value from GetCurrentDownloadSource is used without validation.
+ uint64_t total_bytes_downloaded_[kNumDownloadSources + 1];
+
+ // A small timespan used when comparing wall-clock times for coping
+ // with the fact that clocks drift and consequently are adjusted
+ // (either forwards or backwards) via NTP.
+ static const base::TimeDelta kDurationSlack;
+
+ // The ordered list of the subset of payload URL candidates which are
+ // allowed as per device policy.
+ std::vector<std::string> candidate_urls_;
+
+ // This stores a blacklisted 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.
+ std::string rollback_version_;
+
+ // The number of bytes downloaded per attempt.
+ int64_t attempt_num_bytes_downloaded_;
+
+ // The boot time when the attempt was started.
+ base::Time attempt_start_time_boot_;
+
+ // The monotonic time when the attempt was started.
+ base::Time attempt_start_time_monotonic_;
+
+ // The connection type when the attempt started.
+ metrics::ConnectionType attempt_connection_type_;
+
+ // Whether we're currently rolling back.
+ AttemptType attempt_type_;
+
+ // The current scattering wallclock-based wait period.
+ base::TimeDelta scattering_wait_period_;
+
+ DISALLOW_COPY_AND_ASSIGN(PayloadState);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_STATE_H_
diff --git a/payload_state_interface.h b/payload_state_interface.h
new file mode 100644
index 0000000..40a13dd
--- /dev/null
+++ b/payload_state_interface.h
@@ -0,0 +1,199 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_
+#define UPDATE_ENGINE_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"
+
+namespace chromeos_update_engine {
+
+// Describes the methods that need to be implemented by the PayloadState class.
+// This interface has been carved out to support mocking of the PayloadState
+// object.
+class PayloadStateInterface {
+ public:
+ virtual ~PayloadStateInterface() = default;
+
+ // Sets the internal payload state based on the given Omaha response. This
+ // response could be the same or different from the one for which we've stored
+ // the internal state. If it's different, then this method resets all the
+ // internal state corresponding to the old response. Since the Omaha response
+ // has a lot of fields that are not related to payload state, it uses only
+ // a subset of the fields in the Omaha response to compare equality.
+ virtual void SetResponse(const OmahaResponse& response) = 0;
+
+ // This method should be called whenever we have completed downloading all
+ // the bytes of a payload and have verified that its size and hash match the
+ // expected values. We use this notificaiton to increment the payload attempt
+ // number so that the throttle the next attempt to download the same payload
+ // (in case there's an error in subsequent steps such as post-install)
+ // appropriately.
+ virtual void DownloadComplete() = 0;
+
+ // This method should be called whenever we receive new bytes from the
+ // network for the current payload. We use this notification to reset the
+ // failure count for a given URL since receipt of some bytes means we are
+ // able to make forward progress with the current URL.
+ virtual void DownloadProgress(size_t count) = 0;
+
+ // This method should be called every time we resume an update attempt.
+ virtual void UpdateResumed() = 0;
+
+ // This method should be called every time we begin a new update. This method
+ // should not be called when we resume an update from the previously
+ // downloaded point. This is used to reset the metrics for each new update.
+ virtual void UpdateRestarted() = 0;
+
+ // This method should be called once after an update attempt succeeds. This
+ // is when the relevant UMA metrics that are tracked on a per-update-basis
+ // are uploaded to the UMA server.
+ virtual void UpdateSucceeded() = 0;
+
+ // This method should be called whenever an update attempt fails with the
+ // given error code. We use this notification to update the payload state
+ // depending on the type of the error that happened.
+ virtual void UpdateFailed(ErrorCode error) = 0;
+
+ // This method should be called whenever a succeeded update is canceled, and
+ // thus can only be called after UpdateSucceeded(). This is currently used
+ // only for manual testing using the update_engine_client.
+ virtual void ResetUpdateStatus() = 0;
+
+ // This method should be called every time we initiate a Rollback.
+ virtual void Rollback() = 0;
+
+ // Sets the expectations to boot into the new version in the next reboot.
+ // This function is called every time a new update is marked as ready by
+ // UpdateSuccess(). |target_version_uid| is an unique identifier of the
+ // applied payload. It can be any string, as long as the same string is used
+ // for the same payload.
+ virtual void ExpectRebootInNewVersion(
+ const std::string& target_version_uid) = 0;
+
+ // Sets whether P2P is being used to download the update payload. This
+ // is used to keep track of download sources being used and should be called
+ // before the transfer begins.
+ virtual void SetUsingP2PForDownloading(bool value) = 0;
+
+ // Sets whether P2P is being used for sharing the update payloads.
+ virtual void SetUsingP2PForSharing(bool value) = 0;
+
+ // Returns true if we should backoff the current download attempt.
+ // False otherwise.
+ virtual bool ShouldBackoffDownload() = 0;
+
+ // Returns the currently stored response "signature". The signature is a
+ // subset of fields that are of interest to the PayloadState behavior.
+ virtual std::string GetResponseSignature() = 0;
+
+ // Returns the payload attempt number.
+ virtual int GetPayloadAttemptNumber() = 0;
+
+ // Returns the payload attempt number of the attempted full payload. Returns
+ // 0 for delta payloads.
+ virtual int GetFullPayloadAttemptNumber() = 0;
+
+ // Returns the current URL. Returns an empty string if there's no valid URL.
+ virtual std::string GetCurrentUrl() = 0;
+
+ // Returns the current URL's failure count.
+ virtual uint32_t GetUrlFailureCount() = 0;
+
+ // Returns the total number of times a new URL has been switched to
+ // for the current response.
+ virtual uint32_t GetUrlSwitchCount() = 0;
+
+ // Returns the total number of different responses seen since the
+ // last successful update.
+ virtual int GetNumResponsesSeen() = 0;
+
+ // Returns the expiry time for the current backoff period.
+ virtual base::Time GetBackoffExpiryTime() = 0;
+
+ // Returns the elapsed time used for this update, including time
+ // where the device is powered off and sleeping. If the
+ // update has not completed, returns the time spent so far.
+ virtual base::TimeDelta GetUpdateDuration() = 0;
+
+ // Returns the time used for this update not including time when
+ // the device is powered off or sleeping. If the update has not
+ // completed, returns the time spent so far.
+ virtual base::TimeDelta GetUpdateDurationUptime() = 0;
+
+ // Returns the number of bytes that have been downloaded for each source for
+ // each new update attempt. If we resume an update, we'll continue from the
+ // previous value, but if we get a new response or if the previous attempt
+ // failed, we'll reset this to 0 to start afresh.
+ virtual uint64_t GetCurrentBytesDownloaded(DownloadSource source) = 0;
+
+ // Returns the total number of bytes that have been downloaded for each
+ // source since the the last successful update. This is used to compute the
+ // overhead we incur.
+ virtual uint64_t GetTotalBytesDownloaded(DownloadSource source) = 0;
+
+ // Returns the reboot count for this update attempt.
+ virtual uint32_t GetNumReboots() = 0;
+
+ // Called at update_engine startup to do various house-keeping.
+ virtual void UpdateEngineStarted() = 0;
+
+ // Returns the version from before a rollback if our last update was a
+ // rollback.
+ virtual std::string GetRollbackVersion() = 0;
+
+ // Returns the value of number of attempts we've attempted to
+ // download the payload via p2p.
+ virtual int GetP2PNumAttempts() = 0;
+
+ // Returns the value of timestamp of the first time we've attempted
+ // to download the payload via p2p.
+ virtual base::Time GetP2PFirstAttemptTimestamp() = 0;
+
+ // Should be called every time we decide to use p2p for an update
+ // attempt. This is used to increase the p2p attempt counter and
+ // set the timestamp for first attempt.
+ virtual void P2PNewAttempt() = 0;
+
+ // Returns |true| if we are allowed to continue using p2p for
+ // downloading and |false| otherwise. This is done by recording
+ // and examining how many attempts have been done already as well
+ // as when the first attempt was.
+ virtual bool P2PAttemptAllowed() = 0;
+
+ // Gets the values previously set with SetUsingP2PForDownloading() and
+ // SetUsingP2PForSharing().
+ virtual bool GetUsingP2PForDownloading() const = 0;
+ virtual bool GetUsingP2PForSharing() const = 0;
+
+ // Returns the current (persisted) scattering wallclock-based wait period.
+ virtual base::TimeDelta GetScatteringWaitPeriod() = 0;
+
+ // Sets and persists the scattering wallclock-based wait period.
+ virtual void SetScatteringWaitPeriod(base::TimeDelta wait_period) = 0;
+
+ // Sets/gets the P2P download URL, if one is to be used.
+ virtual void SetP2PUrl(const std::string& url) = 0;
+ virtual std::string GetP2PUrl() const = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_
diff --git a/payload_state_unittest.cc b/payload_state_unittest.cc
new file mode 100644
index 0000000..a8067dc
--- /dev/null
+++ b/payload_state_unittest.cc
@@ -0,0 +1,1663 @@
+//
+// 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_state.h"
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/stringprintf.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.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_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/omaha_request_action.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+using testing::AnyNumber;
+using testing::AtLeast;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Return;
+using testing::SetArgumentPointee;
+using testing::_;
+
+namespace chromeos_update_engine {
+
+const char* kCurrentBytesDownloadedFromHttps =
+ "current-bytes-downloaded-from-HttpsServer";
+const char* kTotalBytesDownloadedFromHttps =
+ "total-bytes-downloaded-from-HttpsServer";
+const char* kCurrentBytesDownloadedFromHttp =
+ "current-bytes-downloaded-from-HttpServer";
+const char* kTotalBytesDownloadedFromHttp =
+ "total-bytes-downloaded-from-HttpServer";
+const char* kCurrentBytesDownloadedFromHttpPeer =
+ "current-bytes-downloaded-from-HttpPeer";
+const char* kTotalBytesDownloadedFromHttpPeer =
+ "total-bytes-downloaded-from-HttpPeer";
+
+static void SetupPayloadStateWith2Urls(string hash,
+ bool http_enabled,
+ PayloadState* payload_state,
+ OmahaResponse* response) {
+ response->payload_urls.clear();
+ response->payload_urls.push_back("http://test");
+ response->payload_urls.push_back("https://test");
+ response->size = 523456789;
+ response->hash = hash;
+ response->metadata_size = 558123;
+ response->metadata_signature = "metasign";
+ response->max_failure_count_per_url = 3;
+ payload_state->SetResponse(*response);
+ string stored_response_sign = payload_state->GetResponseSignature();
+
+ string expected_url_https_only =
+ "NumURLs = 1\n"
+ "Candidate Url0 = https://test\n";
+
+ string expected_urls_both =
+ "NumURLs = 2\n"
+ "Candidate Url0 = http://test\n"
+ "Candidate Url1 = https://test\n";
+
+ string expected_response_sign =
+ (http_enabled ? expected_urls_both : expected_url_https_only) +
+ base::StringPrintf("Payload Size = 523456789\n"
+ "Payload Sha256 Hash = %s\n"
+ "Metadata Size = 558123\n"
+ "Metadata Signature = metasign\n"
+ "Is Delta Payload = %d\n"
+ "Max Failure Count Per Url = %d\n"
+ "Disable Payload Backoff = %d\n",
+ hash.c_str(),
+ response->is_delta_payload,
+ response->max_failure_count_per_url,
+ response->disable_payload_backoff);
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+}
+
+class PayloadStateTest : public ::testing::Test { };
+
+TEST(PayloadStateTest, DidYouAddANewErrorCode) {
+ if (static_cast<int>(ErrorCode::kUmaReportedMax) != 48) {
+ LOG(ERROR) << "The following failure is intentional. If you added a new "
+ << "ErrorCode enum value, make sure to add it to the "
+ << "PayloadState::UpdateFailed method and then update this test "
+ << "to the new value of ErrorCode::kUmaReportedMax, which is "
+ << ErrorCode::kUmaReportedMax;
+ EXPECT_FALSE("Please see the log line above");
+ }
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithEmptyResponse) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0)).Times(AtLeast(1));
+ PayloadState payload_state;
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ payload_state.SetResponse(response);
+ string stored_response_sign = payload_state.GetResponseSignature();
+ string expected_response_sign = "NumURLs = 0\n"
+ "Payload Size = 0\n"
+ "Payload Sha256 Hash = \n"
+ "Metadata Size = 0\n"
+ "Metadata Signature = \n"
+ "Is Delta Payload = 0\n"
+ "Max Failure Count Per Url = 0\n"
+ "Disable Payload Backoff = 0\n";
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+ EXPECT_EQ("", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithSingleUrl) {
+ OmahaResponse response;
+ response.payload_urls.push_back("https://single.url.test");
+ response.size = 123456789;
+ response.hash = "hash";
+ response.metadata_size = 58123;
+ response.metadata_signature = "msign";
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0))
+ .Times(AtLeast(1));
+ PayloadState payload_state;
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ payload_state.SetResponse(response);
+ string stored_response_sign = payload_state.GetResponseSignature();
+ string expected_response_sign = "NumURLs = 1\n"
+ "Candidate Url0 = https://single.url.test\n"
+ "Payload Size = 123456789\n"
+ "Payload Sha256 Hash = hash\n"
+ "Metadata Size = 58123\n"
+ "Metadata Signature = msign\n"
+ "Is Delta Payload = 0\n"
+ "Max Failure Count Per Url = 0\n"
+ "Disable Payload Backoff = 0\n";
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+ EXPECT_EQ("https://single.url.test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, SetResponseWorksWithMultipleUrls) {
+ OmahaResponse response;
+ response.payload_urls.push_back("http://multiple.url.test");
+ response.payload_urls.push_back("https://multiple.url.test");
+ response.size = 523456789;
+ response.hash = "rhash";
+ response.metadata_size = 558123;
+ response.metadata_signature = "metasign";
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0))
+ .Times(AtLeast(1));
+
+ PayloadState payload_state;
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ payload_state.SetResponse(response);
+ string stored_response_sign = payload_state.GetResponseSignature();
+ string expected_response_sign = "NumURLs = 2\n"
+ "Candidate Url0 = http://multiple.url.test\n"
+ "Candidate Url1 = https://multiple.url.test\n"
+ "Payload Size = 523456789\n"
+ "Payload Sha256 Hash = rhash\n"
+ "Metadata Size = 558123\n"
+ "Metadata Signature = metasign\n"
+ "Is Delta Payload = 0\n"
+ "Max Failure Count Per Url = 0\n"
+ "Disable Payload Backoff = 0\n";
+ EXPECT_EQ(expected_response_sign, stored_response_sign);
+ EXPECT_EQ("http://multiple.url.test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, CanAdvanceUrlIndexCorrectly) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+ PayloadState payload_state;
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ // Payload attempt should start with 0 and then advance to 1.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _)).Times(AtLeast(2));
+
+ // Reboots will be set
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, _)).Times(AtLeast(1));
+
+ // Url index should go from 0 to 1 twice.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 1)).Times(AtLeast(1));
+
+ // Failure count should be called each times url index is set, so that's
+ // 4 times for this test.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(4));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // This does a SetResponse which causes all the states to be set to 0 for
+ // the first time.
+ SetupPayloadStateWith2Urls("Hash1235", true, &payload_state, &response);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+ // Verify that on the first error, the URL index advances to 1.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Verify that on the next error, the URL index wraps around to 0.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+ // Verify that on the next error, it again advances to 1.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Verify that we switched URLs three times
+ EXPECT_EQ(3, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, NewResponseResetsPayloadState) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+
+ // Set the first response.
+ SetupPayloadStateWith2Urls("Hash5823", true, &payload_state, &response);
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+ // Advance the URL index to 1 by faking an error.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ // Now, slightly change the response and set it again.
+ SetupPayloadStateWith2Urls("Hash8225", true, &payload_state, &response);
+ EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+ // Fake an error again.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ // Return a third different response.
+ SetupPayloadStateWith2Urls("Hash9999", true, &payload_state, &response);
+ EXPECT_EQ(3, payload_state.GetNumResponsesSeen());
+
+ // Make sure the url index was reset to 0 because of the new response.
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(0,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(0,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+}
+
+TEST(PayloadStateTest, AllCountersGetUpdatedProperlyOnErrorCodesAndEvents) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ int progress_bytes = 100;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(2));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 2))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(2));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 2))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _)).Times(AtLeast(4));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0)).Times(AtLeast(4));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 1)).Times(AtLeast(2));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(7));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 1))
+ .Times(AtLeast(2));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 2))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateTimestampStart, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsUpdateDurationUptime, _))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttps, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttpPeer, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kCurrentBytesDownloadedFromHttp, progress_bytes))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kTotalBytesDownloadedFromHttp, progress_bytes))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsNumReboots, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ SetupPayloadStateWith2Urls("Hash5873", true, &payload_state, &response);
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+ // This should advance the URL index.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ // This should advance the failure count only.
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ // This should advance the failure count only.
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(2, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+
+ // This should advance the URL index as we've reached the
+ // max failure count and reset the failure count for the new URL index.
+ // This should also wrap around the URL index and thus cause the payload
+ // attempt number to be incremented.
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(2, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // This should advance the URL index.
+ payload_state.UpdateFailed(ErrorCode::kPayloadHashMismatchError);
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(3, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // This should advance the URL index and payload attempt number due to
+ // wrap-around of URL index.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMissingError);
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(4, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // This HTTP error code should only increase the failure count.
+ payload_state.UpdateFailed(static_cast<ErrorCode>(
+ static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 404));
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(4, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // And that failure count should be reset when we download some bytes
+ // afterwards.
+ payload_state.DownloadProgress(progress_bytes);
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(4, payload_state.GetUrlSwitchCount());
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+
+ // Now, slightly change the response and set it again.
+ SetupPayloadStateWith2Urls("Hash8532", true, &payload_state, &response);
+ EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+ // Make sure the url index was reset to 0 because of the new response.
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulFullDownload) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _))
+ .Times(AtLeast(2));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // This should just advance the payload attempt number;
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, PayloadAttemptNumberIncreasesOnSuccessfulDeltaDownload) {
+ OmahaResponse response;
+ response.is_delta_payload = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsPayloadAttemptNumber, 1))
+ .Times(AtLeast(1));
+
+ // kPrefsFullPayloadAttemptNumber is not incremented for delta payloads.
+ EXPECT_CALL(*prefs, SetInt64(kPrefsFullPayloadAttemptNumber, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsBackoffExpiryTime, _))
+ .Times(1);
+
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlIndex, 0))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs, SetInt64(kPrefsCurrentUrlFailureCount, 0))
+ .Times(AtLeast(1));
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // This should just advance the payload attempt number;
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, SetResponseResetsInvalidUrlIndex) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash4427", true, &payload_state, &response);
+
+ // Generate enough events to advance URL index, failure count and
+ // payload attempt number all to 1.
+ payload_state.DownloadComplete();
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadTransferError);
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(1, 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, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsFullPayloadAttemptNumber, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsCurrentUrlIndex, _))
+ .WillRepeatedly(DoAll(SetArgumentPointee<1>(2), Return(true)));
+ EXPECT_CALL(*prefs2, GetInt64(kPrefsCurrentUrlFailureCount, _))
+ .Times(AtLeast(1));
+ EXPECT_CALL(*prefs2, 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));
+ SetupPayloadStateWith2Urls("Hash4427", true, &payload_state, &response);
+
+ // Make sure all counters get reset to 0 because of the corrupted URL index
+ // we supplied above.
+ EXPECT_EQ(0, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+}
+
+TEST(PayloadStateTest, NoBackoffInteractiveChecks) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ OmahaRequestParams params(&fake_system_state);
+ params.Init("", "", true); // is_interactive = True.
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Simulate two failures (enough to cause payload backoff) and check
+ // again that we're ready to re-download without any backoff as this is
+ // an interactive check.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, NoBackoffForP2PUpdates) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ OmahaRequestParams params(&fake_system_state);
+ params.Init("", "", false); // is_interactive = False.
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Simulate two failures (enough to cause payload backoff) and check
+ // again that we're ready to re-download without any backoff as this is
+ // an interactive check.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ // Set p2p url.
+ payload_state.SetUsingP2PForDownloading(true);
+ payload_state.SetP2PUrl("http://mypeer:52909/path/to/file");
+ // Should not backoff for p2p updates.
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+ payload_state.SetP2PUrl("");
+ // No actual p2p update if no url is provided.
+ EXPECT_TRUE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, NoBackoffForDeltaPayloads) {
+ OmahaResponse response;
+ response.is_delta_payload = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Simulate a successful download and see that we're ready to download
+ // again without any backoff as this is a delta payload.
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+ // Simulate two failures (enough to cause payload backoff) and check
+ // again that we're ready to re-download without any backoff as this is
+ // a delta payload.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(0, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+static void CheckPayloadBackoffState(PayloadState* payload_state,
+ int expected_attempt_number,
+ TimeDelta expected_days) {
+ payload_state->DownloadComplete();
+ EXPECT_EQ(expected_attempt_number,
+ payload_state->GetFullPayloadAttemptNumber());
+ EXPECT_TRUE(payload_state->ShouldBackoffDownload());
+ Time backoff_expiry_time = payload_state->GetBackoffExpiryTime();
+ // Add 1 hour extra to the 6 hour fuzz check to tolerate edge cases.
+ TimeDelta max_fuzz_delta = TimeDelta::FromHours(7);
+ Time expected_min_time = Time::Now() + expected_days - max_fuzz_delta;
+ Time expected_max_time = Time::Now() + expected_days + max_fuzz_delta;
+ EXPECT_LT(expected_min_time.ToInternalValue(),
+ backoff_expiry_time.ToInternalValue());
+ EXPECT_GT(expected_max_time.ToInternalValue(),
+ backoff_expiry_time.ToInternalValue());
+}
+
+TEST(PayloadStateTest, BackoffPeriodsAreInCorrectRange) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash8939", true, &payload_state, &response);
+
+ CheckPayloadBackoffState(&payload_state, 1, TimeDelta::FromDays(1));
+ CheckPayloadBackoffState(&payload_state, 2, TimeDelta::FromDays(2));
+ CheckPayloadBackoffState(&payload_state, 3, TimeDelta::FromDays(4));
+ CheckPayloadBackoffState(&payload_state, 4, TimeDelta::FromDays(8));
+ CheckPayloadBackoffState(&payload_state, 5, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 6, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 7, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 8, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 9, TimeDelta::FromDays(16));
+ CheckPayloadBackoffState(&payload_state, 10, TimeDelta::FromDays(16));
+}
+
+TEST(PayloadStateTest, BackoffLogicCanBeDisabled) {
+ OmahaResponse response;
+ response.disable_payload_backoff = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash8939", true, &payload_state, &response);
+
+ // Simulate a successful download and see that we are ready to download
+ // again without any backoff.
+ payload_state.DownloadComplete();
+ EXPECT_EQ(1, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(1, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+
+ // Test again, this time by simulating two errors that would cause
+ // the payload attempt number to increment due to wrap around. And
+ // check that we are still ready to re-download without any backoff.
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ payload_state.UpdateFailed(ErrorCode::kDownloadMetadataSignatureMismatch);
+ EXPECT_EQ(2, payload_state.GetPayloadAttemptNumber());
+ EXPECT_EQ(2, payload_state.GetFullPayloadAttemptNumber());
+ EXPECT_FALSE(payload_state.ShouldBackoffDownload());
+}
+
+TEST(PayloadStateTest, BytesDownloadedMetricsGetAddedToCorrectSources) {
+ OmahaResponse response;
+ response.disable_payload_backoff = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ int https_total = 0;
+ int http_total = 0;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash3286", true, &payload_state, &response);
+ EXPECT_EQ(1, payload_state.GetNumResponsesSeen());
+
+ // Simulate a previous attempt with in order to set an initial non-zero value
+ // for the total bytes downloaded for HTTP.
+ int prev_chunk = 323456789;
+ http_total += prev_chunk;
+ payload_state.DownloadProgress(prev_chunk);
+
+ // Ensure that the initial values for HTTP reflect this attempt.
+ EXPECT_EQ(prev_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+
+ // Change the response hash so as to simulate a new response which will
+ // reset the current bytes downloaded, but not the total bytes downloaded.
+ SetupPayloadStateWith2Urls("Hash9904", true, &payload_state, &response);
+ EXPECT_EQ(2, payload_state.GetNumResponsesSeen());
+
+ // First, simulate successful download of a few bytes over HTTP.
+ int first_chunk = 5000000;
+ http_total += first_chunk;
+ payload_state.DownloadProgress(first_chunk);
+ // Test that first all progress is made on HTTP and none on HTTPS.
+ EXPECT_EQ(first_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(https_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ // Simulate an error that'll cause the url index to point to https.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+
+ // Test that no new progress is made on HTTP and new progress is on HTTPS.
+ int second_chunk = 23456789;
+ https_total += second_chunk;
+ payload_state.DownloadProgress(second_chunk);
+ EXPECT_EQ(first_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(second_chunk, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(https_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ // Simulate error to go back to http.
+ payload_state.UpdateFailed(error);
+ int third_chunk = 32345678;
+ int http_chunk = first_chunk + third_chunk;
+ http_total += third_chunk;
+ payload_state.DownloadProgress(third_chunk);
+
+ // Test that third chunk is again back on HTTP. HTTPS remains on second chunk.
+ EXPECT_EQ(http_chunk,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(http_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(second_chunk, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(https_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ // Simulate error (will cause URL switch), set p2p is to be used and
+ // then do 42MB worth of progress
+ payload_state.UpdateFailed(error);
+ payload_state.SetUsingP2PForDownloading(true);
+ int p2p_total = 42 * 1000 * 1000;
+ payload_state.DownloadProgress(p2p_total);
+
+ EXPECT_EQ(p2p_total,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpPeer));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricSuccessfulUpdateUrlSwitchCount,
+ 3, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricSuccessfulUpdateTotalDurationMinutes,
+ _, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage,
+ 314, _, _, _));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricAttemptPayloadType, kPayloadTypeFull, kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeFull,
+ kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricSuccessfulUpdateAttemptCount, 1, _, _, _));
+
+ payload_state.UpdateSucceeded();
+
+ // Make sure the metrics are reset after a successful update.
+ EXPECT_EQ(0,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(0,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+ EXPECT_EQ(0, payload_state.GetNumResponsesSeen());
+}
+
+TEST(PayloadStateTest, DownloadSourcesUsedIsCorrect) {
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash3286", true, &payload_state, &response);
+
+ // Simulate progress in order to mark HTTP as one of the sources used.
+ int num_bytes = 42 * 1000 * 1000;
+ payload_state.DownloadProgress(num_bytes);
+
+ // Check that this was done via HTTP.
+ EXPECT_EQ(num_bytes,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(num_bytes,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+
+ // Check that only HTTP is reported as a download source.
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricSuccessfulUpdateDownloadSourcesUsed,
+ (1 << kDownloadSourceHttpServer),
+ _, _, _));
+
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, RestartingUpdateResetsMetrics) {
+ OmahaResponse response;
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Set the first response.
+ SetupPayloadStateWith2Urls("Hash5823", true, &payload_state, &response);
+
+ int num_bytes = 10000;
+ payload_state.DownloadProgress(num_bytes);
+ EXPECT_EQ(num_bytes,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(num_bytes,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(0, payload_state.GetCurrentBytesDownloaded(
+ kDownloadSourceHttpsServer));
+ EXPECT_EQ(0,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer));
+
+ payload_state.UpdateRestarted();
+ // Make sure the current bytes downloaded is reset, but not the total bytes.
+ EXPECT_EQ(0,
+ payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer));
+ EXPECT_EQ(num_bytes,
+ payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer));
+}
+
+TEST(PayloadStateTest, NumRebootsIncrementsCorrectly) {
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ NiceMock<MockPrefs>* prefs = fake_system_state.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));
+
+ payload_state.UpdateRestarted();
+ EXPECT_EQ(0, payload_state.GetNumReboots());
+
+ fake_system_state.set_system_rebooted(true);
+ payload_state.UpdateResumed();
+ // Num reboots should be incremented because system rebooted detected.
+ EXPECT_EQ(1, payload_state.GetNumReboots());
+
+ fake_system_state.set_system_rebooted(false);
+ payload_state.UpdateResumed();
+ // Num reboots should now be 1 as reboot was not detected.
+ EXPECT_EQ(1, payload_state.GetNumReboots());
+
+ // Restart the update again to verify we set the num of reboots back to 0.
+ payload_state.UpdateRestarted();
+ EXPECT_EQ(0, payload_state.GetNumReboots());
+}
+
+TEST(PayloadStateTest, RollbackVersion) {
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+
+ NiceMock<MockPrefs>* mock_powerwash_safe_prefs =
+ fake_system_state.mock_powerwash_safe_prefs();
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // 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();
+
+ EXPECT_EQ(rollback_version, payload_state.GetRollbackVersion());
+
+ // Change it up a little and verify we load it correctly.
+ rollback_version = "2345.0.1";
+ // Let's verify we can reload it correctly.
+ EXPECT_CALL(*mock_powerwash_safe_prefs, GetString(
+ kPrefsRollbackVersion, _)).WillOnce(DoAll(
+ SetArgumentPointee<1>(rollback_version), Return(true)));
+ EXPECT_CALL(*mock_powerwash_safe_prefs, SetString(kPrefsRollbackVersion,
+ rollback_version));
+ payload_state.LoadRollbackVersion();
+ EXPECT_EQ(rollback_version, payload_state.GetRollbackVersion());
+
+ // Check that we report only UpdateEngine.Rollback.* metrics in
+ // UpdateSucceeded().
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+ .Times(0);
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(0);
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+ SendEnumToUMA(
+ metrics::kMetricRollbackResult,
+ static_cast<int>(metrics::RollbackResult::kSuccess),
+ static_cast<int>(metrics::RollbackResult::kNumConstants)));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, DurationsAreCorrect) {
+ OmahaResponse response;
+ 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));
+
+ fake_system_state.set_clock(&fake_clock);
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // 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, &payload_state, &response);
+ 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);
+
+ // Check that durations are reset when a new response comes in.
+ SetupPayloadStateWith2Urls("Hash8594", true, &payload_state, &response);
+ EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 0);
+ EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 0);
+
+ // 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));
+ payload_state.DownloadProgress(10);
+ EXPECT_EQ(payload_state.GetUpdateDuration().InMicroseconds(), 10000000);
+ EXPECT_EQ(payload_state.GetUpdateDurationUptime().InMicroseconds(), 10000000);
+
+ // 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));
+ PayloadState payload_state2;
+ EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+ EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 10000000);
+ EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(),
+ 10000000);
+
+ // 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));
+ payload_state2.UpdateSucceeded();
+ EXPECT_EQ(payload_state2.GetUpdateDuration().InMicroseconds(), 17000000);
+ EXPECT_EQ(payload_state2.GetUpdateDurationUptime().InMicroseconds(),
+ 16000000);
+}
+
+TEST(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.SetWallclockTime(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));
+
+ // Make the update succeed.
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+ payload_state.UpdateSucceeded();
+
+ // Check that the marker was written.
+ 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.SetWallclockTime(Time::FromInternalValue(
+ 500 * Time::kMicrosecondsPerSecond));
+ PayloadState payload_state2;
+ EXPECT_TRUE(payload_state2.Initialize(&fake_system_state));
+
+ // Expect 500 - 30 seconds = 470 seconds ~= 7 min 50 sec
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricTimeToRebootMinutes,
+ 7, _, _, _));
+ fake_system_state.set_system_rebooted(true);
+
+ payload_state2.UpdateEngineStarted();
+
+ // Check that the marker was nuked.
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsSystemUpdatedMarker));
+}
+
+TEST(PayloadStateTest, RestartAfterCrash) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ NiceMock<MockPrefs>* prefs = fake_system_state.mock_prefs();
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ // Only the |kPrefsAttemptInProgress| state variable should be read.
+ EXPECT_CALL(*prefs, Exists(_)).Times(0);
+ EXPECT_CALL(*prefs, SetString(_, _)).Times(0);
+ EXPECT_CALL(*prefs, SetInt64(_, _)).Times(0);
+ EXPECT_CALL(*prefs, SetBoolean(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetString(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetInt64(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetBoolean(_, _)).Times(0);
+ EXPECT_CALL(*prefs, GetBoolean(kPrefsAttemptInProgress, _));
+
+ // No metrics are reported after a crash.
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+ SendToUMA(_, _, _, _, _)).Times(0);
+
+ // Simulate an update_engine restart without a reboot.
+ fake_system_state.set_system_rebooted(false);
+
+ payload_state.UpdateEngineStarted();
+}
+
+TEST(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_lib(),
+ SendEnumToUMA(
+ metrics::kMetricAttemptResult,
+ static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+ _)).Times(0);
+ payload_state.UpdateEngineStarted();
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsReported) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakePrefs fake_prefs;
+
+ // 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);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+ SendEnumToUMA(
+ metrics::kMetricAttemptResult,
+ static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+ _)).Times(1);
+ payload_state.UpdateEngineStarted();
+
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+}
+
+TEST(PayloadStateTest, AbnormalTerminationAttemptMetricsClearedOnSucceess) {
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+ FakePrefs fake_prefs;
+
+ // 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));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(_, _, _, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(),
+ SendEnumToUMA(
+ metrics::kMetricAttemptResult,
+ static_cast<int>(metrics::AttemptResult::kAbnormalTermination),
+ _)).Times(0);
+
+ // Attempt not in progress, should be clear.
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+
+ payload_state.UpdateRestarted();
+
+ // Attempt not in progress, should be set.
+ EXPECT_TRUE(fake_prefs.Exists(kPrefsAttemptInProgress));
+
+ payload_state.UpdateSucceeded();
+
+ // Attempt not in progress, should be clear.
+ EXPECT_FALSE(fake_prefs.Exists(kPrefsAttemptInProgress));
+}
+
+TEST(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));
+
+ // Test with no device policy. Should default to allowing http.
+ EXPECT_CALL(disable_http_policy, GetHttpDownloadsEnabled(_))
+ .WillRepeatedly(Return(false));
+
+ // Set the first response.
+ SetupPayloadStateWith2Urls("Hash8433", true, &payload_state, &response);
+
+ // Check that we use the HTTP URL since there is no value set for allowing
+ // http.
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+
+ // Test with device policy not allowing http updates.
+ EXPECT_CALL(disable_http_policy, GetHttpDownloadsEnabled(_))
+ .WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true)));
+
+ // Reset state and set again.
+ SetupPayloadStateWith2Urls("Hash8433", false, &payload_state, &response);
+
+ // Check that we skip the HTTP URL and use only the HTTPS url.
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // Advance the URL index to 1 by faking an error.
+ ErrorCode error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ payload_state.UpdateFailed(error);
+
+ // Check that we still skip the HTTP URL and use only the HTTPS url.
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlSwitchCount());
+
+ // Now, slightly change the response and set it again.
+ SetupPayloadStateWith2Urls("Hash2399", false, &payload_state, &response);
+
+ // Check that we still skip the HTTP URL and use only the HTTPS url.
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+
+ // 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);
+ EXPECT_CALL(enable_http_policy, GetHttpDownloadsEnabled(_))
+ .WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true)));
+
+ // Now, set the same response using the same hash
+ // so that we can test that the state is reset not because of the
+ // hash but because of the policy change which results in candidate url
+ // list change.
+ SetupPayloadStateWith2Urls("Hash2399", true, &payload_state, &response);
+
+ // Check that we use the HTTP URL now and the failure count is reset.
+ EXPECT_EQ("http://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+
+ // Fake a failure and see if we're moving over to the HTTPS url and update
+ // the URL switch count properly.
+ payload_state.UpdateFailed(error);
+ EXPECT_EQ("https://test", payload_state.GetCurrentUrl());
+ EXPECT_EQ(1, payload_state.GetUrlSwitchCount());
+ EXPECT_EQ(0, payload_state.GetUrlFailureCount());
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsDelta) {
+ OmahaResponse response;
+ response.is_delta_payload = true;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricAttemptPayloadType, kPayloadTypeDelta, kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeDelta,
+ kNumPayloadTypes));
+ 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);
+ params.set_delta_okay(false);
+ fake_system_state.set_request_params(¶ms);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricAttemptPayloadType, kPayloadTypeDelta,
+ kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeDelta,
+ kNumPayloadTypes));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsForcedFull) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Mock the request to a request where the delta was disabled.
+ OmahaRequestParams params(&fake_system_state);
+ params.set_delta_okay(false);
+ fake_system_state.set_request_params(¶ms);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricAttemptPayloadType, kPayloadTypeForcedFull,
+ kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeForcedFull,
+ kNumPayloadTypes));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, PayloadTypeMetricWhenTypeIsFull) {
+ OmahaResponse response;
+ response.is_delta_payload = false;
+ PayloadState payload_state;
+ FakeSystemState fake_system_state;
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash6437", true, &payload_state, &response);
+
+ // Mock the request to a request where the delta is enabled, although the
+ // result is full.
+ OmahaRequestParams params(&fake_system_state);
+ params.set_delta_okay(true);
+ fake_system_state.set_request_params(¶ms);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(_, _, _))
+ .Times(AnyNumber());
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricAttemptPayloadType, kPayloadTypeFull,
+ kNumPayloadTypes));
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendEnumToUMA(
+ metrics::kMetricSuccessfulUpdatePayloadType, kPayloadTypeFull,
+ kNumPayloadTypes));
+ payload_state.UpdateSucceeded();
+}
+
+TEST(PayloadStateTest, RebootAfterUpdateFailedMetric) {
+ FakeSystemState fake_system_state;
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+ fake_system_state.set_prefs(&fake_prefs);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+ payload_state.UpdateSucceeded();
+ 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_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, 1, _, _, _));
+ payload_state.ReportFailedBootIfNeeded();
+ Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+
+ // 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_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, 2, _, _, _));
+ payload_state.ReportFailedBootIfNeeded();
+ Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+
+ // 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_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, 1, _, _, _));
+ payload_state.ReportFailedBootIfNeeded();
+ Mock::VerifyAndClearExpectations(fake_system_state.mock_metrics_lib());
+}
+
+TEST(PayloadStateTest, RebootAfterUpdateSucceed) {
+ FakeSystemState fake_system_state;
+ 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();
+ fake_boot_control->SetCurrentSlot(0);
+
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+ payload_state.UpdateSucceeded();
+ payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+ // Change the BootDevice to a different one, no metric should be sent.
+ fake_boot_control->SetCurrentSlot(1);
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, _, _, _, _))
+ .Times(0);
+ payload_state.ReportFailedBootIfNeeded();
+
+ // A second reboot in either partition should not send a metric.
+ payload_state.ReportFailedBootIfNeeded();
+ fake_boot_control->SetCurrentSlot(0);
+ payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, RebootAfterCanceledUpdate) {
+ FakeSystemState fake_system_state;
+ OmahaResponse response;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+ SetupPayloadStateWith2Urls("Hash3141", true, &payload_state, &response);
+
+ // Simulate a successful download and update.
+ payload_state.DownloadComplete();
+ payload_state.UpdateSucceeded();
+ payload_state.ExpectRebootInNewVersion("Version:12345678");
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, _, _, _, _))
+ .Times(0);
+
+ // Cancel the applied update.
+ payload_state.ResetUpdateStatus();
+
+ // Simulate a reboot.
+ payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs) {
+ FakeSystemState fake_system_state;
+ PayloadState payload_state;
+ FakePrefs fake_prefs;
+
+ fake_system_state.set_prefs(&fake_prefs);
+ EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+ EXPECT_CALL(*fake_system_state.mock_metrics_lib(), SendToUMA(
+ metrics::kMetricFailedUpdateCount, _, _, _, _))
+ .Times(0);
+
+ // Simulate a reboot in this environment.
+ payload_state.ReportFailedBootIfNeeded();
+}
+
+TEST(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));
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // Should allow exactly kMaxP2PAttempts...
+ for (int n = 0; n < kMaxP2PAttempts; n++) {
+ payload_state.P2PNewAttempt();
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+ }
+ // ... but not more than that.
+ payload_state.P2PNewAttempt();
+ EXPECT_FALSE(payload_state.P2PAttemptAllowed());
+}
+
+TEST(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));
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // Set the clock to 1 second.
+ Time epoch = Time::FromInternalValue(1000000);
+ fake_clock.SetWallclockTime(epoch);
+
+ // Do an attempt - this will set the timestamp.
+ payload_state.P2PNewAttempt();
+
+ // Check that the timestamp equals what we just set.
+ EXPECT_EQ(epoch, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Time hasn't advanced - this should work.
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+ // Set clock to half the deadline - this should work.
+ fake_clock.SetWallclockTime(epoch +
+ TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds) / 2);
+ EXPECT_TRUE(payload_state.P2PAttemptAllowed());
+
+ // Check that the first attempt timestamp hasn't changed just
+ // because the wall-clock time changed.
+ EXPECT_EQ(epoch, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Set clock to _just_ before the deadline - this should work.
+ 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(epoch +
+ TimeDelta::FromSeconds(kMaxP2PAttemptTimeSeconds + 1));
+ EXPECT_FALSE(payload_state.P2PAttemptAllowed());
+}
+
+TEST(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));
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ Time null_time = Time();
+ EXPECT_EQ(null_time, payload_state.GetP2PFirstAttemptTimestamp());
+ EXPECT_EQ(0, payload_state.GetP2PNumAttempts());
+}
+
+TEST(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));
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // Set the clock to something known.
+ Time time = Time::FromInternalValue(12345);
+ fake_clock.SetWallclockTime(time);
+
+ // New p2p attempt - as a side-effect this will update the p2p state vars.
+ payload_state.P2PNewAttempt();
+ EXPECT_EQ(1, payload_state.GetP2PNumAttempts());
+ EXPECT_EQ(time, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // 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_EQ(1, payload_state2.GetP2PNumAttempts());
+ EXPECT_EQ(time, payload_state2.GetP2PFirstAttemptTimestamp());
+}
+
+TEST(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));
+ SetupPayloadStateWith2Urls("Hash8593", true, &payload_state, &response);
+
+ // Set the clock to something known.
+ Time time = Time::FromInternalValue(12345);
+ fake_clock.SetWallclockTime(time);
+
+ // New p2p attempt - as a side-effect this will update the p2p state vars.
+ payload_state.P2PNewAttempt();
+ EXPECT_EQ(1, payload_state.GetP2PNumAttempts());
+ EXPECT_EQ(time, payload_state.GetP2PFirstAttemptTimestamp());
+
+ // Set a new response...
+ SetupPayloadStateWith2Urls("Hash9904", true, &payload_state, &response);
+
+ // ... and check that it clears the P2P state vars.
+ Time null_time = Time();
+ EXPECT_EQ(0, payload_state.GetP2PNumAttempts());
+ EXPECT_EQ(null_time, payload_state.GetP2PFirstAttemptTimestamp());
+}
+
+} // namespace chromeos_update_engine
diff --git a/proxy_resolver.cc b/proxy_resolver.cc
new file mode 100644
index 0000000..abd6f76
--- /dev/null
+++ b/proxy_resolver.cc
@@ -0,0 +1,66 @@
+//
+// 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/proxy_resolver.h"
+
+#include <base/bind.h>
+#include <base/location.h>
+
+using brillo::MessageLoop;
+using std::deque;
+using std::string;
+
+namespace chromeos_update_engine {
+
+const char kNoProxy[] = "direct://";
+
+DirectProxyResolver::~DirectProxyResolver() {
+ if (idle_callback_id_ != MessageLoop::kTaskIdNull) {
+ // The DirectProxyResolver is instantiated as part of the UpdateAttempter
+ // which is also instantiated by default by the FakeSystemState, even when
+ // it is not used. We check the manage_shares_id_ before calling the
+ // MessageLoop::current() since the unit test using a FakeSystemState may
+ // have not define a MessageLoop for the current thread.
+ MessageLoop::current()->CancelTask(idle_callback_id_);
+ idle_callback_id_ = MessageLoop::kTaskIdNull;
+ }
+}
+
+bool DirectProxyResolver::GetProxiesForUrl(const string& url,
+ ProxiesResolvedFn callback,
+ void* data) {
+ idle_callback_id_ = MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &DirectProxyResolver::ReturnCallback,
+ base::Unretained(this),
+ callback,
+ data));
+ return true;
+}
+
+void DirectProxyResolver::ReturnCallback(ProxiesResolvedFn callback,
+ void* data) {
+ idle_callback_id_ = MessageLoop::kTaskIdNull;
+
+ // Initialize proxy pool with as many proxies as indicated (all identical).
+ deque<string> proxies(num_proxies_, kNoProxy);
+
+ (*callback)(proxies, data);
+}
+
+
+} // namespace chromeos_update_engine
diff --git a/proxy_resolver.h b/proxy_resolver.h
new file mode 100644
index 0000000..2c8824f
--- /dev/null
+++ b/proxy_resolver.h
@@ -0,0 +1,88 @@
+//
+// 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_PROXY_RESOLVER_H_
+#define UPDATE_ENGINE_PROXY_RESOLVER_H_
+
+#include <deque>
+#include <string>
+
+#include <base/logging.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+extern const char kNoProxy[];
+
+// Callback for a call to GetProxiesForUrl().
+// Resultant proxies are in |out_proxy|. Each will be in one of the
+// following forms:
+// http://<host>[:<port>] - HTTP proxy
+// socks{4,5}://<host>[:<port>] - SOCKS4/5 proxy
+// kNoProxy - no proxy
+typedef void (*ProxiesResolvedFn)(const std::deque<std::string>& proxies,
+ void* data);
+
+class ProxyResolver {
+ public:
+ ProxyResolver() {}
+ virtual ~ProxyResolver() {}
+
+ // Finds proxies for the given URL and returns them via the callback.
+ // |data| will be passed to the callback.
+ // Returns true on success.
+ virtual bool GetProxiesForUrl(const std::string& url,
+ ProxiesResolvedFn callback,
+ void* data) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProxyResolver);
+};
+
+// Always says to not use a proxy
+class DirectProxyResolver : public ProxyResolver {
+ public:
+ DirectProxyResolver() = default;
+ ~DirectProxyResolver() override;
+ bool GetProxiesForUrl(const std::string& url,
+ ProxiesResolvedFn callback,
+ void* data) override;
+
+ // Set the number of direct (non-) proxies to be returned by resolver.
+ // The default value is 1; higher numbers are currently used in testing.
+ inline void set_num_proxies(size_t num_proxies) {
+ num_proxies_ = num_proxies;
+ }
+
+ private:
+ // The ID of the main loop callback.
+ brillo::MessageLoop::TaskId idle_callback_id_{
+ brillo::MessageLoop::kTaskIdNull};
+
+ // Number of direct proxies to return on resolved list; currently used for
+ // testing.
+ size_t num_proxies_{1};
+
+ // The MainLoop callback, from here we return to the client.
+ void ReturnCallback(ProxiesResolvedFn callback, void* data);
+ DISALLOW_COPY_AND_ASSIGN(DirectProxyResolver);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PROXY_RESOLVER_H_
diff --git a/real_system_state.cc b/real_system_state.cc
new file mode 100644
index 0000000..3a90292
--- /dev/null
+++ b/real_system_state.cc
@@ -0,0 +1,151 @@
+//
+// 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/real_system_state.h"
+
+#include <base/files/file_util.h>
+#include <base/time/time.h>
+#include <brillo/make_unique_ptr.h>
+
+#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/hardware.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/state_factory.h"
+
+namespace chromeos_update_engine {
+
+RealSystemState::RealSystemState(const scoped_refptr<dbus::Bus>& bus)
+ : debugd_proxy_(bus),
+ power_manager_proxy_(bus),
+ session_manager_proxy_(bus),
+ shill_proxy_(bus),
+ libcros_proxy_(bus) {
+}
+
+bool RealSystemState::Initialize() {
+ metrics_lib_.Init();
+
+ boot_control_ = boot_control::CreateBootControl();
+ if (!boot_control_) {
+ LOG(WARNING) << "Unable to create BootControl instance, using stub "
+ << "instead. All update attempts will fail.";
+ boot_control_ = brillo::make_unique_ptr(new BootControlStub());
+ }
+
+ hardware_ = hardware::CreateHardware();
+ if (!hardware_) {
+ LOG(ERROR) << "Error intializing the HardwareInterface.";
+ return false;
+ }
+
+ LOG_IF(INFO, !hardware_->IsNormalBootMode()) << "Booted in dev mode.";
+ LOG_IF(INFO, !hardware_->IsOfficialBuild()) << "Booted non-official build.";
+
+ if (!shill_proxy_.Init()) {
+ LOG(ERROR) << "Failed to initialize shill proxy.";
+ return false;
+ }
+
+ // Initialize standard and powerwash-safe 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;
+ prefs_.reset(prefs = new Prefs());
+ if (!prefs->Init(non_volatile_path.Append(kPrefsSubDirectory))) {
+ LOG(ERROR) << "Failed to initialize preferences.";
+ return false;
+ }
+
+ base::FilePath powerwash_safe_path;
+ if (!hardware_->GetPowerwashSafeDirectory(&powerwash_safe_path)) {
+ // TODO(deymo): Fall-back to in-memory prefs if there's no powerwash-safe
+ // directory, or disable powerwash feature.
+ powerwash_safe_path = non_volatile_path.Append("powerwash-safe");
+ LOG(WARNING) << "No powerwash-safe directory, using non-volatile one.";
+ }
+ powerwash_safe_prefs_.reset(prefs = new Prefs());
+ if (!prefs->Init(
+ powerwash_safe_path.Append(kPowerwashSafePrefsSubDirectory))) {
+ LOG(ERROR) << "Failed to initialize powerwash preferences.";
+ return false;
+ }
+
+ // Check the system rebooted marker file.
+ std::string boot_id;
+ if (utils::GetBootId(&boot_id)) {
+ std::string prev_boot_id;
+ system_rebooted_ = (!prefs_->GetString(kPrefsBootId, &prev_boot_id) ||
+ prev_boot_id != boot_id);
+ prefs_->SetString(kPrefsBootId, boot_id);
+ } else {
+ LOG(WARNING) << "Couldn't detect the bootid, assuming system was rebooted.";
+ system_rebooted_ = true;
+ }
+
+ // Initialize the OmahaRequestParams with the default settings. These settings
+ // 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)) {
+ LOG(WARNING) << "Ignoring OmahaRequestParams initialization error. Some "
+ "features might not work properly.";
+ }
+
+ certificate_checker_.reset(
+ new CertificateChecker(prefs_.get(), &openssl_wrapper_));
+ certificate_checker_->Init();
+
+ // Initialize the UpdateAttempter before the UpdateManager.
+ update_attempter_.reset(
+ new UpdateAttempter(this, certificate_checker_.get(), &libcros_proxy_,
+ &debugd_proxy_));
+ update_attempter_->Init();
+
+ // Initialize the Update Manager using the default state factory.
+ chromeos_update_manager::State* um_state =
+ chromeos_update_manager::DefaultStateFactory(
+ &policy_provider_, &shill_proxy_, &session_manager_proxy_, this);
+ 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));
+
+ // 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)) {
+ LOG(ERROR) << "Failed to initialize the payload state object.";
+ return false;
+ }
+
+ // All is well. Initialization successful.
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/real_system_state.h b/real_system_state.h
new file mode 100644
index 0000000..8ca1abc
--- /dev/null
+++ b/real_system_state.h
@@ -0,0 +1,173 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_REAL_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_REAL_SYSTEM_STATE_H_
+
+#include "update_engine/system_state.h"
+
+#include <memory>
+
+#include <debugd/dbus-proxies.h>
+#include <metrics/metrics_library.h>
+#include <policy/device_policy.h>
+#include <power_manager/dbus-proxies.h>
+#include <session_manager/dbus-proxies.h>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/certificate_checker.h"
+#include "update_engine/common/clock.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/prefs.h"
+#include "update_engine/connection_manager.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state.h"
+#include "update_engine/shill_proxy.h"
+#include "update_engine/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:
+ // Constructs all system objects that do not require separate initialization;
+ // see Initialize() below for the remaining ones.
+ explicit RealSystemState(const scoped_refptr<dbus::Bus>& bus);
+
+ // Initializes and sets systems objects that require an initialization
+ // separately from construction. Returns |true| on success.
+ bool Initialize();
+
+ inline void set_device_policy(
+ const policy::DevicePolicy* device_policy) override {
+ device_policy_ = device_policy;
+ }
+
+ inline const policy::DevicePolicy* device_policy() override {
+ return device_policy_;
+ }
+
+ inline BootControlInterface* boot_control() override {
+ return boot_control_.get();
+ }
+
+ inline ClockInterface* clock() override { return &clock_; }
+
+ inline ConnectionManagerInterface* connection_manager() override {
+ return &connection_manager_;
+ }
+
+ inline HardwareInterface* hardware() override { return hardware_.get(); }
+
+ inline MetricsLibraryInterface* metrics_lib() override {
+ return &metrics_lib_;
+ }
+
+ inline PrefsInterface* prefs() override { return prefs_.get(); }
+
+ inline PrefsInterface* powerwash_safe_prefs() override {
+ return powerwash_safe_prefs_.get();
+ }
+
+ inline PayloadStateInterface* payload_state() override {
+ return &payload_state_;
+ }
+
+ inline UpdateAttempter* update_attempter() override {
+ return update_attempter_.get();
+ }
+
+ inline OmahaRequestParams* request_params() override {
+ return &request_params_;
+ }
+
+ inline P2PManager* p2p_manager() override { return p2p_manager_.get(); }
+
+ inline chromeos_update_manager::UpdateManager* update_manager() override {
+ return update_manager_.get();
+ }
+
+ inline org::chromium::PowerManagerProxyInterface* power_manager_proxy()
+ override {
+ return &power_manager_proxy_;
+ }
+
+ inline bool system_rebooted() override { return system_rebooted_; }
+
+ private:
+ // Real DBus proxies using the DBus connection.
+ org::chromium::debugdProxy debugd_proxy_;
+ org::chromium::PowerManagerProxy power_manager_proxy_;
+ org::chromium::SessionManagerInterfaceProxy session_manager_proxy_;
+ ShillProxy shill_proxy_;
+ LibCrosProxy libcros_proxy_;
+
+ // Interface for the clock.
+ std::unique_ptr<BootControlInterface> boot_control_;
+
+ // Interface for the clock.
+ Clock clock_;
+
+ // The latest device policy object from the policy provider.
+ const policy::DevicePolicy* device_policy_{nullptr};
+
+ // The connection manager object that makes download decisions depending on
+ // the current type of connection.
+ ConnectionManager connection_manager_{&shill_proxy_, this};
+
+ // Interface for the hardware functions.
+ std::unique_ptr<HardwareInterface> hardware_;
+
+ // The Metrics Library interface for reporting UMA stats.
+ MetricsLibrary metrics_lib_;
+
+ // Interface for persisted store.
+ std::unique_ptr<PrefsInterface> prefs_;
+
+ // Interface for persisted store that persists across powerwashes.
+ std::unique_ptr<PrefsInterface> powerwash_safe_prefs_;
+
+ // All state pertaining to payload state such as response, URL, backoff
+ // states.
+ PayloadState payload_state_;
+
+ // OpenSSLWrapper and CertificateChecker used for checking SSL certificates.
+ OpenSSLWrapper openssl_wrapper_;
+ std::unique_ptr<CertificateChecker> certificate_checker_;
+
+ // Pointer to the update attempter object.
+ std::unique_ptr<UpdateAttempter> update_attempter_;
+
+ // Common parameters for all Omaha requests.
+ OmahaRequestParams request_params_{this};
+
+ std::unique_ptr<P2PManager> p2p_manager_;
+
+ std::unique_ptr<chromeos_update_manager::UpdateManager> update_manager_;
+
+ policy::PolicyProvider policy_provider_;
+
+ // If true, this is the first instance of the update engine since the system
+ // 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};
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_REAL_SYSTEM_STATE_H_
diff --git a/run_unittests b/run_unittests
new file mode 100755
index 0000000..f07078d
--- /dev/null
+++ b/run_unittests
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+#
+# 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.
+#
+
+# Runs the update engine unit tests, including both userland and run-as-root
+# tests.
+
+if [ ! -e ./update_engine_unittests ]; then
+ echo 'Error: unit test binary missing' >&2
+ exit 1
+fi
+
+user_pass=0
+./update_engine_unittests --gtest_filter='-*.RunAsRoot*' && user_pass=1
+root_pass=0
+sudo ./update_engine_unittests --gtest_filter='*.RunAsRoot*' && root_pass=1
+
+echo -n "User tests: " && [ $user_pass == 1 ] && echo "PASSED" || echo "FAILED"
+echo -n "Root tests: " && [ $root_pass == 1 ] && echo "PASSED" || echo "FAILED"
+
+exit $((2 - user_pass - root_pass))
diff --git a/sample_images/generate_images.sh b/sample_images/generate_images.sh
new file mode 100755
index 0000000..70fc14b
--- /dev/null
+++ b/sample_images/generate_images.sh
@@ -0,0 +1,189 @@
+#!/bin/bash
+
+#
+# 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.
+#
+
+# This script generates some sample images used in unittests and packages them
+# in the sample_images.tar.bz2 file. The list of generated images and their
+# options are described in the main() function. You need to manually run this
+# script to update the generated images whenever you modify this script.
+
+set -e
+
+# cleanup <path>
+# Unmount and remove the mountpoint <path>
+cleanup() {
+ if ! sudo umount "$1" 2>/dev/null; then
+ if mountpoint -q "$1"; then
+ sync && sudo umount "$1"
+ fi
+ fi
+ rmdir "$1"
+}
+
+# add_files_default <mntdir> <block_size>
+# Add several test files to the image mounted in <mntdir>.
+add_files_default() {
+ local mntdir="$1"
+ local block_size="$2"
+
+ ### Generate the files used in unittest with descriptive names.
+ sudo touch "${mntdir}"/empty-file
+
+ # regular: Regular files.
+ echo "small file" | sudo dd of="${mntdir}"/regular-small status=none
+ dd if=/dev/zero bs=1024 count=16 status=none | tr '\0' '\141' |
+ sudo dd of="${mntdir}"/regular-16k status=none
+ sudo dd if=/dev/zero of="${mntdir}"/regular-32k-zeros bs=1024 count=16 \
+ status=none
+
+ echo "with net_cap" | sudo dd of="${mntdir}"/regular-with_net_cap status=none
+ sudo setcap cap_net_raw=ep "${mntdir}"/regular-with_net_cap
+
+ # sparse_empty: Files with no data blocks at all (only sparse holes).
+ sudo truncate --size=10240 "${mntdir}"/sparse_empty-10k
+ sudo truncate --size=$(( block_size * 2 )) "${mntdir}"/sparse_empty-2blocks
+
+ # sparse: Files with some data blocks but also sparse holes.
+ echo -n "foo" |
+ sudo dd of="${mntdir}"/sparse-16k-last_block bs=1 \
+ seek=$(( 16 * 1024 - 3)) status=none
+
+ # ext2 inodes have 12 direct blocks, one indirect, one double indirect and
+ # one triple indirect. 10000 should be enough to have an indirect and double
+ # indirect block.
+ echo -n "foo" |
+ sudo dd of="${mntdir}"/sparse-10000blocks bs=1 \
+ seek=$(( block_size * 10000 )) status=none
+
+ sudo truncate --size=16384 "${mntdir}"/sparse-16k-first_block
+ echo "first block" | sudo dd of="${mntdir}"/sparse-16k-first_block status=none
+
+ sudo truncate --size=16384 "${mntdir}"/sparse-16k-holes
+ echo "a" | sudo dd of="${mntdir}"/sparse-16k-holes bs=1 seek=100 status=none
+ echo "b" | sudo dd of="${mntdir}"/sparse-16k-holes bs=1 seek=10000 status=none
+
+ # link: symlinks and hardlinks.
+ sudo ln -s "broken-link" "${mntdir}"/link-short_symlink
+ sudo ln -s $(dd if=/dev/zero bs=256 count=1 status=none | tr '\0' '\141') \
+ "${mntdir}"/link-long_symlink
+ sudo ln "${mntdir}"/regular-16k "${mntdir}"/link-hard-regular-16k
+
+ # Directories.
+ sudo mkdir -p "${mntdir}"/dir1/dir2/dir1
+ echo "foo" | sudo tee "${mntdir}"/dir1/dir2/file >/dev/null
+ echo "bar" | sudo tee "${mntdir}"/dir1/file >/dev/null
+
+ # removed: removed files that should not be listed.
+ echo "We will remove this file so it's contents will be somewhere in the " \
+ "empty space data but it won't be all zeros." |
+ sudo dd of="${mntdir}"/removed conv=fsync status=none
+ sudo rm "${mntdir}"/removed
+}
+
+# add_files_ue_settings <mntdir> <block_size>
+# Add the update_engine.conf settings file. This file contains the
+add_files_ue_settings() {
+ local mntdir="$1"
+
+ sudo mkdir -p "${mntdir}"/etc >/dev/null
+ sudo tee "${mntdir}"/etc/update_engine.conf >/dev/null <<EOF
+PAYLOAD_MINOR_VERSION=1234
+EOF
+ # Example of a real lsb-release file released on link stable.
+ sudo tee "${mntdir}"/etc/lsb-release >/dev/null <<EOF
+CHROMEOS_AUSERVER=https://tools.google.com/service/update2
+CHROMEOS_BOARD_APPID={F26D159B-52A3-491A-AE25-B23670A66B32}
+CHROMEOS_CANARY_APPID={90F229CE-83E2-4FAF-8479-E368A34938B1}
+CHROMEOS_DEVSERVER=
+CHROMEOS_RELEASE_APPID={F26D159B-52A3-491A-AE25-B23670A66B32}
+CHROMEOS_RELEASE_BOARD=link-signed-mp-v4keys
+CHROMEOS_RELEASE_BRANCH_NUMBER=63
+CHROMEOS_RELEASE_BUILD_NUMBER=6946
+CHROMEOS_RELEASE_BUILD_TYPE=Official Build
+CHROMEOS_RELEASE_CHROME_MILESTONE=43
+CHROMEOS_RELEASE_DESCRIPTION=6946.63.0 (Official Build) stable-channel link
+CHROMEOS_RELEASE_NAME=Chrome OS
+CHROMEOS_RELEASE_PATCH_NUMBER=0
+CHROMEOS_RELEASE_TRACK=stable-channel
+CHROMEOS_RELEASE_VERSION=6946.63.0
+GOOGLE_RELEASE=6946.63.0
+EOF
+}
+
+# generate_fs <filename> <kind> <size> [block_size] [block_groups]
+generate_fs() {
+ local filename="$1"
+ local kind="$2"
+ local size="$3"
+ local block_size="${4:-4096}"
+ local block_groups="${5:-}"
+
+ local mkfs_opts=( -q -F -b "${block_size}" -L "ROOT-TEST" -t ext2 )
+ if [[ -n "${block_groups}" ]]; then
+ mkfs_opts+=( -G "${block_groups}" )
+ fi
+
+ local mntdir=$(mktemp --tmpdir -d generate_ext2.XXXXXX)
+ trap 'cleanup "${mntdir}"; rm -f "${filename}"' INT TERM EXIT
+
+ # Cleanup old image.
+ if [[ -e "${filename}" ]]; then
+ rm -f "${filename}"
+ fi
+ truncate --size="${size}" "${filename}"
+
+ mkfs.ext2 "${mkfs_opts[@]}" "${filename}"
+ sudo mount "${filename}" "${mntdir}" -o loop
+
+ case "${kind}" in
+ ue_settings)
+ add_files_ue_settings "${mntdir}" "${block_size}"
+ ;;
+ default)
+ add_files_default "${mntdir}" "${block_size}"
+ ;;
+ esac
+
+ cleanup "${mntdir}"
+ trap - INT TERM EXIT
+}
+
+OUTPUT_DIR=$(dirname "$0")
+IMAGES=()
+
+# generate_image <image_name> [<image args> ...]
+generate_image() {
+ echo "Generating image $1.img"
+ IMAGES+=( "$1.img" )
+ generate_fs "${OUTPUT_DIR}/$1.img" "${@:2}"
+}
+
+main() {
+ # Add more sample images here.
+ generate_image disk_ext2_1k default 16777216 1024
+ generate_image disk_ext2_4k default 16777216 4096
+ generate_image disk_ext2_ue_settings ue_settings 16777216 4096
+
+ # Generate the tarball and delete temporary images.
+ echo "Packing tar file sample_images.tar.bz2"
+ tar -jcf "${OUTPUT_DIR}/sample_images.tar.bz2" -C "${OUTPUT_DIR}" \
+ "${IMAGES[@]}"
+ cd "${OUTPUT_DIR}"
+ rm "${IMAGES[@]}"
+}
+
+main
diff --git a/sample_images/sample_images.tar.bz2 b/sample_images/sample_images.tar.bz2
new file mode 100644
index 0000000..0982271
--- /dev/null
+++ b/sample_images/sample_images.tar.bz2
Binary files differ
diff --git a/sample_omaha_v3_response.xml b/sample_omaha_v3_response.xml
new file mode 100644
index 0000000..abba523
--- /dev/null
+++ b/sample_omaha_v3_response.xml
@@ -0,0 +1,19 @@
+<response protocol="3.0" server="prod">
+ <daystart elapsed_seconds="56652"/>
+ <app appid="{90f229ce-83e2-4faf-8479-e368a34938b1}" status="ok">
+ <updatecheck status="ok">
+ <urls>
+ <url codebase="https://storage.googleapis.com/chromeos-releases-public/canary-channel/canary-channel/3095.0.0/"/>
+ </urls>
+ <manifest version="3095.0.0">
+ <packages>
+ <package hash="HVOmp67vBjPdvpWmOC2Uw4UDwsc=" name="chromeos_3095.0.0_x86-zgb_canary-channel_full_mp-v2.bin-df37843370ddf1e3819a2afeaa934faa.signed" required="true" size="400752559"/>
+ </packages>
+ <actions>
+ <action event="update" run="chromeos_3095.0.0_x86-zgb_canary-channel_full_mp-v2.bin-df37843370ddf1e3819a2afeaa934faa.signed"/>
+ <action ChromeOSVersion="3095.0.0" ChromeVersion="24.0.1307.0" IsDelta="true" IsDeltaPayload="false" MaxDaysToScatter="14" MetadataSignatureRsa="xXrO/LahHlKk3YmqEf1qE0PN587Sc2IJV+FN7J7x1h49waNQIy/QwYO4LaOySgETe5JZXtkAEzzqakfJwxQ2pVfzj1GkExwjd5LTn1He2GvA73B8fKbS4bfP7dbUFwD5039xCwf1U2gezFViOiOPiVURx/pEsdhv+Cqx/3HbjIuj5au2dooSyDxLC5AnODzAKyYfAcjMuiLON+9SqmctJW+VjzdY9SbJAnkH2qqVjFyBKAXsYT+hOTIJ3MJpg8OSVxMMtGB99PxbOJ52F37d2Y5Fws/AUkNnNEsan/WRJA1kuWoS6rpeR8JQYuVhLiK2u/KpOcvMVRw3Q2VUxtcAGw==" MetadataSize="58315" event="postinstall" sha256="DIAVxoI+8NpsudUawOA5U92VHlaxQBS3ejN4EPM6T2A="/>
+ </actions>
+ </manifest>
+ </updatecheck>
+ </app>
+</response>
diff --git a/shill_proxy.cc b/shill_proxy.cc
new file mode 100644
index 0000000..1c050b4
--- /dev/null
+++ b/shill_proxy.cc
@@ -0,0 +1,43 @@
+//
+// 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/shill_proxy.h"
+
+using org::chromium::flimflam::ManagerProxy;
+using org::chromium::flimflam::ManagerProxyInterface;
+using org::chromium::flimflam::ServiceProxy;
+using org::chromium::flimflam::ServiceProxyInterface;
+
+namespace chromeos_update_engine {
+
+ShillProxy::ShillProxy(const scoped_refptr<dbus::Bus>& bus) : bus_(bus) {}
+
+bool ShillProxy::Init() {
+ manager_proxy_.reset(new ManagerProxy(bus_));
+ return true;
+}
+
+ManagerProxyInterface* ShillProxy::GetManagerProxy() {
+ return manager_proxy_.get();
+}
+
+std::unique_ptr<ServiceProxyInterface> ShillProxy::GetServiceForPath(
+ const dbus::ObjectPath& path) {
+ DCHECK(bus_.get());
+ return std::unique_ptr<ServiceProxyInterface>(new ServiceProxy(bus_, path));
+}
+
+} // namespace chromeos_update_engine
diff --git a/shill_proxy.h b/shill_proxy.h
new file mode 100644
index 0000000..6d545f6
--- /dev/null
+++ b/shill_proxy.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_SHILL_PROXY_H_
+#define UPDATE_ENGINE_SHILL_PROXY_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <dbus/bus.h>
+#include <dbus/object_path.h>
+#include <shill/dbus-proxies.h>
+
+#include "update_engine/shill_proxy_interface.h"
+
+namespace chromeos_update_engine {
+
+// This class implements the connection to shill using real DBus calls.
+class ShillProxy : public ShillProxyInterface {
+ public:
+ explicit ShillProxy(const scoped_refptr<dbus::Bus>& bus);
+ ~ShillProxy() override = default;
+
+ // Initializes the ShillProxy instance creating the manager proxy from the
+ // |bus_|.
+ bool Init();
+
+ // ShillProxyInterface overrides.
+ org::chromium::flimflam::ManagerProxyInterface* GetManagerProxy() override;
+ std::unique_ptr<org::chromium::flimflam::ServiceProxyInterface>
+ GetServiceForPath(const dbus::ObjectPath& path) override;
+
+ private:
+ // A reference to the main bus for creating new ServiceProxy instances.
+ scoped_refptr<dbus::Bus> bus_;
+ std::unique_ptr<org::chromium::flimflam::ManagerProxyInterface>
+ manager_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(ShillProxy);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_SHILL_PROXY_H_
diff --git a/shill_proxy_interface.h b/shill_proxy_interface.h
new file mode 100644
index 0000000..5f6b44e
--- /dev/null
+++ b/shill_proxy_interface.h
@@ -0,0 +1,56 @@
+//
+// 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_SHILL_PROXY_INTERFACE_H_
+#define UPDATE_ENGINE_SHILL_PROXY_INTERFACE_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+#include <dbus/object_path.h>
+#include <shill/dbus-proxies.h>
+
+namespace chromeos_update_engine {
+
+// This class handles the DBus connection with shill daemon. The DBus interface
+// with shill requires to monitor or request the current service by interacting
+// with the org::chromium::flimflam::ManagerProxy and then request or monitor
+// properties on the selected org::chromium::flimflam::ServiceProxy. This class
+// provides a mockable way to access that.
+class ShillProxyInterface {
+ public:
+ virtual ~ShillProxyInterface() = default;
+
+ // Return the ManagerProxy instance of the shill daemon. The instance is owned
+ // by this ShillProxyInterface instance.
+ virtual org::chromium::flimflam::ManagerProxyInterface* GetManagerProxy() = 0;
+
+ // Return a ServiceProxy for the given path. The ownership of the returned
+ // instance is transferred to the caller.
+ virtual std::unique_ptr<org::chromium::flimflam::ServiceProxyInterface>
+ GetServiceForPath(const dbus::ObjectPath& path) = 0;
+
+ protected:
+ ShillProxyInterface() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShillProxyInterface);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_SHILL_PROXY_INTERFACE_H_
diff --git a/system_state.h b/system_state.h
new file mode 100644
index 0000000..2ba0272
--- /dev/null
+++ b/system_state.h
@@ -0,0 +1,122 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_SYSTEM_STATE_H_
+#define UPDATE_ENGINE_SYSTEM_STATE_H_
+
+namespace org {
+namespace chromium {
+class PowerManagerProxyInterface;
+} // namespace chromium
+} // namespace org
+
+class MetricsLibraryInterface;
+
+namespace chromeos_update_manager {
+
+class UpdateManager;
+
+} // namespace chromeos_update_manager
+
+namespace policy {
+
+class DevicePolicy;
+
+} // namespace policy
+
+namespace chromeos_update_engine {
+
+// SystemState is the root class within the update engine. So we should avoid
+// any circular references in header file inclusion. Hence forward-declaring
+// the required classes.
+class BootControlInterface;
+class ClockInterface;
+class ConnectionManagerInterface;
+class HardwareInterface;
+class OmahaRequestParams;
+class P2PManager;
+class PayloadStateInterface;
+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() {}
+
+ // Sets or gets the latest device policy.
+ virtual void set_device_policy(const policy::DevicePolicy* device_policy) = 0;
+ virtual const policy::DevicePolicy* device_policy() = 0;
+
+ // Gets the interface object for the bootloader control interface.
+ virtual BootControlInterface* boot_control() = 0;
+
+ // Gets the interface object for the clock.
+ virtual ClockInterface* clock() = 0;
+
+ // Gets the connection manager object.
+ virtual ConnectionManagerInterface* connection_manager() = 0;
+
+ // Gets the hardware interface object.
+ virtual HardwareInterface* hardware() = 0;
+
+ // Gets the Metrics Library interface for reporting UMA stats.
+ virtual MetricsLibraryInterface* metrics_lib() = 0;
+
+ // Gets the interface object for persisted store.
+ virtual PrefsInterface* prefs() = 0;
+
+ // Gets the interface object for the persisted store that persists across
+ // powerwashes. Please note that this should be used very seldomly and must
+ // be forwards and backwards compatible as powerwash is used to go back and
+ // forth in system versions.
+ virtual PrefsInterface* powerwash_safe_prefs() = 0;
+
+ // Gets the interface for the payload state object.
+ virtual PayloadStateInterface* payload_state() = 0;
+
+ // Returns a pointer to the update attempter object.
+ virtual UpdateAttempter* update_attempter() = 0;
+
+ // Returns a pointer to the object that stores the parameters that are
+ // common to all Omaha requests.
+ virtual OmahaRequestParams* request_params() = 0;
+
+ // Returns a pointer to the P2PManager singleton.
+ virtual P2PManager* p2p_manager() = 0;
+
+ // Returns a pointer to the UpdateManager singleton.
+ virtual chromeos_update_manager::UpdateManager* update_manager() = 0;
+
+ // DBus proxies. Mocked during test.
+ virtual org::chromium::PowerManagerProxyInterface* power_manager_proxy() = 0;
+
+ // If true, this is the first instance of the update engine since the system
+ // restarted. Important for tracking whether you are running instance of the
+ // update engine on first boot or due to a crash/restart.
+ virtual bool system_rebooted() = 0;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_SYSTEM_STATE_H_
diff --git a/tar_bunzip2.gypi b/tar_bunzip2.gypi
new file mode 100644
index 0000000..8c6614a
--- /dev/null
+++ b/tar_bunzip2.gypi
@@ -0,0 +1,42 @@
+#
+# 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',
+ 'inputs': [
+ '<(RULE_INPUT_PATH)',
+ ],
+ '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_http_server.cc b/test_http_server.cc
new file mode 100644
index 0000000..eaffba8
--- /dev/null
+++ b/test_http_server.cc
@@ -0,0 +1,634 @@
+//
+// 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.
+//
+
+// This file implements a simple HTTP server. It can exhibit odd behavior
+// that's useful for testing. For example, it's useful to test that
+// the updater can continue a connection if it's dropped, or that it
+// handles very slow data transfers.
+
+// To use this, simply make an HTTP connection to localhost:port and
+// GET a url.
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_split.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/http_common.h"
+
+
+// HTTP end-of-line delimiter; sorry, this needs to be a macro.
+#define EOL "\r\n"
+
+using std::string;
+using std::vector;
+
+
+namespace chromeos_update_engine {
+
+static const char* kListeningMsgPrefix = "listening on port ";
+
+enum {
+ RC_OK = 0,
+ RC_BAD_ARGS,
+ RC_ERR_READ,
+ RC_ERR_SETSOCKOPT,
+ RC_ERR_BIND,
+ RC_ERR_LISTEN,
+ RC_ERR_GETSOCKNAME,
+ RC_ERR_REPORT,
+};
+
+struct HttpRequest {
+ HttpRequest()
+ : start_offset(0), end_offset(0), return_code(kHttpResponseOk) {}
+ string host;
+ string url;
+ off_t start_offset;
+ off_t end_offset; // non-inclusive, zero indicates unspecified.
+ HttpResponseCode return_code;
+};
+
+bool ParseRequest(int fd, HttpRequest* request) {
+ string headers;
+ do {
+ char buf[1024];
+ ssize_t r = read(fd, buf, sizeof(buf));
+ if (r < 0) {
+ perror("read");
+ exit(RC_ERR_READ);
+ }
+ headers.append(buf, r);
+ } while (!base::EndsWith(headers, EOL EOL, true));
+
+ LOG(INFO) << "got headers:\n--8<------8<------8<------8<----\n"
+ << headers
+ << "\n--8<------8<------8<------8<----";
+
+ // Break header into lines.
+ vector<string> lines;
+ base::SplitStringUsingSubstr(
+ headers.substr(0, headers.length() - strlen(EOL EOL)), EOL, &lines);
+
+ // Decode URL line.
+ vector<string> terms;
+ base::SplitStringAlongWhitespace(lines[0], &terms);
+ CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(3));
+ CHECK_EQ(terms[0], "GET");
+ request->url = terms[1];
+ LOG(INFO) << "URL: " << request->url;
+
+ // Decode remaining lines.
+ size_t i;
+ for (i = 1; i < lines.size(); i++) {
+ vector<string> terms;
+ base::SplitStringAlongWhitespace(lines[i], &terms);
+
+ if (terms[0] == "Range:") {
+ CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2));
+ string &range = terms[1];
+ LOG(INFO) << "range attribute: " << range;
+ CHECK(base::StartsWithASCII(range, "bytes=", true) &&
+ range.find('-') != string::npos);
+ request->start_offset = atoll(range.c_str() + strlen("bytes="));
+ // Decode end offset and increment it by one (so it is non-inclusive).
+ if (range.find('-') < range.length() - 1)
+ request->end_offset = atoll(range.c_str() + range.find('-') + 1) + 1;
+ request->return_code = kHttpResponsePartialContent;
+ string tmp_str = base::StringPrintf("decoded range offsets: "
+ "start=%jd end=",
+ (intmax_t)request->start_offset);
+ if (request->end_offset > 0)
+ base::StringAppendF(&tmp_str, "%jd (non-inclusive)",
+ (intmax_t)request->end_offset);
+ else
+ base::StringAppendF(&tmp_str, "unspecified");
+ LOG(INFO) << tmp_str;
+ } else if (terms[0] == "Host:") {
+ CHECK_EQ(terms.size(), static_cast<vector<string>::size_type>(2));
+ request->host = terms[1];
+ LOG(INFO) << "host attribute: " << request->host;
+ } else {
+ LOG(WARNING) << "ignoring HTTP attribute: `" << lines[i] << "'";
+ }
+ }
+
+ return true;
+}
+
+string Itoa(off_t num) {
+ char buf[100] = {0};
+ snprintf(buf, sizeof(buf), "%" PRIi64, num);
+ return buf;
+}
+
+// Writes a string into a file. Returns total number of bytes written or -1 if a
+// write error occurred.
+ssize_t WriteString(int fd, const string& str) {
+ const size_t total_size = str.size();
+ size_t remaining_size = total_size;
+ char const *data = str.data();
+
+ while (remaining_size) {
+ ssize_t written = write(fd, data, remaining_size);
+ if (written < 0) {
+ perror("write");
+ LOG(INFO) << "write failed";
+ return -1;
+ }
+ data += written;
+ remaining_size -= written;
+ }
+
+ return total_size;
+}
+
+// Writes the headers of an HTTP response into a file.
+ssize_t WriteHeaders(int fd, const off_t start_offset, const off_t end_offset,
+ HttpResponseCode return_code) {
+ ssize_t written = 0, ret;
+
+ ret = WriteString(fd,
+ string("HTTP/1.1 ") + Itoa(return_code) + " " +
+ GetHttpResponseDescription(return_code) +
+ EOL
+ "Content-Type: application/octet-stream" EOL);
+ if (ret < 0)
+ return -1;
+ written += ret;
+
+ // Compute content legnth.
+ const off_t content_length = end_offset - start_offset;;
+
+ // A start offset that equals the end offset indicates that the response
+ // should contain the full range of bytes in the requested resource.
+ if (start_offset || start_offset == end_offset) {
+ ret = WriteString(fd,
+ string("Accept-Ranges: bytes" EOL
+ "Content-Range: bytes ") +
+ Itoa(start_offset == end_offset ? 0 : start_offset) +
+ "-" + Itoa(end_offset - 1) + "/" + Itoa(end_offset) +
+ EOL);
+ if (ret < 0)
+ return -1;
+ written += ret;
+ }
+
+ ret = WriteString(fd, string("Content-Length: ") + Itoa(content_length) +
+ EOL EOL);
+ if (ret < 0)
+ return -1;
+ written += ret;
+
+ return written;
+}
+
+// Writes a predetermined payload of lines of ascending bytes to a file. The
+// first byte of output is appropriately offset with respect to the request line
+// length. Returns the number of successfully written bytes.
+size_t WritePayload(int fd, const off_t start_offset, const off_t end_offset,
+ const char first_byte, const size_t line_len) {
+ CHECK_LE(start_offset, end_offset);
+ CHECK_GT(line_len, static_cast<size_t>(0));
+
+ LOG(INFO) << "writing payload: " << line_len << "-byte lines starting with `"
+ << first_byte << "', offset range " << start_offset << " -> "
+ << end_offset;
+
+ // Populate line of ascending characters.
+ string line;
+ line.reserve(line_len);
+ char byte = first_byte;
+ size_t i;
+ for (i = 0; i < line_len; i++)
+ line += byte++;
+
+ const size_t total_len = end_offset - start_offset;
+ size_t remaining_len = total_len;
+ bool success = true;
+
+ // If start offset is not aligned with line boundary, output partial line up
+ // to the first line boundary.
+ size_t start_modulo = start_offset % line_len;
+ if (start_modulo) {
+ string partial = line.substr(start_modulo, remaining_len);
+ ssize_t ret = WriteString(fd, partial);
+ if ((success = (ret >= 0 && (size_t) ret == partial.length())))
+ remaining_len -= partial.length();
+ }
+
+ // Output full lines up to the maximal line boundary below the end offset.
+ while (success && remaining_len >= line_len) {
+ ssize_t ret = WriteString(fd, line);
+ if ((success = (ret >= 0 && (size_t) ret == line_len)))
+ remaining_len -= line_len;
+ }
+
+ // Output a partial line up to the end offset.
+ if (success && remaining_len) {
+ string partial = line.substr(0, remaining_len);
+ ssize_t ret = WriteString(fd, partial);
+ if ((success = (ret >= 0 && (size_t) ret == partial.length())))
+ remaining_len -= partial.length();
+ }
+
+ return (total_len - remaining_len);
+}
+
+// Write default payload lines of the form 'abcdefghij'.
+inline size_t WritePayload(int fd, const off_t start_offset,
+ const off_t end_offset) {
+ return WritePayload(fd, start_offset, end_offset, 'a', 10);
+}
+
+// Send an empty response, then kill the server.
+void HandleQuit(int fd) {
+ WriteHeaders(fd, 0, 0, kHttpResponseOk);
+ LOG(INFO) << "pid(" << getpid() << "): HTTP server exiting ...";
+ exit(RC_OK);
+}
+
+
+// Generates an HTTP response with payload corresponding to requested offsets
+// and length. Optionally, truncate the payload at a given length and add a
+// pause midway through the transfer. Returns the total number of bytes
+// delivered or -1 for error.
+ssize_t HandleGet(int fd, const HttpRequest& request, const size_t total_length,
+ const size_t truncate_length, const int sleep_every,
+ const int sleep_secs) {
+ ssize_t ret;
+ size_t written = 0;
+
+ // Obtain start offset, make sure it is within total payload length.
+ const size_t start_offset = request.start_offset;
+ if (start_offset >= total_length) {
+ LOG(WARNING) << "start offset (" << start_offset
+ << ") exceeds total length (" << total_length
+ << "), generating error response ("
+ << kHttpResponseReqRangeNotSat << ")";
+ return WriteHeaders(fd, total_length, total_length,
+ kHttpResponseReqRangeNotSat);
+ }
+
+ // Obtain end offset, adjust to fit in total payload length and ensure it does
+ // not preceded the start offset.
+ size_t end_offset = (request.end_offset > 0 ?
+ request.end_offset : total_length);
+ if (end_offset < start_offset) {
+ LOG(WARNING) << "end offset (" << end_offset << ") precedes start offset ("
+ << start_offset << "), generating error response";
+ return WriteHeaders(fd, 0, 0, kHttpResponseBadRequest);
+ }
+ if (end_offset > total_length) {
+ LOG(INFO) << "requested end offset (" << end_offset
+ << ") exceeds total length (" << total_length << "), adjusting";
+ end_offset = total_length;
+ }
+
+ // Generate headers
+ LOG(INFO) << "generating response header: range=" << start_offset << "-"
+ << (end_offset - 1) << "/" << (end_offset - start_offset)
+ << ", return code=" << request.return_code;
+ if ((ret = WriteHeaders(fd, start_offset, end_offset,
+ request.return_code)) < 0)
+ return -1;
+ LOG(INFO) << ret << " header bytes written";
+ written += ret;
+
+ // Compute payload length, truncate as necessary.
+ size_t payload_length = end_offset - start_offset;
+ if (truncate_length > 0 && truncate_length < payload_length) {
+ LOG(INFO) << "truncating request payload length (" << payload_length
+ << ") at " << truncate_length;
+ payload_length = truncate_length;
+ end_offset = start_offset + payload_length;
+ }
+
+ LOG(INFO) << "generating response payload: range=" << start_offset << "-"
+ << (end_offset - 1) << "/" << (end_offset - start_offset);
+
+ // Decide about optional midway delay.
+ if (truncate_length > 0 && sleep_every > 0 && sleep_secs >= 0 &&
+ start_offset % (truncate_length * sleep_every) == 0) {
+ const off_t midway_offset = start_offset + payload_length / 2;
+
+ if ((ret = WritePayload(fd, start_offset, midway_offset)) < 0)
+ return -1;
+ LOG(INFO) << ret << " payload bytes written (first chunk)";
+ written += ret;
+
+ LOG(INFO) << "sleeping for " << sleep_secs << " seconds...";
+ sleep(sleep_secs);
+
+ if ((ret = WritePayload(fd, midway_offset, end_offset)) < 0)
+ return -1;
+ LOG(INFO) << ret << " payload bytes written (second chunk)";
+ written += ret;
+ } else {
+ if ((ret = WritePayload(fd, start_offset, end_offset)) < 0)
+ return -1;
+ LOG(INFO) << ret << " payload bytes written";
+ written += ret;
+ }
+
+ LOG(INFO) << "response generation complete, " << written
+ << " total bytes written";
+ return written;
+}
+
+ssize_t HandleGet(int fd, const HttpRequest& request,
+ const size_t total_length) {
+ return HandleGet(fd, request, total_length, 0, 0, 0);
+}
+
+// Handles /redirect/<code>/<url> requests by returning the specified
+// redirect <code> with a location pointing to /<url>.
+void HandleRedirect(int fd, const HttpRequest& request) {
+ LOG(INFO) << "Redirecting...";
+ string url = request.url;
+ CHECK_EQ(static_cast<size_t>(0), url.find("/redirect/"));
+ url.erase(0, strlen("/redirect/"));
+ string::size_type url_start = url.find('/');
+ CHECK_NE(url_start, string::npos);
+ HttpResponseCode code = StringToHttpResponseCode(url.c_str());
+ url.erase(0, url_start);
+ url = "http://" + request.host + url;
+ const char *status = GetHttpResponseDescription(code);
+ if (!status)
+ CHECK(false) << "Unrecognized redirection code: " << code;
+ LOG(INFO) << "Code: " << code << " " << status;
+ LOG(INFO) << "New URL: " << url;
+
+ ssize_t ret;
+ if ((ret = WriteString(fd, "HTTP/1.1 " + Itoa(code) + " " +
+ status + EOL)) < 0)
+ return;
+ WriteString(fd, "Location: " + url + EOL);
+}
+
+// Generate a page not found error response with actual text payload. Return
+// number of bytes written or -1 for error.
+ssize_t HandleError(int fd, const HttpRequest& request) {
+ LOG(INFO) << "Generating error HTTP response";
+
+ ssize_t ret;
+ size_t written = 0;
+
+ const string data("This is an error page.");
+
+ if ((ret = WriteHeaders(fd, 0, data.size(), kHttpResponseNotFound)) < 0)
+ return -1;
+ written += ret;
+
+ if ((ret = WriteString(fd, data)) < 0)
+ return -1;
+ written += ret;
+
+ return written;
+}
+
+// Generate an error response if the requested offset is nonzero, up to a given
+// maximal number of successive failures. The error generated is an "Internal
+// Server Error" (500).
+ssize_t HandleErrorIfOffset(int fd, const HttpRequest& request,
+ size_t end_offset, int max_fails) {
+ static int num_fails = 0;
+
+ if (request.start_offset > 0 && num_fails < max_fails) {
+ LOG(INFO) << "Generating error HTTP response";
+
+ ssize_t ret;
+ size_t written = 0;
+
+ const string data("This is an error page.");
+
+ if ((ret = WriteHeaders(fd, 0, data.size(),
+ kHttpResponseInternalServerError)) < 0)
+ return -1;
+ written += ret;
+
+ if ((ret = WriteString(fd, data)) < 0)
+ return -1;
+ written += ret;
+
+ num_fails++;
+ return written;
+ } else {
+ num_fails = 0;
+ return HandleGet(fd, request, end_offset);
+ }
+}
+
+void HandleHang(int fd) {
+ LOG(INFO) << "Hanging until the other side of the connection is closed.";
+ char c;
+ while (HANDLE_EINTR(read(fd, &c, 1)) > 0) {}
+}
+
+void HandleDefault(int fd, const HttpRequest& request) {
+ const off_t start_offset = request.start_offset;
+ const string data("unhandled path");
+ const size_t size = data.size();
+ ssize_t ret;
+
+ if ((ret = WriteHeaders(fd, start_offset, size, request.return_code)) < 0)
+ return;
+ WriteString(fd, (start_offset < static_cast<off_t>(size) ?
+ data.substr(start_offset) : ""));
+}
+
+
+// Break a URL into terms delimited by slashes.
+class UrlTerms {
+ public:
+ UrlTerms(const string &url, size_t num_terms) {
+ // URL must be non-empty and start with a slash.
+ CHECK_GT(url.size(), static_cast<size_t>(0));
+ CHECK_EQ(url[0], '/');
+
+ // Split it into terms delimited by slashes, omitting the preceeding slash.
+ base::SplitStringDontTrim(url.substr(1), '/', &terms);
+
+ // Ensure expected length.
+ CHECK_EQ(terms.size(), num_terms);
+ }
+
+ inline string Get(const off_t index) const {
+ return terms[index];
+ }
+ inline const char *GetCStr(const off_t index) const {
+ return Get(index).c_str();
+ }
+ inline int GetInt(const off_t index) const {
+ return atoi(GetCStr(index));
+ }
+ inline size_t GetSizeT(const off_t index) const {
+ return static_cast<size_t>(atol(GetCStr(index)));
+ }
+
+ private:
+ vector<string> terms;
+};
+
+void HandleConnection(int fd) {
+ HttpRequest request;
+ ParseRequest(fd, &request);
+
+ string &url = request.url;
+ LOG(INFO) << "pid(" << getpid() << "): handling url " << url;
+ if (url == "/quitquitquit") {
+ HandleQuit(fd);
+ } else if (base::StartsWithASCII(url, "/download/", true)) {
+ const UrlTerms terms(url, 2);
+ HandleGet(fd, request, terms.GetSizeT(1));
+ } else if (base::StartsWithASCII(url, "/flaky/", true)) {
+ const UrlTerms terms(url, 5);
+ HandleGet(fd, request, terms.GetSizeT(1), terms.GetSizeT(2),
+ terms.GetInt(3), terms.GetInt(4));
+ } else if (url.find("/redirect/") == 0) {
+ HandleRedirect(fd, request);
+ } else if (url == "/error") {
+ HandleError(fd, request);
+ } else if (base::StartsWithASCII(url, "/error-if-offset/", true)) {
+ const UrlTerms terms(url, 3);
+ HandleErrorIfOffset(fd, request, terms.GetSizeT(1), terms.GetInt(2));
+ } else if (url == "/hang") {
+ HandleHang(fd);
+ } else {
+ HandleDefault(fd, request);
+ }
+
+ close(fd);
+}
+
+} // namespace chromeos_update_engine
+
+using namespace chromeos_update_engine; // NOLINT(build/namespaces)
+
+
+void usage(const char *prog_arg) {
+ fprintf(
+ stderr,
+ "Usage: %s [ FILE ]\n"
+ "Once accepting connections, the following is written to FILE (or "
+ "stdout):\n"
+ "\"%sN\" (where N is an integer port number)\n",
+ basename(prog_arg), kListeningMsgPrefix);
+}
+
+int main(int argc, char** argv) {
+ // Check invocation.
+ if (argc > 2)
+ errx(RC_BAD_ARGS, "unexpected number of arguments (use -h for usage)");
+
+ // Parse (optional) argument.
+ int report_fd = STDOUT_FILENO;
+ if (argc == 2) {
+ if (!strcmp(argv[1], "-h")) {
+ usage(argv[0]);
+ exit(RC_OK);
+ }
+
+ report_fd = open(argv[1], O_WRONLY | O_CREAT, 00644);
+ }
+
+ // Ignore SIGPIPE on write() to sockets.
+ signal(SIGPIPE, SIG_IGN);
+
+ int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (listen_fd < 0)
+ LOG(FATAL) << "socket() failed";
+
+ struct sockaddr_in server_addr = sockaddr_in();
+ server_addr.sin_family = AF_INET;
+ server_addr.sin_addr.s_addr = INADDR_ANY;
+ server_addr.sin_port = 0;
+
+ {
+ // Get rid of "Address in use" error
+ int tr = 1;
+ if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &tr,
+ sizeof(int)) == -1) {
+ perror("setsockopt");
+ exit(RC_ERR_SETSOCKOPT);
+ }
+ }
+
+ // Bind the socket and set for listening.
+ if (bind(listen_fd, reinterpret_cast<struct sockaddr *>(&server_addr),
+ sizeof(server_addr)) < 0) {
+ perror("bind");
+ exit(RC_ERR_BIND);
+ }
+ if (listen(listen_fd, 5) < 0) {
+ perror("listen");
+ exit(RC_ERR_LISTEN);
+ }
+
+ // Check the actual port.
+ struct sockaddr_in bound_addr = sockaddr_in();
+ socklen_t bound_addr_len = sizeof(bound_addr);
+ if (getsockname(listen_fd, reinterpret_cast<struct sockaddr*>(&bound_addr),
+ &bound_addr_len) < 0) {
+ perror("getsockname");
+ exit(RC_ERR_GETSOCKNAME);
+ }
+ in_port_t port = ntohs(bound_addr.sin_port);
+
+ // Output the listening port, indicating that the server is processing
+ // requests. IMPORTANT! (a) the format of this message is as expected by some
+ // unit tests, avoid unilateral changes; (b) it is necessary to flush/sync the
+ // file to prevent the spawning process from waiting indefinitely for this
+ // message.
+ string listening_msg = base::StringPrintf("%s%hu", kListeningMsgPrefix, port);
+ LOG(INFO) << listening_msg;
+ CHECK_EQ(write(report_fd, listening_msg.c_str(), listening_msg.length()),
+ static_cast<int>(listening_msg.length()));
+ CHECK_EQ(write(report_fd, "\n", 1), 1);
+ if (report_fd == STDOUT_FILENO)
+ fsync(report_fd);
+ else
+ close(report_fd);
+
+ while (1) {
+ LOG(INFO) << "pid(" << getpid() << "): waiting to accept new connection";
+ int client_fd = accept(listen_fd, nullptr, nullptr);
+ LOG(INFO) << "got past accept";
+ if (client_fd < 0)
+ LOG(FATAL) << "ERROR on accept";
+ HandleConnection(client_fd);
+ }
+ return 0;
+}
diff --git a/testrunner.cc b/testrunner.cc
new file mode 100644
index 0000000..9e22798
--- /dev/null
+++ b/testrunner.cc
@@ -0,0 +1,47 @@
+//
+// 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.
+//
+
+// based on pam_google_testrunner.cc
+
+#include <xz.h>
+
+#include <base/at_exit.h>
+#include <base/command_line.h>
+#include <brillo/test_helpers.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/terminator.h"
+
+int main(int argc, char **argv) {
+ LOG(INFO) << "started";
+ base::AtExitManager exit_manager;
+ // xz-embedded requires to initialize its CRC-32 table once on startup.
+ xz_crc32_init();
+ // TODO(garnold) temporarily cause the unittest binary to exit with status
+ // code 2 upon catching a SIGTERM. This will help diagnose why the unittest
+ // binary is perceived as failing by the buildbot. We should revert it to use
+ // the default exit status of 1. Corresponding reverts are necessary in
+ // terminator_unittest.cc.
+ chromeos_update_engine::Terminator::Init(2);
+ LOG(INFO) << "parsing command line arguments";
+ base::CommandLine::Init(argc, argv);
+ LOG(INFO) << "initializing gtest";
+ SetUpTests(&argc, argv, true);
+ LOG(INFO) << "running unit tests";
+ int test_result = RUN_ALL_TESTS();
+ LOG(INFO) << "unittest return value: " << test_result;
+ return test_result;
+}
diff --git a/unittest_key.pem b/unittest_key.pem
new file mode 100644
index 0000000..224d3c3
--- /dev/null
+++ b/unittest_key.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAx6hqS+Hbc7jN82ek+OBkHJNvlWXktyS2XCuQtGtnHjEV7Ou5
+hHNF96s5wEn8RDtrdLocm0a+SqXXf7KycFRTZxLdDbqWAMUlPXOaPI+PZLWkB7/K
+V7Cdj6vPGbeq7elu+aT/budhzTvq2stYrrAlcr/21tOVDPiWtfCdykh7FCXi6FaY
+HoNy5A6EKQLfLBuJoU/QoCufldmwlFaFDKl+Koos6R1QYJfCNZfgocrW1PIh+8t1
+JIvw6Izo8+fTme7nev7Ol2YZiMWJpRYKx8MgxW2UgTXlRpmQN56pc9LUsnVA8FND
+BN57+gMJj+XmdDmbtMpOsyXfSNEgnuwMPcEcmwIDAQABAoIBABPkwgKhlH4pUcwI
+7bUmlpMKVbnrFyjwbYMtjBOOCA5IEckzi56Y5cXRt8VjGdGqogBVcvg9ykQh1iER
+KxpqLI0+oev2RW/6NMW0uQ+DtmPwfVGQWJb4MBraoZ4MYOmnsrkJKbJhN6t9Zt86
+F7IANxsB6ZRqLJXIRywFt5MqOak+GAnQJ8C8eSQg70NhbEhSOrD8wrD6tfvgIqta
+XxhtlQWUAILIWetnWrJsalMqnreGn7vhc7+iihhMtXh1xNBMTA+gzpov/Cx21iH5
+DM9ppSA6HHDXrMhauryypIRrhjOUWRyDws/kIHgIW4TCbULOlxqsputQeTmdf0ti
+7lpwqAECgYEA7nNKkct3Vs51Ugk4GUPC4NOyYRPNc9UQAfHViB9gSDRacCo9Ax9J
+83hJGqDXlNGzELOnhzMn8jQMyF13eWzOsMozK6Fj3uW7DBvelg5bfgsZDUUO5WUF
+6BYbOheVqf12rIHR9BKBmCfLEKyxbKmw5bnB0uNo7IuBPBNuhPbvkgECgYEA1lo5
+XHWJpQnVl+JzXLHpXBK2nfnFAOtvzlTW+7gteeU12X2HcFASrzp7C1ULVV+i1Kcz
+tDFIA5yiFjEqmSJ/TsO8aqAhL5BXJjylCepQK7XkEOGCR8eQjlt7E4DulAsQbfpt
+k30HVVlIOFqLCWKSW8M3dy/Plodq/Gyq26rntpsCgYAzsNyGdIQfVkxKh2MY3v6c
+/Gdb8g4EwThiI4m1o4+ct3SvggiN57eBRx8Z3ao+QaM+yKNVhLpxH+VxfgmLUhIQ
+cxTarXbX+BcvTc9X2i7tSPyaStEq21aHdFtcoYY5Po/+X3ojHevoDyBPMhCYTMTj
+V/xzegbh2HAglNnNizZuAQKBgQCyOxEpBP5vgS7d/MgJklFEYrb/wjgBnMI5oSek
+5C7EBUdyUUM1qw7uLsUy1gL3eO7uvRxrvvJvNmU76KPP8vRCLNTVH9KYNv+P5qsg
+BHmm7rX1J11pi9Fx3TUIMZOu+0gs+ib0lOhtGjDH0tl680BZFohfDR0hv/XAcCbd
+Qk0q8wKBgQCGbURFFW/5RZUA77MmpY6jsEMw2gJmVtjO5IWZg9VPvLQQPgCr4Ho/
+bS2LIsT6pS+hrLOoz5KI0YueRS0jQYI+WkRqNf5wYNjxPql9632FiDLHO+Xv8PBe
+kHrPHy0GGT1igXScY4eemw4ZC1OSdZfkVn6Ui/JvBHrydJ2LrutMWQ==
+-----END RSA PRIVATE KEY-----
diff --git a/unittest_key2.pem b/unittest_key2.pem
new file mode 100644
index 0000000..d1f9a78
--- /dev/null
+++ b/unittest_key2.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAqdM7gnd43cWfQzyrt16QDHE+T0yxg18OZO+9sc8jWpjC1zFt
+oMFokESiu9TLUp+KUC274f+DxIgAfSCO6U5DJFRPXUzve1vaXYfqljUBxc+JYcGd
+d6PCUl4Ap9f02DhkrGnf/rkHOCEhFNpm53dovZ9A6Q5KBfcg1HeQ088opVceQGtU
+mL+bONq5tLvNFLUU0Ru0Am4ADJ8Xmw4rpvqvmXJaFmVuf/Gx7+TOnaJvZTeOO9AJ
+K6q6V8E7+ZZiMIc5F4EOs5AX/lZfNO3RVgG2O7Dhzza+nOzJ3ZJGK5R4WwZduhn9
+QRYogYPB0c62878qXpf2Bn3NpUPi8Cf/RLMMmQIDAQABAoIBACyLUWKpP7S770hN
+k6TnUtVQps1aCn2w4y+qipEnCdjrlL+pIV43HNwqhJzL9gDYBAl/1XYz9TYJjkdD
+0Ph1JLtUufR5B5/NufsqeWeow6xFAX34sPr+oyvDqFxeEsTcFdv7cVt44OHiHrE/
+kBpKgdiq+vWmX9gsuBnCuuQzxC+Juo6nupwZXcpa/ow9lC4QsgKqcjaUGrXXy2t9
+Er+9aSl8NdTjK76BXQsDgNkDyJZwNN14xrdS8eFsS4twskaOEYI4hEM0g62NOjgd
+Po8Ap/MnPpGSGcAd3d3Fq8KgT1lpyMKedLFU+k0H+/Y4RBl7grz1XXvSTzGi3Qy6
+38F4eVkCgYEA4mo4iiXSfrov9QffsVM17O0t9hUsOJHnUsEScxWLDm4IzaObyTtv
+tWW33iQSeFu4Wsol0nzjqWo3BaqiRidRUd42yZ07LJvfUDxUX9xPaUPFRs25iwhZ
+6tKAVqGk7/CFrN+R44sIwbsSvbExMAyW6gnj93EWUmMWWYp02hLbN0sCgYEAwAQI
+awVoc56OCtRpfYtlAPD/VOP1mbNzRmVl/UyZ4XYmz6f/hEz63Bk5PhYSZftlmK/r
+nj4qnl7HZ8jrJgZn2e97rPNpk7KDVU1+csCgLWZBTOXl/o9tOTyjh9LoRAjKtBB7
+x6CkWyiyd94xIq5VbnXhvL3a4d4o6OxMWdG5aSsCgYEAo44z1afIzP7WkdzkPIZt
+l/8linR1A1BymBccqsHPN9dIyLP9X3puEc2u6uuH5CXtoLgSZmENXF577L38h0zz
+s34gebf4/RqEUMOj97OAMfxgz+rgs4yO19DEINCYAzPufJjsHEFdTAVFXn5Xl+wg
+QGRwp1ir1Uv64yffjYC9ls0CgYEAjvIxpiKniPNvwUYypmDgt5uyKetvCpaaabzQ
++YpOQJep+wuRYFfCpZotkDf0SHGoR8wnd23GYpIilvPvgyZfp9HuW2n2nhrWROnl
+Cd63IDUwxeOcni7+XA71mwb7HLMC3Jws2geQc8DPZAdIww3P0eT2QYGBcobmI8jO
+akuEYXMCgYAm79Kb/r+3Hew5oAS1Whw70DskVlOutSgNsDPfW9MtDcnETBcGep7A
+1jCL5jjdUYRonimVMFjh1K+UFzV/DQHkgNzjxz9Inbh02y67vL2X836dS9esOcbx
+uZhf+8rL+GnSNqYDqCEuP7qCIloDhguJq9NKyTB4yc59qIkY2zPAzQ==
+-----END RSA PRIVATE KEY-----
diff --git a/update_attempter.cc b/update_attempter.cc
new file mode 100644
index 0000000..5704731
--- /dev/null
+++ b/update_attempter.cc
@@ -0,0 +1,1604 @@
+//
+// 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 <algorithm>
+#include <memory>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_util.h>
+#include <base/logging.h>
+#include <base/rand_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/message_loop.h>
+#include <debugd/dbus-constants.h>
+#include <policy/device_policy.h>
+#include <policy/libpolicy.h>
+#include <power_manager/dbus-constants.h>
+#include <power_manager/dbus-proxies.h>
+#include <update_engine/dbus-constants.h>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/certificate_checker.h"
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/libcurl_http_fetcher.h"
+#include "update_engine/common/multi_range_http_fetcher.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/dbus_service.h"
+#include "update_engine/metrics.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/system_state.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+#include "update_engine/update_status_utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::Time;
+using base::TimeDelta;
+using base::TimeTicks;
+using brillo::MessageLoop;
+using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::Policy;
+using chromeos_update_manager::UpdateCheckParams;
+using std::set;
+using std::shared_ptr;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const int UpdateAttempter::kMaxDeltaUpdateFailures = 3;
+
+namespace {
+const int kMaxConsecutiveObeyProxyRequests = 20;
+
+// By default autest bypasses scattering. If we want to test scattering,
+// use kScheduledAUTestURLRequest. The URL used is same in both cases, but
+// different params are passed to CheckForUpdate().
+const char kAUTestURLRequest[] = "autest";
+const char kScheduledAUTestURLRequest[] = "autest-scheduled";
+} // namespace
+
+// Turns a generic ErrorCode::kError to a generic error code specific
+// to |action| (e.g., ErrorCode::kFilesystemVerifierError). If |code| is
+// not ErrorCode::kError, or the action is not matched, returns |code|
+// unchanged.
+ErrorCode GetErrorCodeForAction(AbstractAction* action,
+ ErrorCode code) {
+ if (code != ErrorCode::kError)
+ return code;
+
+ const string type = action->Type();
+ if (type == OmahaRequestAction::StaticType())
+ return ErrorCode::kOmahaRequestError;
+ if (type == OmahaResponseHandlerAction::StaticType())
+ return ErrorCode::kOmahaResponseHandlerError;
+ if (type == FilesystemVerifierAction::StaticType())
+ return ErrorCode::kFilesystemVerifierError;
+ if (type == PostinstallRunnerAction::StaticType())
+ return ErrorCode::kPostinstallRunnerError;
+
+ return code;
+}
+
+UpdateAttempter::UpdateAttempter(
+ SystemState* system_state,
+ CertificateChecker* cert_checker,
+ LibCrosProxy* libcros_proxy,
+ org::chromium::debugdProxyInterface* debugd_proxy)
+ : processor_(new ActionProcessor()),
+ system_state_(system_state),
+ cert_checker_(cert_checker),
+ chrome_proxy_resolver_(libcros_proxy),
+ debugd_proxy_(debugd_proxy) {
+}
+
+UpdateAttempter::~UpdateAttempter() {
+ // CertificateChecker might not be initialized in unittests.
+ if (cert_checker_)
+ cert_checker_->SetObserver(nullptr);
+ CleanupCpuSharesManagement();
+ // Release ourselves as the ActionProcessor's delegate to prevent
+ // re-scheduling the updates due to the processing stopped.
+ processor_->set_delegate(nullptr);
+}
+
+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();
+
+ 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))
+ status_ = UpdateStatus::UPDATED_NEED_REBOOT;
+ else
+ status_ = UpdateStatus::IDLE;
+
+ chrome_proxy_resolver_.Init();
+}
+
+void UpdateAttempter::ScheduleUpdates() {
+ if (IsUpdateRunningOrScheduled())
+ return;
+
+ chromeos_update_manager::UpdateManager* const update_manager =
+ system_state_->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);
+ waiting_for_scheduled_check_ = true;
+}
+
+void UpdateAttempter::CertificateChecked(ServerToCheck server_to_check,
+ CertificateCheckResult result) {
+ metrics::ReportCertificateCheckMetrics(system_state_,
+ 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 last_reported_at = Time::FromInternalValue(stored_value);
+ TimeDelta time_reported_since = now - last_reported_at;
+ if (time_reported_since.InSeconds() < 0) {
+ LOG(WARNING) << "Last reported daily metrics "
+ << utils::FormatTimeDelta(time_reported_since) << " ago "
+ << "which is negative. Either the system clock is wrong or "
+ << "the kPrefsDailyMetricsLastReportedAt state variable "
+ << "is wrong.";
+ // In this case, report daily metrics to reset.
+ } else {
+ if (time_reported_since.InSeconds() < 24*60*60) {
+ LOG(INFO) << "Last reported daily metrics "
+ << utils::FormatTimeDelta(time_reported_since) << " ago.";
+ return false;
+ }
+ LOG(INFO) << "Last reported daily metrics "
+ << utils::FormatTimeDelta(time_reported_since) << " ago, "
+ << "which is more than 24 hours ago.";
+ }
+ }
+
+ LOG(INFO) << "Reporting daily metrics.";
+ system_state_->prefs()->SetInt64(kPrefsDailyMetricsLastReportedAt,
+ now.ToInternalValue());
+
+ ReportOSAge();
+
+ return true;
+}
+
+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)";
+ return;
+ }
+
+ Time lsb_release_timestamp = utils::TimeFromStructTimespec(&sb.st_ctim);
+ Time now = system_state_->clock()->GetWallclockTime();
+ TimeDelta age = now - lsb_release_timestamp;
+ if (age.InSeconds() < 0) {
+ LOG(ERROR) << "The OS age (" << utils::FormatTimeDelta(age)
+ << ") is negative. Maybe the clock is wrong? "
+ << "(Note: this may happen in some unit tests.)";
+ return;
+ }
+
+ metrics::ReportDailyMetrics(system_state_, age);
+}
+
+void UpdateAttempter::Update(const string& app_version,
+ const string& omaha_url,
+ const string& target_channel,
+ const string& target_version_prefix,
+ bool obey_proxies,
+ bool interactive) {
+ // 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)
+ // timeout event.
+ CheckAndReportDailyMetrics();
+
+ // Notify of the new update attempt, clearing prior interactive requests.
+ if (forced_update_pending_callback_.get())
+ forced_update_pending_callback_->Run(false, false);
+
+ fake_update_success_ = false;
+ if (status_ == UpdateStatus::UPDATED_NEED_REBOOT) {
+ // Although we have applied an update, we still want to ping Omaha
+ // to ensure the number of active statistics is accurate.
+ //
+ // Also convey to the UpdateEngine.Check.Result metric that we're
+ // 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";
+ metrics::ReportUpdateCheckMetrics(system_state_,
+ metrics::CheckResult::kRebootPending,
+ metrics::CheckReaction::kUnset,
+ metrics::DownloadErrorCode::kUnset);
+ PingOmaha();
+ return;
+ }
+ if (status_ != UpdateStatus::IDLE) {
+ // Update in progress. Do nothing
+ return;
+ }
+
+ if (!CalculateUpdateParams(app_version,
+ omaha_url,
+ target_channel,
+ target_version_prefix,
+ obey_proxies,
+ interactive)) {
+ return;
+ }
+
+ BuildUpdateActions(interactive);
+
+ SetStatusAndNotify(UpdateStatus::CHECKING_FOR_UPDATE);
+
+ // Update the last check time here; it may be re-updated when an Omaha
+ // response is received, but this will prevent us from repeatedly scheduling
+ // checks in the case where a response is not received.
+ UpdateLastCheckedTime();
+
+ // Just in case we didn't update boot flags yet, make sure they're updated
+ // before any update processing starts.
+ start_action_processor_ = true;
+ UpdateBootFlags();
+}
+
+void UpdateAttempter::RefreshDevicePolicy() {
+ // Lazy initialize the policy provider, or reload the latest policy data.
+ if (!policy_provider_.get())
+ policy_provider_.reset(new policy::PolicyProvider());
+ policy_provider_->Reload();
+
+ const policy::DevicePolicy* device_policy = nullptr;
+ if (policy_provider_->device_policy_is_loaded())
+ device_policy = &policy_provider_->GetDevicePolicy();
+
+ if (device_policy)
+ LOG(INFO) << "Device policies/settings present";
+ else
+ LOG(INFO) << "No device policies/settings present.";
+
+ system_state_->set_device_policy(device_policy);
+ system_state_->p2p_manager()->SetDevicePolicy(device_policy);
+}
+
+void UpdateAttempter::CalculateP2PParams(bool interactive) {
+ bool use_p2p_for_downloading = false;
+ bool use_p2p_for_sharing = false;
+
+ // Never use p2p for downloading in interactive checks unless the
+ // developer has opted in for it via a marker file.
+ //
+ // (Why would a developer want to opt in? If he's working on the
+ // update_engine or p2p codebases so he can actually test his
+ // code.).
+
+ if (system_state_ != nullptr) {
+ if (!system_state_->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 {
+ LOG(INFO) << "Forcibly disabling use of p2p for downloading "
+ << "since this update attempt is interactive.";
+ }
+ }
+ }
+
+ PayloadStateInterface* const payload_state = system_state_->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 obey_proxies,
+ bool interactive) {
+ http_response_code_ = 0;
+ PayloadStateInterface* const payload_state = system_state_->payload_state();
+
+ // Refresh the policy before computing all the update parameters.
+ RefreshDevicePolicy();
+
+ // Set the target version prefix, if provided.
+ if (!target_version_prefix.empty())
+ omaha_request_params_->set_target_version_prefix(target_version_prefix);
+
+ CalculateScatteringParams(interactive);
+
+ CalculateP2PParams(interactive);
+ if (payload_state->GetUsingP2PForDownloading() ||
+ payload_state->GetUsingP2PForSharing()) {
+ // OK, p2p is to be used - start it and perform housekeeping.
+ if (!StartP2PAndPerformHousekeeping()) {
+ // If this fails, disable p2p for this attempt
+ LOG(INFO) << "Forcibly disabling use of p2p since starting p2p or "
+ << "performing housekeeping failed.";
+ payload_state->SetUsingP2PForDownloading(false);
+ payload_state->SetUsingP2PForSharing(false);
+ }
+ }
+
+ if (!omaha_request_params_->Init(app_version,
+ omaha_url,
+ interactive)) {
+ 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();
+ }
+
+ LOG(INFO) << "target_version_prefix = "
+ << omaha_request_params_->target_version_prefix()
+ << ", scatter_factor_in_seconds = "
+ << utils::FormatSecs(scatter_factor_.InSeconds());
+
+ LOG(INFO) << "Wall Clock Based Wait Enabled = "
+ << omaha_request_params_->wall_clock_based_wait_enabled()
+ << ", Update Check Count Wait Enabled = "
+ << omaha_request_params_->update_check_count_wait_enabled()
+ << ", Waiting Period = " << utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+
+ LOG(INFO) << "Use p2p For Downloading = "
+ << payload_state->GetUsingP2PForDownloading()
+ << ", Use p2p For Sharing = "
+ << payload_state->GetUsingP2PForSharing();
+
+ obeying_proxies_ = true;
+ if (obey_proxies || 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_++;
+ LOG(INFO) << "proxy manual checks: " << proxy_manual_checks_;
+ if (proxy_manual_checks_ >= kMaxConsecutiveObeyProxyRequests) {
+ proxy_manual_checks_ = 0;
+ obeying_proxies_ = false;
+ }
+ } else if (base::RandInt(0, 4) == 0) {
+ obeying_proxies_ = false;
+ }
+ LOG_IF(INFO, !obeying_proxies_) << "To help ensure updates work, this update "
+ "check we are ignoring the proxy settings and using "
+ "direct connections.";
+
+ DisableDeltaUpdateIfNeeded();
+ return true;
+}
+
+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();
+ if (device_policy) {
+ int64_t new_scatter_factor_in_secs = 0;
+ device_policy->GetScatterFactorInSeconds(&new_scatter_factor_in_secs);
+ if (new_scatter_factor_in_secs < 0) // sanitize input, just in case.
+ new_scatter_factor_in_secs = 0;
+ scatter_factor_ = TimeDelta::FromSeconds(new_scatter_factor_in_secs);
+ }
+
+ bool is_scatter_enabled = false;
+ if (scatter_factor_.InSeconds() == 0) {
+ 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()->IsOOBEComplete(nullptr)) {
+ LOG(INFO) << "Scattering disabled since OOBE is not complete yet";
+ } else {
+ is_scatter_enabled = true;
+ LOG(INFO) << "Scattering is enabled";
+ }
+
+ if (is_scatter_enabled) {
+ // This means the scattering policy is turned on.
+ // Now check if we need to update the waiting period. The two cases
+ // in which we'd need to update the waiting period are:
+ // 1. First time in process or a scheduled check after a user-initiated one.
+ // (omaha_request_params_->waiting_period will be zero in this case).
+ // 2. Admin has changed the scattering policy value.
+ // (new scattering value will be different from old one in this case).
+ int64_t wait_period_in_secs = 0;
+ if (omaha_request_params_->waiting_period().InSeconds() == 0) {
+ // First case. Check if we have a suitable value to set for
+ // the waiting period.
+ if (prefs_->GetInt64(kPrefsWallClockWaitPeriod, &wait_period_in_secs) &&
+ wait_period_in_secs > 0 &&
+ wait_period_in_secs <= scatter_factor_.InSeconds()) {
+ // This means:
+ // 1. There's a persisted value for the waiting period available.
+ // 2. And that persisted value is still valid.
+ // So, in this case, we should reuse the persisted value instead of
+ // generating a new random value to improve the chances of a good
+ // distribution for scattering.
+ omaha_request_params_->set_waiting_period(
+ TimeDelta::FromSeconds(wait_period_in_secs));
+ LOG(INFO) << "Using persisted wall-clock waiting period: " <<
+ utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+ } else {
+ // This means there's no persisted value for the waiting period
+ // available or its value is invalid given the new scatter_factor value.
+ // So, we should go ahead and regenerate a new value for the
+ // waiting period.
+ LOG(INFO) << "Persisted value not present or not valid ("
+ << utils::FormatSecs(wait_period_in_secs)
+ << ") for wall-clock waiting period.";
+ GenerateNewWaitingPeriod();
+ }
+ } else if (scatter_factor_ != old_scatter_factor) {
+ // This means there's already a waiting period value, but we detected
+ // a change in the scattering policy value. So, we should regenerate the
+ // waiting period to make sure it's within the bounds of the new scatter
+ // factor value.
+ GenerateNewWaitingPeriod();
+ } else {
+ // Neither the first time scattering is enabled nor the scattering value
+ // changed. Nothing to do.
+ LOG(INFO) << "Keeping current wall-clock waiting period: " <<
+ utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+ }
+
+ // The invariant at this point is that omaha_request_params_->waiting_period
+ // is non-zero no matter which path we took above.
+ LOG_IF(ERROR, omaha_request_params_->waiting_period().InSeconds() == 0)
+ << "Waiting Period should NOT be zero at this point!!!";
+
+ // Since scattering is enabled, wall clock based wait will always be
+ // enabled.
+ omaha_request_params_->set_wall_clock_based_wait_enabled(true);
+
+ // If we don't have any issues in accessing the file system to update
+ // the update check count value, we'll turn that on as well.
+ bool decrement_succeeded = DecrementUpdateCheckCount();
+ omaha_request_params_->set_update_check_count_wait_enabled(
+ decrement_succeeded);
+ } else {
+ // This means the scattering feature is turned off or disabled for
+ // this particular update check. Make sure to disable
+ // all the knobs and artifacts so that we don't invoke any scattering
+ // related code.
+ omaha_request_params_->set_wall_clock_based_wait_enabled(false);
+ omaha_request_params_->set_update_check_count_wait_enabled(false);
+ omaha_request_params_->set_waiting_period(TimeDelta::FromSeconds(0));
+ prefs_->Delete(kPrefsWallClockWaitPeriod);
+ prefs_->Delete(kPrefsUpdateCheckCount);
+ // Don't delete the UpdateFirstSeenAt file as we don't want manual checks
+ // that result in no-updates (e.g. due to server side throttling) to
+ // cause update starvation by having the client generate a new
+ // UpdateFirstSeenAt for each scheduled check that follows a manual check.
+ }
+}
+
+void UpdateAttempter::GenerateNewWaitingPeriod() {
+ omaha_request_params_->set_waiting_period(TimeDelta::FromSeconds(
+ base::RandInt(1, scatter_factor_.InSeconds())));
+
+ LOG(INFO) << "Generated new wall-clock waiting period: " << utils::FormatSecs(
+ omaha_request_params_->waiting_period().InSeconds());
+
+ // Do a best-effort to persist this in all cases. Even if the persistence
+ // 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(
+ omaha_request_params_->waiting_period());
+}
+
+void UpdateAttempter::BuildPostInstallActions(
+ InstallPlanAction* previous_action) {
+ shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
+ new PostinstallRunnerAction(system_state_->boot_control()));
+ actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
+ BondActions(previous_action,
+ postinstall_runner_action.get());
+}
+
+void UpdateAttempter::BuildUpdateActions(bool interactive) {
+ CHECK(!processor_->IsRunning());
+ processor_->set_delegate(this);
+
+ // Actions:
+ std::unique_ptr<LibcurlHttpFetcher> update_check_fetcher(
+ new LibcurlHttpFetcher(GetProxyResolver(), system_state_->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);
+ shared_ptr<OmahaRequestAction> update_check_action(
+ new OmahaRequestAction(system_state_,
+ nullptr,
+ std::move(update_check_fetcher),
+ false));
+ shared_ptr<OmahaResponseHandlerAction> response_handler_action(
+ new OmahaResponseHandlerAction(system_state_));
+ shared_ptr<FilesystemVerifierAction> src_filesystem_verifier_action(
+ new FilesystemVerifierAction(system_state_->boot_control(),
+ VerifierMode::kComputeSourceHash));
+
+ shared_ptr<OmahaRequestAction> download_started_action(
+ new OmahaRequestAction(system_state_,
+ new OmahaEvent(
+ OmahaEvent::kTypeUpdateDownloadStarted),
+ brillo::make_unique_ptr(new LibcurlHttpFetcher(
+ GetProxyResolver(),
+ system_state_->hardware())),
+ false));
+
+ LibcurlHttpFetcher* download_fetcher =
+ new LibcurlHttpFetcher(GetProxyResolver(), system_state_->hardware());
+ download_fetcher->set_server_to_check(ServerToCheck::kDownload);
+ shared_ptr<DownloadAction> download_action(
+ new DownloadAction(prefs_,
+ system_state_,
+ new MultiRangeHttpFetcher(
+ download_fetcher))); // passes ownership
+ shared_ptr<OmahaRequestAction> download_finished_action(
+ new OmahaRequestAction(
+ system_state_,
+ new OmahaEvent(OmahaEvent::kTypeUpdateDownloadFinished),
+ brillo::make_unique_ptr(
+ new LibcurlHttpFetcher(GetProxyResolver(),
+ system_state_->hardware())),
+ false));
+ shared_ptr<FilesystemVerifierAction> dst_filesystem_verifier_action(
+ new FilesystemVerifierAction(system_state_->boot_control(),
+ VerifierMode::kVerifyTargetHash));
+ shared_ptr<OmahaRequestAction> update_complete_action(
+ new OmahaRequestAction(
+ system_state_,
+ new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+ brillo::make_unique_ptr(
+ new LibcurlHttpFetcher(GetProxyResolver(),
+ system_state_->hardware())),
+ false));
+
+ download_action->set_delegate(this);
+ response_handler_action_ = response_handler_action;
+ download_action_ = download_action;
+
+ actions_.push_back(shared_ptr<AbstractAction>(update_check_action));
+ actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
+ actions_.push_back(shared_ptr<AbstractAction>(
+ src_filesystem_verifier_action));
+ actions_.push_back(shared_ptr<AbstractAction>(download_started_action));
+ actions_.push_back(shared_ptr<AbstractAction>(download_action));
+ actions_.push_back(shared_ptr<AbstractAction>(download_finished_action));
+ actions_.push_back(shared_ptr<AbstractAction>(
+ dst_filesystem_verifier_action));
+
+ // Bond them together. We have to use the leaf-types when calling
+ // BondActions().
+ BondActions(update_check_action.get(),
+ response_handler_action.get());
+ BondActions(response_handler_action.get(),
+ src_filesystem_verifier_action.get());
+ BondActions(src_filesystem_verifier_action.get(),
+ download_action.get());
+ BondActions(download_action.get(),
+ dst_filesystem_verifier_action.get());
+ BuildPostInstallActions(dst_filesystem_verifier_action.get());
+
+ actions_.push_back(shared_ptr<AbstractAction>(update_complete_action));
+
+ // Enqueue the actions
+ for (const shared_ptr<AbstractAction>& action : actions_) {
+ processor_->EnqueueAction(action.get());
+ }
+}
+
+bool UpdateAttempter::Rollback(bool powerwash) {
+ if (!CanRollback()) {
+ return false;
+ }
+
+ // Extra check for enterprise-enrolled devices since they don't support
+ // powerwash.
+ if (powerwash) {
+ // Enterprise-enrolled devices have an empty owner in their device policy.
+ string owner;
+ RefreshDevicePolicy();
+ const policy::DevicePolicy* device_policy = system_state_->device_policy();
+ if (device_policy && (!device_policy->GetOwner(&owner) || owner.empty())) {
+ LOG(ERROR) << "Enterprise device detected. "
+ << "Cannot perform a powerwash for enterprise devices.";
+ return false;
+ }
+ }
+
+ processor_->set_delegate(this);
+
+ // Initialize the default request params.
+ if (!omaha_request_params_->Init("", "", true)) {
+ LOG(ERROR) << "Unable to initialize Omaha request params.";
+ return false;
+ }
+
+ LOG(INFO) << "Setting rollback options.";
+ InstallPlan install_plan;
+
+ install_plan.target_slot = GetRollbackSlot();
+ install_plan.source_slot = system_state_->boot_control()->GetCurrentSlot();
+
+ TEST_AND_RETURN_FALSE(
+ install_plan.LoadPartitionsFromSlots(system_state_->boot_control()));
+ install_plan.powerwash_required = powerwash;
+
+ LOG(INFO) << "Using this install plan:";
+ install_plan.Dump();
+
+ shared_ptr<InstallPlanAction> install_plan_action(
+ new InstallPlanAction(install_plan));
+ actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
+
+ BuildPostInstallActions(install_plan_action.get());
+
+ // Enqueue the actions
+ for (const shared_ptr<AbstractAction>& action : actions_) {
+ processor_->EnqueueAction(action.get());
+ }
+
+ // Update the payload state for Rollback.
+ system_state_->payload_state()->Rollback();
+
+ SetStatusAndNotify(UpdateStatus::ATTEMPTING_ROLLBACK);
+
+ // Just in case we didn't update boot flags yet, make sure they're updated
+ // before any update processing starts. This also schedules the start of the
+ // actions we just posted.
+ start_action_processor_ = true;
+ UpdateBootFlags();
+ return true;
+}
+
+bool UpdateAttempter::CanRollback() const {
+ // We can only rollback if the update_engine isn't busy and we have a valid
+ // rollback partition.
+ return (status_ == UpdateStatus::IDLE &&
+ GetRollbackSlot() != BootControlInterface::kInvalidSlot);
+}
+
+BootControlInterface::Slot UpdateAttempter::GetRollbackSlot() const {
+ LOG(INFO) << "UpdateAttempter::GetRollbackSlot";
+ const unsigned int num_slots = system_state_->boot_control()->GetNumSlots();
+ const BootControlInterface::Slot current_slot =
+ system_state_->boot_control()->GetCurrentSlot();
+
+ LOG(INFO) << " Installed slots: " << num_slots;
+ LOG(INFO) << " Booted from slot: "
+ << BootControlInterface::SlotName(current_slot);
+
+ if (current_slot == BootControlInterface::kInvalidSlot || num_slots < 2) {
+ LOG(INFO) << "Device is not updateable.";
+ return BootControlInterface::kInvalidSlot;
+ }
+
+ vector<BootControlInterface::Slot> bootable_slots;
+ for(BootControlInterface::Slot slot = 0; slot < num_slots; slot++) {
+ if (slot != current_slot &&
+ system_state_->boot_control()->IsSlotBootable(slot)) {
+ LOG(INFO) << "Found bootable slot "
+ << BootControlInterface::SlotName(slot);
+ return slot;
+ }
+ }
+ LOG(INFO) << "No other bootable slot found.";
+ return BootControlInterface::kInvalidSlot;
+}
+
+void UpdateAttempter::CheckForUpdate(const string& app_version,
+ const string& omaha_url,
+ bool interactive) {
+ LOG(INFO) << "Forced update check requested.";
+ forced_app_version_.clear();
+ forced_omaha_url_.clear();
+
+ // Certain conditions must be met to allow setting custom version and update
+ // server URLs. However, kScheduledAUTestURLRequest and kAUTestURLRequest are
+ // always allowed regardless of device state.
+ if (IsAnyUpdateSourceAllowed()) {
+ forced_app_version_ = app_version;
+ forced_omaha_url_ = omaha_url;
+ }
+ if (omaha_url == kScheduledAUTestURLRequest) {
+ forced_omaha_url_ = constants::kOmahaDefaultAUTestURL;
+ // Pretend that it's not user-initiated even though it is,
+ // so as to test scattering logic, etc. which get kicked off
+ // only in scheduled update checks.
+ interactive = false;
+ } else if (omaha_url == kAUTestURLRequest) {
+ forced_omaha_url_ = constants::kOmahaDefaultAUTestURL;
+ }
+
+ 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, interactive);
+ }
+}
+
+bool UpdateAttempter::RebootIfNeeded() {
+ if (status_ != UpdateStatus::UPDATED_NEED_REBOOT) {
+ LOG(INFO) << "Reboot requested, but status is "
+ << UpdateStatusToString(status_) << ", so not rebooting.";
+ return false;
+ }
+
+ if (USE_POWER_MANAGEMENT && RequestPowerManagerReboot())
+ return true;
+
+ return RebootDirectly();
+}
+
+void UpdateAttempter::WriteUpdateCompletedMarker() {
+ string boot_id;
+ if (!utils::GetBootId(&boot_id))
+ return;
+ prefs_->SetString(kPrefsUpdateCompletedOnBootId, boot_id);
+
+ int64_t value = system_state_->clock()->GetBootTime().ToInternalValue();
+ prefs_->SetInt64(kPrefsUpdateCompletedBootTime, value);
+}
+
+bool UpdateAttempter::RequestPowerManagerReboot() {
+ org::chromium::PowerManagerProxyInterface* power_manager_proxy =
+ system_state_->power_manager_proxy();
+ if (!power_manager_proxy) {
+ LOG(WARNING) << "No PowerManager proxy defined, skipping reboot.";
+ return false;
+ }
+ LOG(INFO) << "Calling " << power_manager::kPowerManagerInterface << "."
+ << power_manager::kRequestRestartMethod;
+ brillo::ErrorPtr error;
+ return power_manager_proxy->RequestRestart(
+ power_manager::REQUEST_RESTART_FOR_UPDATE, &error);
+}
+
+bool UpdateAttempter::RebootDirectly() {
+ vector<string> command;
+ command.push_back("/sbin/shutdown");
+ command.push_back("-r");
+ command.push_back("now");
+ LOG(INFO) << "Running \"" << JoinString(command, ' ') << "\"";
+ int rc = 0;
+ Subprocess::SynchronousExec(command, &rc, nullptr);
+ return rc == 0;
+}
+
+void UpdateAttempter::OnUpdateScheduled(EvalStatus status,
+ const UpdateCheckParams& params) {
+ waiting_for_scheduled_check_ = false;
+
+ if (status == EvalStatus::kSucceeded) {
+ if (!params.updates_enabled) {
+ LOG(WARNING) << "Updates permanently disabled.";
+ // Signal disabled status, then switch right back to idle. This is
+ // necessary for ensuring that observers waiting for a signal change will
+ // actually notice one on subsequent calls. Note that we don't need to
+ // re-schedule a check in this case as updates are permanently disabled;
+ // further (forced) checks may still initiate a scheduling call.
+ SetStatusAndNotify(UpdateStatus::DISABLED);
+ SetStatusAndNotify(UpdateStatus::IDLE);
+ return;
+ }
+
+ LOG(INFO) << "Running "
+ << (params.is_interactive ? "interactive" : "periodic")
+ << " update.";
+
+ string app_version, omaha_url;
+ if (params.is_interactive) {
+ app_version = forced_app_version_;
+ omaha_url = forced_omaha_url_;
+ }
+
+ Update(app_version, omaha_url, params.target_channel,
+ params.target_version_prefix, false, params.is_interactive);
+ } else {
+ LOG(WARNING)
+ << "Update check scheduling failed (possibly timed out); retrying.";
+ ScheduleUpdates();
+ }
+
+ // This check ensures that future update checks will be or are already
+ // scheduled. The check should never fail. A check failure means that there's
+ // 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());
+}
+
+void UpdateAttempter::UpdateLastCheckedTime() {
+ last_checked_time_ = system_state_->clock()->GetWallclockTime().ToTimeT();
+}
+
+// Delegate methods:
+void UpdateAttempter::ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) {
+ LOG(INFO) << "Processing Done.";
+ actions_.clear();
+
+ // Reset cpu shares back to normal.
+ CleanupCpuSharesManagement();
+
+ if (status_ == UpdateStatus::REPORTING_ERROR_EVENT) {
+ LOG(INFO) << "Error event sent.";
+
+ // Inform scheduler of new status;
+ SetStatusAndNotify(UpdateStatus::IDLE);
+ ScheduleUpdates();
+
+ if (!fake_update_success_) {
+ return;
+ }
+ LOG(INFO) << "Booted from FW B and tried to install new firmware, "
+ "so requesting reboot from user.";
+ }
+
+ if (code == ErrorCode::kSuccess) {
+ WriteUpdateCompletedMarker();
+ 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());
+ prefs_->Delete(kPrefsUpdateFirstSeenAt);
+
+ SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+ ScheduleUpdates();
+ LOG(INFO) << "Update successfully applied, waiting to reboot.";
+
+ // This pointer is null during rollback operations, and the stats
+ // don't make much sense then anyway.
+ if (response_handler_action_) {
+ const InstallPlan& install_plan =
+ response_handler_action_->install_plan();
+
+ // Generate an unique payload identifier.
+ const string target_version_uid =
+ install_plan.payload_hash + ":" + install_plan.metadata_signature;
+
+ // 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 expected "
+ "response_handler_action_ missing.";
+ }
+ }
+ return;
+ }
+
+ if (ScheduleErrorEventAction()) {
+ return;
+ }
+ LOG(INFO) << "No update.";
+ SetStatusAndNotify(UpdateStatus::IDLE);
+ ScheduleUpdates();
+}
+
+void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) {
+ // Reset cpu shares back to normal.
+ CleanupCpuSharesManagement();
+ download_progress_ = 0.0;
+ SetStatusAndNotify(UpdateStatus::IDLE);
+ ScheduleUpdates();
+ actions_.clear();
+ error_event_.reset(nullptr);
+}
+
+// Called whenever an action has finished processing, either successfully
+// or otherwise.
+void UpdateAttempter::ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ // Reset download progress regardless of whether or not the download
+ // action succeeded. Also, get the response code from HTTP request
+ // actions (update download as well as the initial update check
+ // actions).
+ const string type = action->Type();
+ if (type == DownloadAction::StaticType()) {
+ download_progress_ = 0.0;
+ DownloadAction* download_action = static_cast<DownloadAction*>(action);
+ http_response_code_ = download_action->GetHTTPResponseCode();
+ } else if (type == OmahaRequestAction::StaticType()) {
+ OmahaRequestAction* omaha_request_action =
+ static_cast<OmahaRequestAction*>(action);
+ // If the request is not an event, then it's the update-check.
+ if (!omaha_request_action->IsEvent()) {
+ http_response_code_ = omaha_request_action->GetHTTPResponseCode();
+
+ // Record the number of consecutive failed update checks.
+ if (http_response_code_ == kHttpResponseInternalServerError ||
+ http_response_code_ == kHttpResponseServiceUnavailable) {
+ consecutive_failed_update_checks_++;
+ } else {
+ consecutive_failed_update_checks_ = 0;
+ }
+
+ // Store the server-dictated poll interval, if any.
+ server_dictated_poll_interval_ =
+ std::max(0, omaha_request_action->GetOutputObject().poll_interval);
+ }
+ }
+ if (code != ErrorCode::kSuccess) {
+ // If the current state is at or past the download phase, count the failure
+ // in case a switch to full update becomes necessary. Ignore network
+ // transfer timeouts and failures.
+ if (status_ >= UpdateStatus::DOWNLOADING &&
+ code != ErrorCode::kDownloadTransferError) {
+ MarkDeltaUpdateFailure();
+ }
+ // On failure, schedule an error event to be sent to Omaha.
+ CreatePendingErrorEvent(action, code);
+ return;
+ }
+ // Find out which action completed.
+ if (type == OmahaResponseHandlerAction::StaticType()) {
+ // Note that the status will be updated to DOWNLOADING when some bytes get
+ // actually downloaded from the server and the BytesReceived callback is
+ // invoked. This avoids notifying the user that a download has started in
+ // cases when the server and the client are unable to initiate the download.
+ CHECK(action == response_handler_action_.get());
+ const InstallPlan& plan = response_handler_action_->install_plan();
+ UpdateLastCheckedTime();
+ new_version_ = plan.version;
+ new_payload_size_ = plan.payload_size;
+ SetupDownload();
+ SetupCpuSharesManagement();
+ SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
+ } else if (type == DownloadAction::StaticType()) {
+ SetStatusAndNotify(UpdateStatus::FINALIZING);
+ }
+}
+
+void UpdateAttempter::BytesReceived(uint64_t bytes_progressed,
+ uint64_t bytes_received,
+ 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);
+
+ double progress = static_cast<double>(bytes_received) /
+ static_cast<double>(total);
+ // Self throttle based on progress. Also send notifications if
+ // progress is too slow.
+ const double kDeltaPercent = 0.01; // 1%
+ if (status_ != UpdateStatus::DOWNLOADING ||
+ bytes_received == total ||
+ progress - download_progress_ >= kDeltaPercent ||
+ TimeTicks::Now() - last_notify_time_ >= TimeDelta::FromSeconds(10)) {
+ download_progress_ = progress;
+ SetStatusAndNotify(UpdateStatus::DOWNLOADING);
+ }
+}
+
+void UpdateAttempter::DownloadComplete() {
+ system_state_->payload_state()->DownloadComplete();
+}
+
+bool UpdateAttempter::ResetStatus() {
+ LOG(INFO) << "Attempting to reset state from "
+ << UpdateStatusToString(status_) << " to UpdateStatus::IDLE";
+
+ switch (status_) {
+ case UpdateStatus::IDLE:
+ // no-op.
+ return true;
+
+ case UpdateStatus::UPDATED_NEED_REBOOT: {
+ bool ret_value = true;
+ status_ = UpdateStatus::IDLE;
+
+ // Remove the reboot marker so that if the machine is rebooted
+ // after resetting to idle state, it doesn't go back to
+ // UpdateStatus::UPDATED_NEED_REBOOT state.
+ ret_value = prefs_->Delete(kPrefsUpdateCompletedOnBootId) && ret_value;
+ ret_value = prefs_->Delete(kPrefsUpdateCompletedBootTime) && ret_value;
+
+ // Update the boot flags so the current slot has higher priority.
+ BootControlInterface* boot_control = system_state_->boot_control();
+ if (!boot_control->SetActiveBootSlot(boot_control->GetCurrentSlot()))
+ ret_value = false;
+
+ // Notify the PayloadState that the successful payload was canceled.
+ system_state_->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
+ // need to clear out this value now to prevent it being sent on the next
+ // updatecheck request.
+ ret_value = prefs_->SetString(kPrefsPreviousVersion, "") && ret_value;
+
+ LOG(INFO) << "Reset status " << (ret_value ? "successful" : "failed");
+ return ret_value;
+ }
+
+ default:
+ LOG(ERROR) << "Reset not allowed in this state.";
+ return false;
+ }
+}
+
+bool UpdateAttempter::GetStatus(int64_t* last_checked_time,
+ double* progress,
+ string* current_operation,
+ string* new_version,
+ int64_t* new_payload_size) {
+ *last_checked_time = last_checked_time_;
+ *progress = download_progress_;
+ *current_operation = UpdateStatusToString(status_);
+ *new_version = new_version_;
+ *new_payload_size = new_payload_size_;
+ return true;
+}
+
+void UpdateAttempter::UpdateBootFlags() {
+ if (update_boot_flags_running_) {
+ LOG(INFO) << "Update boot flags running, nothing to do.";
+ return;
+ }
+ if (updated_boot_flags_) {
+ LOG(INFO) << "Already updated boot flags. Skipping.";
+ if (start_action_processor_) {
+ ScheduleProcessingStart();
+ }
+ return;
+ }
+ // This is purely best effort. Failures should be logged by Subprocess. Run
+ // the script asynchronously to avoid blocking the event loop regardless of
+ // the script runtime.
+ update_boot_flags_running_ = true;
+ LOG(INFO) << "Marking booted slot as good.";
+ if (!system_state_->boot_control()->MarkBootSuccessfulAsync(Bind(
+ &UpdateAttempter::CompleteUpdateBootFlags, base::Unretained(this)))) {
+ LOG(ERROR) << "Failed to mark current boot as successful.";
+ CompleteUpdateBootFlags(false);
+ }
+}
+
+void UpdateAttempter::CompleteUpdateBootFlags(bool successful) {
+ update_boot_flags_running_ = false;
+ updated_boot_flags_ = true;
+ if (start_action_processor_) {
+ ScheduleProcessingStart();
+ }
+}
+
+void UpdateAttempter::BroadcastStatus() {
+ if (!dbus_adaptor_)
+ return;
+ last_notify_time_ = TimeTicks::Now();
+ dbus_adaptor_->SendStatusUpdateSignal(
+ last_checked_time_,
+ download_progress_,
+ UpdateStatusToString(status_),
+ new_version_.c_str(),
+ new_payload_size_);
+}
+
+uint32_t UpdateAttempter::GetErrorCodeFlags() {
+ uint32_t flags = 0;
+
+ if (!system_state_->hardware()->IsNormalBootMode())
+ flags |= static_cast<uint32_t>(ErrorCode::kDevModeFlag);
+
+ if (response_handler_action_.get() &&
+ response_handler_action_->install_plan().is_resume)
+ flags |= static_cast<uint32_t>(ErrorCode::kResumedFlag);
+
+ if (!system_state_->hardware()->IsOfficialBuild())
+ flags |= static_cast<uint32_t>(ErrorCode::kTestImageFlag);
+
+ if (omaha_request_params_->update_url() !=
+ constants::kOmahaDefaultProductionURL) {
+ flags |= static_cast<uint32_t>(ErrorCode::kTestOmahaUrlFlag);
+ }
+
+ return flags;
+}
+
+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();
+ if (params->download_channel() != params->target_channel()) {
+ LOG(ERROR) << "Aborting download as target channel: "
+ << params->target_channel()
+ << " is different from the download channel: "
+ << params->download_channel();
+ *cancel_reason = ErrorCode::kUpdateCanceledByChannelChange;
+ return true;
+ }
+
+ return false;
+}
+
+void UpdateAttempter::SetStatusAndNotify(UpdateStatus status) {
+ status_ = status;
+ BroadcastStatus();
+}
+
+void UpdateAttempter::CreatePendingErrorEvent(AbstractAction* action,
+ ErrorCode code) {
+ if (error_event_.get()) {
+ // This shouldn't really happen.
+ LOG(WARNING) << "There's already an existing pending error event.";
+ return;
+ }
+
+ // For now assume that a generic Omaha response action failure means that
+ // there's no update so don't send an event. Also, double check that the
+ // failure has not occurred while sending an error event -- in which case
+ // don't schedule another. This shouldn't really happen but just in case...
+ if ((action->Type() == OmahaResponseHandlerAction::StaticType() &&
+ code == ErrorCode::kError) ||
+ status_ == UpdateStatus::REPORTING_ERROR_EVENT) {
+ return;
+ }
+
+ // Classify the code to generate the appropriate result so that
+ // the Borgmon charts show up the results correctly.
+ // Do this before calling GetErrorCodeForAction which could potentially
+ // augment the bit representation of code and thus cause no matches for
+ // the switch cases below.
+ OmahaEvent::Result event_result;
+ switch (code) {
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ event_result = OmahaEvent::kResultUpdateDeferred;
+ break;
+ default:
+ event_result = OmahaEvent::kResultError;
+ break;
+ }
+
+ code = GetErrorCodeForAction(action, code);
+ fake_update_success_ = code == ErrorCode::kPostinstallBootedFromFirmwareB;
+
+ // Compute the final error code with all the bit flags to be sent to Omaha.
+ code = static_cast<ErrorCode>(
+ static_cast<uint32_t>(code) | GetErrorCodeFlags());
+ error_event_.reset(new OmahaEvent(OmahaEvent::kTypeUpdateComplete,
+ event_result,
+ code));
+}
+
+bool UpdateAttempter::ScheduleErrorEventAction() {
+ if (error_event_.get() == nullptr)
+ return false;
+
+ LOG(ERROR) << "Update failed.";
+ system_state_->payload_state()->UpdateFailed(error_event_->error_code);
+
+ // Send it to Omaha.
+ LOG(INFO) << "Reporting the error event";
+ shared_ptr<OmahaRequestAction> error_event_action(
+ new OmahaRequestAction(system_state_,
+ error_event_.release(), // Pass ownership.
+ brillo::make_unique_ptr(new LibcurlHttpFetcher(
+ GetProxyResolver(),
+ system_state_->hardware())),
+ false));
+ actions_.push_back(shared_ptr<AbstractAction>(error_event_action));
+ processor_->EnqueueAction(error_event_action.get());
+ SetStatusAndNotify(UpdateStatus::REPORTING_ERROR_EVENT);
+ processor_->StartProcessing();
+ return true;
+}
+
+void UpdateAttempter::SetCpuShares(utils::CpuShares shares) {
+ if (shares_ == shares) {
+ return;
+ }
+ if (utils::SetCpuShares(shares)) {
+ shares_ = shares;
+ LOG(INFO) << "CPU shares = " << shares_;
+ }
+}
+
+void UpdateAttempter::SetupCpuSharesManagement() {
+ if (manage_shares_id_ != MessageLoop::kTaskIdNull) {
+ LOG(ERROR) << "Cpu shares timeout source hasn't been destroyed.";
+ CleanupCpuSharesManagement();
+ }
+ const int kCpuSharesTimeout = 2 * 60 * 60; // 2 hours
+ manage_shares_id_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ Bind(&UpdateAttempter::ManageCpuSharesCallback, base::Unretained(this)),
+ TimeDelta::FromSeconds(kCpuSharesTimeout));
+ SetCpuShares(utils::kCpuSharesLow);
+}
+
+void UpdateAttempter::CleanupCpuSharesManagement() {
+ if (manage_shares_id_ != MessageLoop::kTaskIdNull) {
+ // The UpdateAttempter is instantiated by default by the FakeSystemState,
+ // even when it is not used. We check the manage_shares_id_ before calling
+ // the MessageLoop::current() since the unit test using a FakeSystemState
+ // may have not define a MessageLoop for the current thread.
+ MessageLoop::current()->CancelTask(manage_shares_id_);
+ manage_shares_id_ = MessageLoop::kTaskIdNull;
+ }
+ SetCpuShares(utils::kCpuSharesNormal);
+}
+
+void UpdateAttempter::ScheduleProcessingStart() {
+ LOG(INFO) << "Scheduling an action processor start.";
+ start_action_processor_ = false;
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ Bind([this] { this->processor_->StartProcessing(); }));
+}
+
+void UpdateAttempter::ManageCpuSharesCallback() {
+ SetCpuShares(utils::kCpuSharesNormal);
+ manage_shares_id_ = MessageLoop::kTaskIdNull;
+}
+
+void UpdateAttempter::DisableDeltaUpdateIfNeeded() {
+ int64_t delta_failures;
+ if (omaha_request_params_->delta_okay() &&
+ prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) &&
+ delta_failures >= kMaxDeltaUpdateFailures) {
+ LOG(WARNING) << "Too many delta update failures, forcing full update.";
+ omaha_request_params_->set_delta_okay(false);
+ }
+}
+
+void UpdateAttempter::MarkDeltaUpdateFailure() {
+ // Don't try to resume a failed delta update.
+ DeltaPerformer::ResetUpdateProgress(prefs_, false);
+ int64_t delta_failures;
+ if (!prefs_->GetInt64(kPrefsDeltaUpdateFailures, &delta_failures) ||
+ delta_failures < 0) {
+ delta_failures = 0;
+ }
+ prefs_->SetInt64(kPrefsDeltaUpdateFailures, ++delta_failures);
+}
+
+void UpdateAttempter::SetupDownload() {
+ MultiRangeHttpFetcher* fetcher =
+ static_cast<MultiRangeHttpFetcher*>(download_action_->http_fetcher());
+ fetcher->ClearRanges();
+ if (response_handler_action_->install_plan().is_resume) {
+ // Resuming an update so fetch the update manifest metadata first.
+ int64_t manifest_metadata_size = 0;
+ prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size);
+ fetcher->AddRange(0, manifest_metadata_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 + next_data_offset;
+ if (resume_offset < response_handler_action_->install_plan().payload_size) {
+ fetcher->AddRange(resume_offset);
+ }
+ } else {
+ fetcher->AddRange(0);
+ }
+}
+
+void UpdateAttempter::PingOmaha() {
+ if (!processor_->IsRunning()) {
+ shared_ptr<OmahaRequestAction> ping_action(new OmahaRequestAction(
+ system_state_,
+ nullptr,
+ brillo::make_unique_ptr(new LibcurlHttpFetcher(
+ GetProxyResolver(),
+ system_state_->hardware())),
+ true));
+ actions_.push_back(shared_ptr<OmahaRequestAction>(ping_action));
+ processor_->set_delegate(nullptr);
+ processor_->EnqueueAction(ping_action.get());
+ // Call StartProcessing() synchronously here to avoid any race conditions
+ // caused by multiple outstanding ping Omaha requests. If we call
+ // StartProcessing() asynchronously, the device can be suspended before we
+ // get a chance to callback to StartProcessing(). When the device resumes
+ // (assuming the device sleeps longer than the next update check period),
+ // StartProcessing() is called back and at the same time, the next update
+ // check is fired which eventually invokes StartProcessing(). A crash
+ // can occur because StartProcessing() checks to make sure that the
+ // processor is idle which it isn't due to the two concurrent ping Omaha
+ // requests.
+ processor_->StartProcessing();
+ } else {
+ LOG(WARNING) << "Action processor running, Omaha ping suppressed.";
+ }
+
+ // Update the last check time here; it may be re-updated when an Omaha
+ // response is received, but this will prevent us from repeatedly scheduling
+ // checks in the case where a response is not received.
+ UpdateLastCheckedTime();
+
+ // Update the status which will schedule the next update check
+ SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+ ScheduleUpdates();
+}
+
+
+bool UpdateAttempter::DecrementUpdateCheckCount() {
+ int64_t update_check_count_value;
+
+ if (!prefs_->Exists(kPrefsUpdateCheckCount)) {
+ // This file does not exist. This means we haven't started our update
+ // check count down yet, so nothing more to do. This file will be created
+ // later when we first satisfy the wall-clock-based-wait period.
+ LOG(INFO) << "No existing update check count. That's normal.";
+ return true;
+ }
+
+ if (prefs_->GetInt64(kPrefsUpdateCheckCount, &update_check_count_value)) {
+ // Only if we're able to read a proper integer value, then go ahead
+ // and decrement and write back the result in the same file, if needed.
+ LOG(INFO) << "Update check count = " << update_check_count_value;
+
+ if (update_check_count_value == 0) {
+ // It could be 0, if, for some reason, the file didn't get deleted
+ // when we set our status to waiting for reboot. so we just leave it
+ // as is so that we can prevent another update_check wait for this client.
+ LOG(INFO) << "Not decrementing update check count as it's already 0.";
+ return true;
+ }
+
+ if (update_check_count_value > 0)
+ update_check_count_value--;
+ else
+ update_check_count_value = 0;
+
+ // Write out the new value of update_check_count_value.
+ if (prefs_->SetInt64(kPrefsUpdateCheckCount, update_check_count_value)) {
+ // We successfully wrote out te new value, so enable the
+ // update check based wait.
+ LOG(INFO) << "New update check count = " << update_check_count_value;
+ return true;
+ }
+ }
+
+ LOG(INFO) << "Deleting update check count state due to read/write errors.";
+
+ // We cannot read/write to the file, so disable the update check based wait
+ // so that we don't get stuck in this OS version by any chance (which could
+ // happen if there's some bug that causes to read/write incorrectly).
+ // Also attempt to delete the file to do our best effort to cleanup.
+ prefs_->Delete(kPrefsUpdateCheckCount);
+ return false;
+}
+
+
+void UpdateAttempter::UpdateEngineStarted() {
+ // If we just booted into a new update, keep the previous OS version
+ // 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
+ // delete SystemUpdated marker file.
+ if (system_state_->system_rebooted() &&
+ prefs_->Exists(kPrefsSystemUpdatedMarker)) {
+ if (!prefs_->GetString(kPrefsPreviousVersion, &prev_version_)) {
+ // If we fail to get the version string, make sure it stays empty.
+ prev_version_.clear();
+ }
+ }
+
+ system_state_->payload_state()->UpdateEngineStarted();
+ StartP2PAtStartup();
+}
+
+bool UpdateAttempter::StartP2PAtStartup() {
+ if (system_state_ == nullptr ||
+ !system_state_->p2p_manager()->IsP2PEnabled()) {
+ LOG(INFO) << "Not starting p2p at startup since it's not enabled.";
+ return false;
+ }
+
+ if (system_state_->p2p_manager()->CountSharedFiles() < 1) {
+ LOG(INFO) << "Not starting p2p at startup since our application "
+ << "is not sharing any files.";
+ return false;
+ }
+
+ return StartP2PAndPerformHousekeeping();
+}
+
+bool UpdateAttempter::StartP2PAndPerformHousekeeping() {
+ if (system_state_ == nullptr)
+ return false;
+
+ if (!system_state_->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()) {
+ LOG(ERROR) << "Error starting p2p.";
+ return false;
+ }
+
+ LOG(INFO) << "Performing p2p housekeeping.";
+ if (!system_state_->p2p_manager()->PerformHousekeeping()) {
+ LOG(ERROR) << "Error performing housekeeping for p2p.";
+ return false;
+ }
+
+ LOG(INFO) << "Done performing p2p housekeeping.";
+ return true;
+}
+
+bool UpdateAttempter::GetBootTimeAtUpdate(Time *out_boot_time) {
+ // In case of an update_engine restart without a reboot, we stored the boot_id
+ // when the update was completed by setting a pref, so we can check whether
+ // the last update was on this boot or a previous one.
+ string boot_id;
+ TEST_AND_RETURN_FALSE(utils::GetBootId(&boot_id));
+
+ string update_completed_on_boot_id;
+ if (!prefs_->Exists(kPrefsUpdateCompletedOnBootId) ||
+ !prefs_->GetString(kPrefsUpdateCompletedOnBootId,
+ &update_completed_on_boot_id) ||
+ update_completed_on_boot_id != boot_id)
+ return false;
+
+ // Short-circuit avoiding the read in case out_boot_time is nullptr.
+ if (out_boot_time) {
+ int64_t boot_time = 0;
+ // Since the kPrefsUpdateCompletedOnBootId was correctly set, this pref
+ // should not fail.
+ TEST_AND_RETURN_FALSE(
+ prefs_->GetInt64(kPrefsUpdateCompletedBootTime, &boot_time));
+ *out_boot_time = Time::FromInternalValue(boot_time);
+ }
+ return true;
+}
+
+bool UpdateAttempter::IsUpdateRunningOrScheduled() {
+ return ((status_ != UpdateStatus::IDLE &&
+ status_ != UpdateStatus::UPDATED_NEED_REBOOT) ||
+ waiting_for_scheduled_check_);
+}
+
+bool UpdateAttempter::IsAnyUpdateSourceAllowed() {
+ // We allow updates from any source if either of these are true:
+ // * The device is running an unofficial (dev/test) image.
+ // * 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()) {
+ LOG(INFO) << "Non-official build; allowing any update source.";
+ return true;
+ }
+
+ // Even though the debugd tools are also gated on devmode, checking here can
+ // save us a D-Bus call so it's worth doing explicitly.
+ if (system_state_->hardware()->IsNormalBootMode()) {
+ LOG(INFO) << "Not in devmode; disallowing custom update sources.";
+ return false;
+ }
+
+ // Official images in devmode are allowed a custom update source iff the
+ // debugd dev tools are enabled.
+ if (!debugd_proxy_)
+ return false;
+ int32_t dev_features = debugd::DEV_FEATURES_DISABLED;
+ brillo::ErrorPtr error;
+ bool success = debugd_proxy_->QueryDevFeatures(&dev_features, &error);
+
+ // Some boards may not include debugd so it's expected that this may fail,
+ // in which case we default to disallowing custom update sources.
+ if (success && !(dev_features & debugd::DEV_FEATURES_DISABLED)) {
+ LOG(INFO) << "Debugd dev tools enabled; allowing any update source.";
+ return true;
+ }
+ LOG(INFO) << "Debugd dev tools disabled; disallowing custom update sources.";
+ return false;
+}
+
+} // namespace chromeos_update_engine
diff --git a/update_attempter.h b/update_attempter.h
new file mode 100644
index 0000000..cfe3e32
--- /dev/null
+++ b/update_attempter.h
@@ -0,0 +1,506 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
+#define UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
+
+#include <time.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "debugd/dbus-proxies.h"
+#include "update_engine/chrome_browser_proxy_resolver.h"
+#include "update_engine/client_library/include/update_engine/update_status.h"
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/certificate_checker.h"
+#include "update_engine/libcros_proxy.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/proxy_resolver.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/update_manager.h"
+
+class MetricsLibraryInterface;
+
+namespace policy {
+class PolicyProvider;
+}
+
+namespace chromeos_update_engine {
+
+class UpdateEngineAdaptor;
+
+class UpdateAttempter : public ActionProcessorDelegate,
+ public DownloadActionDelegate,
+ public CertificateChecker::Observer {
+ public:
+ using UpdateStatus = update_engine::UpdateStatus;
+ static const int kMaxDeltaUpdateFailures;
+
+ UpdateAttempter(SystemState* system_state,
+ CertificateChecker* cert_checker,
+ LibCrosProxy* libcros_proxy,
+ org::chromium::debugdProxyInterface* debugd_proxy);
+ ~UpdateAttempter() override;
+
+ // Further initialization to be done post construction.
+ void Init();
+
+ // Initiates scheduling of update checks.
+ virtual void 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 obey_proxies,
+ bool interactive);
+
+ // ActionProcessorDelegate methods:
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) override;
+ void ProcessingStopped(const ActionProcessor* processor) override;
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) override;
+
+ // Resets the current state to UPDATE_STATUS_IDLE.
+ // Used by update_engine_client for restarting a new update without
+ // having to reboot once the previous update has reached
+ // UPDATE_STATUS_UPDATED_NEED_REBOOT state. This is used only
+ // for testing purposes.
+ virtual bool ResetStatus();
+
+ // Returns the current status in the out params. Returns true on success.
+ virtual bool GetStatus(int64_t* last_checked_time,
+ double* progress,
+ std::string* current_operation,
+ std::string* new_version,
+ int64_t* new_size);
+
+ // Runs chromeos-setgoodkernel, whose responsibility it is to mark the
+ // currently booted partition has high priority/permanent/etc. The execution
+ // is asynchronous. On completion, the action processor may be started
+ // depending on the |start_action_processor_| field. Note that every update
+ // attempt goes through this method.
+ void UpdateBootFlags();
+
+ // Called when the boot flags have been updated.
+ void CompleteUpdateBootFlags(bool success);
+
+ UpdateStatus status() const { return status_; }
+
+ int http_response_code() const { return http_response_code_; }
+ void set_http_response_code(int code) { http_response_code_ = code; }
+
+ void set_dbus_adaptor(UpdateEngineAdaptor* dbus_adaptor) {
+ dbus_adaptor_ = dbus_adaptor;
+ }
+
+ // This is the internal entry point for going through an
+ // update. If the current status is idle invokes Update.
+ // This is called by the DBus implementation.
+ virtual void CheckForUpdate(const std::string& app_version,
+ const std::string& omaha_url,
+ bool is_interactive);
+
+ // This is the internal entry point for going through a rollback. This will
+ // attempt to run the postinstall on the non-active partition and set it as
+ // the partition to boot from. If |powerwash| is True, perform a powerwash
+ // as part of rollback. Returns True on success.
+ bool Rollback(bool powerwash);
+
+ // This is the internal entry point for checking if we can rollback.
+ bool CanRollback() const;
+
+ // This is the internal entry point for getting a rollback partition name,
+ // if one exists. It returns the bootable rollback kernel device partition
+ // name or empty string if none is available.
+ BootControlInterface::Slot GetRollbackSlot() const;
+
+ // Initiates a reboot if the current state is
+ // UPDATED_NEED_REBOOT. Returns true on sucess, false otherwise.
+ bool RebootIfNeeded();
+
+ // DownloadActionDelegate methods:
+ void BytesReceived(uint64_t bytes_progressed,
+ uint64_t bytes_received,
+ uint64_t total) override;
+
+ // Returns that the update should be canceled when the download channel was
+ // changed.
+ bool ShouldCancel(ErrorCode* cancel_reason) override;
+
+ void DownloadComplete() override;
+
+ // Broadcasts the current status over D-Bus.
+ void BroadcastStatus();
+
+ // Returns the special flags to be added to ErrorCode values based on the
+ // parameters used in the current update attempt.
+ uint32_t GetErrorCodeFlags();
+
+ // Called at update_engine startup to do various house-keeping.
+ void UpdateEngineStarted();
+
+ // 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
+ // from the server asynchronously at its own frequency.
+ virtual void RefreshDevicePolicy();
+
+ // Stores in |out_boot_time| the boottime (CLOCK_BOOTTIME) recorded at the
+ // time of the last successful update in the current boot. Returns false if
+ // there wasn't a successful update in the current boot.
+ virtual bool GetBootTimeAtUpdate(base::Time *out_boot_time);
+
+ // Returns a version OS version that was being used before the last reboot,
+ // and if that reboot happended to be into an update (current version).
+ // This will return an empty string otherwise.
+ std::string const& GetPrevVersion() const { return prev_version_; }
+
+ // Returns the number of consecutive failed update checks.
+ virtual unsigned int consecutive_failed_update_checks() const {
+ return consecutive_failed_update_checks_;
+ }
+
+ // Returns the poll interval dictated by Omaha, if provided; zero otherwise.
+ virtual unsigned int server_dictated_poll_interval() const {
+ return server_dictated_poll_interval_;
+ }
+
+ // Sets a callback to be used when either a forced update request is received
+ // (first argument set to true) or cleared by an update attempt (first
+ // argument set to false). The callback further encodes whether the forced
+ // check is an interactive one (second argument set to true). Takes ownership
+ // of the callback object. A null value disables callback on these events.
+ // Note that only one callback can be set, so effectively at most one client
+ // can be notified.
+ virtual void set_forced_update_pending_callback(
+ base::Callback<void(bool, bool)>* // NOLINT(readability/function)
+ callback) {
+ forced_update_pending_callback_.reset(callback);
+ }
+
+ // Returns true if we should allow updates from any source. In official builds
+ // we want to restrict updates to known safe sources, but under certain
+ // conditions it's useful to allow updating from anywhere (e.g. to allow
+ // 'cros flash' to function properly).
+ virtual bool IsAnyUpdateSourceAllowed();
+
+ private:
+ // Update server URL for automated lab test.
+ static const char* const kTestUpdateUrl;
+
+ // Friend declarations for testing purposes.
+ friend class UpdateAttempterUnderTest;
+ friend class UpdateAttempterTest;
+ FRIEND_TEST(UpdateAttempterTest, ActionCompletedDownloadTest);
+ FRIEND_TEST(UpdateAttempterTest, ActionCompletedErrorTest);
+ FRIEND_TEST(UpdateAttempterTest, ActionCompletedOmahaRequestTest);
+ FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventTest);
+ FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+ FRIEND_TEST(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest);
+ FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest);
+ FRIEND_TEST(UpdateAttempterTest, PingOmahaTest);
+ FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest);
+ FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
+ FRIEND_TEST(UpdateAttempterTest, UpdateTest);
+ FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
+ FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
+
+ // CertificateChecker::Observer method.
+ // Report metrics about the certificate being checked.
+ void CertificateChecked(ServerToCheck server_to_check,
+ CertificateCheckResult result) override;
+
+ // Checks if it's more than 24 hours since daily metrics were last
+ // reported and, if so, reports daily metrics. Returns |true| if
+ // metrics were reported, |false| otherwise.
+ bool CheckAndReportDailyMetrics();
+
+ // Calculates and reports the age of the currently running OS. This
+ // is defined as the age of the /etc/lsb-release file.
+ void ReportOSAge();
+
+ // Sets the status to the given status and notifies a status update over dbus.
+ void SetStatusAndNotify(UpdateStatus status);
+
+ // Sets up the download parameters after receiving the update check response.
+ void SetupDownload();
+
+ // Creates an error event object in |error_event_| to be included in an
+ // OmahaRequestAction once the current action processor is done.
+ void CreatePendingErrorEvent(AbstractAction* action, ErrorCode code);
+
+ // If there's a pending error event allocated in |error_event_|, schedules an
+ // OmahaRequestAction with that event in the current processor, clears the
+ // pending event, updates the status and returns true. Returns false
+ // otherwise.
+ bool ScheduleErrorEventAction();
+
+ // Sets the cpu shares to |shares| and updates |shares_| if the new
+ // |shares| is different than the current |shares_|, otherwise simply
+ // returns.
+ void SetCpuShares(utils::CpuShares shares);
+
+ // Sets the cpu shares to low and sets up timeout events to increase it.
+ void SetupCpuSharesManagement();
+
+ // Resets the cpu shares to normal and destroys any scheduled timeout
+ // sources.
+ void CleanupCpuSharesManagement();
+
+ // The cpu shares timeout source callback sets the current cpu shares to
+ // normal.
+ void ManageCpuSharesCallback();
+
+ // Schedules an event loop callback to start the action processor. This is
+ // scheduled asynchronously to unblock the event loop.
+ void ScheduleProcessingStart();
+
+ // Checks if a full update is needed and forces it by updating the Omaha
+ // request params.
+ void DisableDeltaUpdateIfNeeded();
+
+ // If this was a delta update attempt that failed, count it so that a full
+ // update can be tried when needed.
+ void MarkDeltaUpdateFailure();
+
+ ProxyResolver* GetProxyResolver() {
+ return obeying_proxies_ ?
+ reinterpret_cast<ProxyResolver*>(&chrome_proxy_resolver_) :
+ reinterpret_cast<ProxyResolver*>(&direct_proxy_resolver_);
+ }
+
+ // Sends a ping to Omaha.
+ // This is used after an update has been applied and we're waiting for the
+ // user to reboot. This ping helps keep the number of actives count
+ // accurate in case a user takes a long time to reboot the device after an
+ // update has been applied.
+ void PingOmaha();
+
+ // 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 parametes.
+ bool CalculateUpdateParams(const std::string& app_version,
+ const std::string& omaha_url,
+ const std::string& target_channel,
+ const std::string& target_version_prefix,
+ bool obey_proxies,
+ bool interactive);
+
+ // 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_.
+ void CalculateScatteringParams(bool is_interactive);
+
+ // Sets a random value for the waiting period to wait for before downloading
+ // an update, if one available. This value will be upperbounded by the
+ // scatter factor value specified from policy.
+ void GenerateNewWaitingPeriod();
+
+ // Helper method of Update() and Rollback() to construct the sequence of
+ // actions to be performed for the postinstall.
+ // |previous_action| is the previous action to get
+ // bonded with the install_plan that gets passed to postinstall.
+ void BuildPostInstallActions(InstallPlanAction* previous_action);
+
+ // Helper method of Update() to construct the sequence of actions to
+ // be performed for an update check. Please refer to
+ // Update() method for the meaning of the parameters.
+ void BuildUpdateActions(bool interactive);
+
+ // Decrements the count in the kUpdateCheckCountFilePath.
+ // Returns True if successfully decremented, false otherwise.
+ bool DecrementUpdateCheckCount();
+
+ // Starts p2p and performs housekeeping. Returns true only if p2p is
+ // running and housekeeping was done.
+ bool StartP2PAndPerformHousekeeping();
+
+ // Calculates whether peer-to-peer should be used. Sets the
+ // |use_p2p_to_download_| and |use_p2p_to_share_| parameters
+ // on the |omaha_request_params_| object.
+ void CalculateP2PParams(bool interactive);
+
+ // 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.
+ bool StartP2PAtStartup();
+
+ // Writes to the processing completed marker. Does nothing if
+ // |update_completed_marker_| is empty.
+ void WriteUpdateCompletedMarker();
+
+ // Sends a D-Bus message to the Chrome OS power manager asking it to reboot
+ // the system. Returns true on success.
+ bool RequestPowerManagerReboot();
+
+ // Reboots the system directly by calling /sbin/shutdown. Returns true on
+ // success.
+ bool RebootDirectly();
+
+ // Callback for the async UpdateCheckAllowed policy request. If |status| is
+ // |EvalStatus::kSucceeded|, either runs or suppresses periodic update checks,
+ // based on the content of |params|. Otherwise, retries the policy request.
+ void OnUpdateScheduled(
+ chromeos_update_manager::EvalStatus status,
+ const chromeos_update_manager::UpdateCheckParams& params);
+
+ // Updates the time an update was last attempted to the current time.
+ void UpdateLastCheckedTime();
+
+ // Returns whether an update is currently running or scheduled.
+ bool IsUpdateRunningOrScheduled();
+
+ // 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.
+ base::TimeTicks last_notify_time_;
+
+ std::vector<std::shared_ptr<AbstractAction>> actions_;
+ 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_;
+
+ // Pointer to the certificate checker instance to use.
+ CertificateChecker* cert_checker_;
+
+ // If non-null, this UpdateAttempter will send status updates over this
+ // dbus service.
+ UpdateEngineAdaptor* dbus_adaptor_ = nullptr;
+
+ // Pointer to the OmahaResponseHandlerAction in the actions_ vector.
+ std::shared_ptr<OmahaResponseHandlerAction> response_handler_action_;
+
+ // Pointer to the DownloadAction in the actions_ vector.
+ std::shared_ptr<DownloadAction> download_action_;
+
+ // Pointer to the preferences store interface. This is just a cached
+ // copy of system_state->prefs() because it's used in many methods and
+ // is convenient this way.
+ PrefsInterface* prefs_ = nullptr;
+
+ // Pending error event, if any.
+ std::unique_ptr<OmahaEvent> error_event_;
+
+ // If we should request a reboot even tho we failed the update
+ bool fake_update_success_ = false;
+
+ // HTTP server response code from the last HTTP request action.
+ int http_response_code_ = 0;
+
+ // Current cpu shares.
+ utils::CpuShares shares_ = utils::kCpuSharesNormal;
+
+ // The cpu shares management timeout task id.
+ brillo::MessageLoop::TaskId manage_shares_id_{
+ brillo::MessageLoop::kTaskIdNull};
+
+ // For status:
+ UpdateStatus status_{UpdateStatus::IDLE};
+ double download_progress_ = 0.0;
+ int64_t last_checked_time_ = 0;
+ std::string prev_version_;
+ std::string new_version_ = "0.0.0.0";
+ int64_t new_payload_size_ = 0;
+
+ // Common parameters for all Omaha requests.
+ OmahaRequestParams* omaha_request_params_ = nullptr;
+
+ // Number of consecutive manual update checks we've had where we obeyed
+ // Chrome's proxy settings.
+ int proxy_manual_checks_ = 0;
+
+ // If true, this update cycle we are obeying proxies
+ bool obeying_proxies_ = true;
+
+ // Our two proxy resolvers
+ DirectProxyResolver direct_proxy_resolver_;
+ ChromeBrowserProxyResolver chrome_proxy_resolver_;
+
+ // Originally, both of these flags are false. Once UpdateBootFlags is called,
+ // |update_boot_flags_running_| is set to true. As soon as UpdateBootFlags
+ // completes its asynchronous run, |update_boot_flags_running_| is reset to
+ // false and |updated_boot_flags_| is set to true. From that point on there
+ // will be no more changes to these flags.
+ //
+ // True if UpdateBootFlags has completed.
+ bool updated_boot_flags_ = false;
+ // True if UpdateBootFlags is running.
+ bool update_boot_flags_running_ = false;
+
+ // True if the action processor needs to be started by the boot flag updater.
+ bool start_action_processor_ = false;
+
+ // Used for fetching information about the device policy.
+ std::unique_ptr<policy::PolicyProvider> policy_provider_;
+
+ // The current scatter factor as found in the policy setting.
+ base::TimeDelta scatter_factor_;
+
+ // The number of consecutive failed update checks. Needed for calculating the
+ // next update check interval.
+ unsigned int consecutive_failed_update_checks_ = 0;
+
+ // The poll interval (in seconds) that was dictated by Omaha, if any; zero
+ // otherwise. This is needed for calculating the update check interval.
+ unsigned int server_dictated_poll_interval_ = 0;
+
+ // Tracks whether we have scheduled update checks.
+ bool waiting_for_scheduled_check_ = false;
+
+ // A callback to use when a forced update request is either received (true) or
+ // cleared by an update attempt (false). The second argument indicates whether
+ // this is an interactive update, and its value is significant iff the first
+ // argument is true.
+ std::unique_ptr<base::Callback<void(bool, bool)>>
+ forced_update_pending_callback_;
+
+ // The |app_version| and |omaha_url| parameters received during the latest
+ // forced update request. They are retrieved for use once the update is
+ // actually scheduled.
+ std::string forced_app_version_;
+ std::string forced_omaha_url_;
+
+ org::chromium::debugdProxyInterface* debugd_proxy_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateAttempter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
new file mode 100644
index 0000000..747a974
--- /dev/null
+++ b/update_attempter_unittest.cc
@@ -0,0 +1,1010 @@
+//
+// 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/bind_lambda.h>
+#include <brillo/make_unique_ptr.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 <debugd/dbus-constants.h>
+#include <debugd/dbus-proxies.h>
+#include <debugd/dbus-proxy-mocks.h>
+#include <gtest/gtest.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_device_policy.h>
+
+#include "libcros/dbus-proxies.h"
+#include "libcros/dbus-proxy-mocks.h"
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/common/fake_prefs.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_action.h"
+#include "update_engine/mock_action_processor.h"
+#include "update_engine/mock_p2p_manager.h"
+#include "update_engine/mock_payload_state.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"
+
+using base::Time;
+using base::TimeDelta;
+using brillo::MessageLoop;
+using org::chromium::LibCrosServiceInterfaceProxyMock;
+using org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyMock;
+using std::string;
+using std::unique_ptr;
+using testing::DoAll;
+using testing::InSequence;
+using testing::Ne;
+using testing::NiceMock;
+using testing::Property;
+using testing::Return;
+using testing::ReturnPointee;
+using testing::SaveArg;
+using testing::SetArgumentPointee;
+using testing::_;
+using update_engine::UpdateStatus;
+
+namespace chromeos_update_engine {
+
+// 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(SystemState* system_state,
+ LibCrosProxy* libcros_proxy,
+ org::chromium::debugdProxyInterface* debugd_proxy)
+ : UpdateAttempter(system_state, nullptr, libcros_proxy, debugd_proxy) {}
+
+ // Wrap the update scheduling method, allowing us to opt out of scheduled
+ // updates for testing purposes.
+ void ScheduleUpdates() override {
+ schedule_updates_called_ = true;
+ if (do_schedule_updates_) {
+ UpdateAttempter::ScheduleUpdates();
+ } else {
+ LOG(INFO) << "[TEST] Update scheduling disabled.";
+ }
+ }
+ 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()
+ : service_interface_mock_(new LibCrosServiceInterfaceProxyMock()),
+ ue_proxy_resolved_interface_mock_(
+ new NiceMock<UpdateEngineLibcrosProxyResolvedInterfaceProxyMock>()),
+ libcros_proxy_(
+ brillo::make_unique_ptr(service_interface_mock_),
+ brillo::make_unique_ptr(ue_proxy_resolved_interface_mock_)),
+ 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_);
+ loop_.SetAsCurrent();
+
+ certificate_checker_.Init();
+
+ // Finish initializing the attempter.
+ attempter_.Init();
+ }
+
+ void SetUp() override {
+ CHECK(utils::MakeTempDirectory("UpdateAttempterTest-XXXXXX", &test_dir_));
+
+ EXPECT_EQ(nullptr, attempter_.dbus_adaptor_);
+ EXPECT_NE(nullptr, attempter_.system_state_);
+ EXPECT_EQ(0, attempter_.http_response_code_);
+ EXPECT_EQ(utils::kCpuSharesNormal, attempter_.shares_);
+ EXPECT_EQ(MessageLoop::kTaskIdNull, attempter_.manage_shares_id_);
+ 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(0, 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_));
+ }
+
+ void TearDown() override {
+ base::DeleteFile(base::FilePath(test_dir_), true);
+ }
+
+ 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();
+
+ 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_;
+ org::chromium::debugdProxyMock debugd_proxy_mock_;
+ LibCrosServiceInterfaceProxyMock* service_interface_mock_;
+ UpdateEngineLibcrosProxyResolvedInterfaceProxyMock*
+ ue_proxy_resolved_interface_mock_;
+ LibCrosProxy libcros_proxy_;
+ OpenSSLWrapper openssl_wrapper_;
+ CertificateChecker certificate_checker_;
+ UpdateAttempterUnderTest attempter_{&fake_system_state_,
+ &libcros_proxy_,
+ &debugd_proxy_mock_};
+
+ NiceMock<MockActionProcessor>* processor_;
+ NiceMock<MockPrefs>* prefs_; // Shortcut to fake_system_state_->mock_prefs().
+ NiceMock<MockConnectionManager> mock_connection_manager;
+
+ string test_dir_;
+
+ bool actual_using_p2p_for_downloading_;
+ bool actual_using_p2p_for_sharing_;
+};
+
+void UpdateAttempterTest::ScheduleQuitMainLoop() {
+ loop_.PostTask(FROM_HERE, base::Bind([this] { this->loop_.BreakLoop(); }));
+}
+
+TEST_F(UpdateAttempterTest, ActionCompletedDownloadTest) {
+ unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr));
+ fetcher->FailTransfer(503); // Sets the HTTP response code.
+ DownloadAction action(prefs_, nullptr, fetcher.release());
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)).Times(0);
+ attempter_.ActionCompleted(nullptr, &action, ErrorCode::kSuccess);
+ EXPECT_EQ(503, attempter_.http_response_code());
+ EXPECT_EQ(UpdateStatus::FINALIZING, attempter_.status());
+ 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, 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(234, 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);
+ UpdateAttempterUnderTest attempter(&fake_system_state_, nullptr,
+ &debugd_proxy_mock_);
+ attempter.Init();
+ EXPECT_EQ(UpdateStatus::UPDATED_NEED_REBOOT, attempter.status());
+}
+
+TEST_F(UpdateAttempterTest, GetErrorCodeForActionTest) {
+ extern ErrorCode GetErrorCodeForAction(AbstractAction* action,
+ ErrorCode code);
+ 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(
+ fake_system_state_.boot_control(), VerifierMode::kVerifyTargetHash);
+ EXPECT_EQ(ErrorCode::kFilesystemVerifierError,
+ GetErrorCodeForAction(&filesystem_verifier_action,
+ ErrorCode::kError));
+ PostinstallRunnerAction postinstall_runner_action(
+ fake_system_state.fake_boot_control());
+ 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(
+ SetArgumentPointee<1>(UpdateAttempter::kMaxDeltaUpdateFailures - 1),
+ Return(true)));
+ attempter_.DisableDeltaUpdateIfNeeded();
+ EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay());
+ EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _))
+ .WillOnce(DoAll(
+ SetArgumentPointee<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(SetArgumentPointee<1>(-1), Return(true)))
+ .WillOnce(DoAll(SetArgumentPointee<1>(1), Return(true)))
+ .WillOnce(DoAll(
+ SetArgumentPointee<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.payload_urls.push_back(url1);
+ response.payload_urls.push_back("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(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(),
+ FilesystemVerifierAction::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(),
+};
+
+} // 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.
+ policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+ attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+ EXPECT_CALL(*device_policy, LoadPolicy())
+ .Times(testing::AtLeast(1)).WillRepeatedly(Return(true));
+
+ {
+ InSequence s;
+ for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) {
+ EXPECT_CALL(*processor_,
+ EnqueueAction(Property(&AbstractAction::Type,
+ kUpdateActionTypes[i])));
+ }
+ EXPECT_CALL(*processor_, StartProcessing());
+ }
+
+ attempter_.Update("", "", "", "", 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(arraysize(kUpdateActionTypes), attempter_.actions_.size());
+ for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) {
+ EXPECT_EQ(kUpdateActionTypes[i], attempter_.actions_[i]->Type());
+ }
+ EXPECT_EQ(attempter_.response_handler_action_.get(),
+ attempter_.actions_[1].get());
+ DownloadAction* download_action =
+ dynamic_cast<DownloadAction*>(attempter_.actions_[4].get());
+ ASSERT_NE(nullptr, download_action);
+ EXPECT_EQ(&attempter_, download_action->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.
+ policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+ attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(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 (enterprise_rollback) {
+ // We return an empty owner as this is an enterprise.
+ EXPECT_CALL(*device_policy, GetOwner(_)).WillRepeatedly(
+ DoAll(SetArgumentPointee<0>(string("")),
+ Return(true)));
+ } else {
+ // We return a fake owner as this is an owned consumer device.
+ EXPECT_CALL(*device_policy, GetOwner(_)).WillRepeatedly(
+ DoAll(SetArgumentPointee<0>(string("fake.mail@fake.com")),
+ Return(true)));
+ }
+
+ if (is_rollback_allowed) {
+ InSequence s;
+ for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) {
+ EXPECT_CALL(*processor_,
+ EnqueueAction(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(arraysize(kRollbackActionTypes), attempter_.actions_.size());
+ for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) {
+ EXPECT_EQ(kRollbackActionTypes[i], attempter_.actions_[i]->Type());
+ }
+ EXPECT_EQ(UpdateStatus::ATTEMPTING_ROLLBACK, attempter_.status());
+ InstallPlanAction* install_plan_action =
+ dynamic_cast<InstallPlanAction*>(attempter_.actions_[0].get());
+ InstallPlan* install_plan = install_plan_action->install_plan();
+ EXPECT_EQ(0, install_plan->partitions.size());
+ EXPECT_EQ(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(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) {
+ OmahaResponseHandlerAction *response_action =
+ new OmahaResponseHandlerAction(&fake_system_state_);
+ response_action->install_plan_.is_resume = true;
+ attempter_.response_handler_action_.reset(response_action);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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, true /* interactive */);
+ 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;
+
+ policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+ attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy);
+
+ EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+ .WillRepeatedly(DoAll(
+ SetArgumentPointee<0>(scatter_factor_in_seconds),
+ Return(true)));
+
+ attempter_.Update("", "", "", "", 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;
+
+ policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+ attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy);
+
+ EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+ .WillRepeatedly(DoAll(
+ SetArgumentPointee<0>(scatter_factor_in_seconds),
+ Return(true)));
+
+ attempter_.Update("", "", "", "", 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);
+ 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(kPrefsWallClockWaitPeriod, 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;
+
+ policy::MockDevicePolicy* device_policy = new policy::MockDevicePolicy();
+ attempter_.policy_provider_.reset(new policy::PolicyProvider(device_policy));
+
+ EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+ fake_system_state_.set_device_policy(device_policy);
+
+ EXPECT_CALL(*device_policy, GetScatterFactorInSeconds(_))
+ .WillRepeatedly(DoAll(
+ SetArgumentPointee<0>(scatter_factor_in_seconds),
+ Return(true)));
+
+ // Trigger an interactive check so we can test that scattering is disabled.
+ attempter_.Update("", "", "", "", false, 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(kPrefsWallClockWaitPeriod));
+ 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();
+}
+
+// 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) {
+ UpdateAttempterUnderTest attempter{&fake_system_state_,
+ nullptr, // libcros_proxy
+ &debugd_proxy_mock_};
+ 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()->SetIsNormalBootMode(false);
+ EXPECT_CALL(debugd_proxy_mock_, QueryDevFeatures(_, _, _))
+ .WillRepeatedly(DoAll(SetArgumentPointee<0>(0), Return(true)));
+ EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedOfficialNormal) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(true);
+ // debugd should not be queried in this case.
+ EXPECT_CALL(debugd_proxy_mock_, QueryDevFeatures(_, _, _)).Times(0);
+ EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedDebugdDisabled) {
+ using debugd::DEV_FEATURES_DISABLED;
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(false);
+ EXPECT_CALL(debugd_proxy_mock_, QueryDevFeatures(_, _, _))
+ .WillRepeatedly(
+ DoAll(SetArgumentPointee<0>(DEV_FEATURES_DISABLED), Return(true)));
+ EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedDebugdFailure) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(false);
+ EXPECT_CALL(debugd_proxy_mock_, QueryDevFeatures(_, _, _))
+ .WillRepeatedly(Return(false));
+ EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateAUTest) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(true);
+ attempter_.CheckForUpdate("", "autest", true);
+ EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
+}
+
+TEST_F(UpdateAttempterTest, CheckForUpdateScheduledAUTest) {
+ fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+ fake_system_state_.fake_hardware()->SetIsNormalBootMode(true);
+ attempter_.CheckForUpdate("", "autest-scheduled", true);
+ EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
+}
+
+} // namespace chromeos_update_engine
diff --git a/update_engine-client.gyp b/update_engine-client.gyp
new file mode 100644
index 0000000..588fc63
--- /dev/null
+++ b/update_engine-client.gyp
@@ -0,0 +1,41 @@
+#
+# 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
new file mode 100644
index 0000000..449e669
--- /dev/null
+++ b/update_engine.conf
@@ -0,0 +1,2 @@
+PAYLOAD_MAJOR_VERSION=2
+PAYLOAD_MINOR_VERSION=3
diff --git a/update_engine.gyp b/update_engine.gyp
new file mode 100644
index 0000000..2fa6ae8
--- /dev/null
+++ b/update_engine.gyp
@@ -0,0 +1,508 @@
+#
+# 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.
+#
+{
+ 'target_defaults': {
+ 'variables': {
+ 'deps': [
+ 'libbrillo-<(libbase_ver)',
+ 'libchrome-<(libbase_ver)',
+ ],
+ # 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_dbus%': '1',
+ 'USE_hwid_override%': '0',
+ 'USE_mtd%': '0',
+ 'USE_power_management%': '0',
+ },
+ 'cflags': [
+ '-g',
+ '-ffunction-sections',
+ '-Wall',
+ '-Wextra',
+ '-Werror',
+ '-Wno-unused-parameter',
+ ],
+ 'cflags_cc': [
+ '-fno-strict-aliasing',
+ '-Wnon-virtual-dtor',
+ ],
+ 'ldflags': [
+ '-Wl,--gc-sections',
+ ],
+ 'defines': [
+ '_FILE_OFFSET_BITS=64',
+ '_POSIX_C_SOURCE=199309L',
+ 'USE_DBUS=<(USE_dbus)',
+ 'USE_HWID_OVERRIDE=<(USE_hwid_override)',
+ 'USE_MTD=<(USE_mtd)',
+ 'USE_POWER_MANAGEMENT=<(USE_power_management)',
+ ],
+ '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',
+ 'exported_deps': [
+ 'protobuf-lite',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ '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-other-dbus-proxies',
+ 'type': 'none',
+ 'actions': [
+ {
+ 'action_name': 'update_engine-dbus-libcros-client',
+ 'variables': {
+ 'mock_output_file': 'include/libcros/dbus-proxy-mocks.h',
+ 'proxy_output_file': 'include/libcros/dbus-proxies.h'
+ },
+ 'sources': [
+ 'dbus_bindings/org.chromium.LibCrosService.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',
+ 'libcurl',
+ 'libssl',
+ 'xz-embedded',
+ ],
+ '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/certificate_checker.cc',
+ 'common/clock.cc',
+ 'common/constants.cc',
+ 'common/hash_calculator.cc',
+ 'common/http_common.cc',
+ 'common/http_fetcher.cc',
+ 'common/hwid_override.cc',
+ 'common/libcurl_http_fetcher.cc',
+ 'common/multi_range_http_fetcher.cc',
+ 'common/platform_constants_chromeos.cc',
+ 'common/prefs.cc',
+ 'common/subprocess.cc',
+ 'common/terminator.cc',
+ 'common/utils.cc',
+ 'payload_consumer/bzip_extent_writer.cc',
+ 'payload_consumer/delta_performer.cc',
+ 'payload_consumer/download_action.cc',
+ 'payload_consumer/extent_writer.cc',
+ 'payload_consumer/file_descriptor.cc',
+ 'payload_consumer/file_writer.cc',
+ 'payload_consumer/filesystem_verifier_action.cc',
+ 'payload_consumer/install_plan.cc',
+ 'payload_consumer/payload_constants.cc',
+ 'payload_consumer/payload_verifier.cc',
+ 'payload_consumer/postinstall_runner_action.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',
+ 'update_engine-other-dbus-proxies',
+ ],
+ 'variables': {
+ 'exported_deps': [
+ 'dbus-1',
+ 'libdebugd-client',
+ 'libsession_manager-client',
+ 'libmetrics-<(libbase_ver)',
+ 'libpower_manager-client',
+ 'libupdate_engine-client',
+ 'libshill-client',
+ 'expat',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'link_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ 'libraries': [
+ '-lbz2',
+ '-lpolicy-<(libbase_ver)',
+ '-lrootdev',
+ '-lrt',
+ '-lvboot_host',
+ ],
+ },
+ 'sources': [
+ 'boot_control_chromeos.cc',
+ 'chrome_browser_proxy_resolver.cc',
+ 'connection_manager.cc',
+ 'daemon.cc',
+ 'dbus_service.cc',
+ 'hardware_chromeos.cc',
+ 'image_properties_chromeos.cc',
+ 'libcros_proxy.cc',
+ 'metrics.cc',
+ 'metrics_utils.cc',
+ 'omaha_request_action.cc',
+ 'omaha_request_params.cc',
+ 'omaha_response_handler_action.cc',
+ 'p2p_manager.cc',
+ 'payload_state.cc',
+ 'proxy_resolver.cc',
+ 'real_system_state.cc',
+ 'shill_proxy.cc',
+ 'update_attempter.cc',
+ 'update_manager/boxed_value.cc',
+ 'update_manager/chromeos_policy.cc',
+ 'update_manager/default_policy.cc',
+ 'update_manager/evaluation_context.cc',
+ 'update_manager/policy.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/state_factory.cc',
+ 'update_manager/update_manager.cc',
+ 'update_status_utils.cc',
+ ],
+ },
+ # update_engine daemon.
+ {
+ 'target_name': 'update_engine',
+ 'type': 'executable',
+ 'dependencies': [
+ 'libupdate_engine',
+ ],
+ 'sources': [
+ 'main.cc',
+ ],
+ },
+ # update_engine console client.
+ {
+ 'target_name': 'update_engine_client',
+ 'type': 'executable',
+ 'variables': {
+ 'exported_deps': [
+ 'libupdate_engine-client',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'sources': [
+ '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',
+ ],
+ 'deps': ['<@(exported_deps)'],
+ },
+ 'all_dependent_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'link_settings': {
+ 'variables': {
+ 'deps': [
+ '<@(exported_deps)',
+ ],
+ },
+ },
+ 'sources': [
+ 'payload_generator/ab_generator.cc',
+ 'payload_generator/annotated_operation.cc',
+ 'payload_generator/blob_file_writer.cc',
+ 'payload_generator/block_mapping.cc',
+ 'payload_generator/bzip.cc',
+ 'payload_generator/cycle_breaker.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/payload_file.cc',
+ 'payload_generator/payload_generation_config.cc',
+ 'payload_generator/payload_signer.cc',
+ 'payload_generator/raw_filesystem.cc',
+ 'payload_generator/tarjan.cc',
+ 'payload_generator/topological_sort.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',
+ ],
+ },
+ ],
+ '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',
+ ],
+ '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',
+ 'dependencies': ['libupdate_engine'],
+ 'sources': [
+ 'test_http_server.cc',
+ ],
+ },
+ # Main unittest file.
+ {
+ 'target_name': 'update_engine_unittests',
+ 'type': 'executable',
+ 'includes': ['../../../platform2/common-mk/common_test.gypi'],
+ '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',
+ ],
+ 'includes': ['../../../platform2/common-mk/common_test.gypi'],
+ 'sources': [
+ 'chrome_browser_proxy_resolver_unittest.cc',
+ 'common/action_pipe_unittest.cc',
+ 'common/action_processor_unittest.cc',
+ 'common/action_unittest.cc',
+ 'common/boot_control_chromeos_unittest.cc',
+ 'common/certificate_checker_unittest.cc',
+ 'common/fake_prefs.cc',
+ 'common/hash_calculator_unittest.cc',
+ 'common/http_fetcher_unittest.cc',
+ 'common/hwid_override_unittest.cc',
+ 'common/mock_http_fetcher.cc',
+ 'common/prefs_unittest.cc',
+ 'common/subprocess_unittest.cc',
+ 'common/terminator_unittest.cc',
+ 'common/test_utils.cc',
+ 'common/utils_unittest.cc',
+ 'connection_manager_unittest.cc',
+ 'dbus_service_unittest.cc',
+ 'fake_shill_proxy.cc',
+ 'fake_system_state.cc',
+ 'metrics_utils_unittest.cc',
+ 'omaha_request_action_unittest.cc',
+ 'omaha_request_params_unittest.cc',
+ 'omaha_response_handler_action_unittest.cc',
+ 'p2p_manager_unittest.cc',
+ 'payload_consumer/bzip_extent_writer_unittest.cc',
+ 'payload_consumer/delta_performer_integration_test.cc',
+ 'payload_consumer/delta_performer_unittest.cc',
+ 'payload_consumer/download_action_unittest.cc',
+ 'payload_consumer/extent_writer_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/cycle_breaker_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/fake_filesystem.cc',
+ 'payload_generator/full_update_generator_unittest.cc',
+ 'payload_generator/graph_utils_unittest.cc',
+ 'payload_generator/inplace_generator_unittest.cc',
+ 'payload_generator/payload_file_unittest.cc',
+ 'payload_generator/payload_generation_config_unittest.cc',
+ 'payload_generator/payload_signer_unittest.cc',
+ 'payload_generator/tarjan_unittest.cc',
+ 'payload_generator/topological_sort_unittest.cc',
+ 'payload_generator/zip_unittest.cc',
+ 'payload_state_unittest.cc',
+ 'update_attempter_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_config_provider_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/umtest_utils.cc',
+ 'update_manager/update_manager_unittest.cc',
+ 'update_manager/variable_unittest.cc',
+ # Main entry point for runnning tests.
+ 'testrunner.cc',
+ ],
+ },
+ ],
+ }],
+ ],
+}
diff --git a/update_engine.rc b/update_engine.rc
new file mode 100644
index 0000000..69fc7af
--- /dev/null
+++ b/update_engine.rc
@@ -0,0 +1,4 @@
+service update_engine /system/bin/update_engine --logtostderr --foreground
+ class late_start
+ user root
+ group root system dbus inet
diff --git a/update_engine_client.cc b/update_engine_client.cc
new file mode 100644
index 0000000..b59f22f
--- /dev/null
+++ b/update_engine_client.cc
@@ -0,0 +1,653 @@
+//
+// 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 <inttypes.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/command_line.h>
+#include <base/logging.h>
+#include <base/macros.h>
+#include <brillo/daemons/dbus_daemon.h>
+#include <brillo/flag_helper.h>
+#include <dbus/bus.h>
+#include <update_engine/dbus-constants.h>
+#include <update_engine/dbus-proxies.h>
+
+using std::string;
+using update_engine::kAttemptUpdateFlagNonInteractive;
+using update_engine::kUpdateEngineServiceName;
+
+namespace {
+
+// Constant to signal that we need to continue running the daemon after
+// initialization.
+const int kContinueRunning = -1;
+
+class UpdateEngineClient : public brillo::DBusDaemon {
+ public:
+ UpdateEngineClient(int argc, char** argv) : argc_(argc), argv_(argv) {}
+ ~UpdateEngineClient() override = default;
+
+ protected:
+ int OnInit() override {
+ int ret = DBusDaemon::OnInit();
+ if (ret != EX_OK)
+ return ret;
+ if (!InitProxy())
+ return 1;
+ // Wait for the UpdateEngine to be available or timeout.
+ proxy_->GetObjectProxy()->WaitForServiceToBeAvailable(
+ base::Bind(&UpdateEngineClient::OnServiceAvailable,
+ base::Unretained(this)));
+ base::MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&UpdateEngineClient::OnServiceAvailableTimeout,
+ base::Unretained(this)),
+ base::TimeDelta::FromSeconds(10));
+ return EX_OK;
+ }
+
+ private:
+ bool InitProxy();
+
+ // Callback called when the UpdateEngine service becomes available.
+ void OnServiceAvailable(bool service_is_available);
+
+ // Callback called when the UpdateEngine service doesn't become available
+ // after a timeout.
+ void OnServiceAvailableTimeout();
+
+
+ // Callback called when a StatusUpdate signal is received.
+ void OnStatusUpdateSignal(int64_t last_checked_time,
+ double progress,
+ const string& current_operation,
+ const string& new_version,
+ int64_t new_size);
+ // Callback called when the OnStatusUpdateSignal() handler is registered.
+ void OnStatusUpdateSignalRegistration(const string& interface,
+ const string& signal_name,
+ bool success);
+
+ // Registers a callback that prints on stderr the received StatusUpdate
+ // signals.
+ // The daemon should continue running for this to work.
+ void WatchForUpdates();
+
+ void ResetStatus();
+
+ // Show the status of the update engine in stdout.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ void ShowStatus();
+
+ // Return the current operation status, such as UPDATE_STATUS_IDLE.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ string GetCurrentOperation();
+
+ void Rollback(bool rollback);
+ string GetRollbackPartition();
+ void CheckForUpdates(const string& app_version,
+ const string& omaha_url,
+ bool interactive);
+
+ // Reboot the device if a reboot is needed.
+ // Blocking call. Ignores failures.
+ void RebootIfNeeded();
+
+ // Getter and setter for the target channel. If |get_current_channel| is true,
+ // the current channel instead of the target channel will be returned.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ void SetTargetChannel(const string& target_channel, bool allow_powerwash);
+ string GetChannel(bool get_current_channel);
+
+ // Getter and setter for the updates over cellular connections.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ void SetUpdateOverCellularPermission(bool allowed);
+ bool GetUpdateOverCellularPermission();
+
+ // Getter and setter for the updates from P2P permission.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ void SetP2PUpdatePermission(bool enabled);
+ bool GetP2PUpdatePermission();
+
+ // This is similar to watching for updates but rather than registering
+ // a signal watch, actively poll the daemon just in case it stops
+ // sending notifications.
+ void WaitForUpdateComplete();
+ void OnUpdateCompleteCheck(int64_t last_checked_time,
+ double progress,
+ const string& current_operation,
+ const string& new_version,
+ int64_t new_size);
+
+ // Blocking call. Exits the program with error 1 in case of an error.
+ void ShowPrevVersion();
+
+ // Returns whether the current status is such that a reboot is needed.
+ // Blocking call. Exits the program with error 1 in case of an error.
+ bool GetIsRebootNeeded();
+
+ // Blocks until a reboot is needed. If the reboot is needed, exits the program
+ // with 0. Otherwise it exits the program with 1 if an error occurs before
+ // the reboot is needed.
+ void WaitForRebootNeeded();
+ void OnRebootNeededCheck(int64_t last_checked_time,
+ double progress,
+ const string& current_operation,
+ const string& new_version,
+ int64_t new_size);
+ // Callback called when the OnRebootNeededCheck() handler is registered. This
+ // is useful to check at this point if the reboot is needed, without loosing
+ // any StatusUpdate signals and avoiding the race condition.
+ void OnRebootNeededCheckRegistration(const string& interface,
+ const string& signal_name,
+ bool success);
+
+ // Main method that parses and triggers all the actions based on the passed
+ // flags.
+ int ProcessFlags();
+
+ // DBus Proxy to the update_engine daemon object used for all the calls.
+ std::unique_ptr<org::chromium::UpdateEngineInterfaceProxy> proxy_;
+
+ // Copy of argc and argv passed to main().
+ int argc_;
+ char** argv_;
+
+ // Tell whether the UpdateEngine service is available after startup.
+ bool service_is_available_{false};
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateEngineClient);
+};
+
+bool UpdateEngineClient::InitProxy() {
+ proxy_.reset(new org::chromium::UpdateEngineInterfaceProxy(bus_));
+
+ if (!proxy_->GetObjectProxy()) {
+ LOG(ERROR) << "Error getting dbus proxy for " << kUpdateEngineServiceName;
+ return false;
+ }
+ return true;
+}
+
+void UpdateEngineClient::OnServiceAvailable(bool service_is_available) {
+ service_is_available_ = service_is_available;
+ if (!service_is_available) {
+ LOG(ERROR) << "UpdateEngineService not available.";
+ QuitWithExitCode(-1);
+ }
+ int ret = ProcessFlags();
+ if (ret != kContinueRunning)
+ QuitWithExitCode(ret);
+}
+
+void UpdateEngineClient::OnServiceAvailableTimeout() {
+ if (!service_is_available_) {
+ LOG(ERROR) << "Waiting for UpdateEngineService timeout. Is update_engine "
+ "daemon running?";
+ QuitWithExitCode(-1);
+ }
+}
+
+void UpdateEngineClient::OnStatusUpdateSignal(
+ int64_t last_checked_time,
+ double progress,
+ const string& 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: " << current_operation;
+ LOG(INFO) << " new_version: " << new_version;
+ LOG(INFO) << " new_size: " << new_size;
+}
+
+void UpdateEngineClient::OnStatusUpdateSignalRegistration(
+ const string& interface,
+ const string& signal_name,
+ bool success) {
+ VLOG(1) << "OnStatusUpdateSignalRegistration(" << interface << ", "
+ << signal_name << ", " << success << ");";
+ if (!success) {
+ LOG(ERROR) << "Couldn't connect to the " << signal_name << " signal.";
+ exit(1);
+ }
+}
+
+void UpdateEngineClient::WatchForUpdates() {
+ proxy_->RegisterStatusUpdateSignalHandler(
+ base::Bind(&UpdateEngineClient::OnStatusUpdateSignal,
+ base::Unretained(this)),
+ base::Bind(&UpdateEngineClient::OnStatusUpdateSignalRegistration,
+ base::Unretained(this)));
+}
+
+void UpdateEngineClient::ResetStatus() {
+ bool ret = proxy_->ResetStatus(nullptr);
+ CHECK(ret) << "ResetStatus() failed.";
+}
+
+void UpdateEngineClient::ShowStatus() {
+ int64_t last_checked_time = 0;
+ double progress = 0.0;
+ string current_op;
+ string new_version;
+ int64_t new_size = 0;
+
+ bool ret = proxy_->GetStatus(
+ &last_checked_time, &progress, ¤t_op, &new_version, &new_size,
+ nullptr);
+ CHECK(ret) << "GetStatus() failed";
+ printf("LAST_CHECKED_TIME=%" PRIi64 "\nPROGRESS=%f\nCURRENT_OP=%s\n"
+ "NEW_VERSION=%s\nNEW_SIZE=%" PRIi64 "\n",
+ last_checked_time,
+ progress,
+ current_op.c_str(),
+ new_version.c_str(),
+ new_size);
+}
+
+string UpdateEngineClient::GetCurrentOperation() {
+ int64_t last_checked_time = 0;
+ double progress = 0.0;
+ string current_op;
+ string new_version;
+ int64_t new_size = 0;
+
+ bool ret = proxy_->GetStatus(
+ &last_checked_time, &progress, ¤t_op, &new_version, &new_size,
+ nullptr);
+ CHECK(ret) << "GetStatus() failed";
+ return current_op;
+}
+
+void UpdateEngineClient::Rollback(bool rollback) {
+ bool ret = proxy_->AttemptRollback(rollback, nullptr);
+ CHECK(ret) << "Rollback request failed.";
+}
+
+string UpdateEngineClient::GetRollbackPartition() {
+ string rollback_partition;
+ bool ret = proxy_->GetRollbackPartition(&rollback_partition, nullptr);
+ CHECK(ret) << "Error while querying rollback partition availabilty.";
+ return rollback_partition;
+}
+
+void UpdateEngineClient::CheckForUpdates(const string& app_version,
+ const string& omaha_url,
+ bool interactive) {
+ int32_t flags = interactive ? 0 : kAttemptUpdateFlagNonInteractive;
+ bool ret = proxy_->AttemptUpdateWithFlags(app_version, omaha_url, flags,
+ nullptr);
+ CHECK(ret) << "Error checking for update.";
+}
+
+void UpdateEngineClient::RebootIfNeeded() {
+ bool ret = proxy_->RebootIfNeeded(nullptr);
+ if (!ret) {
+ // 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.";
+ }
+}
+
+void UpdateEngineClient::SetTargetChannel(const string& target_channel,
+ bool allow_powerwash) {
+ bool ret = proxy_->SetChannel(target_channel, allow_powerwash, nullptr);
+ CHECK(ret) << "Error setting the channel.";
+ LOG(INFO) << "Channel permanently set to: " << target_channel;
+}
+
+string UpdateEngineClient::GetChannel(bool get_current_channel) {
+ string channel;
+ bool ret = proxy_->GetChannel(get_current_channel, &channel, nullptr);
+ CHECK(ret) << "Error getting the channel.";
+ return channel;
+}
+
+void UpdateEngineClient::SetUpdateOverCellularPermission(bool allowed) {
+ bool ret = proxy_->SetUpdateOverCellularPermission(allowed, nullptr);
+ CHECK(ret) << "Error setting the update over cellular setting.";
+}
+
+bool UpdateEngineClient::GetUpdateOverCellularPermission() {
+ bool allowed;
+ bool ret = proxy_->GetUpdateOverCellularPermission(&allowed, nullptr);
+ CHECK(ret) << "Error getting the update over cellular setting.";
+ return allowed;
+}
+
+void UpdateEngineClient::SetP2PUpdatePermission(bool enabled) {
+ bool ret = proxy_->SetP2PUpdatePermission(enabled, nullptr);
+ CHECK(ret) << "Error setting the peer-to-peer update setting.";
+}
+
+bool UpdateEngineClient::GetP2PUpdatePermission() {
+ bool enabled;
+ bool ret = proxy_->GetP2PUpdatePermission(&enabled, nullptr);
+ CHECK(ret) << "Error getting the peer-to-peer update setting.";
+ return enabled;
+}
+
+void UpdateEngineClient::OnUpdateCompleteCheck(
+ int64_t /* last_checked_time */,
+ double /* progress */,
+ const string& current_operation,
+ const string& /* new_version */,
+ int64_t /* new_size */) {
+ if (current_operation == update_engine::kUpdateStatusIdle) {
+ LOG(ERROR) << "Update failed, current operations is " << current_operation;
+ exit(1);
+ }
+ if (current_operation == update_engine::kUpdateStatusUpdatedNeedReboot) {
+ LOG(INFO) << "Update succeeded -- reboot needed.";
+ exit(0);
+ }
+}
+
+void UpdateEngineClient::WaitForUpdateComplete() {
+ proxy_->RegisterStatusUpdateSignalHandler(
+ base::Bind(&UpdateEngineClient::OnUpdateCompleteCheck,
+ base::Unretained(this)),
+ base::Bind(&UpdateEngineClient::OnStatusUpdateSignalRegistration,
+ base::Unretained(this)));
+}
+
+void UpdateEngineClient::ShowPrevVersion() {
+ string prev_version = nullptr;
+
+ bool ret = proxy_->GetPrevVersion(&prev_version, nullptr);;
+ if (!ret) {
+ LOG(ERROR) << "Error getting previous version.";
+ } else {
+ LOG(INFO) << "Previous version = " << prev_version;
+ }
+}
+
+bool UpdateEngineClient::GetIsRebootNeeded() {
+ return GetCurrentOperation() == update_engine::kUpdateStatusUpdatedNeedReboot;
+}
+
+void UpdateEngineClient::OnRebootNeededCheck(
+ int64_t /* last_checked_time */,
+ double /* progress */,
+ const string& current_operation,
+ const string& /* new_version */,
+ int64_t /* new_size */) {
+ if (current_operation == update_engine::kUpdateStatusUpdatedNeedReboot) {
+ LOG(INFO) << "Reboot needed.";
+ exit(0);
+ }
+}
+
+void UpdateEngineClient::OnRebootNeededCheckRegistration(
+ const string& interface,
+ const string& signal_name,
+ bool success) {
+ if (GetIsRebootNeeded())
+ exit(0);
+ if (!success) {
+ LOG(ERROR) << "Couldn't connect to the " << signal_name << " signal.";
+ exit(1);
+ }
+}
+
+// Blocks until a reboot is needed. Returns true if waiting succeeded,
+// false if an error occurred.
+void UpdateEngineClient::WaitForRebootNeeded() {
+ proxy_->RegisterStatusUpdateSignalHandler(
+ base::Bind(&UpdateEngineClient::OnUpdateCompleteCheck,
+ base::Unretained(this)),
+ base::Bind(&UpdateEngineClient::OnStatusUpdateSignalRegistration,
+ base::Unretained(this)));
+ if (GetIsRebootNeeded())
+ exit(0);
+}
+
+int UpdateEngineClient::ProcessFlags() {
+ DEFINE_string(app_version, "", "Force the current app version.");
+ DEFINE_string(channel, "",
+ "Set the target channel. The device will be powerwashed if the "
+ "target channel is more stable than the current channel unless "
+ "--nopowerwash is specified.");
+ DEFINE_bool(check_for_update, false, "Initiate check for updates.");
+ DEFINE_bool(follow, false, "Wait for any update operations to complete."
+ "Exit status is 0 if the update succeeded, and 1 otherwise.");
+ DEFINE_bool(interactive, true, "Mark the update request as interactive.");
+ DEFINE_string(omaha_url, "", "The URL of the Omaha update server.");
+ DEFINE_string(p2p_update, "",
+ "Enables (\"yes\") or disables (\"no\") the peer-to-peer update"
+ " sharing.");
+ DEFINE_bool(powerwash, true, "When performing rollback or channel change, "
+ "do a powerwash or allow it respectively.");
+ DEFINE_bool(reboot, false, "Initiate a reboot if needed.");
+ DEFINE_bool(is_reboot_needed, false, "Exit status 0 if reboot is needed, "
+ "2 if reboot is not needed or 1 if an error occurred.");
+ DEFINE_bool(block_until_reboot_is_needed, false, "Blocks until reboot is "
+ "needed. Returns non-zero exit status if an error occurred.");
+ DEFINE_bool(reset_status, false, "Sets the status in update_engine to idle.");
+ DEFINE_bool(rollback, false,
+ "Perform a rollback to the previous partition. The device will "
+ "be powerwashed unless --nopowerwash is specified.");
+ DEFINE_bool(can_rollback, false, "Shows whether rollback partition "
+ "is available.");
+ DEFINE_bool(show_channel, false, "Show the current and target channels.");
+ DEFINE_bool(show_p2p_update, false,
+ "Show the current setting for peer-to-peer update sharing.");
+ DEFINE_bool(show_update_over_cellular, false,
+ "Show the current setting for updates over cellular networks.");
+ DEFINE_bool(status, false, "Print the status to stdout.");
+ DEFINE_bool(update, false, "Forces an update and waits for it to complete. "
+ "Implies --follow.");
+ DEFINE_string(update_over_cellular, "",
+ "Enables (\"yes\") or disables (\"no\") the updates over "
+ "cellular networks.");
+ DEFINE_bool(watch_for_updates, false,
+ "Listen for status updates and print them to the screen.");
+ DEFINE_bool(prev_version, false,
+ "Show the previous OS version used before the update reboot.");
+
+ // Boilerplate init commands.
+ base::CommandLine::Init(argc_, argv_);
+ brillo::FlagHelper::Init(argc_, argv_, "Chromium OS Update Engine Client");
+
+ // Ensure there are no positional arguments.
+ const std::vector<string> positional_args =
+ base::CommandLine::ForCurrentProcess()->GetArgs();
+ if (!positional_args.empty()) {
+ LOG(ERROR) << "Found a positional argument '" << positional_args.front()
+ << "'. If you want to pass a value to a flag, pass it as "
+ "--flag=value.";
+ return 1;
+ }
+
+ // Update the status if requested.
+ if (FLAGS_reset_status) {
+ LOG(INFO) << "Setting Update Engine status to idle ...";
+ ResetStatus();
+ LOG(INFO) << "ResetStatus succeeded; to undo partition table changes run:\n"
+ "(D=$(rootdev -d) P=$(rootdev -s); cgpt p -i$(($(echo ${P#$D} "
+ "| sed 's/^[^0-9]*//')-1)) $D;)";
+ }
+
+ // Changes the current update over cellular network setting.
+ if (!FLAGS_update_over_cellular.empty()) {
+ bool allowed = FLAGS_update_over_cellular == "yes";
+ if (!allowed && FLAGS_update_over_cellular != "no") {
+ LOG(ERROR) << "Unknown option: \"" << FLAGS_update_over_cellular
+ << "\". Please specify \"yes\" or \"no\".";
+ } else {
+ SetUpdateOverCellularPermission(allowed);
+ }
+ }
+
+ // Show the current update over cellular network setting.
+ if (FLAGS_show_update_over_cellular) {
+ bool allowed = GetUpdateOverCellularPermission();
+ LOG(INFO) << "Current update over cellular network setting: "
+ << (allowed ? "ENABLED" : "DISABLED");
+ }
+
+ if (!FLAGS_powerwash && !FLAGS_rollback && FLAGS_channel.empty()) {
+ LOG(ERROR) << "powerwash flag only makes sense rollback or channel change";
+ return 1;
+ }
+
+ // Change the P2P enabled setting.
+ if (!FLAGS_p2p_update.empty()) {
+ bool enabled = FLAGS_p2p_update == "yes";
+ if (!enabled && FLAGS_p2p_update != "no") {
+ LOG(ERROR) << "Unknown option: \"" << FLAGS_p2p_update
+ << "\". Please specify \"yes\" or \"no\".";
+ } else {
+ SetP2PUpdatePermission(enabled);
+ }
+ }
+
+ // Show the rollback availability.
+ if (FLAGS_can_rollback) {
+ string rollback_partition = GetRollbackPartition();
+ bool can_rollback = true;
+ if (rollback_partition.empty()) {
+ rollback_partition = "UNAVAILABLE";
+ can_rollback = false;
+ } else {
+ rollback_partition = "AVAILABLE: " + rollback_partition;
+ }
+
+ LOG(INFO) << "Rollback partition: " << rollback_partition;
+ if (!can_rollback) {
+ return 1;
+ }
+ }
+
+ // Show the current P2P enabled setting.
+ if (FLAGS_show_p2p_update) {
+ bool enabled = GetP2PUpdatePermission();
+ LOG(INFO) << "Current update using P2P setting: "
+ << (enabled ? "ENABLED" : "DISABLED");
+ }
+
+ // First, update the target channel if requested.
+ if (!FLAGS_channel.empty())
+ SetTargetChannel(FLAGS_channel, FLAGS_powerwash);
+
+ // Show the current and target channels if requested.
+ if (FLAGS_show_channel) {
+ string current_channel = GetChannel(true);
+ LOG(INFO) << "Current Channel: " << current_channel;
+
+ string target_channel = GetChannel(false);
+ if (!target_channel.empty())
+ LOG(INFO) << "Target Channel (pending update): " << target_channel;
+ }
+
+ bool do_update_request = FLAGS_check_for_update | FLAGS_update |
+ !FLAGS_app_version.empty() | !FLAGS_omaha_url.empty();
+ if (FLAGS_update)
+ FLAGS_follow = true;
+
+ if (do_update_request && FLAGS_rollback) {
+ LOG(ERROR) << "Incompatible flags specified with rollback."
+ << "Rollback should not include update-related flags.";
+ return 1;
+ }
+
+ if (FLAGS_rollback) {
+ LOG(INFO) << "Requesting rollback.";
+ Rollback(FLAGS_powerwash);
+ }
+
+ // Initiate an update check, if necessary.
+ if (do_update_request) {
+ LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored.";
+ string app_version = FLAGS_app_version;
+ if (FLAGS_update && app_version.empty()) {
+ app_version = "ForcedUpdate";
+ LOG(INFO) << "Forcing an update by setting app_version to ForcedUpdate.";
+ }
+ LOG(INFO) << "Initiating update check and install.";
+ CheckForUpdates(app_version, FLAGS_omaha_url, FLAGS_interactive);
+ }
+
+ // These final options are all mutually exclusive with one another.
+ if (FLAGS_follow + FLAGS_watch_for_updates + FLAGS_reboot +
+ FLAGS_status + FLAGS_is_reboot_needed +
+ FLAGS_block_until_reboot_is_needed > 1) {
+ LOG(ERROR) << "Multiple exclusive options selected. "
+ << "Select only one of --follow, --watch_for_updates, --reboot, "
+ << "--is_reboot_needed, --block_until_reboot_is_needed, "
+ << "or --status.";
+ return 1;
+ }
+
+ if (FLAGS_status) {
+ LOG(INFO) << "Querying Update Engine status...";
+ ShowStatus();
+ return 0;
+ }
+
+ if (FLAGS_follow) {
+ LOG(INFO) << "Waiting for update to complete.";
+ WaitForUpdateComplete();
+ return kContinueRunning;
+ }
+
+ if (FLAGS_watch_for_updates) {
+ LOG(INFO) << "Watching for status updates.";
+ WatchForUpdates();
+ return kContinueRunning;
+ }
+
+ if (FLAGS_reboot) {
+ LOG(INFO) << "Requesting a reboot...";
+ RebootIfNeeded();
+ return 0;
+ }
+
+ if (FLAGS_prev_version) {
+ ShowPrevVersion();
+ }
+
+ if (FLAGS_is_reboot_needed) {
+ // In case of error GetIsRebootNeeded() will exit with 1.
+ if (GetIsRebootNeeded()) {
+ return 0;
+ } else {
+ return 2;
+ }
+ }
+
+ if (FLAGS_block_until_reboot_is_needed) {
+ WaitForRebootNeeded();
+ return kContinueRunning;
+ }
+
+ return 0;
+}
+
+} // namespace
+
+int main(int argc, char** argv) {
+ UpdateEngineClient client(argc, argv);
+ return client.Run();
+}
diff --git a/update_manager/boxed_value.cc b/update_manager/boxed_value.cc
new file mode 100644
index 0000000..a4aeede
--- /dev/null
+++ b/update_manager/boxed_value.cc
@@ -0,0 +1,194 @@
+//
+// 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/update_manager/boxed_value.h"
+
+#include <stdint.h>
+
+#include <set>
+#include <string>
+
+#include <base/strings/string_number_conversions.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+using std::set;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// Template instantiation for common types; used in BoxedValue::ToString().
+// Keep in sync with boxed_value_unitttest.cc.
+
+template<>
+string BoxedValue::ValuePrinter<string>(const void* value) {
+ const string* val = reinterpret_cast<const string*>(value);
+ return *val;
+}
+
+template<>
+string BoxedValue::ValuePrinter<int>(const void* value) {
+ const int* val = reinterpret_cast<const int*>(value);
+ return base::IntToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<unsigned int>(const void* value) {
+ const unsigned int* val = reinterpret_cast<const unsigned int*>(value);
+ return base::UintToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<int64_t>(const void* value) {
+ const int64_t* val = reinterpret_cast<const int64_t*>(value);
+ return base::Int64ToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<uint64_t>(const void* value) {
+ const uint64_t* val =
+ reinterpret_cast<const uint64_t*>(value);
+ return base::Uint64ToString(static_cast<uint64_t>(*val));
+}
+
+template<>
+string BoxedValue::ValuePrinter<bool>(const void* value) {
+ const bool* val = reinterpret_cast<const bool*>(value);
+ return *val ? "true" : "false";
+}
+
+template<>
+string BoxedValue::ValuePrinter<double>(const void* value) {
+ const double* val = reinterpret_cast<const double*>(value);
+ return base::DoubleToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<base::Time>(const void* value) {
+ const base::Time* val = reinterpret_cast<const base::Time*>(value);
+ return chromeos_update_engine::utils::ToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<base::TimeDelta>(const void* value) {
+ const base::TimeDelta* val = reinterpret_cast<const base::TimeDelta*>(value);
+ return chromeos_update_engine::utils::FormatTimeDelta(*val);
+}
+
+static string ConnectionTypeToString(ConnectionType type) {
+ switch (type) {
+ case ConnectionType::kEthernet:
+ return "Ethernet";
+ case ConnectionType::kWifi:
+ return "Wifi";
+ case ConnectionType::kWimax:
+ return "Wimax";
+ case ConnectionType::kBluetooth:
+ return "Bluetooth";
+ case ConnectionType::kCellular:
+ return "Cellular";
+ case ConnectionType::kUnknown:
+ return "Unknown";
+ }
+ NOTREACHED();
+ return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<ConnectionType>(const void* value) {
+ const ConnectionType* val = reinterpret_cast<const ConnectionType*>(value);
+ return ConnectionTypeToString(*val);
+}
+
+template<>
+string BoxedValue::ValuePrinter<set<ConnectionType>>(const void* value) {
+ string ret = "";
+ const set<ConnectionType>* val =
+ reinterpret_cast<const set<ConnectionType>*>(value);
+ for (auto& it : *val) {
+ ConnectionType type = it;
+ if (ret.size() > 0)
+ ret += ",";
+ ret += ConnectionTypeToString(type);
+ }
+ return ret;
+}
+
+template<>
+string BoxedValue::ValuePrinter<ConnectionTethering>(const void* value) {
+ const ConnectionTethering* val =
+ reinterpret_cast<const ConnectionTethering*>(value);
+ switch (*val) {
+ case ConnectionTethering::kNotDetected:
+ return "Not Detected";
+ case ConnectionTethering::kSuspected:
+ return "Suspected";
+ case ConnectionTethering::kConfirmed:
+ return "Confirmed";
+ case ConnectionTethering::kUnknown:
+ return "Unknown";
+ }
+ NOTREACHED();
+ return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<Stage>(const void* value) {
+ const Stage* val = reinterpret_cast<const Stage*>(value);
+ switch (*val) {
+ case Stage::kIdle:
+ return "Idle";
+ case Stage::kCheckingForUpdate:
+ return "Checking For Update";
+ case Stage::kUpdateAvailable:
+ return "Update Available";
+ case Stage::kDownloading:
+ return "Downloading";
+ case Stage::kVerifying:
+ return "Verifying";
+ case Stage::kFinalizing:
+ return "Finalizing";
+ case Stage::kUpdatedNeedReboot:
+ return "Updated, Need Reboot";
+ case Stage::kReportingErrorEvent:
+ return "Reporting Error Event";
+ case Stage::kAttemptingRollback:
+ return "Attempting Rollback";
+ }
+ NOTREACHED();
+ return "Unknown";
+}
+
+template<>
+string BoxedValue::ValuePrinter<UpdateRequestStatus>(const void* value) {
+ const UpdateRequestStatus* val =
+ reinterpret_cast<const UpdateRequestStatus*>(value);
+ switch (*val) {
+ case UpdateRequestStatus::kNone:
+ return "None";
+ case UpdateRequestStatus::kInteractive:
+ return "Interactive";
+ case UpdateRequestStatus::kPeriodic:
+ return "Periodic";
+ }
+ NOTREACHED();
+ return "Unknown";
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/boxed_value.h b/update_manager/boxed_value.h
new file mode 100644
index 0000000..5f41835
--- /dev/null
+++ b/update_manager/boxed_value.h
@@ -0,0 +1,124 @@
+//
+// 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_UPDATE_MANAGER_BOXED_VALUE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_BOXED_VALUE_H_
+
+#include <memory>
+#include <string>
+
+#include <base/macros.h>
+
+namespace chromeos_update_manager {
+
+// BoxedValue is a class to hold pointers of a given type that deletes them when
+// the instance goes out of scope, as std::unique_ptr<T> does. The main
+// difference with it is that the type T is not part of the class, i.e., this
+// isn't a parametric class. The class has a parametric constructor that accepts
+// a const T* which will define the type of the object passed on delete.
+//
+// It is safe to use this class in linked containers such as std::list and
+// std::map but the object can't be copied. This means that you need to
+// construct the BoxedValue in place using a container method like emplace()
+// or move it with std::move().
+//
+// list<BoxedValue> lst;
+// lst.emplace_back(new const int(42));
+// lst.emplace_back(new const string("Hello world!"));
+//
+// map<int, BoxedValue> m;
+// m.emplace(123, std::move(BoxedValue(new const string("Hola mundo!"))));
+//
+// auto it = m.find(42);
+// if (it != m.end())
+// cout << "m[42] points to " << it->second.value() << endl;
+// cout << "m[33] points to " << m[33].value() << endl;
+//
+// Since copy and assign are not allowed, you can't create a copy of the
+// BoxedValue which means that you can only use a reference to it.
+//
+
+class BoxedValue {
+ public:
+ // Creates an empty BoxedValue. Since the pointer can't be assigned from other
+ // BoxedValues or pointers, this is only useful in places where a default
+ // constructor is required, such as std::map::operator[].
+ BoxedValue() : value_(nullptr), deleter_(nullptr), printer_(nullptr) {}
+
+ // Creates a BoxedValue for the passed pointer |value|. The BoxedValue keeps
+ // the ownership of this pointer and can't be released.
+ template<typename T>
+ explicit BoxedValue(const T* value)
+ : value_(static_cast<const void*>(value)), deleter_(ValueDeleter<T>),
+ printer_(ValuePrinter<T>) {}
+
+ // The move constructor takes ownership of the pointer since the semantics of
+ // it allows to render the passed BoxedValue undefined. You need to use the
+ // move constructor explicitly preventing it from accidental references,
+ // like in:
+ // BoxedValue new_box(std::move(other_box));
+ BoxedValue(BoxedValue&& other) // NOLINT(build/c++11)
+ : value_(other.value_), deleter_(other.deleter_),
+ printer_(other.printer_) {
+ other.value_ = nullptr;
+ other.deleter_ = nullptr;
+ other.printer_ = nullptr;
+ }
+
+ // Deletes the |value| passed on construction using the delete for the passed
+ // type.
+ ~BoxedValue() {
+ if (deleter_)
+ deleter_(value_);
+ }
+
+ const void* value() const { return value_; }
+
+ std::string ToString() const {
+ if (!printer_)
+ return "(no printer)";
+ if (!value_)
+ return "(no value)";
+ return printer_(value_);
+ }
+
+ // Static method to call the destructor of the right type.
+ template<typename T>
+ static void ValueDeleter(const void* value) {
+ delete reinterpret_cast<const T*>(value);
+ }
+
+ // Static method to print a type. See boxed_value.cc for common
+ // instantiations.
+ template<typename T>
+ static std::string ValuePrinter(const void* value);
+
+ private:
+ // A pointer to the cached value.
+ const void* value_;
+
+ // A function that calls delete for the right type of value_.
+ void (*deleter_)(const void*);
+
+ // A function that converts value_ to a string.
+ std::string (*printer_)(const void*);
+
+ DISALLOW_COPY_AND_ASSIGN(BoxedValue);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_BOXED_VALUE_H_
diff --git a/update_manager/boxed_value_unittest.cc b/update_manager/boxed_value_unittest.cc
new file mode 100644
index 0000000..47bfd8f
--- /dev/null
+++ b/update_manager/boxed_value_unittest.cc
@@ -0,0 +1,232 @@
+//
+// 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/update_manager/boxed_value.h"
+
+#include <gtest/gtest.h>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/umtest_utils.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+using base::Time;
+using base::TimeDelta;
+using std::list;
+using std::map;
+using std::set;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// The DeleterMarker flags a bool variable when the class is destroyed.
+class DeleterMarker {
+ public:
+ explicit DeleterMarker(bool* marker) : marker_(marker) { *marker_ = false; }
+
+ ~DeleterMarker() { *marker_ = true; }
+
+ private:
+ friend string BoxedValue::ValuePrinter<DeleterMarker>(const void *);
+
+ // Pointer to the bool marker.
+ bool* marker_;
+};
+
+template<>
+string BoxedValue::ValuePrinter<DeleterMarker>(const void *value) {
+ const DeleterMarker* val = reinterpret_cast<const DeleterMarker*>(value);
+ return base::StringPrintf("DeleterMarker:%s",
+ *val->marker_ ? "true" : "false");
+}
+
+TEST(UmBoxedValueTest, Deleted) {
+ bool marker = true;
+ const DeleterMarker* deleter_marker = new DeleterMarker(&marker);
+
+ EXPECT_FALSE(marker);
+ BoxedValue* box = new BoxedValue(deleter_marker);
+ EXPECT_FALSE(marker);
+ delete box;
+ EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MoveConstructor) {
+ bool marker = true;
+ const DeleterMarker* deleter_marker = new DeleterMarker(&marker);
+
+ BoxedValue* box = new BoxedValue(deleter_marker);
+ BoxedValue* new_box = new BoxedValue(std::move(*box));
+ // box is now undefined but valid.
+ delete box;
+ EXPECT_FALSE(marker);
+ // The deleter_marker gets deleted at this point.
+ delete new_box;
+ EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MixedList) {
+ list<BoxedValue> lst;
+ // This is mostly a compile test.
+ lst.emplace_back(new const int{42});
+ lst.emplace_back(new const string("Hello world!"));
+ bool marker;
+ lst.emplace_back(new const DeleterMarker(&marker));
+ EXPECT_FALSE(marker);
+ lst.clear();
+ EXPECT_TRUE(marker);
+}
+
+TEST(UmBoxedValueTest, MixedMap) {
+ map<int, BoxedValue> m;
+ m.emplace(42, BoxedValue(new const string("Hola mundo!")));
+
+ auto it = m.find(42);
+ ASSERT_NE(it, m.end());
+ EXPECT_NE(nullptr, it->second.value());
+ EXPECT_EQ(nullptr, m[33].value());
+}
+
+TEST(UmBoxedValueTest, StringToString) {
+ EXPECT_EQ("Hej Verden!",
+ BoxedValue(new string("Hej Verden!")).ToString());
+}
+
+TEST(UmBoxedValueTest, IntToString) {
+ EXPECT_EQ("42", BoxedValue(new int(42)).ToString());
+}
+
+TEST(UmBoxedValueTest, Int64ToString) {
+ // -123456789012345 doesn't fit in 32-bit integers.
+ EXPECT_EQ("-123456789012345", BoxedValue(
+ new int64_t(-123456789012345LL)).ToString());
+}
+
+TEST(UmBoxedValueTest, UnsignedIntToString) {
+ // 4294967295 is the biggest possible 32-bit unsigned integer.
+ EXPECT_EQ("4294967295",
+ BoxedValue(new unsigned int(4294967295U)).ToString()); // NOLINT
+}
+
+TEST(UmBoxedValueTest, UnsignedInt64ToString) {
+ // 18446744073709551615 is the biggest possible 64-bit unsigned integer.
+ EXPECT_EQ("18446744073709551615", BoxedValue(
+ new uint64_t(18446744073709551615ULL)).ToString());
+}
+
+TEST(UmBoxedValueTest, BoolToString) {
+ EXPECT_EQ("false", BoxedValue(new bool(false)).ToString());
+ EXPECT_EQ("true", BoxedValue(new bool(true)).ToString());
+}
+
+TEST(UmBoxedValueTest, DoubleToString) {
+ EXPECT_EQ("1.501", BoxedValue(new double(1.501)).ToString());
+}
+
+TEST(UmBoxedValueTest, TimeToString) {
+ // Tue Apr 29 22:30:55 UTC 2014 is 1398810655 seconds since the Unix Epoch.
+ EXPECT_EQ("4/29/2014 22:30:55 GMT",
+ BoxedValue(new Time(Time::FromTimeT(1398810655))).ToString());
+}
+
+TEST(UmBoxedValueTest, TimeDeltaToString) {
+ // 12345 seconds is 3 hours, 25 minutes and 45 seconds.
+ EXPECT_EQ("3h25m45s",
+ BoxedValue(new TimeDelta(TimeDelta::FromSeconds(12345)))
+ .ToString());
+}
+
+TEST(UmBoxedValueTest, ConnectionTypeToString) {
+ EXPECT_EQ("Ethernet",
+ 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());
+ EXPECT_EQ("Unknown",
+ BoxedValue(new ConnectionType(ConnectionType::kUnknown))
+ .ToString());
+}
+
+TEST(UmBoxedValueTest, ConnectionTetheringToString) {
+ EXPECT_EQ("Not Detected",
+ BoxedValue(new ConnectionTethering(
+ ConnectionTethering::kNotDetected)).ToString());
+ EXPECT_EQ("Suspected",
+ BoxedValue(new ConnectionTethering(ConnectionTethering::kSuspected))
+ .ToString());
+ EXPECT_EQ("Confirmed",
+ BoxedValue(new ConnectionTethering(ConnectionTethering::kConfirmed))
+ .ToString());
+ EXPECT_EQ("Unknown",
+ BoxedValue(new ConnectionTethering(ConnectionTethering::kUnknown))
+ .ToString());
+}
+
+TEST(UmBoxedValueTest, SetConnectionTypeToString) {
+ set<ConnectionType>* set1 = new set<ConnectionType>;
+ set1->insert(ConnectionType::kWimax);
+ set1->insert(ConnectionType::kEthernet);
+ EXPECT_EQ("Ethernet,Wimax", BoxedValue(set1).ToString());
+
+ set<ConnectionType>* set2 = new set<ConnectionType>;
+ set2->insert(ConnectionType::kWifi);
+ EXPECT_EQ("Wifi", BoxedValue(set2).ToString());
+}
+
+TEST(UmBoxedValueTest, StageToString) {
+ EXPECT_EQ("Idle",
+ BoxedValue(new Stage(Stage::kIdle)).ToString());
+ EXPECT_EQ("Checking For Update",
+ BoxedValue(new Stage(Stage::kCheckingForUpdate)).ToString());
+ EXPECT_EQ("Update Available",
+ BoxedValue(new Stage(Stage::kUpdateAvailable)).ToString());
+ EXPECT_EQ("Downloading",
+ BoxedValue(new Stage(Stage::kDownloading)).ToString());
+ EXPECT_EQ("Verifying",
+ BoxedValue(new Stage(Stage::kVerifying)).ToString());
+ EXPECT_EQ("Finalizing",
+ BoxedValue(new Stage(Stage::kFinalizing)).ToString());
+ EXPECT_EQ("Updated, Need Reboot",
+ BoxedValue(new Stage(Stage::kUpdatedNeedReboot)).ToString());
+ EXPECT_EQ("Reporting Error Event",
+ BoxedValue(new Stage(Stage::kReportingErrorEvent)).ToString());
+ EXPECT_EQ("Attempting Rollback",
+ BoxedValue(new Stage(Stage::kAttemptingRollback)).ToString());
+}
+
+TEST(UmBoxedValueTest, DeleterMarkerToString) {
+ bool marker = false;
+ BoxedValue value = BoxedValue(new DeleterMarker(&marker));
+ EXPECT_EQ("DeleterMarker:false", value.ToString());
+ marker = true;
+ EXPECT_EQ("DeleterMarker:true", value.ToString());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
new file mode 100644
index 0000000..6ba6f82
--- /dev/null
+++ b/update_manager/chromeos_policy.cc
@@ -0,0 +1,942 @@
+//
+// 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/update_manager/chromeos_policy.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/policy_utils.h"
+#include "update_engine/update_manager/shill_provider.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ErrorCode;
+using std::get;
+using std::max;
+using std::min;
+using std::set;
+using std::string;
+
+namespace {
+
+// Examines |err_code| and decides whether the URL index needs to be advanced,
+// the error count for the URL incremented, or none of the above. In the first
+// case, returns true; in the second case, increments |*url_num_error_p| and
+// returns false; otherwise just returns false.
+//
+// TODO(garnold) Adapted from PayloadState::UpdateFailed() (to be retired).
+bool HandleErrorCode(ErrorCode err_code, int* url_num_error_p) {
+ err_code = chromeos_update_engine::utils::GetBaseErrorCode(err_code);
+ switch (err_code) {
+ // Errors which are good indicators of a problem with a particular URL or
+ // the protocol used in the URL or entities in the communication channel
+ // (e.g. proxies). We should try the next available URL in the next update
+ // check to quickly recover from these errors.
+ case ErrorCode::kPayloadHashMismatchError:
+ case ErrorCode::kPayloadSizeMismatchError:
+ case ErrorCode::kDownloadPayloadVerificationError:
+ case ErrorCode::kDownloadPayloadPubKeyVerificationError:
+ case ErrorCode::kSignedDeltaPayloadExpectedError:
+ case ErrorCode::kDownloadInvalidMetadataMagicString:
+ case ErrorCode::kDownloadSignatureMissingInManifest:
+ case ErrorCode::kDownloadManifestParseError:
+ case ErrorCode::kDownloadMetadataSignatureError:
+ case ErrorCode::kDownloadMetadataSignatureVerificationError:
+ case ErrorCode::kDownloadMetadataSignatureMismatch:
+ case ErrorCode::kDownloadOperationHashVerificationError:
+ case ErrorCode::kDownloadOperationExecutionError:
+ case ErrorCode::kDownloadOperationHashMismatch:
+ case ErrorCode::kDownloadInvalidMetadataSize:
+ case ErrorCode::kDownloadInvalidMetadataSignature:
+ case ErrorCode::kDownloadOperationHashMissingError:
+ case ErrorCode::kDownloadMetadataSignatureMissingError:
+ case ErrorCode::kPayloadMismatchedType:
+ case ErrorCode::kUnsupportedMajorPayloadVersion:
+ case ErrorCode::kUnsupportedMinorPayloadVersion:
+ LOG(INFO) << "Advancing download URL due to error "
+ << chromeos_update_engine::utils::CodeToString(err_code)
+ << " (" << static_cast<int>(err_code) << ")";
+ return true;
+
+ // Errors which seem to be just transient network/communication related
+ // failures and do not indicate any inherent problem with the URL itself.
+ // So, we should keep the current URL but just increment the
+ // failure count to give it more chances. This way, while we maximize our
+ // chances of downloading from the URLs that appear earlier in the response
+ // (because download from a local server URL that appears earlier in a
+ // response is preferable than downloading from the next URL which could be
+ // an Internet URL and thus could be more expensive).
+ case ErrorCode::kError:
+ case ErrorCode::kDownloadTransferError:
+ case ErrorCode::kDownloadWriteError:
+ case ErrorCode::kDownloadStateInitializationError:
+ case ErrorCode::kOmahaErrorInHTTPResponse: // Aggregate for HTTP errors.
+ LOG(INFO) << "Incrementing URL failure count due to error "
+ << chromeos_update_engine::utils::CodeToString(err_code)
+ << " (" << static_cast<int>(err_code) << ")";
+ *url_num_error_p += 1;
+ return false;
+
+ // Errors which are not specific to a URL and hence shouldn't result in
+ // the URL being penalized. This can happen in two cases:
+ // 1. We haven't started downloading anything: These errors don't cost us
+ // anything in terms of actual payload bytes, so we should just do the
+ // regular retries at the next update check.
+ // 2. We have successfully downloaded the payload: In this case, the
+ // payload attempt number would have been incremented and would take care
+ // of the back-off at the next update check.
+ // In either case, there's no need to update URL index or failure count.
+ case ErrorCode::kOmahaRequestError:
+ case ErrorCode::kOmahaResponseHandlerError:
+ case ErrorCode::kPostinstallRunnerError:
+ case ErrorCode::kFilesystemCopierError:
+ case ErrorCode::kInstallDeviceOpenError:
+ case ErrorCode::kKernelDeviceOpenError:
+ case ErrorCode::kDownloadNewPartitionInfoError:
+ case ErrorCode::kNewRootfsVerificationError:
+ case ErrorCode::kNewKernelVerificationError:
+ case ErrorCode::kPostinstallBootedFromFirmwareB:
+ case ErrorCode::kPostinstallFirmwareRONotUpdatable:
+ case ErrorCode::kOmahaRequestEmptyResponseError:
+ case ErrorCode::kOmahaRequestXMLParseError:
+ case ErrorCode::kOmahaResponseInvalid:
+ case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+ case ErrorCode::kOmahaUpdateDeferredForBackoff:
+ case ErrorCode::kPostinstallPowerwashError:
+ case ErrorCode::kUpdateCanceledByChannelChange:
+ case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+ case ErrorCode::kFilesystemVerifierError:
+ LOG(INFO) << "Not changing URL index or failure count due to error "
+ << chromeos_update_engine::utils::CodeToString(err_code)
+ << " (" << static_cast<int>(err_code) << ")";
+ return false;
+
+ case ErrorCode::kSuccess: // success code
+ case ErrorCode::kUmaReportedMax: // not an error code
+ case ErrorCode::kOmahaRequestHTTPResponseBase: // aggregated already
+ case ErrorCode::kDevModeFlag: // not an error code
+ case ErrorCode::kResumedFlag: // not an error code
+ case ErrorCode::kTestImageFlag: // not an error code
+ case ErrorCode::kTestOmahaUrlFlag: // not an error code
+ case ErrorCode::kSpecialFlags: // not an error code
+ // These shouldn't happen. Enumerating these explicitly here so that we
+ // can let the compiler warn about new error codes that are added to
+ // action_processor.h but not added here.
+ LOG(WARNING) << "Unexpected error "
+ << chromeos_update_engine::utils::CodeToString(err_code)
+ << " (" << static_cast<int>(err_code) << ")";
+ // Note: Not adding a default here so as to let the compiler warn us of
+ // any new enums that were added in the .h but not listed in this switch.
+ }
+ return false;
+}
+
+// Checks whether |url| can be used under given download restrictions.
+bool IsUrlUsable(const string& url, bool http_allowed) {
+ return http_allowed || !base::StartsWithASCII(url, "http://", false);
+}
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+const int ChromeOSPolicy::kTimeoutInitialInterval = 7 * 60;
+
+// TODO(deymo): Split the update_manager policies for Brillo and ChromeOS and
+// make the update check periodic interval configurable.
+#ifdef __ANDROID__
+const int ChromeOSPolicy::kTimeoutPeriodicInterval = 5 * 60 * 60;
+const int ChromeOSPolicy::kTimeoutMaxBackoffInterval = 26 * 60 * 60;
+#else
+const int ChromeOSPolicy::kTimeoutPeriodicInterval = 45 * 60;
+const int ChromeOSPolicy::kTimeoutMaxBackoffInterval = 4 * 60 * 60;
+#endif // __ANDROID__
+
+const int ChromeOSPolicy::kTimeoutRegularFuzz = 10 * 60;
+const int ChromeOSPolicy::kAttemptBackoffMaxIntervalInDays = 16;
+const int ChromeOSPolicy::kAttemptBackoffFuzzInHours = 12;
+const int ChromeOSPolicy::kMaxP2PAttempts = 10;
+const int ChromeOSPolicy::kMaxP2PAttemptsPeriodInSeconds = 5 * 24 * 60 * 60;
+
+EvalStatus ChromeOSPolicy::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->is_interactive = false;
+
+ DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+ UpdaterProvider* const updater_provider = state->updater_provider();
+ SystemProvider* const system_provider = state->system_provider();
+
+ // Do not perform any updates if booted from removable device. This decision
+ // is final.
+ const unsigned int* num_slots_p = ec->GetValue(
+ system_provider->var_num_slots());
+ if (!num_slots_p || *num_slots_p < 2) {
+ LOG(INFO) << "Not enough slots for A/B updates, disabling update checks.";
+ result->updates_enabled = false;
+ return EvalStatus::kSucceeded;
+ }
+
+ 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) {
+ // Check whether updates are disabled by policy.
+ const bool* update_disabled_p = ec->GetValue(
+ dp_provider->var_update_disabled());
+ if (update_disabled_p && *update_disabled_p) {
+ LOG(INFO) << "Updates disabled by policy, blocking update checks.";
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ // Determine whether a target version prefix is dictated by policy.
+ const string* target_version_prefix_p = ec->GetValue(
+ dp_provider->var_target_version_prefix());
+ if (target_version_prefix_p)
+ result->target_version_prefix = *target_version_prefix_p;
+
+ // Determine whether a target channel is dictated by policy.
+ 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)
+ result->target_channel = *release_channel_p;
+ }
+ }
+
+ // First, check to see if an interactive update was requested.
+ const UpdateRequestStatus* forced_update_requested_p = ec->GetValue(
+ updater_provider->var_forced_update_requested());
+ if (forced_update_requested_p &&
+ *forced_update_requested_p != UpdateRequestStatus::kNone) {
+ result->is_interactive =
+ (*forced_update_requested_p == UpdateRequestStatus::kInteractive);
+ LOG(INFO) << "Forced update signaled ("
+ << (result->is_interactive ? "interactive" : "periodic")
+ << "), allowing update check.";
+ return EvalStatus::kSucceeded;
+ }
+
+ // The logic thereafter applies to periodic updates. Bear in mind that we
+ // should not return a final "no" if any of these criteria are not satisfied,
+ // because the system may still update due to an interactive update request.
+
+ // Unofficial builds should not perform periodic update checks.
+ const bool* is_official_build_p = ec->GetValue(
+ system_provider->var_is_official_build());
+ if (is_official_build_p && !(*is_official_build_p)) {
+ LOG(INFO) << "Unofficial build, blocking periodic update checks.";
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ // If OOBE is enabled, wait until it is completed.
+ const bool* is_oobe_enabled_p = ec->GetValue(
+ state->config_provider()->var_is_oobe_enabled());
+ if (is_oobe_enabled_p && *is_oobe_enabled_p) {
+ const bool* is_oobe_complete_p = ec->GetValue(
+ system_provider->var_is_oobe_complete());
+ if (is_oobe_complete_p && !(*is_oobe_complete_p)) {
+ LOG(INFO) << "OOBE not completed, blocking update checks.";
+ return EvalStatus::kAskMeAgainLater;
+ }
+ }
+
+ // Ensure that periodic update checks are timed properly.
+ Time next_update_check;
+ if (NextUpdateCheckTime(ec, state, error, &next_update_check) !=
+ EvalStatus::kSucceeded) {
+ return EvalStatus::kFailed;
+ }
+ if (!ec->IsWallclockTimeGreaterThan(next_update_check)) {
+ LOG(INFO) << "Periodic check interval not satisfied, blocking until "
+ << chromeos_update_engine::utils::ToString(next_update_check);
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ // It is time to check for an update.
+ LOG(INFO) << "Allowing update check.";
+ return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::UpdateCanStart(
+ EvaluationContext* ec,
+ State* state,
+ string* error,
+ UpdateDownloadParams* result,
+ const UpdateState update_state) const {
+ // Set the default return values. Note that we set persisted values (backoff,
+ // scattering) to the same values presented in the update state. The reason is
+ // that preemptive returns, such as the case where an update check is due,
+ // should not clear off the said values; rather, it is the deliberate
+ // inference of new values that should cause them to be reset.
+ result->update_can_start = false;
+ result->cannot_start_reason = UpdateCannotStartReason::kUndefined;
+ result->download_url_idx = -1;
+ result->download_url_allowed = true;
+ result->download_url_num_errors = 0;
+ result->p2p_downloading_allowed = false;
+ result->p2p_sharing_allowed = false;
+ result->do_increment_failures = false;
+ result->backoff_expiry = update_state.backoff_expiry;
+ result->scatter_wait_period = update_state.scatter_wait_period;
+ result->scatter_check_threshold = update_state.scatter_check_threshold;
+
+ // Make sure that we're not due for an update check.
+ UpdateCheckParams check_result;
+ EvalStatus check_status = UpdateCheckAllowed(ec, state, error, &check_result);
+ if (check_status == EvalStatus::kFailed)
+ return EvalStatus::kFailed;
+ bool is_check_due = (check_status == EvalStatus::kSucceeded &&
+ check_result.updates_enabled == true);
+
+ // Check whether backoff applies, and if not then which URL can be used for
+ // downloading. These require scanning the download error log, and so they are
+ // done together.
+ UpdateBackoffAndDownloadUrlResult backoff_url_result;
+ EvalStatus backoff_url_status = UpdateBackoffAndDownloadUrl(
+ ec, state, error, &backoff_url_result, update_state);
+ if (backoff_url_status == EvalStatus::kFailed)
+ return EvalStatus::kFailed;
+ result->download_url_idx = backoff_url_result.url_idx;
+ result->download_url_num_errors = backoff_url_result.url_num_errors;
+ result->do_increment_failures = backoff_url_result.do_increment_failures;
+ result->backoff_expiry = backoff_url_result.backoff_expiry;
+ bool is_backoff_active =
+ (backoff_url_status == EvalStatus::kAskMeAgainLater) ||
+ !backoff_url_result.backoff_expiry.is_null();
+
+ DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+ bool is_scattering_active = false;
+ EvalStatus scattering_status = EvalStatus::kSucceeded;
+
+ 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) {
+ // Check whether scattering applies to this update attempt. We should not be
+ // scattering if this is an interactive update check, or if OOBE is enabled
+ // but not completed.
+ //
+ // Note: current code further suppresses scattering if a "deadline"
+ // attribute is found in the Omaha response. However, it appears that the
+ // presence of this attribute is merely indicative of an OOBE update, during
+ // which we suppress scattering anyway.
+ bool is_scattering_applicable = false;
+ result->scatter_wait_period = kZeroInterval;
+ result->scatter_check_threshold = 0;
+ if (!update_state.is_interactive) {
+ const bool* is_oobe_enabled_p = ec->GetValue(
+ state->config_provider()->var_is_oobe_enabled());
+ if (is_oobe_enabled_p && !(*is_oobe_enabled_p)) {
+ is_scattering_applicable = true;
+ } else {
+ const bool* is_oobe_complete_p = ec->GetValue(
+ state->system_provider()->var_is_oobe_complete());
+ is_scattering_applicable = (is_oobe_complete_p && *is_oobe_complete_p);
+ }
+ }
+
+ // Compute scattering values.
+ if (is_scattering_applicable) {
+ UpdateScatteringResult scatter_result;
+ scattering_status = UpdateScattering(ec, state, error, &scatter_result,
+ update_state);
+ if (scattering_status == EvalStatus::kFailed) {
+ return EvalStatus::kFailed;
+ } else {
+ result->scatter_wait_period = scatter_result.wait_period;
+ result->scatter_check_threshold = scatter_result.check_threshold;
+ if (scattering_status == EvalStatus::kAskMeAgainLater ||
+ scatter_result.is_scattering)
+ is_scattering_active = true;
+ }
+ }
+ }
+
+ // Find out whether P2P is globally enabled.
+ bool p2p_enabled;
+ EvalStatus p2p_enabled_status = P2PEnabled(ec, state, error, &p2p_enabled);
+ if (p2p_enabled_status != EvalStatus::kSucceeded)
+ return EvalStatus::kFailed;
+
+ // Is P2P is enabled, consider allowing it for downloading and/or sharing.
+ if (p2p_enabled) {
+ // Sharing via P2P is allowed if not disabled by Omaha.
+ if (update_state.p2p_sharing_disabled) {
+ LOG(INFO) << "Blocked P2P sharing because it is disabled by Omaha.";
+ } else {
+ result->p2p_sharing_allowed = true;
+ }
+
+ // Downloading via P2P is allowed if not disabled by Omaha, an update is not
+ // interactive, and other limits haven't been reached.
+ if (update_state.p2p_downloading_disabled) {
+ LOG(INFO) << "Blocked P2P downloading because it is disabled by Omaha.";
+ } else if (update_state.is_interactive) {
+ LOG(INFO) << "Blocked P2P downloading because update is interactive.";
+ } else if (update_state.p2p_num_attempts >= kMaxP2PAttempts) {
+ LOG(INFO) << "Blocked P2P downloading as it was attempted too many "
+ "times.";
+ } else if (!update_state.p2p_first_attempted.is_null() &&
+ ec->IsWallclockTimeGreaterThan(
+ update_state.p2p_first_attempted +
+ TimeDelta::FromSeconds(kMaxP2PAttemptsPeriodInSeconds))) {
+ LOG(INFO) << "Blocked P2P downloading as its usage timespan exceeds "
+ "limit.";
+ } else {
+ // P2P download is allowed; if backoff or scattering are active, be sure
+ // to suppress them, yet prevent any download URL from being used.
+ result->p2p_downloading_allowed = true;
+ if (is_backoff_active || is_scattering_active) {
+ is_backoff_active = is_scattering_active = false;
+ result->download_url_allowed = false;
+ }
+ }
+ }
+
+ // Check for various deterrents.
+ if (is_check_due) {
+ result->cannot_start_reason = UpdateCannotStartReason::kCheckDue;
+ return EvalStatus::kSucceeded;
+ }
+ if (is_backoff_active) {
+ result->cannot_start_reason = UpdateCannotStartReason::kBackoff;
+ return backoff_url_status;
+ }
+ if (is_scattering_active) {
+ result->cannot_start_reason = UpdateCannotStartReason::kScattering;
+ return scattering_status;
+ }
+ if (result->download_url_idx < 0 && !result->p2p_downloading_allowed) {
+ result->cannot_start_reason = UpdateCannotStartReason::kCannotDownload;
+ return EvalStatus::kSucceeded;
+ }
+
+ // Update is good to go.
+ result->update_can_start = true;
+ 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,
+ bool* result) const {
+ bool enabled = false;
+
+ // Determine whether use of P2P is allowed by policy. Even if P2P is not
+ // explicitly allowed, we allow it if the device is enterprise enrolled (that
+ // is, missing or empty owner string).
+ 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 bool* policy_au_p2p_enabled_p = ec->GetValue(
+ dp_provider->var_au_p2p_enabled());
+ 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())
+ enabled = true;
+ }
+ }
+
+ // Enable P2P, if so mandated by the updater configuration. This is additive
+ // to whether or not P2P is enabled by device policy.
+ if (!enabled) {
+ const bool* updater_p2p_enabled_p = ec->GetValue(
+ state->updater_provider()->var_p2p_enabled());
+ enabled = updater_p2p_enabled_p && *updater_p2p_enabled_p;
+ }
+
+ *result = enabled;
+ return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::P2PEnabledChanged(EvaluationContext* ec,
+ State* state,
+ string* error,
+ bool* result,
+ bool prev_result) const {
+ EvalStatus status = P2PEnabled(ec, state, error, result);
+ if (status == EvalStatus::kSucceeded && *result == prev_result)
+ return EvalStatus::kAskMeAgainLater;
+ return status;
+}
+
+EvalStatus ChromeOSPolicy::NextUpdateCheckTime(EvaluationContext* ec,
+ State* state, string* error,
+ Time* next_update_check) const {
+ UpdaterProvider* const updater_provider = state->updater_provider();
+
+ // Don't check for updates too often. We limit the update checks to once every
+ // some interval. The interval is kTimeoutInitialInterval the first time and
+ // kTimeoutPeriodicInterval for the subsequent update checks. If the update
+ // check fails, we increase the interval between the update checks
+ // exponentially until kTimeoutMaxBackoffInterval. Finally, to avoid having
+ // many chromebooks running update checks at the exact same time, we add some
+ // fuzz to the interval.
+ const Time* updater_started_time =
+ ec->GetValue(updater_provider->var_updater_started_time());
+ POLICY_CHECK_VALUE_AND_FAIL(updater_started_time, error);
+
+ const Time* last_checked_time =
+ ec->GetValue(updater_provider->var_last_checked_time());
+
+ const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+ POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+
+ PRNG prng(*seed);
+
+ // If this is the first attempt, compute and return an initial value.
+ if (!last_checked_time || *last_checked_time < *updater_started_time) {
+ *next_update_check = *updater_started_time + FuzzedInterval(
+ &prng, kTimeoutInitialInterval, kTimeoutRegularFuzz);
+ 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 = ec->GetValue(
+ updater_provider->var_server_dictated_poll_interval());
+ POLICY_CHECK_VALUE_AND_FAIL(server_dictated_poll_interval, error);
+
+ int interval = *server_dictated_poll_interval;
+ int fuzz = 0;
+
+ // If no poll interval was dictated by server compute a back-off period,
+ // starting from a predetermined base periodic interval and increasing
+ // exponentially by the number of consecutive failed attempts.
+ if (interval == 0) {
+ const unsigned int* consecutive_failed_update_checks = ec->GetValue(
+ updater_provider->var_consecutive_failed_update_checks());
+ POLICY_CHECK_VALUE_AND_FAIL(consecutive_failed_update_checks, error);
+
+ interval = kTimeoutPeriodicInterval;
+ unsigned int num_failures = *consecutive_failed_update_checks;
+ while (interval < kTimeoutMaxBackoffInterval && num_failures) {
+ interval *= 2;
+ num_failures--;
+ }
+ }
+
+ // We cannot back off longer than the predetermined maximum interval.
+ if (interval > kTimeoutMaxBackoffInterval)
+ interval = kTimeoutMaxBackoffInterval;
+
+ // We cannot back off shorter than the predetermined periodic interval. Also,
+ // in this case set the fuzz to a predetermined regular value.
+ if (interval <= kTimeoutPeriodicInterval) {
+ interval = kTimeoutPeriodicInterval;
+ fuzz = kTimeoutRegularFuzz;
+ }
+
+ // If not otherwise determined, defer to a fuzz of +/-(interval / 2).
+ if (fuzz == 0)
+ fuzz = interval;
+
+ *next_update_check = *last_checked_time + FuzzedInterval(
+ &prng, interval, fuzz);
+ return EvalStatus::kSucceeded;
+}
+
+TimeDelta ChromeOSPolicy::FuzzedInterval(PRNG* prng, int interval, int fuzz) {
+ DCHECK_GE(interval, 0);
+ DCHECK_GE(fuzz, 0);
+ int half_fuzz = fuzz / 2;
+ // This guarantees the output interval is non negative.
+ int interval_min = max(interval - half_fuzz, 0);
+ int interval_max = interval + half_fuzz;
+ return TimeDelta::FromSeconds(prng->RandMinMax(interval_min, interval_max));
+}
+
+EvalStatus ChromeOSPolicy::UpdateBackoffAndDownloadUrl(
+ EvaluationContext* ec, State* state, string* error,
+ UpdateBackoffAndDownloadUrlResult* result,
+ const UpdateState& update_state) const {
+ // Sanity checks.
+ DCHECK_GE(update_state.download_errors_max, 0);
+
+ // Set default result values.
+ result->do_increment_failures = false;
+ result->backoff_expiry = update_state.backoff_expiry;
+ result->url_idx = -1;
+ result->url_num_errors = 0;
+
+ const bool* is_official_build_p = ec->GetValue(
+ state->system_provider()->var_is_official_build());
+ bool is_official_build = (is_official_build_p ? *is_official_build_p : true);
+
+ // Check whether backoff is enabled.
+ bool may_backoff = false;
+ if (update_state.is_backoff_disabled) {
+ LOG(INFO) << "Backoff disabled by Omaha.";
+ } else if (update_state.is_interactive) {
+ LOG(INFO) << "No backoff for interactive updates.";
+ } else if (update_state.is_delta_payload) {
+ LOG(INFO) << "No backoff for delta payloads.";
+ } else if (!is_official_build) {
+ LOG(INFO) << "No backoff for unofficial builds.";
+ } else {
+ may_backoff = true;
+ }
+
+ // If previous backoff still in effect, block.
+ if (may_backoff && !update_state.backoff_expiry.is_null() &&
+ !ec->IsWallclockTimeGreaterThan(update_state.backoff_expiry)) {
+ LOG(INFO) << "Previous backoff has not expired, waiting.";
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ // Determine whether HTTP downloads are forbidden by policy. This only
+ // applies to official system builds; otherwise, HTTP is always enabled.
+ bool http_allowed = true;
+ if (is_official_build) {
+ 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 bool* policy_http_downloads_enabled_p = ec->GetValue(
+ dp_provider->var_http_downloads_enabled());
+ http_allowed = (!policy_http_downloads_enabled_p ||
+ *policy_http_downloads_enabled_p);
+ }
+ }
+
+ int url_idx = update_state.last_download_url_idx;
+ if (url_idx < 0)
+ url_idx = -1;
+ bool do_advance_url = false;
+ bool is_failure_occurred = false;
+ Time err_time;
+
+ // Scan the relevant part of the download error log, tracking which URLs are
+ // being used, and accounting the number of errors for each URL. Note that
+ // this process may not traverse all errors provided, as it may decide to bail
+ // out midway depending on the particular errors exhibited, the number of
+ // failures allowed, etc. When this ends, |url_idx| will point to the last URL
+ // used (-1 if starting fresh), |do_advance_url| will determine whether the
+ // URL needs to be advanced, and |err_time| the point in time when the last
+ // reported error occurred. Additionally, if the error log indicates that an
+ // update attempt has failed (abnormal), then |is_failure_occurred| will be
+ // set to true.
+ const int num_urls = update_state.download_urls.size();
+ int prev_url_idx = -1;
+ int url_num_errors = update_state.last_download_url_num_errors;
+ Time prev_err_time;
+ bool is_first = true;
+ for (const auto& err_tuple : update_state.download_errors) {
+ // Do some sanity 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
+ << ") not as expected (" << url_idx << ")";
+ }
+ is_first = false;
+ url_idx = used_url_idx;
+ if (url_idx < 0 || url_idx >= num_urls) {
+ LOG(ERROR) << "Download error log contains an invalid URL index ("
+ << url_idx << ")";
+ return EvalStatus::kFailed;
+ }
+ err_time = get<2>(err_tuple);
+ if (!(prev_err_time.is_null() || err_time >= prev_err_time)) {
+ // TODO(garnold) Monotonicity cannot really be assumed when dealing with
+ // wallclock-based timestamps. However, we're making a simplifying
+ // assumption so as to keep the policy implementation straightforward, for
+ // now. In general, we should convert all timestamp handling in the
+ // UpdateManager to use monotonic time (instead of wallclock), including
+ // the computation of various expiration times (backoff, scattering, etc).
+ // The client will do whatever conversions necessary when
+ // persisting/retrieving these values across reboots. See chromium:408794.
+ LOG(ERROR) << "Download error timestamps not monotonically increasing.";
+ return EvalStatus::kFailed;
+ }
+ prev_err_time = err_time;
+
+ // Ignore errors that happened before the last known failed attempt.
+ if (!update_state.failures_last_updated.is_null() &&
+ err_time <= update_state.failures_last_updated)
+ continue;
+
+ if (prev_url_idx >= 0) {
+ if (url_idx < prev_url_idx) {
+ LOG(ERROR) << "The URLs in the download error log have wrapped around ("
+ << prev_url_idx << "->" << url_idx
+ << "). This should not have happened and means that there's "
+ "a bug. To be conservative, we record a failed attempt "
+ "(invalidating the rest of the error log) and resume "
+ "download from the first usable URL.";
+ url_idx = -1;
+ is_failure_occurred = true;
+ break;
+ }
+
+ if (url_idx > prev_url_idx) {
+ url_num_errors = 0;
+ do_advance_url = false;
+ }
+ }
+
+ if (HandleErrorCode(get<1>(err_tuple), &url_num_errors) ||
+ url_num_errors > update_state.download_errors_max)
+ do_advance_url = true;
+
+ prev_url_idx = url_idx;
+ }
+
+ // If required, advance to the next usable URL. If the URLs wraparound, we
+ // mark an update attempt failure. Also be sure to set the download error
+ // count to zero.
+ if (url_idx < 0 || do_advance_url) {
+ url_num_errors = 0;
+ int start_url_idx = -1;
+ do {
+ if (++url_idx == num_urls) {
+ url_idx = 0;
+ // We only mark failure if an actual advancing of a URL was required.
+ if (do_advance_url)
+ is_failure_occurred = true;
+ }
+
+ if (start_url_idx < 0)
+ start_url_idx = url_idx;
+ else if (url_idx == start_url_idx)
+ url_idx = -1; // No usable URL.
+ } while (url_idx >= 0 &&
+ !IsUrlUsable(update_state.download_urls[url_idx], http_allowed));
+ }
+
+ // If we have a download URL but a failure was observed, compute a new backoff
+ // expiry (if allowed). The backoff period is generally 2 ^ (num_failures - 1)
+ // days, bounded by the size of int and kAttemptBackoffMaxIntervalInDays, and
+ // fuzzed by kAttemptBackoffFuzzInHours hours. Backoff expiry is computed from
+ // the latest recorded time of error.
+ Time backoff_expiry;
+ if (url_idx >= 0 && is_failure_occurred && may_backoff) {
+ CHECK(!err_time.is_null())
+ << "We must have an error timestamp if a failure occurred!";
+ const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+ POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+ PRNG prng(*seed);
+ int exp = min(update_state.num_failures,
+ static_cast<int>(sizeof(int)) * 8 - 2);
+ TimeDelta backoff_interval = TimeDelta::FromDays(
+ min(1 << exp, kAttemptBackoffMaxIntervalInDays));
+ TimeDelta backoff_fuzz = TimeDelta::FromHours(kAttemptBackoffFuzzInHours);
+ TimeDelta wait_period = FuzzedInterval(&prng, backoff_interval.InSeconds(),
+ backoff_fuzz.InSeconds());
+ backoff_expiry = err_time + wait_period;
+
+ // If the newly computed backoff already expired, nullify it.
+ if (ec->IsWallclockTimeGreaterThan(backoff_expiry))
+ backoff_expiry = Time();
+ }
+
+ result->do_increment_failures = is_failure_occurred;
+ result->backoff_expiry = backoff_expiry;
+ result->url_idx = url_idx;
+ result->url_num_errors = url_num_errors;
+ return EvalStatus::kSucceeded;
+}
+
+EvalStatus ChromeOSPolicy::UpdateScattering(
+ EvaluationContext* ec,
+ State* state,
+ string* error,
+ UpdateScatteringResult* result,
+ const UpdateState& update_state) const {
+ // Preconditions. These stem from the postconditions and usage contract.
+ DCHECK(update_state.scatter_wait_period >= kZeroInterval);
+ DCHECK_GE(update_state.scatter_check_threshold, 0);
+
+ // Set default result values.
+ result->is_scattering = false;
+ result->wait_period = kZeroInterval;
+ result->check_threshold = 0;
+
+ DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+
+ // Ensure that a device policy is loaded.
+ 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))
+ return EvalStatus::kSucceeded;
+
+ // Is scattering enabled by policy?
+ const TimeDelta* scatter_factor_p = ec->GetValue(
+ dp_provider->var_scatter_factor());
+ if (!scatter_factor_p || *scatter_factor_p == kZeroInterval)
+ return EvalStatus::kSucceeded;
+
+ // Obtain a pseudo-random number generator.
+ const uint64_t* seed = ec->GetValue(state->random_provider()->var_seed());
+ POLICY_CHECK_VALUE_AND_FAIL(seed, error);
+ PRNG prng(*seed);
+
+ // Step 1: Maintain the scattering wait period.
+ //
+ // If no wait period was previously determined, or it no longer fits in the
+ // scatter factor, then generate a new one. Otherwise, keep the one we have.
+ TimeDelta wait_period = update_state.scatter_wait_period;
+ if (wait_period == kZeroInterval || wait_period > *scatter_factor_p) {
+ wait_period = TimeDelta::FromSeconds(
+ prng.RandMinMax(1, scatter_factor_p->InSeconds()));
+ }
+
+ // If we surpassed the wait period or the max scatter period associated with
+ // the update, then no wait is needed.
+ Time wait_expires = (update_state.first_seen +
+ min(wait_period, update_state.scatter_wait_period_max));
+ if (ec->IsWallclockTimeGreaterThan(wait_expires))
+ wait_period = kZeroInterval;
+
+ // Step 2: Maintain the update check threshold count.
+ //
+ // If an update check threshold is not specified then generate a new
+ // one.
+ int check_threshold = update_state.scatter_check_threshold;
+ if (check_threshold == 0) {
+ check_threshold = prng.RandMinMax(
+ update_state.scatter_check_threshold_min,
+ update_state.scatter_check_threshold_max);
+ }
+
+ // If the update check threshold is not within allowed range then nullify it.
+ // TODO(garnold) This is compliant with current logic found in
+ // OmahaRequestAction::IsUpdateCheckCountBasedWaitingSatisfied(). We may want
+ // to change it so that it behaves similarly to the wait period case, namely
+ // if the current value exceeds the maximum, we set a new one within range.
+ if (check_threshold > update_state.scatter_check_threshold_max)
+ check_threshold = 0;
+
+ // If the update check threshold is non-zero and satisfied, then nullify it.
+ if (check_threshold > 0 && update_state.num_checks >= check_threshold)
+ check_threshold = 0;
+
+ bool is_scattering = (wait_period != kZeroInterval || check_threshold);
+ EvalStatus ret = EvalStatus::kSucceeded;
+ if (is_scattering && wait_period == update_state.scatter_wait_period &&
+ check_threshold == update_state.scatter_check_threshold)
+ ret = EvalStatus::kAskMeAgainLater;
+ result->is_scattering = is_scattering;
+ result->wait_period = wait_period;
+ result->check_threshold = check_threshold;
+ return ret;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/chromeos_policy.h b/update_manager/chromeos_policy.h
new file mode 100644
index 0000000..b4370c4
--- /dev/null
+++ b/update_manager/chromeos_policy.h
@@ -0,0 +1,203 @@
+//
+// 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_UPDATE_MANAGER_CHROMEOS_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_CHROMEOS_POLICY_H_
+
+#include <string>
+
+#include <base/time/time.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/prng.h"
+
+namespace chromeos_update_manager {
+
+// Output information from UpdateBackoffAndDownloadUrl.
+struct UpdateBackoffAndDownloadUrlResult {
+ // Whether the failed attempt count (maintained by the caller) needs to be
+ // incremented.
+ bool do_increment_failures;
+ // The current backoff expiry. Null if backoff is not in effect.
+ base::Time backoff_expiry;
+ // The new URL index to use and number of download errors associated with it.
+ // Significant iff |do_increment_failures| is false and |backoff_expiry| is
+ // null. Negative value means no usable URL was found.
+ int url_idx;
+ int url_num_errors;
+};
+
+// Parameters for update scattering, as returned by UpdateScattering.
+struct UpdateScatteringResult {
+ bool is_scattering;
+ base::TimeDelta wait_period;
+ int check_threshold;
+};
+
+// ChromeOSPolicy implements the policy-related logic used in ChromeOS.
+class ChromeOSPolicy : public Policy {
+ public:
+ ChromeOSPolicy() {}
+ ~ChromeOSPolicy() override {}
+
+ // Policy overrides.
+ EvalStatus UpdateCheckAllowed(
+ EvaluationContext* ec, State* state, std::string* error,
+ UpdateCheckParams* result) const override;
+
+ EvalStatus UpdateCanStart(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ 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,
+ bool* result) const override;
+
+ 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 "ChromeOSPolicy"; }
+
+ private:
+ friend class UmChromeOSPolicyTest;
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ FirstCheckIsAtMostInitialIntervalAfterStart);
+ FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckBaseIntervalAndFuzz);
+ FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckBackoffIntervalAndFuzz);
+ FRIEND_TEST(UmChromeOSPolicyTest, RecurringCheckServerDictatedPollInterval);
+ FRIEND_TEST(UmChromeOSPolicyTest, ExponentialBackoffIsCapped);
+ FRIEND_TEST(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout);
+ FRIEND_TEST(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringNewWaitPeriodApplies);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringPrevWaitPeriodStillApplies);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringNewCountThresholdApplies);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringPrevCountThresholdStillApplies);
+ FRIEND_TEST(UmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSatisfied);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedInteractivePreventsScattering);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PDownloadingBlockedDueToNumAttempts);
+ FRIEND_TEST(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PDownloadingBlockedDueToAttemptsPeriod);
+
+ // Auxiliary constant (zero by default).
+ const base::TimeDelta kZeroInterval;
+
+ // Default update check timeout interval/fuzz values used to compute the
+ // NextUpdateCheckTime(), in seconds. Actual fuzz is within +/- half of the
+ // indicated value.
+ static const int kTimeoutInitialInterval;
+ static const int kTimeoutPeriodicInterval;
+ static const int kTimeoutMaxBackoffInterval;
+ static const int kTimeoutRegularFuzz;
+
+ // Maximum update attempt backoff interval and fuzz.
+ static const int kAttemptBackoffMaxIntervalInDays;
+ static const int kAttemptBackoffFuzzInHours;
+
+ // Maximum number of times we'll allow using P2P for the same update payload.
+ static const int kMaxP2PAttempts;
+ // Maximum period of time allowed for download a payload via P2P, in seconds.
+ static const int kMaxP2PAttemptsPeriodInSeconds;
+
+ // A private policy implementation returning the wallclock timestamp when
+ // the next update check should happen.
+ // TODO(garnold) We should probably change that to infer a monotonic
+ // timestamp, which will make the update check intervals more resilient to
+ // clock skews. Might require switching some of the variables exported by the
+ // UpdaterProvider to report monotonic time, as well.
+ EvalStatus NextUpdateCheckTime(EvaluationContext* ec, State* state,
+ std::string* error,
+ base::Time* next_update_check) const;
+
+ // Returns a TimeDelta based on the provided |interval| seconds +/- half
+ // |fuzz| seconds. The return value is guaranteed to be a non-negative
+ // TimeDelta.
+ static base::TimeDelta FuzzedInterval(PRNG* prng, int interval, int fuzz);
+
+ // A private policy for determining backoff and the download URL to use.
+ // Within |update_state|, |backoff_expiry| and |is_backoff_disabled| are used
+ // for determining whether backoff is still in effect; if not,
+ // |download_errors| is scanned past |failures_last_updated|, and a new
+ // download URL from |download_urls| is found and written to |result->url_idx|
+ // (-1 means no usable URL exists); |download_errors_max| determines the
+ // maximum number of attempts per URL, according to the Omaha response. If an
+ // update failure is identified then |result->do_increment_failures| is set to
+ // true; if backoff is enabled, a new backoff period is computed (from the
+ // time of failure) based on |num_failures|. Otherwise, backoff expiry is
+ // nullified, indicating that no backoff is in effect.
+ //
+ // If backing off but the previous backoff expiry is unchanged, returns
+ // |EvalStatus::kAskMeAgainLater|. Otherwise:
+ //
+ // * If backing off with a new expiry time, then |result->backoff_expiry| is
+ // set to this time.
+ //
+ // * Else, |result->backoff_expiry| is set to null, indicating that no backoff
+ // is in effect.
+ //
+ // In any of these cases, returns |EvalStatus::kSucceeded|. If an error
+ // occurred, returns |EvalStatus::kFailed|.
+ EvalStatus UpdateBackoffAndDownloadUrl(
+ EvaluationContext* ec, State* state, std::string* error,
+ UpdateBackoffAndDownloadUrlResult* result,
+ const UpdateState& update_state) const;
+
+ // A private policy for checking whether scattering is due. Writes in |result|
+ // the decision as to whether or not to scatter; a wallclock-based scatter
+ // wait period, which ranges from zero (do not wait) and no greater than the
+ // current scatter factor provided by the device policy (if available) or the
+ // maximum wait period determined by Omaha; and an update check-based
+ // threshold between zero (no threshold) and the maximum number determined by
+ // the update engine. Within |update_state|, |scatter_wait_period| should
+ // contain the last scattering period returned by this function, or zero if no
+ // wait period is known; |scatter_check_threshold| is the last update check
+ // threshold, or zero if no such threshold is known. If not scattering, or if
+ // any of the scattering values has changed, returns |EvalStatus::kSucceeded|;
+ // otherwise, |EvalStatus::kAskMeAgainLater|.
+ EvalStatus UpdateScattering(EvaluationContext* ec, State* state,
+ std::string* error,
+ UpdateScatteringResult* result,
+ const UpdateState& update_state) const;
+
+ DISALLOW_COPY_AND_ASSIGN(ChromeOSPolicy);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_CHROMEOS_POLICY_H_
diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc
new file mode 100644
index 0000000..a78257d
--- /dev/null
+++ b/update_manager/chromeos_policy_unittest.cc
@@ -0,0 +1,1618 @@
+//
+// 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/update_manager/chromeos_policy.h"
+
+#include <set>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include <base/time/time.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/fake_state.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::FakeClock;
+using std::set;
+using std::string;
+using std::tuple;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+class UmChromeOSPolicyTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ SetUpDefaultClock();
+ eval_ctx_ = new EvaluationContext(&fake_clock_, TimeDelta::FromSeconds(5));
+ SetUpDefaultState();
+ SetUpDefaultDevicePolicy();
+ }
+
+ void TearDown() override {
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ // Sets the clock to fixed values.
+ void SetUpDefaultClock() {
+ fake_clock_.SetMonotonicTime(Time::FromInternalValue(12345678L));
+ fake_clock_.SetWallclockTime(Time::FromInternalValue(12345678901234L));
+ }
+
+ void SetUpDefaultState() {
+ fake_state_.updater_provider()->var_updater_started_time()->reset(
+ new Time(fake_clock_.GetWallclockTime()));
+ fake_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(fake_clock_.GetWallclockTime()));
+ fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+ reset(new unsigned int{0});
+ fake_state_.updater_provider()->var_server_dictated_poll_interval()->
+ reset(new unsigned int{0});
+ fake_state_.updater_provider()->var_forced_update_requested()->
+ reset(new UpdateRequestStatus{UpdateRequestStatus::kNone});
+
+ fake_state_.random_provider()->var_seed()->reset(
+ new uint64_t(4)); // chosen by fair dice roll.
+ // guaranteed to be random.
+
+ // No device policy loaded by default.
+ fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+ new bool(false));
+
+ // OOBE is enabled by default.
+ fake_state_.config_provider()->var_is_oobe_enabled()->reset(
+ new bool(true));
+
+ // For the purpose of the tests, this is an official build and OOBE was
+ // completed.
+ fake_state_.system_provider()->var_is_official_build()->reset(
+ new bool(true));
+ fake_state_.system_provider()->var_is_oobe_complete()->reset(
+ new bool(true));
+ fake_state_.system_provider()->var_num_slots()->reset(new unsigned int(2));
+
+ // Connection is wifi, untethered.
+ fake_state_.shill_provider()->var_conn_type()->
+ reset(new ConnectionType(ConnectionType::kWifi));
+ fake_state_.shill_provider()->var_conn_tethering()->
+ reset(new ConnectionTethering(ConnectionTethering::kNotDetected));
+ }
+
+ // Sets up a default device policy that does not impose any restrictions
+ // (HTTP) nor enables any features (P2P).
+ void SetUpDefaultDevicePolicy() {
+ fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_update_disabled()->reset(
+ new bool(false));
+ fake_state_.device_policy_provider()->
+ var_allowed_connection_types_for_update()->reset(nullptr);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta());
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(false));
+ fake_state_.device_policy_provider()->var_release_channel_delegated()->
+ reset(new bool(true));
+ }
+
+ // Configures the UpdateCheckAllowed policy to return a desired value by
+ // faking the current wall clock time as needed. Restores the default state.
+ // This is used when testing policies that depend on this one.
+ void SetUpdateCheckAllowed(bool allow_check) {
+ Time next_update_check;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime,
+ &next_update_check);
+ SetUpDefaultState();
+ SetUpDefaultDevicePolicy();
+ 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);
+ }
+
+ // Returns a default UpdateState structure:
+ UpdateState GetDefaultUpdateState(TimeDelta first_seen_period) {
+ Time first_seen_time = fake_clock_.GetWallclockTime() - first_seen_period;
+ UpdateState update_state = UpdateState();
+
+ // This is a non-interactive check returning a delta payload, seen for the
+ // first time (|first_seen_period| ago). Clearly, there were no failed
+ // attempts so far.
+ update_state.is_interactive = false;
+ update_state.is_delta_payload = false;
+ update_state.first_seen = first_seen_time;
+ update_state.num_checks = 1;
+ update_state.num_failures = 0;
+ update_state.failures_last_updated = Time(); // Needs to be zero.
+ // There's a single HTTP download URL with a maximum of 10 retries.
+ update_state.download_urls = vector<string>{"http://fake/url/"};
+ update_state.download_errors_max = 10;
+ // Download was never attempted.
+ update_state.last_download_url_idx = -1;
+ update_state.last_download_url_num_errors = 0;
+ // There were no download errors.
+ update_state.download_errors = vector<tuple<int, ErrorCode, Time>>();
+ // P2P is not disabled by Omaha.
+ update_state.p2p_downloading_disabled = false;
+ update_state.p2p_sharing_disabled = false;
+ // P2P was not attempted.
+ update_state.p2p_num_attempts = 0;
+ update_state.p2p_first_attempted = Time();
+ // No active backoff period, backoff is not disabled by Omaha.
+ update_state.backoff_expiry = Time();
+ update_state.is_backoff_disabled = false;
+ // There is no active scattering wait period (max 7 days allowed) nor check
+ // threshold (none allowed).
+ update_state.scatter_wait_period = TimeDelta();
+ update_state.scatter_check_threshold = 0;
+ update_state.scatter_wait_period_max = TimeDelta::FromDays(7);
+ update_state.scatter_check_threshold_min = 0;
+ update_state.scatter_check_threshold_max = 0;
+
+ return update_state;
+ }
+
+ // Runs the passed |policy_method| policy and expects it to return the
+ // |expected| return value.
+ template<typename T, typename R, typename... Args>
+ void ExpectPolicyStatus(
+ EvalStatus expected,
+ T policy_method,
+ R* result, Args... args) {
+ string error = "<None>";
+ eval_ctx_->ResetEvaluation();
+ EXPECT_EQ(expected,
+ (policy_.*policy_method)(eval_ctx_.get(), &fake_state_, &error,
+ result, args...))
+ << "Returned error: " << error
+ << "\nEvaluation context: " << eval_ctx_->DumpContext();
+ }
+
+ brillo::FakeMessageLoop loop_{nullptr};
+ FakeClock fake_clock_;
+ FakeState fake_state_;
+ scoped_refptr<EvaluationContext> eval_ctx_;
+ ChromeOSPolicy policy_; // ChromeOSPolicy under test.
+};
+
+TEST_F(UmChromeOSPolicyTest, FirstCheckIsAtMostInitialIntervalAfterStart) {
+ Time next_update_check;
+
+ // 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)));
+
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ EXPECT_LE(fake_clock_.GetWallclockTime(), next_update_check);
+ EXPECT_GE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ ChromeOSPolicy::kTimeoutInitialInterval +
+ ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+ next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckBaseIntervalAndFuzz) {
+ // Ensure that we're using the correct interval (kPeriodicInterval) and fuzz
+ // (kTimeoutRegularFuzz) as base values for period updates.
+ Time next_update_check;
+
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ EXPECT_LE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ ChromeOSPolicy::kTimeoutPeriodicInterval -
+ ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+ next_update_check);
+ EXPECT_GE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ ChromeOSPolicy::kTimeoutPeriodicInterval +
+ ChromeOSPolicy::kTimeoutRegularFuzz / 2),
+ next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckBackoffIntervalAndFuzz) {
+ // Ensure that we're properly backing off and fuzzing in the presence of
+ // failed updates attempts.
+ Time next_update_check;
+
+ fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+ reset(new unsigned int{2});
+
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ int expected_interval = ChromeOSPolicy::kTimeoutPeriodicInterval * 4;
+ EXPECT_LE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ expected_interval - expected_interval / 2),
+ next_update_check);
+ EXPECT_GE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ expected_interval + expected_interval / 2),
+ next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, RecurringCheckServerDictatedPollInterval) {
+ // Policy honors the server provided check poll interval.
+ Time next_update_check;
+
+ const unsigned int kInterval = ChromeOSPolicy::kTimeoutPeriodicInterval * 4;
+ fake_state_.updater_provider()->var_server_dictated_poll_interval()->
+ reset(new unsigned int{kInterval});
+ // We should not be backing off in this case.
+ fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+ reset(new unsigned int{2});
+
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ EXPECT_LE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ kInterval - kInterval / 2),
+ next_update_check);
+ EXPECT_GE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ kInterval + kInterval / 2),
+ next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, ExponentialBackoffIsCapped) {
+ Time next_update_check;
+
+ fake_state_.updater_provider()->var_consecutive_failed_update_checks()->
+ reset(new unsigned int{100});
+
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ EXPECT_LE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ ChromeOSPolicy::kTimeoutMaxBackoffInterval -
+ ChromeOSPolicy::kTimeoutMaxBackoffInterval / 2),
+ next_update_check);
+ EXPECT_GE(
+ fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(
+ ChromeOSPolicy::kTimeoutMaxBackoffInterval +
+ ChromeOSPolicy::kTimeoutMaxBackoffInterval /2),
+ next_update_check);
+}
+
+TEST_F(UmChromeOSPolicyTest, 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);
+
+ fake_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(last_checked_time));
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ UpdateCheckParams result;
+
+ // 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));
+ 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.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForOOBE) {
+ // Update checks are deferred until OOBE is completed.
+
+ // 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_state_.updater_provider()->var_last_checked_time()->reset(
+ new Time(last_checked_time));
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &ChromeOSPolicy::NextUpdateCheckTime, &next_update_check);
+
+ 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));
+ fake_state_.system_provider()->var_is_oobe_complete()->reset(
+ new bool(false));
+
+ UpdateCheckParams result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+ &Policy::UpdateCheckAllowed, &result);
+
+ // Now check that it is allowed if OOBE is completed.
+ 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.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWithAttributes) {
+ // Update check is allowed, response includes attributes for use in the
+ // request.
+ SetUpdateCheckAllowed(true);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_target_version_prefix()->
+ reset(new string("1.2"));
+ fake_state_.device_policy_provider()->var_release_channel_delegated()->
+ reset(new bool(false));
+ fake_state_.device_policy_provider()->var_release_channel()->
+ reset(new string("foo-channel"));
+
+ UpdateCheckParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCheckAllowed, &result);
+ EXPECT_TRUE(result.updates_enabled);
+ EXPECT_EQ("1.2", result.target_version_prefix);
+ EXPECT_EQ("foo-channel", result.target_channel);
+ EXPECT_FALSE(result.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ 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(UmChromeOSPolicyTest,
+ UpdateCheckAllowedUpdatesDisabledForRemovableBootDevice) {
+ // UpdateCheckAllowed should return false (kSucceeded) if the image booted
+ // from a removable device.
+
+ 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(UmChromeOSPolicyTest, UpdateCheckAllowedUpdatesDisabledByPolicy) {
+ // UpdateCheckAllowed should return kAskMeAgainLater because a device policy
+ // is loaded and prohibits updates.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_update_disabled()->reset(
+ new bool(true));
+
+ UpdateCheckParams result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater,
+ &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ 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.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, 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.is_interactive);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartFailsCheckAllowedError) {
+ // The UpdateCanStart policy fails, not being able to query
+ // UpdateCheckAllowed.
+
+ // Configure the UpdateCheckAllowed policy to fail.
+ fake_state_.updater_provider()->var_updater_started_time()->reset(nullptr);
+
+ // Check that the UpdateCanStart fails.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kFailed,
+ &Policy::UpdateCanStart, &result, update_state);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartNotAllowedCheckDue) {
+ // The UpdateCanStart policy returns false because we are due for another
+ // update check. Ensure that download related values are still returned.
+
+ SetUpdateCheckAllowed(true);
+
+ // Check that the UpdateCanStart returns false.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCanStart, &result, update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kCheckDue, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_EQ(0, result.download_url_num_errors);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoDevicePolicy) {
+ // The UpdateCanStart policy returns true; no device policy is loaded.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+ new bool(false));
+
+ // Check that the UpdateCanStart returns true with no further attributes.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCanStart, &result, update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_FALSE(result.p2p_downloading_allowed);
+ EXPECT_FALSE(result.p2p_sharing_allowed);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBlankPolicy) {
+ // The UpdateCanStart policy returns true; device policy is loaded but imposes
+ // no restrictions on updating.
+
+ SetUpdateCheckAllowed(false);
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded,
+ &Policy::UpdateCanStart, &result, update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_FALSE(result.p2p_downloading_allowed);
+ EXPECT_FALSE(result.p2p_sharing_allowed);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedBackoffNewWaitPeriodApplies) {
+ // The UpdateCanStart policy returns false; failures are reported and a new
+ // backoff period is enacted.
+
+ SetUpdateCheckAllowed(false);
+
+ 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(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kBackoff, result.cannot_start_reason);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedBackoffPrevWaitPeriodStillApplies) {
+ // The UpdateCanStart policy returns false; a previously enacted backoff
+ // period still applies.
+
+ SetUpdateCheckAllowed(false);
+
+ 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(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ update_state.failures_last_updated = curr_time;
+ update_state.backoff_expiry = curr_time + TimeDelta::FromMinutes(3);
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::UpdateCanStart,
+ &result, update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kBackoff, result.cannot_start_reason);
+ EXPECT_FALSE(result.do_increment_failures);
+ EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffSatisfied) {
+ // The UpdateCanStart policy returns true; a previously enacted backoff period
+ // has elapsed, we're good to go.
+
+ SetUpdateCheckAllowed(false);
+
+ 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(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ update_state.failures_last_updated = curr_time - TimeDelta::FromSeconds(1);
+ update_state.backoff_expiry = curr_time - TimeDelta::FromSeconds(1);
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart,
+ &result, update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+ EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedBackoffDisabled) {
+ // The UpdateCanStart policy returns false; failures are reported but backoff
+ // is disabled.
+
+ SetUpdateCheckAllowed(false);
+
+ 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(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ update_state.is_backoff_disabled = true;
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffInteractive) {
+ // The UpdateCanStart policy returns false; failures are reported but this is
+ // an interactive update check.
+
+ SetUpdateCheckAllowed(false);
+
+ 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(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ update_state.is_interactive = true;
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffDelta) {
+ // The UpdateCanStart policy returns false; failures are reported but this is
+ // a delta payload.
+
+ SetUpdateCheckAllowed(false);
+
+ 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(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ update_state.is_delta_payload = true;
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoBackoffUnofficialBuild) {
+ // The UpdateCanStart policy returns false; failures are reported but this is
+ // an unofficial build.
+
+ SetUpdateCheckAllowed(false);
+
+ 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(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+
+ fake_state_.system_provider()->var_is_official_build()->
+ reset(new bool(false));
+
+ // Check that UpdateCanStart returns false and a new backoff expiry is
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kUndefined, result.cannot_start_reason);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_EQ(Time(), result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartFailsScatteringFailed) {
+ // The UpdateCanStart policy fails because the UpdateScattering policy it
+ // depends on fails (unset variable).
+
+ SetUpdateCheckAllowed(false);
+
+ // Override the default seed variable with a null value so that the policy
+ // request would fail.
+ // TODO(garnold) This failure may or may not fail a number
+ // sub-policies/decisions, like scattering and backoff. We'll need a more
+ // deliberate setup to ensure that we're failing what we want to be failing.
+ fake_state_.random_provider()->var_seed()->reset(nullptr);
+
+ // Check that the UpdateCanStart fails.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kFailed,
+ &Policy::UpdateCanStart, &result, update_state);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringNewWaitPeriodApplies) {
+ // The UpdateCanStart policy returns false; device policy is loaded and
+ // scattering applies due to an unsatisfied wait period, which was newly
+ // generated.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromMinutes(2)));
+
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+
+ // Check that the UpdateCanStart returns false and a new wait period
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+ EXPECT_LT(TimeDelta(), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringPrevWaitPeriodStillApplies) {
+ // The UpdateCanStart policy returns false w/ kAskMeAgainLater; device policy
+ // is loaded and a previously generated scattering period still applies, none
+ // of the scattering values has changed.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromMinutes(2)));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.scatter_wait_period = TimeDelta::FromSeconds(35);
+
+ // Check that the UpdateCanStart returns false and a new wait period
+ // generated.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::UpdateCanStart,
+ &result, update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+ EXPECT_EQ(TimeDelta::FromSeconds(35), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringNewCountThresholdApplies) {
+ // The UpdateCanStart policy returns false; device policy is loaded and
+ // scattering applies due to an unsatisfied update check count threshold.
+ //
+ // This ensures a non-zero check threshold, which may or may not be combined
+ // with a non-zero wait period (for which we cannot reliably control).
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromSeconds(1)));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 5;
+
+ // Check that the UpdateCanStart returns false.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+ EXPECT_LE(2, result.scatter_check_threshold);
+ EXPECT_GE(5, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartNotAllowedScatteringPrevCountThresholdStillApplies) {
+ // The UpdateCanStart policy returns false; device policy is loaded and
+ // scattering due to a previously generated count threshold still applies.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromSeconds(1)));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.scatter_check_threshold = 3;
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 5;
+
+ // Check that the UpdateCanStart returns false.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kScattering, result.cannot_start_reason);
+ EXPECT_EQ(3, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedScatteringSatisfied) {
+ // The UpdateCanStart policy returns true; device policy is loaded and
+ // scattering is enabled, but both wait period and check threshold are
+ // satisfied.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromSeconds(120)));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(75));
+ update_state.num_checks = 4;
+ update_state.scatter_wait_period = TimeDelta::FromSeconds(60);
+ update_state.scatter_check_threshold = 3;
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 5;
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedInteractivePreventsScattering) {
+ // The UpdateCanStart policy returns true; device policy is loaded and
+ // scattering would have applied, except that the update check is interactive
+ // and so it is suppressed.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromSeconds(1)));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.is_interactive = true;
+ update_state.scatter_check_threshold = 0;
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 5;
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedOobePreventsScattering) {
+ // The UpdateCanStart policy returns true; device policy is loaded and
+ // scattering would have applied, except that OOBE was not completed and so it
+ // is suppressed.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromSeconds(1)));
+ fake_state_.system_provider()->var_is_oobe_complete()->reset(new bool(false));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.is_interactive = true;
+ update_state.scatter_check_threshold = 0;
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 5;
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(TimeDelta(), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithAttributes) {
+ // The UpdateCanStart policy returns true; device policy permits both HTTP and
+ // P2P updates, as well as a non-empty target channel string.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithP2PFromUpdater) {
+ // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+ // P2P updates, but the updater is configured to allow P2P and overrules the
+ // setting.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PDownloadingBlockedDueToOmaha) {
+ // The UpdateCanStart policy returns true; device policy permits HTTP, but
+ // policy blocks P2P downloading because Omaha forbids it. P2P sharing is
+ // still permitted.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.p2p_downloading_disabled = true;
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_FALSE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PSharingBlockedDueToOmaha) {
+ // The UpdateCanStart policy returns true; device policy permits HTTP, but
+ // policy blocks P2P sharing because Omaha forbids it. P2P downloading is
+ // still permitted.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.p2p_sharing_disabled = true;
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_FALSE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PDownloadingBlockedDueToNumAttempts) {
+ // The UpdateCanStart policy returns true; device policy permits HTTP but
+ // blocks P2P download, because the max number of P2P downloads have been
+ // attempted. P2P sharing is still permitted.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.p2p_num_attempts = ChromeOSPolicy::kMaxP2PAttempts;
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_FALSE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedP2PDownloadingBlockedDueToAttemptsPeriod) {
+ // The UpdateCanStart policy returns true; device policy permits HTTP but
+ // blocks P2P download, because the max period for attempt to download via P2P
+ // has elapsed. P2P sharing is still permitted.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.p2p_num_attempts = 1;
+ update_state.p2p_first_attempted =
+ fake_clock_.GetWallclockTime() -
+ TimeDelta::FromSeconds(
+ ChromeOSPolicy::kMaxP2PAttemptsPeriodInSeconds + 1);
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_FALSE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedWithHttpUrlForUnofficialBuild) {
+ // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+ // P2P updates, but marking this an unofficial build overrules the HTTP
+ // setting.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(false));
+ fake_state_.system_provider()->var_is_official_build()->
+ reset(new bool(false));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithHttpsUrl) {
+ // The UpdateCanStart policy returns true; device policy forbids both HTTP and
+ // P2P updates, but an HTTPS URL is provided and selected for download.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(false));
+
+ // Add an HTTPS URL.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.download_urls.emplace_back("https://secure/url/");
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(1, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedMaxErrorsNotExceeded) {
+ // The UpdateCanStart policy returns true; the first URL has download errors
+ // but does not exceed the maximum allowed number of failures, so it is stilli
+ // usable.
+
+ SetUpdateCheckAllowed(false);
+
+ // Add a second URL; update with this URL attempted and failed enough times to
+ // disqualify the current (first) URL.
+ 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);
+ for (int i = 0; i < 5; i++) {
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError, t);
+ t += TimeDelta::FromSeconds(1);
+ }
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(5, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithSecondUrlMaxExceeded) {
+ // The UpdateCanStart policy returns true; the first URL exceeded the maximum
+ // allowed number of failures, but a second URL is available.
+
+ SetUpdateCheckAllowed(false);
+
+ // Add a second URL; update with this URL attempted and failed enough times to
+ // disqualify the current (first) URL.
+ 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);
+ for (int i = 0; i < 11; i++) {
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError, t);
+ t += TimeDelta::FromSeconds(1);
+ }
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(1, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedWithSecondUrlHardError) {
+ // The UpdateCanStart policy returns true; the first URL fails with a hard
+ // error, but a second URL is available.
+
+ SetUpdateCheckAllowed(false);
+
+ // Add a second URL; update with this URL attempted and failed in a way that
+ // causes it to switch directly to the next URL.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.num_checks = 10;
+ update_state.download_urls.emplace_back("http://another/fake/url/");
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kPayloadHashMismatchError,
+ fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(1, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedUrlWrapsAround) {
+ // The UpdateCanStart policy returns true; URL search properly wraps around
+ // the last one on the list.
+
+ SetUpdateCheckAllowed(false);
+
+ // Add a second URL; update with this URL attempted and failed in a way that
+ // causes it to switch directly to the next URL. We must disable backoff in
+ // order for it not to interfere.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ update_state.num_checks = 1;
+ update_state.is_backoff_disabled = true;
+ update_state.download_urls.emplace_back("http://another/fake/url/");
+ update_state.download_errors.emplace_back(
+ 1, ErrorCode::kPayloadHashMismatchError,
+ fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartNotAllowedNoUsableUrls) {
+ // The UpdateCanStart policy returns false; there's a single HTTP URL but its
+ // use is forbidden by policy.
+ //
+ // Note: In the case where no usable URLs are found, the policy should not
+ // increment the number of failed attempts! Doing so would result in a
+ // non-idempotent semantics, and does not fall within the intended purpose of
+ // the backoff mechanism anyway.
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(false));
+
+ // Check that the UpdateCanStart returns false.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_FALSE(result.update_can_start);
+ EXPECT_EQ(UpdateCannotStartReason::kCannotDownload,
+ result.cannot_start_reason);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanStartAllowedNoUsableUrlsButP2PEnabled) {
+ // The UpdateCanStart policy returns true; there's a single HTTP URL but its
+ // use is forbidden by policy, however P2P is enabled. The result indicates
+ // that no URL can be used.
+ //
+ // Note: The number of failed attempts should not increase in this case (see
+ // above test).
+
+ SetUpdateCheckAllowed(false);
+
+ // Override specific device policy attributes.
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+ fake_state_.device_policy_provider()->var_http_downloads_enabled()->reset(
+ new bool(false));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_GT(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_FALSE(result.do_increment_failures);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedNoUsableUrlsButEnterpriseEnrolled) {
+ // The UpdateCanStart policy returns true; there's a single HTTP URL but its
+ // use is forbidden by policy, and P2P is unset on the policy, however the
+ // device is enterprise-enrolled so P2P is allowed. The result indicates that
+ // no URL can be used.
+ //
+ // Note: The number of failed attempts should not increase in this case (see
+ // above test).
+
+ SetUpdateCheckAllowed(false);
+
+ // 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_http_downloads_enabled()->reset(
+ new bool(false));
+
+ // Check that the UpdateCanStart returns true.
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromMinutes(10));
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_GT(0, result.download_url_idx);
+ EXPECT_TRUE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ 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
+ // are download URL values, albeit the latter are not allowed to be used.
+
+ SetUpdateCheckAllowed(false);
+ fake_state_.device_policy_provider()->var_scatter_factor()->reset(
+ new TimeDelta(TimeDelta::FromMinutes(2)));
+ fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+ UpdateState update_state = GetDefaultUpdateState(TimeDelta::FromSeconds(1));
+ update_state.scatter_wait_period = TimeDelta::FromSeconds(35);
+
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart,
+ &result, update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_FALSE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_FALSE(result.do_increment_failures);
+ EXPECT_EQ(TimeDelta::FromSeconds(35), result.scatter_wait_period);
+ EXPECT_EQ(0, result.scatter_check_threshold);
+}
+
+TEST_F(UmChromeOSPolicyTest,
+ UpdateCanStartAllowedBackoffSupressedDueToP2P) {
+ // The UpdateCanStart policy returns true; backoff should have applied, but
+ // P2P download is allowed. Backoff values are nonetheless returned, and so
+ // are download URL values, albeit the latter are not allowed to be used.
+
+ SetUpdateCheckAllowed(false);
+
+ 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(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(8));
+ update_state.download_errors.emplace_back(
+ 0, ErrorCode::kDownloadTransferError,
+ curr_time - TimeDelta::FromSeconds(2));
+ fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+ UpdateDownloadParams result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::UpdateCanStart, &result,
+ update_state);
+ EXPECT_TRUE(result.update_can_start);
+ EXPECT_EQ(0, result.download_url_idx);
+ EXPECT_FALSE(result.download_url_allowed);
+ EXPECT_EQ(0, result.download_url_num_errors);
+ EXPECT_TRUE(result.p2p_downloading_allowed);
+ EXPECT_TRUE(result.p2p_sharing_allowed);
+ EXPECT_TRUE(result.do_increment_failures);
+ EXPECT_LT(curr_time, result.backoff_expiry);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledNotAllowed) {
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+ EXPECT_FALSE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedByDevicePolicy) {
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(
+ new bool(true));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedByUpdater) {
+ fake_state_.updater_provider()->var_p2p_enabled()->reset(new bool(true));
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledAllowedDeviceEnterpriseEnrolled) {
+ fake_state_.device_policy_provider()->var_au_p2p_enabled()->reset(nullptr);
+ fake_state_.device_policy_provider()->var_owner()->reset(nullptr);
+
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kSucceeded, &Policy::P2PEnabled, &result);
+ EXPECT_TRUE(result);
+}
+
+TEST_F(UmChromeOSPolicyTest, P2PEnabledChangedBlocks) {
+ bool result;
+ ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, &Policy::P2PEnabledChanged,
+ &result, false);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/config_provider.h b/update_manager/config_provider.h
new file mode 100644
index 0000000..36d57a7
--- /dev/null
+++ b/update_manager/config_provider.h
@@ -0,0 +1,43 @@
+//
+// 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_UPDATE_MANAGER_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_CONFIG_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for const system configurations. This provider reads the
+// configuration from a file on /etc.
+class ConfigProvider : public Provider {
+ public:
+ // Returns a variable stating whether the out of the box experience (OOBE) is
+ // enabled on this device. A value of false means that the device doesn't have
+ // an OOBE workflow.
+ virtual Variable<bool>* var_is_oobe_enabled() = 0;
+
+ protected:
+ ConfigProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ConfigProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_CONFIG_PROVIDER_H_
diff --git a/update_manager/default_policy.cc b/update_manager/default_policy.cc
new file mode 100644
index 0000000..9a5ce7e
--- /dev/null
+++ b/update_manager/default_policy.cc
@@ -0,0 +1,107 @@
+//
+// 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/update_manager/default_policy.h"
+
+namespace {
+
+// A fixed minimum interval between consecutive allowed update checks. This
+// needs to be long enough to prevent busywork and/or DDoS attacks on Omaha, but
+// at the same time short enough to allow the machine to update itself
+// reasonably soon.
+const int kCheckIntervalInSeconds = 15 * 60;
+
+} // namespace
+
+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->target_version_prefix.clear();
+ result->is_interactive = false;
+
+ // Ensure that the minimum interval is set. If there's no clock, this defaults
+ // to always allowing the update.
+ if (!aux_state_->IsLastCheckAllowedTimeSet() ||
+ ec->IsMonotonicTimeGreaterThan(
+ aux_state_->last_check_allowed_time() +
+ base::TimeDelta::FromSeconds(kCheckIntervalInSeconds))) {
+ if (clock_)
+ aux_state_->set_last_check_allowed_time(clock_->GetMonotonicTime());
+ return EvalStatus::kSucceeded;
+ }
+
+ return EvalStatus::kAskMeAgainLater;
+}
+
+EvalStatus DefaultPolicy::UpdateCanStart(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ UpdateDownloadParams* result,
+ const UpdateState update_state) const {
+ result->update_can_start = true;
+ result->cannot_start_reason = UpdateCannotStartReason::kUndefined;
+ result->download_url_idx = 0;
+ result->download_url_allowed = true;
+ result->download_url_num_errors = 0;
+ result->p2p_downloading_allowed = false;
+ result->p2p_sharing_allowed = false;
+ result->do_increment_failures = false;
+ result->backoff_expiry = base::Time();
+ result->scatter_wait_period = base::TimeDelta();
+ result->scatter_check_threshold = 0;
+ 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,
+ bool* result) const {
+ *result = false;
+ return EvalStatus::kSucceeded;
+}
+
+EvalStatus DefaultPolicy::P2PEnabledChanged(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ bool* result,
+ bool prev_result) const {
+ // This policy will always prohibit P2P, so this is signaling to the caller
+ // that the decision is final (because the current value is the same as the
+ // previous one) and there's no need to issue another call.
+ *result = false;
+ return EvalStatus::kSucceeded;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/default_policy.h b/update_manager/default_policy.h
new file mode 100644
index 0000000..3f41178
--- /dev/null
+++ b/update_manager/default_policy.h
@@ -0,0 +1,105 @@
+//
+// 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_UPDATE_MANAGER_DEFAULT_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_DEFAULT_POLICY_H_
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/update_manager/policy.h"
+
+namespace chromeos_update_manager {
+
+// Auxiliary state class for DefaultPolicy evaluations.
+//
+// IMPORTANT: The use of a state object in policies is generally forbidden, as
+// it was a design decision to keep policy calls side-effect free. We make an
+// exception here to ensure that DefaultPolicy indeed serves as a safe (and
+// secure) fallback option. This practice should be avoided when imlpementing
+// other policies.
+class DefaultPolicyState {
+ public:
+ DefaultPolicyState() {}
+
+ bool IsLastCheckAllowedTimeSet() const {
+ return last_check_allowed_time_ != base::Time::Max();
+ }
+
+ // Sets/returns the point time on the monotonic time scale when the latest
+ // check allowed was recorded.
+ void set_last_check_allowed_time(base::Time timestamp) {
+ last_check_allowed_time_ = timestamp;
+ }
+ base::Time last_check_allowed_time() const {
+ return last_check_allowed_time_;
+ }
+
+ private:
+ base::Time last_check_allowed_time_ = base::Time::Max();
+};
+
+// The DefaultPolicy is a safe Policy implementation that doesn't fail. The
+// values returned by this policy are safe default in case of failure of the
+// actual policy being used by the UpdateManager.
+class DefaultPolicy : public Policy {
+ public:
+ explicit DefaultPolicy(chromeos_update_engine::ClockInterface* clock);
+ DefaultPolicy() : DefaultPolicy(nullptr) {}
+ ~DefaultPolicy() override {}
+
+ // Policy overrides.
+ EvalStatus UpdateCheckAllowed(
+ EvaluationContext* ec, State* state, std::string* error,
+ UpdateCheckParams* result) const override;
+
+ EvalStatus UpdateCanStart(
+ EvaluationContext* ec, State* state, std::string* error,
+ 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,
+ bool* result) const override;
+
+ 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 "DefaultPolicy"; }
+
+ private:
+ // A clock interface.
+ chromeos_update_engine::ClockInterface* clock_;
+
+ // An auxiliary state object.
+ std::unique_ptr<DefaultPolicyState> aux_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(DefaultPolicy);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_DEFAULT_POLICY_H_
diff --git a/update_manager/device_policy_provider.h b/update_manager/device_policy_provider.h
new file mode 100644
index 0000000..f75d470
--- /dev/null
+++ b/update_manager/device_policy_provider.h
@@ -0,0 +1,75 @@
+//
+// 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_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include <base/time/time.h>
+#include <policy/libpolicy.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provides access to the current DevicePolicy.
+class DevicePolicyProvider : public Provider {
+ public:
+ ~DevicePolicyProvider() override {}
+
+ // Variable stating whether the DevicePolicy was loaded.
+ virtual Variable<bool>* var_device_policy_is_loaded() = 0;
+
+ // Variables mapping the information received on the DevicePolicy protobuf.
+ virtual Variable<std::string>* var_release_channel() = 0;
+
+ virtual Variable<bool>* var_release_channel_delegated() = 0;
+
+ virtual Variable<bool>* var_update_disabled() = 0;
+
+ virtual Variable<std::string>* var_target_version_prefix() = 0;
+
+ // Returns a non-negative scatter interval used for updates.
+ virtual Variable<base::TimeDelta>* var_scatter_factor() = 0;
+
+ // Variable returning the set of connection types allowed for updates. The
+ // identifiers returned are consistent with the ones returned by the
+ // ShillProvider.
+ virtual Variable<std::set<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;
+
+ virtual Variable<bool>* var_http_downloads_enabled() = 0;
+
+ virtual Variable<bool>* var_au_p2p_enabled() = 0;
+
+ protected:
+ DevicePolicyProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DevicePolicyProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_manager/evaluation_context-inl.h b/update_manager/evaluation_context-inl.h
new file mode 100644
index 0000000..937adf4
--- /dev/null
+++ b/update_manager/evaluation_context-inl.h
@@ -0,0 +1,55 @@
+//
+// 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_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
+
+#include <string>
+
+#include <base/logging.h>
+
+namespace chromeos_update_manager {
+
+template<typename T>
+const T* EvaluationContext::GetValue(Variable<T>* var) {
+ if (var == nullptr) {
+ LOG(ERROR) << "GetValue received an uninitialized variable.";
+ return nullptr;
+ }
+
+ // Search for the value on the cache first.
+ ValueCacheMap::iterator it = value_cache_.find(var);
+ if (it != value_cache_.end())
+ return reinterpret_cast<const T*>(it->second.value());
+
+ // Get the value from the variable if not found on the cache.
+ std::string errmsg;
+ const T* result = var->GetValue(RemainingTime(evaluation_monotonic_deadline_),
+ &errmsg);
+ if (result == nullptr) {
+ LOG(WARNING) << "Error reading Variable " << var->GetName() << ": \""
+ << errmsg << "\"";
+ }
+ // Cache the value for the next time. The map of CachedValues keeps the
+ // ownership of the pointer until the map is destroyed.
+ value_cache_.emplace(
+ static_cast<BaseVariable*>(var), BoxedValue(result));
+ return result;
+}
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_INL_H_
diff --git a/update_manager/evaluation_context.cc b/update_manager/evaluation_context.cc
new file mode 100644
index 0000000..03ac0b7
--- /dev/null
+++ b/update_manager/evaluation_context.cc
@@ -0,0 +1,252 @@
+//
+// 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/update_manager/evaluation_context.h"
+
+#include <algorithm>
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/json/json_writer.h>
+#include <base/location.h>
+#include <base/strings/string_util.h>
+#include <base/values.h>
+
+#include "update_engine/common/utils.h"
+
+using base::Callback;
+using base::Closure;
+using base::Time;
+using base::TimeDelta;
+using brillo::MessageLoop;
+using chromeos_update_engine::ClockInterface;
+using std::string;
+using std::unique_ptr;
+
+namespace {
+
+// Returns whether |curr_time| surpassed |ref_time|; if not, also checks whether
+// |ref_time| is sooner than the current value of |*reeval_time|, in which case
+// the latter is updated to the former.
+bool IsTimeGreaterThanHelper(Time ref_time, Time curr_time,
+ Time* reeval_time) {
+ if (curr_time > ref_time)
+ return true;
+ // Remember the nearest reference we've checked against in this evaluation.
+ if (*reeval_time > ref_time)
+ *reeval_time = ref_time;
+ return false;
+}
+
+// If |expires| never happens (maximal value), returns the maximal interval;
+// otherwise, returns the difference between |expires| and |curr|.
+TimeDelta GetTimeout(Time curr, Time expires) {
+ if (expires.is_max())
+ return TimeDelta::Max();
+ return expires - curr;
+}
+
+} // namespace
+
+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),
+ expiration_timeout_(expiration_timeout),
+ unregister_cb_(std::move(unregister_cb)),
+ weak_ptr_factory_(this) {
+ ResetEvaluation();
+ ResetExpiration();
+}
+
+EvaluationContext::~EvaluationContext() {
+ RemoveObserversAndTimeout();
+ if (unregister_cb_.get())
+ unregister_cb_->Run(this);
+}
+
+unique_ptr<Closure> EvaluationContext::RemoveObserversAndTimeout() {
+ for (auto& it : value_cache_) {
+ if (it.first->GetMode() == kVariableModeAsync)
+ it.first->RemoveObserver(this);
+ }
+ MessageLoop::current()->CancelTask(timeout_event_);
+ timeout_event_ = MessageLoop::kTaskIdNull;
+
+ return unique_ptr<Closure>(callback_.release());
+}
+
+TimeDelta EvaluationContext::RemainingTime(Time monotonic_deadline) const {
+ if (monotonic_deadline.is_max())
+ return TimeDelta::Max();
+ TimeDelta remaining = monotonic_deadline - clock_->GetMonotonicTime();
+ return std::max(remaining, TimeDelta());
+}
+
+Time EvaluationContext::MonotonicDeadline(TimeDelta timeout) {
+ return (timeout.is_max() ? Time::Max() :
+ clock_->GetMonotonicTime() + timeout);
+}
+
+void EvaluationContext::ValueChanged(BaseVariable* var) {
+ DLOG(INFO) << "ValueChanged() called for variable " << var->GetName();
+ OnValueChangedOrTimeout();
+}
+
+void EvaluationContext::OnTimeout() {
+ DLOG(INFO) << "OnTimeout() called due to "
+ << (timeout_marks_expiration_ ? "expiration" : "poll interval");
+ timeout_event_ = MessageLoop::kTaskIdNull;
+ is_expired_ = timeout_marks_expiration_;
+ OnValueChangedOrTimeout();
+}
+
+void EvaluationContext::OnValueChangedOrTimeout() {
+ // Copy the callback handle locally, allowing it to be reassigned.
+ unique_ptr<Closure> callback = RemoveObserversAndTimeout();
+
+ if (callback.get())
+ callback->Run();
+}
+
+bool EvaluationContext::IsWallclockTimeGreaterThan(Time timestamp) {
+ return IsTimeGreaterThanHelper(timestamp, evaluation_start_wallclock_,
+ &reevaluation_time_wallclock_);
+}
+
+bool EvaluationContext::IsMonotonicTimeGreaterThan(Time timestamp) {
+ return IsTimeGreaterThanHelper(timestamp, evaluation_start_monotonic_,
+ &reevaluation_time_monotonic_);
+}
+
+void EvaluationContext::ResetEvaluation() {
+ 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_);
+
+ // Remove the cached values of non-const variables
+ for (auto it = value_cache_.begin(); it != value_cache_.end(); ) {
+ if (it->first->GetMode() == kVariableModeConst) {
+ ++it;
+ } else {
+ it = value_cache_.erase(it);
+ }
+ }
+}
+
+void EvaluationContext::ResetExpiration() {
+ expiration_monotonic_deadline_ = MonotonicDeadline(expiration_timeout_);
+ is_expired_ = false;
+}
+
+bool EvaluationContext::RunOnValueChangeOrTimeout(Closure callback) {
+ // Check that the method was not called more than once.
+ if (callback_.get()) {
+ LOG(ERROR) << "RunOnValueChangeOrTimeout called more than once.";
+ return false;
+ }
+
+ // Check that the context did not yet expire.
+ if (is_expired()) {
+ LOG(ERROR) << "RunOnValueChangeOrTimeout called on an expired context.";
+ return false;
+ }
+
+ // Handle reevaluation due to a Is{Wallclock,Monotonic}TimeGreaterThan(). We
+ // choose the smaller of the differences between evaluation start time and
+ // reevaluation time among the wallclock and monotonic scales.
+ TimeDelta timeout = std::min(
+ GetTimeout(evaluation_start_wallclock_, reevaluation_time_wallclock_),
+ GetTimeout(evaluation_start_monotonic_, reevaluation_time_monotonic_));
+
+ // Handle reevaluation due to async or poll variables.
+ bool waiting_for_value_change = false;
+ for (auto& it : value_cache_) {
+ switch (it.first->GetMode()) {
+ case kVariableModeAsync:
+ DLOG(INFO) << "Waiting for value on " << it.first->GetName();
+ it.first->AddObserver(this);
+ waiting_for_value_change = true;
+ break;
+ case kVariableModePoll:
+ timeout = std::min(timeout, it.first->GetPollInterval());
+ break;
+ case kVariableModeConst:
+ // Ignored.
+ break;
+ }
+ }
+
+ // Check if the re-evaluation is actually being scheduled. If there are no
+ // events waited for, this function should return false.
+ if (!waiting_for_value_change && timeout.is_max())
+ return false;
+
+ // Ensure that we take into account the expiration timeout.
+ TimeDelta expiration = RemainingTime(expiration_monotonic_deadline_);
+ timeout_marks_expiration_ = expiration < timeout;
+ if (timeout_marks_expiration_)
+ timeout = expiration;
+
+ // Store the reevaluation callback.
+ callback_.reset(new Closure(callback));
+
+ // Schedule a timeout event, if one is set.
+ if (!timeout.is_max()) {
+ DLOG(INFO) << "Waiting for timeout in "
+ << chromeos_update_engine::utils::FormatTimeDelta(timeout);
+ timeout_event_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&EvaluationContext::OnTimeout,
+ weak_ptr_factory_.GetWeakPtr()),
+ timeout);
+ }
+
+ return true;
+}
+
+string EvaluationContext::DumpContext() const {
+ base::DictionaryValue* variables = new base::DictionaryValue();
+ for (auto& it : value_cache_) {
+ variables->SetString(it.first->GetName(), it.second.ToString());
+ }
+
+ base::DictionaryValue value;
+ value.Set("variables", variables); // Adopts |variables|.
+ value.SetString(
+ "evaluation_start_wallclock",
+ chromeos_update_engine::utils::ToString(evaluation_start_wallclock_));
+ value.SetString(
+ "evaluation_start_monotonic",
+ chromeos_update_engine::utils::ToString(evaluation_start_monotonic_));
+
+ string json_str;
+ base::JSONWriter::WriteWithOptions(
+ value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_str);
+ base::TrimWhitespaceASCII(json_str, base::TRIM_TRAILING, &json_str);
+
+ return json_str;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/evaluation_context.h b/update_manager/evaluation_context.h
new file mode 100644
index 0000000..df5816a
--- /dev/null
+++ b/update_manager/evaluation_context.h
@@ -0,0 +1,221 @@
+//
+// 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_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#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"
+
+namespace chromeos_update_manager {
+
+// The EvaluationContext class is the interface between a policy implementation
+// and the state. The EvaluationContext tracks the variables used by a policy
+// request and caches the returned values, owning those cached values.
+// The same EvaluationContext should be re-used for all the evaluations of the
+// same policy request (an AsyncPolicyRequest might involve several
+// re-evaluations). Each evaluation of the EvaluationContext is run at a given
+// point in time, which is used as a reference for the evaluation timeout and
+// the time based queries of the policy, such as
+// Is{Wallclock,Monotonic}TimeGreaterThan().
+//
+// Example:
+//
+// scoped_refptr<EvaluationContext> ec = new EvaluationContext(...);
+//
+// ...
+// // The following call to ResetEvaluation() is optional. Use it to reset the
+// // evaluation time if the EvaluationContext isn't used right after its
+// // construction.
+// ec->ResetEvaluation();
+// EvalStatus status = policy->SomeMethod(ec, state, &result, args...);
+//
+// ...
+// // Run a closure when any of the used async variables changes its value or
+// // the timeout for re-query the values happens again.
+// ec->RunOnValueChangeOrTimeout(closure);
+// // 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 {
+ 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)
+ : EvaluationContext(
+ clock, evaluation_timeout, base::TimeDelta::Max(),
+ std::unique_ptr<base::Callback<void(EvaluationContext*)>>()) {}
+ ~EvaluationContext();
+
+ // Returns a pointer to the value returned by the passed variable |var|. The
+ // EvaluationContext instance keeps the ownership of the returned object. The
+ // returned object is valid during the life of the evaluation, even if the
+ // passed Variable changes it.
+ //
+ // In case of error, a null value is returned.
+ template<typename T>
+ const T* GetValue(Variable<T>* var);
+
+ // Returns whether the evaluation time has surpassed |timestamp|, on either
+ // the ClockInterface::GetWallclockTime() or
+ // ClockInterface::GetMonotonicTime() scales, respectively.
+ bool IsWallclockTimeGreaterThan(base::Time timestamp);
+ bool IsMonotonicTimeGreaterThan(base::Time timestamp);
+
+ // Returns whether the evaluation context has expired.
+ bool is_expired() const { return is_expired_; }
+
+ // TODO(deymo): Move the following methods to an interface only visible by the
+ // UpdateManager class and not the policy implementations.
+
+ // Resets the EvaluationContext to its initial state removing all the
+ // non-const cached variables and re-setting the evaluation time. This should
+ // be called right before any new evaluation starts.
+ void ResetEvaluation();
+
+ // Clears the expiration status of the EvaluationContext and resets its
+ // expiration timeout based on |expiration_timeout_|. This should be called if
+ // expiration occurred, prior to re-evaluating the policy.
+ void ResetExpiration();
+
+ // Schedules the passed |callback| closure to be called when a cached
+ // variable changes its value, a polling interval passes, or the context
+ // expiration occurs. If none of these events can happen, for example if
+ // there's no cached variable, this method returns false.
+ //
+ // Right before the passed closure is called the EvaluationContext is
+ // reseted, removing all the non-const cached values.
+ bool RunOnValueChangeOrTimeout(base::Closure callback);
+
+ // Returns a textual representation of the evaluation context,
+ // including the variables and their values. This is intended only
+ // to help with debugging and the format may change in the future.
+ std::string DumpContext() const;
+
+ // Removes all the Observers callbacks and timeout events scheduled by
+ // RunOnValueChangeOrTimeout(). Also releases and returns the closure
+ // associated with these events. This method is idempotent.
+ std::unique_ptr<base::Closure> RemoveObserversAndTimeout();
+
+ private:
+ friend class UmEvaluationContextTest;
+
+ // BaseVariable::ObserverInterface override.
+ void ValueChanged(BaseVariable* var) override;
+
+ // Called from the main loop when a scheduled timeout has passed.
+ void OnTimeout();
+
+ // Removes the observers from the used Variables and cancels the timeout,
+ // then executes the scheduled callback.
+ void OnValueChangedOrTimeout();
+
+ // If |monotonic_deadline| is not Time::Max(), returns the remaining time
+ // until it is reached, or zero if it has passed. Otherwise, returns
+ // TimeDelta::Max().
+ base::TimeDelta RemainingTime(base::Time monotonic_deadline) const;
+
+ // Returns a monotonic clock timestamp at which |timeout| will have elapsed
+ // since the current time.
+ base::Time MonotonicDeadline(base::TimeDelta timeout);
+
+ // A map to hold the cached values for every variable.
+ typedef std::map<BaseVariable*, BoxedValue> ValueCacheMap;
+
+ // The cached values of the called Variables.
+ ValueCacheMap value_cache_;
+
+ // A callback used for triggering re-evaluation upon a value change or poll
+ // timeout, or notifying about the evaluation context expiration. It is up to
+ // the caller to determine whether or not expiration occurred via
+ // is_expired().
+ std::unique_ptr<base::Closure> callback_;
+
+ // The TaskId returned by the message loop identifying the timeout callback.
+ // Used for canceling the timeout callback.
+ brillo::MessageLoop::TaskId timeout_event_ =
+ brillo::MessageLoop::kTaskIdNull;
+
+ // Whether a timeout event firing marks the expiration of the evaluation
+ // context.
+ bool timeout_marks_expiration_;
+
+ // 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
+ // every time ResetEvaluation() is called.
+ base::Time evaluation_start_wallclock_;
+ base::Time evaluation_start_monotonic_;
+
+ // The timestamps when a reevaluation should be triggered due to various
+ // expected value changes, corresponding to ClockInterface::GetWallclockTime()
+ // and ClockInterface::GetMonotonicTIme(), respectively. These timestamps are
+ // greater or equal to corresponding |evaluation_start_{wallclock,monotonic}_|
+ // counterparts since they are in the future; however, they may be smaller
+ // than the current corresponding times during the course of evaluation.
+ base::Time reevaluation_time_wallclock_;
+ base::Time reevaluation_time_monotonic_;
+
+ // The timeout of an evaluation.
+ const base::TimeDelta evaluation_timeout_;
+
+ // The timestamp in the ClockInterface::GetMonotonicTime() scale at which the
+ // current evaluation should finish.
+ base::Time evaluation_monotonic_deadline_;
+
+ // The expiration timeout of the evaluation context.
+ const base::TimeDelta expiration_timeout_;
+
+ // The monotonic clock deadline at which expiration occurs.
+ base::Time expiration_monotonic_deadline_;
+
+ // A callback for unregistering the context upon destruction.
+ std::unique_ptr<base::Callback<void(EvaluationContext*)>> unregister_cb_;
+
+ base::WeakPtrFactory<EvaluationContext> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(EvaluationContext);
+};
+
+} // namespace chromeos_update_manager
+
+// Include the implementation of the template methods.
+#include "update_engine/update_manager/evaluation_context-inl.h"
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_EVALUATION_CONTEXT_H_
diff --git a/update_manager/evaluation_context_unittest.cc b/update_manager/evaluation_context_unittest.cc
new file mode 100644
index 0000000..1e61db7
--- /dev/null
+++ b/update_manager/evaluation_context_unittest.cc
@@ -0,0 +1,489 @@
+//
+// 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/update_manager/evaluation_context.h"
+
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.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"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Bind;
+using base::Closure;
+using base::Time;
+using base::TimeDelta;
+using brillo::MessageLoop;
+using brillo::MessageLoopRunMaxIterations;
+using brillo::MessageLoopRunUntil;
+using chromeos_update_engine::FakeClock;
+using std::string;
+using std::unique_ptr;
+using testing::Return;
+using testing::StrictMock;
+using testing::_;
+
+namespace chromeos_update_manager {
+
+namespace {
+
+void DoNothing() {}
+
+// Sets the value of the passed pointer to true.
+void SetTrue(bool* value) {
+ *value = true;
+}
+
+bool GetBoolean(bool* value) {
+ return *value;
+}
+
+template<typename T>
+void ReadVar(scoped_refptr<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,
+ int* count_p, bool* done_p) {
+ evaluation.Run();
+
+ // Schedule reevaluation if needed.
+ if (*count_p > 0) {
+ Closure closure = Bind(EvaluateRepeatedly, evaluation, ec, count_p, done_p);
+ ASSERT_TRUE(ec->RunOnValueChangeOrTimeout(closure))
+ << "Failed to schedule reevaluation, count_p=" << *count_p;
+ (*count_p)--;
+ } else {
+ *done_p = true;
+ }
+}
+
+} // namespace
+
+class UmEvaluationContextTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ // Apr 22, 2009 19:25:00 UTC (this is a random reference point).
+ 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_, default_timeout_, default_timeout_,
+ unique_ptr<base::Callback<void(EvaluationContext*)>>(nullptr));
+ }
+
+ void TearDown() override {
+ // Ensure that the evaluation context did not leak and is actually being
+ // destroyed.
+ if (eval_ctx_) {
+ base::WeakPtr<EvaluationContext> eval_ctx_weak_alias =
+ eval_ctx_->weak_ptr_factory_.GetWeakPtr();
+ ASSERT_NE(nullptr, eval_ctx_weak_alias.get());
+ eval_ctx_ = nullptr;
+ EXPECT_EQ(nullptr, eval_ctx_weak_alias.get())
+ << "The evaluation context was not destroyed! This is likely a bug "
+ "in how the test was written, look for leaking handles to the EC, "
+ "possibly through closure objects.";
+ }
+
+ // Check that the evaluation context removed all the observers.
+ EXPECT_TRUE(fake_int_var_.observer_list_.empty());
+ EXPECT_TRUE(fake_async_var_.observer_list_.empty());
+ EXPECT_TRUE(fake_const_var_.observer_list_.empty());
+ EXPECT_TRUE(fake_poll_var_.observer_list_.empty());
+
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ TimeDelta default_timeout_ = TimeDelta::FromSeconds(5);
+
+ brillo::FakeMessageLoop loop_{nullptr};
+ FakeClock fake_clock_;
+ scoped_refptr<EvaluationContext> eval_ctx_;
+
+ // FakeVariables used for testing the EvaluationContext. These are required
+ // here to prevent them from going away *before* the EvaluationContext under
+ // test does, which keeps a reference to them.
+ FakeVariable<bool> fail_var_ = {"fail_var", kVariableModePoll};
+ FakeVariable<int> fake_int_var_ = {"fake_int", kVariableModePoll};
+ FakeVariable<string> fake_async_var_ = {"fake_async", kVariableModeAsync};
+ FakeVariable<string> fake_const_var_ = {"fake_const", kVariableModeConst};
+ FakeVariable<string> fake_poll_var_ = {"fake_poll",
+ TimeDelta::FromSeconds(1)};
+ StrictMock<MockVariable<string>> mock_var_async_ {
+ "mock_var_async", kVariableModeAsync};
+ StrictMock<MockVariable<string>> mock_var_poll_ {
+ "mock_var_poll", kVariableModePoll};
+};
+
+TEST_F(UmEvaluationContextTest, GetValueFails) {
+ // FakeVariable is initialized as returning null.
+ EXPECT_EQ(nullptr, eval_ctx_->GetValue(&fake_int_var_));
+}
+
+TEST_F(UmEvaluationContextTest, GetValueFailsWithInvalidVar) {
+ EXPECT_EQ(nullptr, eval_ctx_->GetValue(static_cast<Variable<int>*>(nullptr)));
+}
+
+TEST_F(UmEvaluationContextTest, GetValueReturns) {
+ const int* p_fake_int;
+
+ fake_int_var_.reset(new int(42));
+ p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+ ASSERT_NE(nullptr, p_fake_int);
+ EXPECT_EQ(42, *p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueCached) {
+ const int* p_fake_int;
+
+ fake_int_var_.reset(new int(42));
+ p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+
+ // Check that if the variable changes, the EvaluationContext keeps returning
+ // the cached value.
+ fake_int_var_.reset(new int(5));
+
+ p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+ ASSERT_NE(nullptr, p_fake_int);
+ EXPECT_EQ(42, *p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueCachesNull) {
+ const int* p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+ EXPECT_EQ(nullptr, p_fake_int);
+
+ fake_int_var_.reset(new int(42));
+ // A second attempt to read the variable should not work because this
+ // EvaluationContext already got a null value.
+ p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+ EXPECT_EQ(nullptr, p_fake_int);
+}
+
+TEST_F(UmEvaluationContextTest, GetValueMixedTypes) {
+ const int* p_fake_int;
+ const string* p_fake_string;
+
+ fake_int_var_.reset(new int(42));
+ fake_poll_var_.reset(new string("Hello world!"));
+ // Check that the EvaluationContext can handle multiple Variable types. This
+ // is mostly a compile-time check due to the template nature of this method.
+ p_fake_int = eval_ctx_->GetValue(&fake_int_var_);
+ p_fake_string = eval_ctx_->GetValue(&fake_poll_var_);
+
+ ASSERT_NE(nullptr, p_fake_int);
+ EXPECT_EQ(42, *p_fake_int);
+
+ ASSERT_NE(nullptr, p_fake_string);
+ EXPECT_EQ("Hello world!", *p_fake_string);
+}
+
+// Test that we don't schedule an event if there's no variable to wait for.
+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(Bind(&DoNothing)));
+}
+
+// Test that reevaluation occurs when an async variable it depends on changes.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutWithVariables) {
+ fake_async_var_.reset(new string("Async value"));
+ eval_ctx_->GetValue(&fake_async_var_);
+
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ // Check that the scheduled callback isn't run until we signal a ValueChaged.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(value);
+
+ fake_async_var_.NotifyValueChanged();
+ EXPECT_FALSE(value);
+ // Ensure that the scheduled callback isn't run until we are back on the main
+ // loop.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_TRUE(value);
+}
+
+// Test that we don't re-schedule the events if we are attending one.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutCalledTwice) {
+ fake_async_var_.reset(new string("Async value"));
+ eval_ctx_->GetValue(&fake_async_var_);
+
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+
+ // The scheduled event should still work.
+ fake_async_var_.NotifyValueChanged();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_TRUE(value);
+}
+
+// Test that reevaluation occurs when a polling timeout fires.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutRunsFromTimeout) {
+ fake_poll_var_.reset(new string("Polled value"));
+ eval_ctx_->GetValue(&fake_poll_var_);
+
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ // Check that the scheduled callback isn't run until the timeout occurs.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ EXPECT_FALSE(value);
+ MessageLoopRunUntil(MessageLoop::current(),
+ TimeDelta::FromSeconds(10),
+ Bind(&GetBoolean, &value));
+ EXPECT_TRUE(value);
+}
+
+// Test that callback is called when evaluation context expires, and that it
+// cannot be used again unless the expiration deadline is reset.
+TEST_F(UmEvaluationContextTest, RunOnValueChangeOrTimeoutExpires) {
+ fake_async_var_.reset(new string("Async value"));
+ eval_ctx_->GetValue(&fake_async_var_);
+
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ // Check that the scheduled callback isn't run until the timeout occurs.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ EXPECT_FALSE(value);
+ MessageLoopRunUntil(MessageLoop::current(),
+ TimeDelta::FromSeconds(10),
+ Bind(&GetBoolean, &value));
+ EXPECT_TRUE(value);
+
+ // Ensure that we cannot reschedule an evaluation.
+ EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+
+ // Ensure that we can reschedule an evaluation after resetting expiration.
+ eval_ctx_->ResetExpiration();
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+// Test that we clear the events when destroying the EvaluationContext.
+TEST_F(UmEvaluationContextTest, RemoveObserversAndTimeoutTest) {
+ fake_async_var_.reset(new string("Async value"));
+ eval_ctx_->GetValue(&fake_async_var_);
+
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ eval_ctx_ = nullptr;
+
+ // This should not trigger the callback since the EvaluationContext waiting
+ // for it is gone, and it should have remove all its observers.
+ fake_async_var_.NotifyValueChanged();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_FALSE(value);
+}
+
+// Scheduling two reevaluations from the callback should succeed.
+TEST_F(UmEvaluationContextTest,
+ RunOnValueChangeOrTimeoutReevaluatesRepeatedly) {
+ fake_poll_var_.reset(new string("Polled value"));
+ Closure evaluation = Bind(ReadVar<string>, eval_ctx_, &fake_poll_var_);
+ int num_reevaluations = 2;
+ bool done = false;
+
+ // Run the evaluation once.
+ evaluation.Run();
+
+ // Schedule repeated reevaluations.
+ Closure closure = Bind(EvaluateRepeatedly, evaluation, eval_ctx_,
+ &num_reevaluations, &done);
+ ASSERT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(closure));
+ MessageLoopRunUntil(MessageLoop::current(),
+ TimeDelta::FromSeconds(10),
+ Bind(&GetBoolean, &done));
+ EXPECT_EQ(0, num_reevaluations);
+}
+
+// Test that we can delete the EvaluationContext while having pending events.
+TEST_F(UmEvaluationContextTest, ObjectDeletedWithPendingEventsTest) {
+ fake_async_var_.reset(new string("Async value"));
+ 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(Bind(&DoNothing)));
+ // TearDown() checks for leaked observers on this async_variable, which means
+ // that our object is still alive after removing its reference.
+}
+
+// Test that timed events fired after removal of the EvaluationContext don't
+// crash.
+TEST_F(UmEvaluationContextTest, TimeoutEventAfterDeleteTest) {
+ FakeVariable<string> fake_short_poll_var = {"fake_short_poll",
+ TimeDelta::FromSeconds(1)};
+ fake_short_poll_var.reset(new string("Polled value"));
+ eval_ctx_->GetValue(&fake_short_poll_var);
+ bool value = false;
+ EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&SetTrue, &value)));
+ // Remove the last reference to the EvaluationContext and run the loop for
+ // 10 seconds to give time to the main loop to trigger the timeout Event (of 1
+ // second). Our callback should not be called because the EvaluationContext
+ // was removed before the timeout event is attended.
+ eval_ctx_ = nullptr;
+ MessageLoopRunUntil(MessageLoop::current(),
+ TimeDelta::FromSeconds(10),
+ Bind(&GetBoolean, &value));
+ EXPECT_FALSE(value);
+}
+
+TEST_F(UmEvaluationContextTest, DefaultTimeout) {
+ // Test that the evaluation timeout calculation uses the default timeout on
+ // setup.
+ EXPECT_CALL(mock_var_async_, GetValue(default_timeout_, _))
+ .WillOnce(Return(nullptr));
+ EXPECT_EQ(nullptr, eval_ctx_->GetValue(&mock_var_async_));
+}
+
+TEST_F(UmEvaluationContextTest, TimeoutUpdatesWithMonotonicTime) {
+ fake_clock_.SetMonotonicTime(
+ fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1));
+
+ TimeDelta timeout = default_timeout_ - TimeDelta::FromSeconds(1);
+
+ EXPECT_CALL(mock_var_async_, GetValue(timeout, _))
+ .WillOnce(Return(nullptr));
+ EXPECT_EQ(nullptr, eval_ctx_->GetValue(&mock_var_async_));
+}
+
+TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesWallclock) {
+ 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));
+
+ EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+ cur_time - TimeDelta::FromSeconds(1)));
+ EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
+ EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+ cur_time + TimeDelta::FromSeconds(1)));
+ // Call ResetEvaluation now, which should use the new evaluation time.
+ eval_ctx_->ResetEvaluation();
+
+ cur_time = fake_clock_.GetWallclockTime();
+ EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+ cur_time - TimeDelta::FromSeconds(1)));
+ EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
+ EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+ cur_time + TimeDelta::FromSeconds(1)));
+}
+
+TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesMonotonic) {
+ 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));
+
+ EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ cur_time - TimeDelta::FromSeconds(1)));
+ EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
+ EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ cur_time + TimeDelta::FromSeconds(1)));
+ // Call ResetEvaluation now, which should use the new evaluation time.
+ eval_ctx_->ResetEvaluation();
+
+ cur_time = fake_clock_.GetMonotonicTime();
+ EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ cur_time - TimeDelta::FromSeconds(1)));
+ EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
+ EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ cur_time + TimeDelta::FromSeconds(1)));
+}
+
+TEST_F(UmEvaluationContextTest,
+ IsWallclockTimeGreaterThanSignalsTriggerReevaluation) {
+ EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+ 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(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+ IsMonotonicTimeGreaterThanSignalsTriggerReevaluation) {
+ EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ 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(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+ IsWallclockTimeGreaterThanDoesntRecordPastTimestamps) {
+ // IsWallclockTimeGreaterThan() should ignore timestamps on the past for
+ // reevaluation.
+ EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+ fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(20)));
+ EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+ fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1)));
+
+ // Callback should not be scheduled.
+ EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+ IsMonotonicTimeGreaterThanDoesntRecordPastTimestamps) {
+ // IsMonotonicTimeGreaterThan() should ignore timestamps on the past for
+ // reevaluation.
+ EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(20)));
+ EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+ fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(1)));
+
+ // Callback should not be scheduled.
+ EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest, DumpContext) {
+ // |fail_var_| yield "(no value)" since it is unset.
+ eval_ctx_->GetValue(&fail_var_);
+
+ // Check that this is included.
+ fake_int_var_.reset(new int(42));
+ eval_ctx_->GetValue(&fake_int_var_);
+
+ // Check that double-quotes are escaped properly.
+ fake_poll_var_.reset(new string("Hello \"world\"!"));
+ eval_ctx_->GetValue(&fake_poll_var_);
+
+ // Note that the variables are printed in alphabetical order. Also
+ // see UmEvaluationContextText::SetUp() where the values used for
+ // |evaluation_start_{monotonic,wallclock| are set.
+ EXPECT_EQ("{\n"
+ " \"evaluation_start_monotonic\": \"4/22/2009 19:25:00 GMT\",\n"
+ " \"evaluation_start_wallclock\": \"3/2/2006 1:23:45 GMT\",\n"
+ " \"variables\": {\n"
+ " \"fail_var\": \"(no value)\",\n"
+ " \"fake_int\": \"42\",\n"
+ " \"fake_poll\": \"Hello \\\"world\\\"!\"\n"
+ " }\n"
+ "}",
+ eval_ctx_->DumpContext());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/fake_config_provider.h b/update_manager/fake_config_provider.h
new file mode 100644
index 0000000..6a324df
--- /dev/null
+++ b/update_manager/fake_config_provider.h
@@ -0,0 +1,43 @@
+//
+// 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_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
+
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/fake_variable.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the ConfigProvider base class.
+class FakeConfigProvider : public ConfigProvider {
+ public:
+ FakeConfigProvider() {}
+
+ FakeVariable<bool>* var_is_oobe_enabled() override {
+ return &var_is_oobe_enabled_;
+ }
+
+ private:
+ FakeVariable<bool> var_is_oobe_enabled_{ // NOLINT(whitespace/braces)
+ "is_oobe_enabled", kVariableModeConst};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeConfigProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_CONFIG_PROVIDER_H_
diff --git a/update_manager/fake_device_policy_provider.h b/update_manager/fake_device_policy_provider.h
new file mode 100644
index 0000000..ad15c99
--- /dev/null
+++ b/update_manager/fake_device_policy_provider.h
@@ -0,0 +1,100 @@
+//
+// 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_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/fake_variable.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the DevicePolicyProvider base class.
+class FakeDevicePolicyProvider : public DevicePolicyProvider {
+ public:
+ FakeDevicePolicyProvider() {}
+
+ FakeVariable<bool>* var_device_policy_is_loaded() override {
+ return &var_device_policy_is_loaded_;
+ }
+
+ FakeVariable<std::string>* var_release_channel() override {
+ return &var_release_channel_;
+ }
+
+ FakeVariable<bool>* var_release_channel_delegated() override {
+ return &var_release_channel_delegated_;
+ }
+
+ FakeVariable<bool>* var_update_disabled() override {
+ return &var_update_disabled_;
+ }
+
+ FakeVariable<std::string>* var_target_version_prefix() override {
+ return &var_target_version_prefix_;
+ }
+
+ FakeVariable<base::TimeDelta>* var_scatter_factor() override {
+ return &var_scatter_factor_;
+ }
+
+ FakeVariable<std::set<ConnectionType>>*
+ var_allowed_connection_types_for_update() override {
+ return &var_allowed_connection_types_for_update_;
+ }
+
+ FakeVariable<std::string>* var_owner() override {
+ return &var_owner_;
+ }
+
+ FakeVariable<bool>* var_http_downloads_enabled() override {
+ return &var_http_downloads_enabled_;
+ }
+
+ FakeVariable<bool>* var_au_p2p_enabled() override {
+ return &var_au_p2p_enabled_;
+ }
+
+ private:
+ FakeVariable<bool> var_device_policy_is_loaded_{
+ "policy_is_loaded", kVariableModePoll};
+ FakeVariable<std::string> var_release_channel_{
+ "release_channel", kVariableModePoll};
+ FakeVariable<bool> var_release_channel_delegated_{
+ "release_channel_delegated", kVariableModePoll};
+ FakeVariable<bool> var_update_disabled_{
+ "update_disabled", kVariableModePoll};
+ FakeVariable<std::string> var_target_version_prefix_{
+ "target_version_prefix", kVariableModePoll};
+ FakeVariable<base::TimeDelta> var_scatter_factor_{
+ "scatter_factor", kVariableModePoll};
+ FakeVariable<std::set<ConnectionType>>
+ var_allowed_connection_types_for_update_{
+ "allowed_connection_types_for_update", kVariableModePoll};
+ FakeVariable<std::string> var_owner_{"owner", kVariableModePoll};
+ FakeVariable<bool> var_http_downloads_enabled_{
+ "http_downloads_enabled", kVariableModePoll};
+ FakeVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled", kVariableModePoll};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeDevicePolicyProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_manager/fake_random_provider.h b/update_manager/fake_random_provider.h
new file mode 100644
index 0000000..643a194
--- /dev/null
+++ b/update_manager/fake_random_provider.h
@@ -0,0 +1,40 @@
+//
+// 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_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/random_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the RandomProvider base class.
+class FakeRandomProvider : public RandomProvider {
+ public:
+ FakeRandomProvider() {}
+
+ FakeVariable<uint64_t>* var_seed() override { return &var_seed_; }
+
+ private:
+ FakeVariable<uint64_t> var_seed_{"seed", kVariableModePoll};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeRandomProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_RANDOM_PROVIDER_H_
diff --git a/update_manager/fake_shill_provider.h b/update_manager/fake_shill_provider.h
new file mode 100644
index 0000000..b68e858
--- /dev/null
+++ b/update_manager/fake_shill_provider.h
@@ -0,0 +1,60 @@
+//
+// 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_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/shill_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the ShillProvider base class.
+class FakeShillProvider : public ShillProvider {
+ public:
+ FakeShillProvider() {}
+
+ FakeVariable<bool>* var_is_connected() override {
+ return &var_is_connected_;
+ }
+
+ FakeVariable<ConnectionType>* var_conn_type() override {
+ return &var_conn_type_;
+ }
+
+ FakeVariable<ConnectionTethering>*
+ var_conn_tethering() override {
+ return &var_conn_tethering_;
+ }
+
+ FakeVariable<base::Time>* var_conn_last_changed() override {
+ return &var_conn_last_changed_;
+ }
+
+ private:
+ FakeVariable<bool> var_is_connected_{"is_connected", kVariableModePoll};
+ FakeVariable<ConnectionType> var_conn_type_{"conn_type", kVariableModePoll};
+ FakeVariable<ConnectionTethering> var_conn_tethering_{
+ "conn_tethering", kVariableModePoll};
+ FakeVariable<base::Time> var_conn_last_changed_{
+ "conn_last_changed", kVariableModePoll};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeShillProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SHILL_PROVIDER_H_
diff --git a/update_manager/fake_state.h b/update_manager/fake_state.h
new file mode 100644
index 0000000..fd7a88c
--- /dev/null
+++ b/update_manager/fake_state.h
@@ -0,0 +1,91 @@
+//
+// 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_UPDATE_MANAGER_FAKE_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_STATE_H_
+
+#include "update_engine/update_manager/fake_config_provider.h"
+#include "update_engine/update_manager/fake_device_policy_provider.h"
+#include "update_engine/update_manager/fake_random_provider.h"
+#include "update_engine/update_manager/fake_shill_provider.h"
+#include "update_engine/update_manager/fake_system_provider.h"
+#include "update_engine/update_manager/fake_time_provider.h"
+#include "update_engine/update_manager/fake_updater_provider.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// A fake State class that creates fake providers for all the providers.
+// This fake can be used in unit testing of Policy subclasses. To fake out the
+// value a variable is exposing, just call FakeVariable<T>::SetValue() on the
+// variable you fake out. For example:
+//
+// FakeState fake_state_;
+// fake_state_.random_provider_->var_seed()->SetValue(new uint64_t(12345));
+//
+// You can call SetValue more than once and the FakeVariable will take care of
+// the memory, but only the last value will remain.
+class FakeState : public State {
+ public:
+ // Creates and initializes the FakeState using fake providers.
+ FakeState() {}
+
+ ~FakeState() override {}
+
+ // Downcasted getters to access the fake instances during testing.
+ FakeConfigProvider* config_provider() override {
+ return &config_provider_;
+ }
+
+ FakeDevicePolicyProvider* device_policy_provider() override {
+ return &device_policy_provider_;
+ }
+
+ FakeRandomProvider* random_provider() override {
+ return &random_provider_;
+ }
+
+ FakeShillProvider* shill_provider() override {
+ return &shill_provider_;
+ }
+
+ FakeSystemProvider* system_provider() override {
+ return &system_provider_;
+ }
+
+ FakeTimeProvider* time_provider() override {
+ return &time_provider_;
+ }
+
+ FakeUpdaterProvider* updater_provider() override {
+ return &updater_provider_;
+ }
+
+ private:
+ FakeConfigProvider config_provider_;
+ FakeDevicePolicyProvider device_policy_provider_;
+ FakeRandomProvider random_provider_;
+ FakeShillProvider shill_provider_;
+ FakeSystemProvider system_provider_;
+ FakeTimeProvider time_provider_;
+ FakeUpdaterProvider updater_provider_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeState);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_STATE_H_
diff --git a/update_manager/fake_system_provider.h b/update_manager/fake_system_provider.h
new file mode 100644
index 0000000..6036198
--- /dev/null
+++ b/update_manager/fake_system_provider.h
@@ -0,0 +1,60 @@
+//
+// 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_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/system_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the SystemProvider base class.
+class FakeSystemProvider : public SystemProvider {
+ public:
+ FakeSystemProvider() {}
+
+ FakeVariable<bool>* var_is_normal_boot_mode() override {
+ return &var_is_normal_boot_mode_;
+ }
+
+ FakeVariable<bool>* var_is_official_build() override {
+ return &var_is_official_build_;
+ }
+
+ FakeVariable<bool>* var_is_oobe_complete() override {
+ return &var_is_oobe_complete_;
+ }
+
+ FakeVariable<unsigned int>* var_num_slots() override {
+ return &var_num_slots_;
+ }
+
+ private:
+ FakeVariable<bool> var_is_normal_boot_mode_{ // NOLINT(whitespace/braces)
+ "is_normal_boot_mode", kVariableModeConst};
+ FakeVariable<bool> var_is_official_build_{ // NOLINT(whitespace/braces)
+ "is_official_build", kVariableModeConst};
+ FakeVariable<bool> var_is_oobe_complete_{ // NOLINT(whitespace/braces)
+ "is_oobe_complete", kVariableModePoll};
+ FakeVariable<unsigned int> var_num_slots_{"num_slots", kVariableModePoll};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeSystemProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
diff --git a/update_manager/fake_time_provider.h b/update_manager/fake_time_provider.h
new file mode 100644
index 0000000..2aea2e7
--- /dev/null
+++ b/update_manager/fake_time_provider.h
@@ -0,0 +1,42 @@
+//
+// 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_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/time_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the TimeProvider base class.
+class FakeTimeProvider : public TimeProvider {
+ public:
+ FakeTimeProvider() {}
+
+ FakeVariable<base::Time>* var_curr_date() override { return &var_curr_date_; }
+ FakeVariable<int>* var_curr_hour() override { return &var_curr_hour_; }
+
+ private:
+ FakeVariable<base::Time> var_curr_date_{"curr_date", kVariableModePoll};
+ FakeVariable<int> var_curr_hour_{"curr_hour", kVariableModePoll};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeTimeProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_TIME_PROVIDER_H_
diff --git a/update_manager/fake_update_manager.h b/update_manager/fake_update_manager.h
new file mode 100644
index 0000000..2ea00b6
--- /dev/null
+++ b/update_manager/fake_update_manager.h
@@ -0,0 +1,49 @@
+//
+// 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_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
+
+#include "update_engine/update_manager/update_manager.h"
+
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/fake_state.h"
+
+namespace chromeos_update_manager {
+
+class FakeUpdateManager : public UpdateManager {
+ public:
+ explicit FakeUpdateManager(chromeos_update_engine::ClockInterface* clock)
+ : UpdateManager(clock, base::TimeDelta::FromSeconds(5),
+ base::TimeDelta::FromHours(1), new FakeState()) {
+ // The FakeUpdateManager uses a DefaultPolicy.
+ set_policy(new DefaultPolicy(clock));
+ }
+
+ // UpdateManager overrides.
+ using UpdateManager::set_policy;
+
+ FakeState* state() {
+ return reinterpret_cast<FakeState*>(UpdateManager::state());
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FakeUpdateManager);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATE_MANAGER_H_
diff --git a/update_manager/fake_updater_provider.h b/update_manager/fake_updater_provider.h
new file mode 100644
index 0000000..44389f4
--- /dev/null
+++ b/update_manager/fake_updater_provider.h
@@ -0,0 +1,130 @@
+//
+// 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_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
+
+#include <string>
+
+#include "update_engine/update_manager/fake_variable.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// Fake implementation of the UpdaterProvider base class.
+class FakeUpdaterProvider : public UpdaterProvider {
+ public:
+ FakeUpdaterProvider() {}
+
+ FakeVariable<base::Time>* var_updater_started_time() override {
+ return &var_updater_started_time_;
+ }
+
+ FakeVariable<base::Time>* var_last_checked_time() override {
+ return &var_last_checked_time_;
+ }
+
+ FakeVariable<base::Time>* var_update_completed_time() override {
+ return &var_update_completed_time_;
+ }
+
+ FakeVariable<double>* var_progress() override {
+ return &var_progress_;
+ }
+
+ FakeVariable<Stage>* var_stage() override {
+ return &var_stage_;
+ }
+
+ FakeVariable<std::string>* var_new_version() override {
+ return &var_new_version_;
+ }
+
+ FakeVariable<int64_t>* var_payload_size() override {
+ return &var_payload_size_;
+ }
+
+ FakeVariable<std::string>* var_curr_channel() override {
+ return &var_curr_channel_;
+ }
+
+ FakeVariable<std::string>* var_new_channel() override {
+ return &var_new_channel_;
+ }
+
+ FakeVariable<bool>* var_p2p_enabled() override {
+ return &var_p2p_enabled_;
+ }
+
+ FakeVariable<bool>* var_cellular_enabled() override {
+ return &var_cellular_enabled_;
+ }
+
+ FakeVariable<unsigned int>* var_consecutive_failed_update_checks() override {
+ return &var_consecutive_failed_update_checks_;
+ }
+
+ FakeVariable<unsigned int>* var_server_dictated_poll_interval() override {
+ return &var_server_dictated_poll_interval_;
+ }
+
+ FakeVariable<UpdateRequestStatus>* var_forced_update_requested() override {
+ return &var_forced_update_requested_;
+ }
+
+ private:
+ FakeVariable<base::Time>
+ var_updater_started_time_{ // NOLINT(whitespace/braces)
+ "updater_started_time", kVariableModePoll};
+ FakeVariable<base::Time> var_last_checked_time_{ // NOLINT(whitespace/braces)
+ "last_checked_time", kVariableModePoll};
+ FakeVariable<base::Time>
+ var_update_completed_time_{ // NOLINT(whitespace/braces)
+ "update_completed_time", kVariableModePoll};
+ FakeVariable<double> var_progress_{ // NOLINT(whitespace/braces)
+ "progress", kVariableModePoll};
+ FakeVariable<Stage> var_stage_{ // NOLINT(whitespace/braces)
+ "stage", kVariableModePoll};
+ FakeVariable<std::string> var_new_version_{ // NOLINT(whitespace/braces)
+ "new_version", kVariableModePoll};
+ FakeVariable<int64_t> var_payload_size_{ // NOLINT(whitespace/braces)
+ "payload_size", kVariableModePoll};
+ FakeVariable<std::string> var_curr_channel_{ // NOLINT(whitespace/braces)
+ "curr_channel", kVariableModePoll};
+ FakeVariable<std::string> var_new_channel_{ // NOLINT(whitespace/braces)
+ "new_channel", kVariableModePoll};
+ FakeVariable<bool> var_p2p_enabled_{// NOLINT(whitespace/braces)
+ "p2p_enabled",
+ kVariableModeAsync};
+ FakeVariable<bool> var_cellular_enabled_{// NOLINT(whitespace/braces)
+ "cellular_enabled",
+ kVariableModeAsync};
+ FakeVariable<unsigned int>
+ var_consecutive_failed_update_checks_{ // NOLINT(whitespace/braces)
+ "consecutive_failed_update_checks", kVariableModePoll};
+ FakeVariable<unsigned int>
+ var_server_dictated_poll_interval_{ // NOLINT(whitespace/braces)
+ "server_dictated_poll_interval", kVariableModePoll};
+ FakeVariable<UpdateRequestStatus>
+ var_forced_update_requested_{ // NOLINT(whitespace/braces)
+ "forced_update_requested", kVariableModeAsync};
+
+ DISALLOW_COPY_AND_ASSIGN(FakeUpdaterProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_UPDATER_PROVIDER_H_
diff --git a/update_manager/fake_variable.h b/update_manager/fake_variable.h
new file mode 100644
index 0000000..2f8e079
--- /dev/null
+++ b/update_manager/fake_variable.h
@@ -0,0 +1,74 @@
+//
+// 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_UPDATE_MANAGER_FAKE_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_FAKE_VARIABLE_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// A fake typed variable to use while testing policy implementations. The
+// variable can be instructed to return any object of its type.
+template<typename T>
+class FakeVariable : public Variable<T> {
+ public:
+ FakeVariable(const std::string& name, VariableMode mode)
+ : Variable<T>(name, mode) {}
+ FakeVariable(const std::string& name, base::TimeDelta poll_interval)
+ : Variable<T>(name, poll_interval) {}
+ ~FakeVariable() override {}
+
+ // Sets the next value of this variable to the passed |p_value| pointer. Once
+ // returned by GetValue(), the pointer is released and has to be set again.
+ // A value of null means that the GetValue() call will fail and return
+ // null.
+ void reset(const T* p_value) {
+ ptr_.reset(p_value);
+ }
+
+ // Make the NotifyValueChanged() public for FakeVariables.
+ void NotifyValueChanged() {
+ Variable<T>::NotifyValueChanged();
+ }
+
+ protected:
+ // Variable<T> overrides.
+ // Returns the pointer set with reset(). The ownership of the object is passed
+ // to the caller and the pointer is release from the FakeVariable. A second
+ // call to GetValue() without reset() will return null and set the error
+ // message.
+ const T* GetValue(base::TimeDelta /* timeout */,
+ std::string* errmsg) override {
+ if (ptr_ == nullptr && errmsg != nullptr)
+ *errmsg = this->GetName() + " is an empty FakeVariable";
+ // Passes the pointer ownership to the caller.
+ return ptr_.release();
+ }
+
+ private:
+ // The pointer returned by GetValue().
+ std::unique_ptr<const T> ptr_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeVariable);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_VARIABLE_H_
diff --git a/update_manager/generic_variables.h b/update_manager/generic_variables.h
new file mode 100644
index 0000000..f87a05e
--- /dev/null
+++ b/update_manager/generic_variables.h
@@ -0,0 +1,218 @@
+//
+// 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.
+//
+
+// Generic and provider-independent Variable subclasses. These variables can be
+// used by any state provider to implement simple variables to avoid repeat the
+// same common code on different state providers.
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
+
+#include <string>
+
+#include <base/callback.h>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Variable class returning a copy of a given object using the copy constructor.
+// This template class can be used to define variables that expose as a variable
+// any fixed object, such as the a provider's private member. The variable will
+// create copies of the provided object using the copy constructor of that
+// class.
+//
+// For example, a state provider exposing a private member as a variable can
+// implement this as follows:
+//
+// class SomethingProvider {
+// public:
+// SomethingProvider(...) {
+// var_something_foo = new PollCopyVariable<MyType>(foo_);
+// }
+// ...
+// private:
+// MyType foo_;
+// };
+template<typename T>
+class PollCopyVariable : public Variable<T> {
+ public:
+ // Creates the variable returning copies of the passed |ref|. The reference to
+ // this object is kept and it should be available whenever the GetValue()
+ // method is called. If |is_set_p| is not null, then this flag will be
+ // consulted prior to returning the value, and an |errmsg| will be returned if
+ // it is not set.
+ PollCopyVariable(const std::string& name, const T& ref, const bool* is_set_p,
+ const std::string& errmsg)
+ : Variable<T>(name, kVariableModePoll), ref_(ref), is_set_p_(is_set_p),
+ errmsg_(errmsg) {}
+ PollCopyVariable(const std::string& name, const T& ref, const bool* is_set_p)
+ : PollCopyVariable(name, ref, is_set_p, std::string()) {}
+ PollCopyVariable(const std::string& name, const T& ref)
+ : PollCopyVariable(name, ref, nullptr) {}
+
+ PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+ const T& ref, const bool* is_set_p,
+ const std::string& errmsg)
+ : Variable<T>(name, poll_interval), ref_(ref), is_set_p_(is_set_p),
+ errmsg_(errmsg) {}
+ PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+ const T& ref, const bool* is_set_p)
+ : PollCopyVariable(name, poll_interval, ref, is_set_p, std::string()) {}
+ PollCopyVariable(const std::string& name, const base::TimeDelta poll_interval,
+ const T& ref)
+ : PollCopyVariable(name, poll_interval, ref, nullptr) {}
+
+ protected:
+ FRIEND_TEST(UmPollCopyVariableTest, SimpleTest);
+ FRIEND_TEST(UmPollCopyVariableTest, UseCopyConstructorTest);
+
+ // Variable override.
+ inline const T* GetValue(base::TimeDelta /* timeout */,
+ std::string* errmsg) override {
+ if (is_set_p_ && !(*is_set_p_)) {
+ if (errmsg) {
+ if (errmsg_.empty())
+ *errmsg = "No value set for " + this->GetName();
+ else
+ *errmsg = errmsg_;
+ }
+ return nullptr;
+ }
+ return new T(ref_);
+ }
+
+ private:
+ // Reference to the object to be copied by GetValue().
+ const T& ref_;
+
+ // A pointer to a flag indicating whether the value is set. If null, then the
+ // value is assumed to be set.
+ const bool* const is_set_p_;
+
+ // An error message to be returned when attempting to get an unset value.
+ const std::string errmsg_;
+};
+
+// Variable class returning a constant value that is cached on the variable when
+// it is created.
+template<typename T>
+class ConstCopyVariable : public Variable<T> {
+ public:
+ // Creates the variable returning copies of the passed |obj|. The value passed
+ // is copied in this variable, and new copies of it will be returned by
+ // GetValue().
+ ConstCopyVariable(const std::string& name, const T& obj)
+ : Variable<T>(name, kVariableModeConst), obj_(obj) {}
+
+ protected:
+ // Variable override.
+ const T* GetValue(base::TimeDelta /* timeout */,
+ std::string* /* errmsg */) override {
+ return new T(obj_);
+ }
+
+ private:
+ // Value to be copied by GetValue().
+ const T obj_;
+};
+
+// Variable class returning a copy of a value returned by a given function. The
+// function is called every time the variable is being polled.
+template<typename T>
+class CallCopyVariable : public Variable<T> {
+ public:
+ CallCopyVariable(const std::string& name, base::Callback<T(void)> func)
+ : Variable<T>(name, kVariableModePoll), func_(func) {}
+ CallCopyVariable(const std::string& name,
+ const base::TimeDelta poll_interval,
+ base::Callback<T(void)> func)
+ : Variable<T>(name, poll_interval), func_(func) {}
+
+ protected:
+ // Variable override.
+ const T* GetValue(base::TimeDelta /* timeout */,
+ std::string* /* errmsg */) override {
+ if (func_.is_null())
+ return nullptr;
+ return new T(func_.Run());
+ }
+
+ private:
+ FRIEND_TEST(UmCallCopyVariableTest, SimpleTest);
+
+ // The function to be called, stored as a base::Callback.
+ base::Callback<T(void)> func_;
+
+ DISALLOW_COPY_AND_ASSIGN(CallCopyVariable);
+};
+
+
+// A Variable class to implement simple Async variables. It provides two methods
+// SetValue and UnsetValue to modify the current value of the variable and
+// notify the registered observers whenever the value changed.
+//
+// The type T needs to be copy-constructible, default-constructible and have an
+// operator== (to determine if the value changed), which makes this class
+// suitable for basic types.
+template<typename T>
+class AsyncCopyVariable : public Variable<T> {
+ public:
+ explicit AsyncCopyVariable(const std::string& name)
+ : Variable<T>(name, kVariableModeAsync), has_value_(false) {}
+
+ AsyncCopyVariable(const std::string& name, const T value)
+ : Variable<T>(name, kVariableModeAsync),
+ has_value_(true), value_(value) {}
+
+ void SetValue(const T& new_value) {
+ bool should_notify = !(has_value_ && new_value == value_);
+ value_ = new_value;
+ has_value_ = true;
+ if (should_notify)
+ this->NotifyValueChanged();
+ }
+
+ void UnsetValue() {
+ if (has_value_) {
+ has_value_ = false;
+ this->NotifyValueChanged();
+ }
+ }
+
+ protected:
+ // Variable override.
+ const T* GetValue(base::TimeDelta /* timeout */,
+ std::string* errmsg) override {
+ if (!has_value_) {
+ if (errmsg)
+ *errmsg = "No value set for " + this->GetName();
+ return nullptr;
+ }
+ return new T(value_);
+ }
+
+ private:
+ // Whether the variable has a value set.
+ bool has_value_;
+
+ // Copy of the object to be returned by GetValue().
+ T value_;
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_GENERIC_VARIABLES_H_
diff --git a/update_manager/generic_variables_unittest.cc b/update_manager/generic_variables_unittest.cc
new file mode 100644
index 0000000..cb0c48f
--- /dev/null
+++ b/update_manager/generic_variables_unittest.cc
@@ -0,0 +1,224 @@
+//
+// 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/update_manager/generic_variables.h"
+
+#include <memory>
+
+#include <base/callback.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 "update_engine/update_manager/umtest_utils.h"
+
+using brillo::MessageLoop;
+using brillo::MessageLoopRunMaxIterations;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmPollCopyVariableTest : public ::testing::Test {};
+
+
+TEST_F(UmPollCopyVariableTest, SimpleTest) {
+ // Tests that copies are generated as intended.
+ int source = 5;
+ PollCopyVariable<int> var("var", source);
+
+ // Generate and validate a copy.
+ unique_ptr<const int> copy_1(var.GetValue(
+ UmTestUtils::DefaultTimeout(), nullptr));
+ ASSERT_NE(nullptr, copy_1.get());
+ EXPECT_EQ(5, *copy_1);
+
+ // Assign a different value to the source variable.
+ source = 42;
+
+ // Check that the content of the copy was not affected (distinct instance).
+ EXPECT_EQ(5, *copy_1);
+
+ // Generate and validate a second copy.
+ UmTestUtils::ExpectVariableHasValue(42, &var);
+}
+
+TEST_F(UmPollCopyVariableTest, SetFlagTest) {
+ // Tests that the set flag is being referred to as expected.
+ int source = 5;
+ bool is_set = false;
+ PollCopyVariable<int> var("var", source, &is_set);
+
+ // Flag marked unset, nothing should be returned.
+ UmTestUtils::ExpectVariableNotSet(&var);
+
+ // Flag marked set, we should be getting a value.
+ is_set = true;
+ UmTestUtils::ExpectVariableHasValue(5, &var);
+}
+
+
+class CopyConstructorTestClass {
+ public:
+ CopyConstructorTestClass(void) : copied_(false) {}
+ CopyConstructorTestClass(const CopyConstructorTestClass& other)
+ : copied_(true), val_(other.val_ * 2) {}
+
+ // Tells if the instance was constructed using the copy-constructor.
+ const bool copied_;
+
+ // An auxiliary internal value.
+ int val_ = 0;
+};
+
+
+TEST_F(UmPollCopyVariableTest, UseCopyConstructorTest) {
+ // Ensures that CopyVariables indeed uses the copy constructor.
+ const CopyConstructorTestClass source;
+ ASSERT_FALSE(source.copied_);
+
+ PollCopyVariable<CopyConstructorTestClass> var("var", source);
+ unique_ptr<const CopyConstructorTestClass> copy(
+ var.GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+ ASSERT_NE(nullptr, copy.get());
+ EXPECT_TRUE(copy->copied_);
+}
+
+
+class UmConstCopyVariableTest : public ::testing::Test {};
+
+TEST_F(UmConstCopyVariableTest, SimpleTest) {
+ int source = 5;
+ ConstCopyVariable<int> var("var", source);
+ UmTestUtils::ExpectVariableHasValue(5, &var);
+
+ // Ensure the value is cached.
+ source = 42;
+ UmTestUtils::ExpectVariableHasValue(5, &var);
+}
+
+
+class UmCallCopyVariableTest : public ::testing::Test {};
+
+CopyConstructorTestClass test_func(CopyConstructorTestClass* obj) {
+ obj->val_++; // So we can check that the function was called.
+ return *obj;
+}
+
+TEST_F(UmCallCopyVariableTest, SimpleTest) {
+ // Tests that the returned value is generated by copying the value returned by
+ // the function call.
+
+ CopyConstructorTestClass test_obj;
+ ASSERT_FALSE(test_obj.copied_);
+ test_obj.val_ = 5;
+
+ base::Callback<CopyConstructorTestClass(void)> cb = base::Bind(
+ test_func, &test_obj);
+ CallCopyVariable<CopyConstructorTestClass> var("var", cb);
+
+ unique_ptr<const CopyConstructorTestClass> copy(
+ var.GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+ EXPECT_EQ(6, test_obj.val_); // Check that the function was called.
+ ASSERT_NE(nullptr, copy.get());
+ EXPECT_TRUE(copy->copied_);
+ EXPECT_EQ(12, copy->val_); // Check that copying occurred once.
+}
+
+TEST_F(UmCallCopyVariableTest, NullTest) {
+ // Ensures that the variable returns null when the callback is null.
+
+ base::Callback<bool(void)> cb;
+ CallCopyVariable<bool> var("var", cb);
+ UmTestUtils::ExpectVariableNotSet(&var);
+}
+
+class UmAsyncCopyVariableTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ // No remaining event on the main loop.
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+
+ brillo::FakeMessageLoop loop_{nullptr};
+};
+
+TEST_F(UmAsyncCopyVariableTest, ConstructorTest) {
+ AsyncCopyVariable<int> var("var");
+ UmTestUtils::ExpectVariableNotSet(&var);
+ EXPECT_EQ(kVariableModeAsync, var.GetMode());
+}
+
+TEST_F(UmAsyncCopyVariableTest, SetValueTest) {
+ AsyncCopyVariable<int> var("var");
+ var.SetValue(5);
+ UmTestUtils::ExpectVariableHasValue(5, &var);
+ // Execute all the pending observers.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+}
+
+TEST_F(UmAsyncCopyVariableTest, UnsetValueTest) {
+ AsyncCopyVariable<int> var("var", 42);
+ var.UnsetValue();
+ UmTestUtils::ExpectVariableNotSet(&var);
+ // Execute all the pending observers.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+}
+
+class CallCounterObserver : public BaseVariable::ObserverInterface {
+ public:
+ void ValueChanged(BaseVariable* variable) {
+ calls_count_++;
+ }
+
+ int calls_count_ = 0;
+};
+
+TEST_F(UmAsyncCopyVariableTest, ObserverCalledTest) {
+ AsyncCopyVariable<int> var("var", 42);
+ CallCounterObserver observer;
+ var.AddObserver(&observer);
+ EXPECT_EQ(0, observer.calls_count_);
+
+ // Check that a different value fires the notification.
+ var.SetValue(5);
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(1, observer.calls_count_);
+
+ // Check the same value doesn't.
+ var.SetValue(5);
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(1, observer.calls_count_);
+
+ // Check that unsetting a previously set value fires the notification.
+ var.UnsetValue();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(2, observer.calls_count_);
+
+ // Check that unsetting again doesn't.
+ var.UnsetValue();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(2, observer.calls_count_);
+
+ var.RemoveObserver(&observer);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/mock_policy.h b/update_manager/mock_policy.h
new file mode 100644
index 0000000..14470e9
--- /dev/null
+++ b/update_manager/mock_policy.h
@@ -0,0 +1,92 @@
+//
+// 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_UPDATE_MANAGER_MOCK_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_MOCK_POLICY_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/update_manager/default_policy.h"
+#include "update_engine/update_manager/policy.h"
+
+namespace chromeos_update_manager {
+
+// A mocked implementation of Policy.
+class MockPolicy : public Policy {
+ public:
+ explicit MockPolicy(chromeos_update_engine::ClockInterface* clock)
+ : default_policy_(clock) {
+ // We defer to the corresponding DefaultPolicy methods, by default.
+ ON_CALL(*this, UpdateCheckAllowed(testing::_, testing::_, testing::_,
+ testing::_))
+ .WillByDefault(testing::Invoke(
+ &default_policy_, &DefaultPolicy::UpdateCheckAllowed));
+ ON_CALL(*this, UpdateCanStart(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));
+ ON_CALL(*this, P2PEnabledChanged(testing::_, testing::_, testing::_,
+ testing::_, testing::_))
+ .WillByDefault(testing::Invoke(
+ &default_policy_, &DefaultPolicy::P2PEnabledChanged));
+ }
+
+ MockPolicy() : MockPolicy(nullptr) {}
+ ~MockPolicy() override {}
+
+ // Policy overrides.
+ MOCK_CONST_METHOD4(UpdateCheckAllowed,
+ EvalStatus(EvaluationContext*, State*, std::string*,
+ UpdateCheckParams*));
+
+ MOCK_CONST_METHOD5(UpdateCanStart,
+ EvalStatus(EvaluationContext*, State*, std::string*,
+ UpdateDownloadParams*, UpdateState));
+
+ MOCK_CONST_METHOD4(UpdateDownloadAllowed,
+ EvalStatus(EvaluationContext*, State*, std::string*,
+ bool*));
+
+ MOCK_CONST_METHOD4(P2PEnabled,
+ EvalStatus(EvaluationContext*, State*, std::string*,
+ bool*));
+
+ MOCK_CONST_METHOD5(P2PEnabledChanged,
+ EvalStatus(EvaluationContext*, State*, std::string*,
+ bool*, bool));
+
+ protected:
+ // Policy override.
+ std::string PolicyName() const override { return "MockPolicy"; }
+
+ private:
+ DefaultPolicy default_policy_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockPolicy);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_MOCK_POLICY_H_
diff --git a/update_manager/mock_variable.h b/update_manager/mock_variable.h
new file mode 100644
index 0000000..1493491
--- /dev/null
+++ b/update_manager/mock_variable.h
@@ -0,0 +1,42 @@
+//
+// 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_UPDATE_MANAGER_MOCK_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_MOCK_VARIABLE_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// This is a generic mock of the Variable class.
+template<typename T>
+class MockVariable : public Variable<T> {
+ public:
+ using Variable<T>::Variable;
+
+ MOCK_METHOD2_T(GetValue, const T*(base::TimeDelta, std::string*));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockVariable);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_MOCK_VARIABLE_H_
diff --git a/update_manager/policy.cc b/update_manager/policy.cc
new file mode 100644
index 0000000..151c225
--- /dev/null
+++ b/update_manager/policy.cc
@@ -0,0 +1,37 @@
+//
+// 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/update_manager/policy.h"
+
+#include <string>
+
+using std::string;
+
+namespace chromeos_update_manager {
+
+string ToString(EvalStatus status) {
+ switch (status) {
+ case EvalStatus::kFailed:
+ return "kFailed";
+ case EvalStatus::kSucceeded:
+ return "kSucceeded";
+ case EvalStatus::kAskMeAgainLater:
+ return "kAskMeAgainLater";
+ }
+ return "Invalid";
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/policy.h b/update_manager/policy.h
new file mode 100644
index 0000000..fae1494
--- /dev/null
+++ b/update_manager/policy.h
@@ -0,0 +1,290 @@
+//
+// 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_UPDATE_MANAGER_POLICY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_
+
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// The three different results of a policy request.
+enum class EvalStatus {
+ kFailed,
+ kSucceeded,
+ kAskMeAgainLater,
+};
+
+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.
+
+ // 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;
+ // A target channel, if so imposed by policy; otherwise, an empty string.
+ std::string target_channel;
+
+ // Whether the allowed update is interactive (user-initiated) or periodic.
+ bool is_interactive;
+};
+
+// Input arguments to UpdateCanStart.
+//
+// A snapshot of the state of the current update process. This includes
+// everything that a policy might need and that occurred since the first time
+// the current payload was first seen and attempted (consecutively).
+struct UpdateState {
+ // Information pertaining to the current update payload and/or check.
+ //
+ // Whether the current update check is an interactive one. The caller should
+ // feed the value returned by the preceding call to UpdateCheckAllowed().
+ bool is_interactive;
+ // Whether it is a delta payload.
+ bool is_delta_payload;
+ // Wallclock time when payload was first (consecutively) offered by Omaha.
+ base::Time first_seen;
+ // Number of consecutive update checks returning the current update.
+ int num_checks;
+ // Number of update payload failures and the wallclock time when it was last
+ // updated by the updater. These should both be nullified whenever a new
+ // update is seen; they are updated at the policy's descretion (via
+ // UpdateDownloadParams.do_increment_failures) once all of the usable download
+ // URLs for the payload have been used without success. They should be
+ // persisted across reboots.
+ int num_failures;
+ base::Time failures_last_updated;
+
+ // Information pertaining to downloading and applying of the current update.
+ //
+ // An array of download URLs provided by Omaha.
+ std::vector<std::string> download_urls;
+ // Max number of errors allowed per download URL.
+ int download_errors_max;
+ // The index of the URL to download from, as determined in the previous call
+ // to the policy. For a newly seen payload, this should be -1.
+ int last_download_url_idx;
+ // The number of successive download errors pertaining to this last URL, as
+ // determined in the previous call to the policy. For a newly seen payload,
+ // this should be zero.
+ int last_download_url_num_errors;
+ // An array of errors that occurred while trying to download this update since
+ // the previous call to this policy has returned, or since this payload was
+ // first seen, or since the updater process has started (whichever is later).
+ // Includes the URL index attempted, the error code, and the wallclock-based
+ // timestamp when it occurred.
+ std::vector<std::tuple<int, chromeos_update_engine::ErrorCode, base::Time>>
+ download_errors;
+ // Whether Omaha forbids use of P2P for downloading and/or sharing.
+ bool p2p_downloading_disabled;
+ bool p2p_sharing_disabled;
+ // The number of P2P download attempts and wallclock-based time when P2P
+ // download was first attempted.
+ int p2p_num_attempts;
+ base::Time p2p_first_attempted;
+
+ // Information pertaining to update backoff mechanism.
+ //
+ // The currently known (persisted) wallclock-based backoff expiration time;
+ // zero if none.
+ base::Time backoff_expiry;
+ // Whether backoff is disabled by Omaha.
+ bool is_backoff_disabled;
+
+ // Information pertaining to update scattering.
+ //
+ // The currently knwon (persisted) scattering wallclock-based wait period and
+ // update check threshold; zero if none.
+ base::TimeDelta scatter_wait_period;
+ int scatter_check_threshold;
+ // Maximum wait period allowed for this update, as determined by Omaha.
+ base::TimeDelta scatter_wait_period_max;
+ // Minimum/maximum check threshold values.
+ // TODO(garnold) These appear to not be related to the current update and so
+ // should probably be obtained as variables via UpdaterProvider.
+ int scatter_check_threshold_min;
+ int scatter_check_threshold_max;
+};
+
+// Results regarding the downloading and applying of an update, as determined by
+// UpdateCanStart.
+//
+// An enumerator for the reasons of not allowing an update to start.
+enum class UpdateCannotStartReason {
+ kUndefined,
+ kCheckDue,
+ kScattering,
+ kBackoff,
+ kCannotDownload,
+};
+
+struct UpdateDownloadParams {
+ // Whether the update attempt is allowed to proceed.
+ bool update_can_start;
+ // If update cannot proceed, a reason code for why it cannot do so.
+ UpdateCannotStartReason cannot_start_reason;
+
+ // Download related attributes. The update engine uses them to choose the
+ // means for downloading and applying an update.
+ //
+ // The index of the download URL to use (-1 means no suitable URL was found)
+ // and whether it can be used. Even if there's no URL or its use is not
+ // allowed (backoff, scattering) there may still be other means for download
+ // (like P2P). The URL index needs to be persisted and handed back to the
+ // policy on the next time it is called.
+ int download_url_idx;
+ bool download_url_allowed;
+ // The number of download errors associated with this download URL. This value
+ // needs to be persisted and handed back to the policy on the next time it is
+ // called.
+ int download_url_num_errors;
+ // Whether P2P download and sharing are allowed.
+ bool p2p_downloading_allowed;
+ bool p2p_sharing_allowed;
+
+ // Other values that need to be persisted and handed to the policy as need on
+ // the next call.
+ //
+ // Whether an update failure has been identified by the policy. The client
+ // should increment and persist its update failure count, and record the time
+ // when this was done; it needs to hand these values back to the policy
+ // (UpdateState.{num_failures,failures_last_updated}) on the next time it is
+ // called.
+ bool do_increment_failures;
+ // The current backof expiry.
+ base::Time backoff_expiry;
+ // The scattering wait period and check threshold.
+ base::TimeDelta scatter_wait_period;
+ int scatter_check_threshold;
+};
+
+// The Policy class is an interface to the ensemble of policy requests that the
+// client can make. A derived class includes the policy implementations of
+// these.
+//
+// When compile-time selection of the policy is required due to missing or extra
+// parts in a given platform, a different Policy subclass can be used.
+class Policy {
+ public:
+ virtual ~Policy() {}
+
+ // Returns the name of a public policy request.
+ // IMPORTANT: Be sure to add a conditional for each new public policy that is
+ // being added to this class in the future.
+ template<typename R, typename... Args>
+ std::string PolicyRequestName(
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ Args...) const) const {
+ std::string class_name = PolicyName() + "::";
+
+ if (reinterpret_cast<typeof(&Policy::UpdateCheckAllowed)>(
+ policy_method) == &Policy::UpdateCheckAllowed)
+ return class_name + "UpdateCheckAllowed";
+ 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";
+ if (reinterpret_cast<typeof(&Policy::P2PEnabledChanged)>(
+ policy_method) == &Policy::P2PEnabledChanged)
+ return class_name + "P2PEnabledChanged";
+
+ NOTREACHED();
+ return class_name + "(unknown)";
+ }
+
+
+ // List of policy requests. A policy request takes an EvaluationContext as the
+ // first argument, a State instance, a returned error message, a returned
+ // value and optionally followed by one or more arbitrary constant arguments.
+ //
+ // When the implementation fails, the method returns EvalStatus::kFailed and
+ // sets the |error| string.
+
+ // UpdateCheckAllowed returns whether it is allowed to request an update check
+ // to Omaha.
+ virtual EvalStatus UpdateCheckAllowed(
+ EvaluationContext* ec, State* state, std::string* error,
+ UpdateCheckParams* result) const = 0;
+
+ // Returns EvalStatus::kSucceeded if either an update can start being
+ // processed, or the attempt needs to be aborted. In cases where the update
+ // needs to wait for some condition to be satisfied, but none of the values
+ // that need to be persisted has changed, returns
+ // EvalStatus::kAskMeAgainLater. Arguments include an |update_state| that
+ // encapsulates data pertaining to the current ongoing update process.
+ virtual EvalStatus UpdateCanStart(
+ EvaluationContext* ec,
+ State* state,
+ std::string* error,
+ 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, State* state, std::string* error,
+ bool* result) const = 0;
+
+ // Checks whether P2P is enabled, but blocks (returns
+ // |EvalStatus::kAskMeAgainLater|) until it is different from |prev_result|.
+ // If the P2P enabled status is not expected to change, will return
+ // immediately with |EvalStatus::kSucceeded|. This internally uses the
+ // P2PEnabled() policy above.
+ virtual EvalStatus P2PEnabledChanged(
+ EvaluationContext* ec, State* state, std::string* error,
+ bool* result, bool prev_result) const = 0;
+
+ protected:
+ Policy() {}
+
+ // Returns the name of the actual policy class.
+ virtual std::string PolicyName() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Policy);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_H_
diff --git a/update_manager/policy_utils.h b/update_manager/policy_utils.h
new file mode 100644
index 0000000..960987e
--- /dev/null
+++ b/update_manager/policy_utils.h
@@ -0,0 +1,38 @@
+//
+// 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_UPDATE_MANAGER_POLICY_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
+
+#include "update_engine/update_manager/policy.h"
+
+// Checks that the passed pointer value is not null, returning kFailed on the
+// current context and setting the *error description when it is null. The
+// intended use is to validate variable failures while using
+// EvaluationContext::GetValue, for example:
+//
+// const int* my_value = ec->GetValue(state->my_provider()->var_my_value());
+// POLICY_CHECK_VALUE_AND_FAIL(my_value, error);
+//
+#define POLICY_CHECK_VALUE_AND_FAIL(ptr, error) \
+ do { \
+ if ((ptr) == nullptr) { \
+ *(error) = #ptr " is required but is null."; \
+ return EvalStatus::kFailed; \
+ } \
+ } while (false)
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_POLICY_UTILS_H_
diff --git a/update_manager/prng.h b/update_manager/prng.h
new file mode 100644
index 0000000..64f886c
--- /dev/null
+++ b/update_manager/prng.h
@@ -0,0 +1,51 @@
+//
+// 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_UPDATE_MANAGER_PRNG_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_PRNG_H_
+
+#include <random>
+
+#include <base/logging.h>
+
+namespace chromeos_update_manager {
+
+// A thread-safe, unsecure, 32-bit pseudo-random number generator based on
+// std::mt19937.
+class PRNG {
+ public:
+ // Initializes the generator with the passed |seed| value.
+ explicit PRNG(uint32_t seed) : gen_(seed) {}
+
+ // Returns a random unsigned 32-bit integer.
+ uint32_t Rand() { return gen_(); }
+
+ // Returns a random integer uniformly distributed in the range [min, max].
+ int RandMinMax(int min, int max) {
+ DCHECK_LE(min, max);
+ return std::uniform_int_distribution<>(min, max)(gen_);
+ }
+
+ private:
+ // A pseudo-random number generator.
+ std::mt19937 gen_;
+
+ DISALLOW_COPY_AND_ASSIGN(PRNG);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_PRNG_H_
diff --git a/update_manager/prng_unittest.cc b/update_manager/prng_unittest.cc
new file mode 100644
index 0000000..2a3f689
--- /dev/null
+++ b/update_manager/prng_unittest.cc
@@ -0,0 +1,79 @@
+//
+// 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/update_manager/prng.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+using std::vector;
+
+namespace chromeos_update_manager {
+
+TEST(UmPRNGTest, ShouldBeDeterministic) {
+ PRNG a(42);
+ PRNG b(42);
+
+ for (int i = 0; i < 1000; ++i) {
+ EXPECT_EQ(a.Rand(), b.Rand()) << "Iteration i=" << i;
+ }
+}
+
+TEST(UmPRNGTest, SeedChangesGeneratedSequence) {
+ PRNG a(42);
+ PRNG b(5);
+
+ vector<uint32_t> values_a;
+ vector<uint32_t> values_b;
+
+ for (int i = 0; i < 100; ++i) {
+ values_a.push_back(a.Rand());
+ values_b.push_back(b.Rand());
+ }
+ EXPECT_NE(values_a, values_b);
+}
+
+TEST(UmPRNGTest, IsNotConstant) {
+ PRNG prng(5);
+
+ uint32_t initial_value = prng.Rand();
+ bool prng_is_constant = true;
+ for (int i = 0; i < 100; ++i) {
+ if (prng.Rand() != initial_value) {
+ prng_is_constant = false;
+ break;
+ }
+ }
+ EXPECT_FALSE(prng_is_constant) << "After 100 iterations.";
+}
+
+TEST(UmPRNGTest, RandCoversRange) {
+ PRNG a(42);
+ int hits[11] = { 0 };
+
+ for (int i = 0; i < 1000; i++) {
+ int r = a.RandMinMax(0, 10);
+ ASSERT_LE(0, r);
+ ASSERT_GE(10, r);
+ hits[r]++;
+ }
+
+ for (auto& hit : hits)
+ EXPECT_LT(0, hit);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/provider.h b/update_manager/provider.h
new file mode 100644
index 0000000..84335a2
--- /dev/null
+++ b/update_manager/provider.h
@@ -0,0 +1,38 @@
+//
+// 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_UPDATE_MANAGER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_PROVIDER_H_
+
+#include <base/macros.h>
+
+namespace chromeos_update_manager {
+
+// Abstract base class for a policy provider.
+class Provider {
+ public:
+ virtual ~Provider() {}
+
+ protected:
+ Provider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Provider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_PROVIDER_H_
diff --git a/update_manager/random_provider.h b/update_manager/random_provider.h
new file mode 100644
index 0000000..60df62d
--- /dev/null
+++ b/update_manager/random_provider.h
@@ -0,0 +1,45 @@
+//
+// 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_UPDATE_MANAGER_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_RANDOM_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider of random values.
+class RandomProvider : public Provider {
+ public:
+ ~RandomProvider() override {}
+
+ // Return a random number every time it is requested. Note that values
+ // returned by the variables are cached by the EvaluationContext, so the
+ // returned value will be the same during the same policy request. If more
+ // random values are needed use a PRNG seeded with this value.
+ virtual Variable<uint64_t>* var_seed() = 0;
+
+ protected:
+ RandomProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RandomProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_RANDOM_PROVIDER_H_
diff --git a/update_manager/real_config_provider.cc b/update_manager/real_config_provider.cc
new file mode 100644
index 0000000..2d17a7f
--- /dev/null
+++ b/update_manager/real_config_provider.cc
@@ -0,0 +1,64 @@
+//
+// 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/update_manager/real_config_provider.h"
+
+#include <base/files/file_path.h>
+#include <base/logging.h>
+#include <brillo/key_value_store.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+using brillo::KeyValueStore;
+
+namespace {
+
+const char* kConfigFilePath = "/etc/update_manager.conf";
+
+// Config options:
+const char* kConfigOptsIsOOBEEnabled = "is_oobe_enabled";
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+bool RealConfigProvider::Init() {
+ KeyValueStore store;
+
+ if (hardware_->IsNormalBootMode()) {
+ store.Load(base::FilePath(root_prefix_ + kConfigFilePath));
+ } else {
+ if (store.Load(base::FilePath(root_prefix_ +
+ chromeos_update_engine::kStatefulPartition +
+ kConfigFilePath))) {
+ LOG(INFO) << "UpdateManager Config loaded from stateful partition.";
+ } else {
+ store.Load(base::FilePath(root_prefix_ + kConfigFilePath));
+ }
+ }
+
+ bool is_oobe_enabled;
+ if (!store.GetBoolean(kConfigOptsIsOOBEEnabled, &is_oobe_enabled))
+ is_oobe_enabled = true; // Default value.
+ var_is_oobe_enabled_.reset(
+ new ConstCopyVariable<bool>(kConfigOptsIsOOBEEnabled, is_oobe_enabled));
+
+ return true;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_config_provider.h b/update_manager/real_config_provider.h
new file mode 100644
index 0000000..4de910c
--- /dev/null
+++ b/update_manager/real_config_provider.h
@@ -0,0 +1,65 @@
+//
+// 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_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+namespace chromeos_update_manager {
+
+// ConfigProvider concrete implementation.
+class RealConfigProvider : public ConfigProvider {
+ public:
+ explicit RealConfigProvider(
+ chromeos_update_engine::HardwareInterface* hardware)
+ : hardware_(hardware) {}
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ Variable<bool>* var_is_oobe_enabled() override {
+ return var_is_oobe_enabled_.get();
+ }
+
+ private:
+ friend class UmRealConfigProviderTest;
+
+ // Used for testing. Sets the root prefix, which is by default "". Call this
+ // method before calling Init() in order to mock out the place where the files
+ // are being read from.
+ void SetRootPrefix(const std::string& prefix) {
+ root_prefix_ = prefix;
+ }
+
+ std::unique_ptr<ConstCopyVariable<bool>> var_is_oobe_enabled_;
+
+ chromeos_update_engine::HardwareInterface* hardware_;
+
+ // Prefix to prepend to the file paths. Useful for testing.
+ std::string root_prefix_;
+
+ DISALLOW_COPY_AND_ASSIGN(RealConfigProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
diff --git a/update_manager/real_config_provider_unittest.cc b/update_manager/real_config_provider_unittest.cc
new file mode 100644
index 0000000..2d7dc0d
--- /dev/null
+++ b/update_manager/real_config_provider_unittest.cc
@@ -0,0 +1,102 @@
+//
+// 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/update_manager/real_config_provider.h"
+
+#include <memory>
+
+#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_hardware.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::TimeDelta;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealConfigProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(root_dir_.CreateUniqueTempDir());
+ provider_.reset(new RealConfigProvider(&fake_hardware_));
+ provider_->SetRootPrefix(root_dir_.path().value());
+ }
+
+ void WriteStatefulConfig(const string& config) {
+ base::FilePath kFile(root_dir_.path().value()
+ + chromeos_update_engine::kStatefulPartition
+ + "/etc/update_manager.conf");
+ ASSERT_TRUE(base::CreateDirectory(kFile.DirName()));
+ ASSERT_TRUE(WriteFileString(kFile.value(), config));
+ }
+
+ void WriteRootfsConfig(const string& config) {
+ base::FilePath kFile(root_dir_.path().value()
+ + "/etc/update_manager.conf");
+ ASSERT_TRUE(base::CreateDirectory(kFile.DirName()));
+ ASSERT_TRUE(WriteFileString(kFile.value(), config));
+ }
+
+ unique_ptr<RealConfigProvider> provider_;
+ chromeos_update_engine::FakeHardware fake_hardware_;
+ TimeDelta default_timeout_ = TimeDelta::FromSeconds(1);
+ base::ScopedTempDir root_dir_;
+};
+
+TEST_F(UmRealConfigProviderTest, InitTest) {
+ EXPECT_TRUE(provider_->Init());
+ EXPECT_NE(nullptr, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, NoFileFoundReturnsDefault) {
+ EXPECT_TRUE(provider_->Init());
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, DontReadStatefulInNormalMode) {
+ fake_hardware_.SetIsNormalBootMode(true);
+ WriteStatefulConfig("is_oobe_enabled=false");
+
+ EXPECT_TRUE(provider_->Init());
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, ReadStatefulInDevMode) {
+ fake_hardware_.SetIsNormalBootMode(false);
+ WriteRootfsConfig("is_oobe_enabled=true");
+ // Since the stateful is present, this should read that one.
+ WriteStatefulConfig("is_oobe_enabled=false");
+
+ EXPECT_TRUE(provider_->Init());
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_enabled());
+}
+
+TEST_F(UmRealConfigProviderTest, ReadRootfsIfStatefulNotFound) {
+ fake_hardware_.SetIsNormalBootMode(false);
+ WriteRootfsConfig("is_oobe_enabled=false");
+
+ EXPECT_TRUE(provider_->Init());
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_enabled());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_device_policy_provider.cc b/update_manager/real_device_policy_provider.cc
new file mode 100644
index 0000000..0abd9f7
--- /dev/null
+++ b/update_manager/real_device_policy_provider.cc
@@ -0,0 +1,187 @@
+//
+// 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/update_manager/real_device_policy_provider.h"
+
+#include <stdint.h>
+
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/generic_variables.h"
+#include "update_engine/update_manager/real_shill_provider.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using policy::DevicePolicy;
+using std::set;
+using std::string;
+
+namespace {
+
+const int kDevicePolicyRefreshRateInMinutes = 60;
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+RealDevicePolicyProvider::~RealDevicePolicyProvider() {
+ MessageLoop::current()->CancelTask(scheduled_refresh_);
+}
+
+bool RealDevicePolicyProvider::Init() {
+ CHECK(policy_provider_ != nullptr);
+
+ // On Init() we try to get the device policy and keep updating it.
+ RefreshDevicePolicyAndReschedule();
+
+ // We also listen for signals from the session manager to force a device
+ // policy refresh.
+ session_manager_proxy_->RegisterPropertyChangeCompleteSignalHandler(
+ base::Bind(&RealDevicePolicyProvider::OnPropertyChangedCompletedSignal,
+ base::Unretained(this)),
+ base::Bind(&RealDevicePolicyProvider::OnSignalConnected,
+ base::Unretained(this)));
+ return true;
+}
+
+void RealDevicePolicyProvider::OnPropertyChangedCompletedSignal(
+ const string& success) {
+ if (success != "success") {
+ LOG(WARNING) << "Received device policy updated signal with a failure.";
+ }
+ // We refresh the policy file even if the payload string is kSignalFailure.
+ LOG(INFO) << "Reloading and re-scheduling device policy due to signal "
+ "received.";
+ MessageLoop::current()->CancelTask(scheduled_refresh_);
+ scheduled_refresh_ = MessageLoop::kTaskIdNull;
+ RefreshDevicePolicyAndReschedule();
+}
+
+void RealDevicePolicyProvider::OnSignalConnected(const string& interface_name,
+ const string& signal_name,
+ bool successful) {
+ if (!successful) {
+ LOG(WARNING) << "We couldn't connect to SessionManager signal for updates "
+ "on the device policy blob. We will reload the policy file "
+ "periodically.";
+ }
+ // We do a one-time refresh of the DevicePolicy just in case we missed a
+ // signal between the first refresh and the time the signal handler was
+ // actually connected.
+ RefreshDevicePolicy();
+}
+
+void RealDevicePolicyProvider::RefreshDevicePolicyAndReschedule() {
+ RefreshDevicePolicy();
+ scheduled_refresh_ = MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&RealDevicePolicyProvider::RefreshDevicePolicyAndReschedule,
+ base::Unretained(this)),
+ TimeDelta::FromMinutes(kDevicePolicyRefreshRateInMinutes));
+}
+
+template<typename T>
+void RealDevicePolicyProvider::UpdateVariable(
+ AsyncCopyVariable<T>* var,
+ bool (DevicePolicy::*getter_method)(T*) const) {
+ T new_value;
+ if (policy_provider_->device_policy_is_loaded() &&
+ (policy_provider_->GetDevicePolicy().*getter_method)(&new_value)) {
+ var->SetValue(new_value);
+ } else {
+ var->UnsetValue();
+ }
+}
+
+template<typename T>
+void RealDevicePolicyProvider::UpdateVariable(
+ AsyncCopyVariable<T>* var,
+ bool (RealDevicePolicyProvider::*getter_method)(T*) const) {
+ T new_value;
+ if (policy_provider_->device_policy_is_loaded() &&
+ (this->*getter_method)(&new_value)) {
+ var->SetValue(new_value);
+ } else {
+ var->UnsetValue();
+ }
+}
+
+bool RealDevicePolicyProvider::ConvertAllowedConnectionTypesForUpdate(
+ set<ConnectionType>* allowed_types) const {
+ set<string> allowed_types_str;
+ if (!policy_provider_->GetDevicePolicy()
+ .GetAllowedConnectionTypesForUpdate(&allowed_types_str)) {
+ return false;
+ }
+ allowed_types->clear();
+ for (auto& type_str : allowed_types_str) {
+ ConnectionType type =
+ RealShillProvider::ParseConnectionType(type_str.c_str());
+ if (type != ConnectionType::kUnknown) {
+ allowed_types->insert(type);
+ } else {
+ LOG(WARNING) << "Policy includes unknown connection type: " << type_str;
+ }
+ }
+ return true;
+}
+
+bool RealDevicePolicyProvider::ConvertScatterFactor(
+ TimeDelta* scatter_factor) const {
+ int64_t scatter_factor_in_seconds;
+ if (!policy_provider_->GetDevicePolicy().GetScatterFactorInSeconds(
+ &scatter_factor_in_seconds)) {
+ return false;
+ }
+ if (scatter_factor_in_seconds < 0) {
+ LOG(WARNING) << "Ignoring negative scatter factor: "
+ << scatter_factor_in_seconds;
+ return false;
+ }
+ *scatter_factor = TimeDelta::FromSeconds(scatter_factor_in_seconds);
+ return true;
+}
+
+void RealDevicePolicyProvider::RefreshDevicePolicy() {
+ if (!policy_provider_->Reload()) {
+ LOG(INFO) << "No device policies/settings present.";
+ }
+
+ var_device_policy_is_loaded_.SetValue(
+ policy_provider_->device_policy_is_loaded());
+
+ UpdateVariable(&var_release_channel_, &DevicePolicy::GetReleaseChannel);
+ UpdateVariable(&var_release_channel_delegated_,
+ &DevicePolicy::GetReleaseChannelDelegated);
+ UpdateVariable(&var_update_disabled_, &DevicePolicy::GetUpdateDisabled);
+ UpdateVariable(&var_target_version_prefix_,
+ &DevicePolicy::GetTargetVersionPrefix);
+ UpdateVariable(&var_scatter_factor_,
+ &RealDevicePolicyProvider::ConvertScatterFactor);
+ UpdateVariable(
+ &var_allowed_connection_types_for_update_,
+ &RealDevicePolicyProvider::ConvertAllowedConnectionTypesForUpdate);
+ UpdateVariable(&var_owner_, &DevicePolicy::GetOwner);
+ UpdateVariable(&var_http_downloads_enabled_,
+ &DevicePolicy::GetHttpDownloadsEnabled);
+ UpdateVariable(&var_au_p2p_enabled_, &DevicePolicy::GetAuP2PEnabled);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_device_policy_provider.h b/update_manager/real_device_policy_provider.h
new file mode 100644
index 0000000..6094e93
--- /dev/null
+++ b/update_manager/real_device_policy_provider.h
@@ -0,0 +1,166 @@
+//
+// 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_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
+
+#include <set>
+#include <string>
+
+#include <brillo/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+#include <policy/libpolicy.h>
+#include <session_manager/dbus-proxies.h>
+
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+namespace chromeos_update_manager {
+
+// DevicePolicyProvider concrete implementation.
+class RealDevicePolicyProvider : public DevicePolicyProvider {
+ public:
+ RealDevicePolicyProvider(org::chromium::SessionManagerInterfaceProxyInterface*
+ session_manager_proxy,
+ policy::PolicyProvider* policy_provider)
+ : policy_provider_(policy_provider),
+ session_manager_proxy_(session_manager_proxy) {}
+ ~RealDevicePolicyProvider();
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ Variable<bool>* var_device_policy_is_loaded() override {
+ return &var_device_policy_is_loaded_;
+ }
+
+ Variable<std::string>* var_release_channel() override {
+ return &var_release_channel_;
+ }
+
+ Variable<bool>* var_release_channel_delegated() override {
+ return &var_release_channel_delegated_;
+ }
+
+ Variable<bool>* var_update_disabled() override {
+ return &var_update_disabled_;
+ }
+
+ Variable<std::string>* var_target_version_prefix() override {
+ return &var_target_version_prefix_;
+ }
+
+ Variable<base::TimeDelta>* var_scatter_factor() override {
+ return &var_scatter_factor_;
+ }
+
+ Variable<std::set<ConnectionType>>*
+ var_allowed_connection_types_for_update() override {
+ return &var_allowed_connection_types_for_update_;
+ }
+
+ Variable<std::string>* var_owner() override {
+ return &var_owner_;
+ }
+
+ Variable<bool>* var_http_downloads_enabled() override {
+ return &var_http_downloads_enabled_;
+ }
+
+ Variable<bool>* var_au_p2p_enabled() override {
+ return &var_au_p2p_enabled_;
+ }
+
+ private:
+ FRIEND_TEST(UmRealDevicePolicyProviderTest, RefreshScheduledTest);
+ FRIEND_TEST(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded);
+ FRIEND_TEST(UmRealDevicePolicyProviderTest, ValuesUpdated);
+
+ // 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
+ // connected.
+ void OnSignalConnected(const std::string& interface_name,
+ const std::string& signal_name,
+ bool successful);
+
+ // Schedules a call to periodically refresh the device policy.
+ void RefreshDevicePolicyAndReschedule();
+
+ // Reloads the device policy and updates all the exposed variables.
+ void RefreshDevicePolicy();
+
+ // Updates the async variable |var| based on the result value of the method
+ // passed, which is a DevicePolicy getter method.
+ template<typename T>
+ void UpdateVariable(AsyncCopyVariable<T>* var,
+ bool (policy::DevicePolicy::*getter_method)(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);
+
+ // 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.
+ bool ConvertAllowedConnectionTypesForUpdate(
+ std::set<ConnectionType>* allowed_types) const;
+
+ // Used for fetching information about the device policy.
+ policy::PolicyProvider* policy_provider_;
+
+ // Used to schedule refreshes of the device policy.
+ brillo::MessageLoop::TaskId scheduled_refresh_{
+ brillo::MessageLoop::kTaskIdNull};
+
+ // The DBus (mockable) session manager proxy, owned by the caller.
+ org::chromium::SessionManagerInterfaceProxyInterface* session_manager_proxy_{
+ nullptr};
+
+ // Variable exposing whether the policy is loaded.
+ AsyncCopyVariable<bool> var_device_policy_is_loaded_{
+ "policy_is_loaded", false};
+
+ // 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<bool> var_update_disabled_{"update_disabled"};
+ AsyncCopyVariable<std::string> var_target_version_prefix_{
+ "target_version_prefix"};
+ AsyncCopyVariable<base::TimeDelta> var_scatter_factor_{"scatter_factor"};
+ AsyncCopyVariable<std::set<ConnectionType>>
+ var_allowed_connection_types_for_update_{
+ "allowed_connection_types_for_update"};
+ AsyncCopyVariable<std::string> var_owner_{"owner"};
+ AsyncCopyVariable<bool> var_http_downloads_enabled_{"http_downloads_enabled"};
+ AsyncCopyVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled"};
+
+ DISALLOW_COPY_AND_ASSIGN(RealDevicePolicyProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_manager/real_device_policy_provider_unittest.cc b/update_manager/real_device_policy_provider_unittest.cc
new file mode 100644
index 0000000..c480b60
--- /dev/null
+++ b/update_manager/real_device_policy_provider_unittest.cc
@@ -0,0 +1,227 @@
+//
+// 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/update_manager/real_device_policy_provider.h"
+
+#include <memory>
+
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <policy/mock_device_policy.h>
+#include <policy/mock_libpolicy.h>
+#include <session_manager/dbus-proxies.h>
+#include <session_manager/dbus-proxy-mocks.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/dbus_test_utils.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using chromeos_update_engine::dbus_test_utils::MockSignalHandler;
+using std::set;
+using std::string;
+using std::unique_ptr;
+using testing::DoAll;
+using testing::Mock;
+using testing::Return;
+using testing::ReturnRef;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace chromeos_update_manager {
+
+class UmRealDevicePolicyProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ provider_.reset(new RealDevicePolicyProvider(&session_manager_proxy_mock_,
+ &mock_policy_provider_));
+ // By default, we have a device policy loaded. Tests can call
+ // SetUpNonExistentDevicePolicy() to override this.
+ SetUpExistentDevicePolicy();
+
+ // Setup the session manager_proxy such that it will accept the signal
+ // handler and store it in the |property_change_complete_| once registered.
+ MOCK_SIGNAL_HANDLER_EXPECT_SIGNAL_HANDLER(property_change_complete_,
+ session_manager_proxy_mock_,
+ PropertyChangeComplete);
+ }
+
+ void TearDown() override {
+ provider_.reset();
+ // Check for leaked callbacks on the main loop.
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ void SetUpNonExistentDevicePolicy() {
+ ON_CALL(mock_policy_provider_, Reload())
+ .WillByDefault(Return(false));
+ ON_CALL(mock_policy_provider_, device_policy_is_loaded())
+ .WillByDefault(Return(false));
+ EXPECT_CALL(mock_policy_provider_, GetDevicePolicy()).Times(0);
+ }
+
+ void SetUpExistentDevicePolicy() {
+ // Setup the default behavior of the mocked PolicyProvider.
+ ON_CALL(mock_policy_provider_, Reload())
+ .WillByDefault(Return(true));
+ ON_CALL(mock_policy_provider_, device_policy_is_loaded())
+ .WillByDefault(Return(true));
+ ON_CALL(mock_policy_provider_, GetDevicePolicy())
+ .WillByDefault(ReturnRef(mock_device_policy_));
+ }
+
+ brillo::FakeMessageLoop loop_{nullptr};
+ org::chromium::SessionManagerInterfaceProxyMock session_manager_proxy_mock_;
+ testing::NiceMock<policy::MockDevicePolicy> mock_device_policy_;
+ testing::NiceMock<policy::MockPolicyProvider> mock_policy_provider_;
+ unique_ptr<RealDevicePolicyProvider> provider_;
+
+ // The registered signal handler for the signal.
+ MockSignalHandler<void(const string&)> property_change_complete_;
+};
+
+TEST_F(UmRealDevicePolicyProviderTest, RefreshScheduledTest) {
+ // Check that the RefreshPolicy gets scheduled by checking the TaskId.
+ EXPECT_TRUE(provider_->Init());
+ EXPECT_NE(MessageLoop::kTaskIdNull, provider_->scheduled_refresh_);
+ loop_.RunOnce(false);
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, FirstReload) {
+ // Checks that the policy is reloaded and the DevicePolicy is consulted twice:
+ // once on Init() and once again when the signal is connected.
+ EXPECT_CALL(mock_policy_provider_, Reload());
+ EXPECT_TRUE(provider_->Init());
+ Mock::VerifyAndClearExpectations(&mock_policy_provider_);
+
+ EXPECT_CALL(mock_policy_provider_, Reload());
+ loop_.RunOnce(false);
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded) {
+ // Checks that the policy is reloaded by RefreshDevicePolicy().
+ SetUpNonExistentDevicePolicy();
+ EXPECT_CALL(mock_policy_provider_, Reload()).Times(3);
+ EXPECT_TRUE(provider_->Init());
+ loop_.RunOnce(false);
+ // Force the policy refresh.
+ provider_->RefreshDevicePolicy();
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, SessionManagerSignalForcesReload) {
+ // Checks that a signal from the SessionManager forces a reload.
+ SetUpNonExistentDevicePolicy();
+ EXPECT_CALL(mock_policy_provider_, Reload()).Times(2);
+ EXPECT_TRUE(provider_->Init());
+ loop_.RunOnce(false);
+ Mock::VerifyAndClearExpectations(&mock_policy_provider_);
+
+ EXPECT_CALL(mock_policy_provider_, Reload());
+ ASSERT_TRUE(property_change_complete_.IsHandlerRegistered());
+ property_change_complete_.signal_callback().Run("success");
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyEmptyVariables) {
+ SetUpNonExistentDevicePolicy();
+ EXPECT_CALL(mock_policy_provider_, GetDevicePolicy()).Times(0);
+ EXPECT_TRUE(provider_->Init());
+ loop_.RunOnce(false);
+
+ UmTestUtils::ExpectVariableHasValue(false,
+ provider_->var_device_policy_is_loaded());
+
+ UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel_delegated());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_update_disabled());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_target_version_prefix());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor());
+ UmTestUtils::ExpectVariableNotSet(
+ provider_->var_allowed_connection_types_for_update());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_owner());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_http_downloads_enabled());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_au_p2p_enabled());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, ValuesUpdated) {
+ SetUpNonExistentDevicePolicy();
+ EXPECT_TRUE(provider_->Init());
+ loop_.RunOnce(false);
+ Mock::VerifyAndClearExpectations(&mock_policy_provider_);
+
+ // Reload the policy with a good one and set some values as present. The
+ // remaining values are false.
+ SetUpExistentDevicePolicy();
+ EXPECT_CALL(mock_device_policy_, GetReleaseChannel(_))
+ .WillOnce(DoAll(SetArgPointee<0>(string("mychannel")), Return(true)));
+ EXPECT_CALL(mock_device_policy_, GetAllowedConnectionTypesForUpdate(_))
+ .WillOnce(Return(false));
+
+ provider_->RefreshDevicePolicy();
+
+ UmTestUtils::ExpectVariableHasValue(true,
+ provider_->var_device_policy_is_loaded());
+
+ // Test that at least one variable is set, to ensure the refresh occurred.
+ UmTestUtils::ExpectVariableHasValue(string("mychannel"),
+ provider_->var_release_channel());
+ UmTestUtils::ExpectVariableNotSet(
+ provider_->var_allowed_connection_types_for_update());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, ScatterFactorConverted) {
+ SetUpExistentDevicePolicy();
+ EXPECT_CALL(mock_device_policy_, GetScatterFactorInSeconds(_))
+ .Times(2)
+ .WillRepeatedly(DoAll(SetArgPointee<0>(1234), Return(true)));
+ EXPECT_TRUE(provider_->Init());
+ loop_.RunOnce(false);
+
+ UmTestUtils::ExpectVariableHasValue(TimeDelta::FromSeconds(1234),
+ provider_->var_scatter_factor());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, NegativeScatterFactorIgnored) {
+ SetUpExistentDevicePolicy();
+ EXPECT_CALL(mock_device_policy_, GetScatterFactorInSeconds(_))
+ .Times(2)
+ .WillRepeatedly(DoAll(SetArgPointee<0>(-1), Return(true)));
+ EXPECT_TRUE(provider_->Init());
+ loop_.RunOnce(false);
+
+ UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, AllowedTypesConverted) {
+ SetUpExistentDevicePolicy();
+ EXPECT_CALL(mock_device_policy_, GetAllowedConnectionTypesForUpdate(_))
+ .Times(2)
+ .WillRepeatedly(DoAll(
+ SetArgPointee<0>(set<string>{"bluetooth", "wifi", "not-a-type"}),
+ Return(true)));
+ EXPECT_TRUE(provider_->Init());
+ loop_.RunOnce(false);
+
+ UmTestUtils::ExpectVariableHasValue(
+ set<ConnectionType>{ConnectionType::kWifi, ConnectionType::kBluetooth},
+ provider_->var_allowed_connection_types_for_update());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_random_provider.cc b/update_manager/real_random_provider.cc
new file mode 100644
index 0000000..ed0eb4d
--- /dev/null
+++ b/update_manager/real_random_provider.cc
@@ -0,0 +1,89 @@
+//
+// 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/update_manager/real_random_provider.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/scoped_file.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/update_manager/variable.h"
+
+using std::string;
+
+namespace {
+
+// The device providing randomness.
+const char* kRandomDevice = "/dev/urandom";
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+// A random seed variable.
+class RandomSeedVariable : public Variable<uint64_t> {
+ public:
+ // RandomSeedVariable is initialized as kVariableModeConst to let the
+ // EvaluationContext cache the value between different evaluations of the same
+ // policy request.
+ RandomSeedVariable(const string& name, FILE* fp)
+ : Variable<uint64_t>(name, kVariableModeConst), fp_(fp) {}
+ ~RandomSeedVariable() override {}
+
+ protected:
+ const uint64_t* GetValue(base::TimeDelta /* timeout */,
+ string* errmsg) override {
+ uint64_t result;
+ // Aliasing via char pointer abides by the C/C++ strict-aliasing rules.
+ char* const buf = reinterpret_cast<char*>(&result);
+ unsigned int buf_rd = 0;
+
+ while (buf_rd < sizeof(result)) {
+ int rd = fread(buf + buf_rd, 1, sizeof(result) - buf_rd, fp_.get());
+ if (rd == 0 || ferror(fp_.get())) {
+ // Either EOF on fp or read failed.
+ if (errmsg) {
+ *errmsg = base::StringPrintf(
+ "Error reading from the random device: %s", kRandomDevice);
+ }
+ return nullptr;
+ }
+ buf_rd += rd;
+ }
+
+ return new uint64_t(result);
+ }
+
+ private:
+ base::ScopedFILE fp_;
+
+ DISALLOW_COPY_AND_ASSIGN(RandomSeedVariable);
+};
+
+bool RealRandomProvider::Init(void) {
+ FILE* fp = fopen(kRandomDevice, "r");
+ if (!fp)
+ return false;
+ var_seed_.reset(new RandomSeedVariable("seed", fp));
+ return true;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_random_provider.h b/update_manager/real_random_provider.h
new file mode 100644
index 0000000..14ce7a3
--- /dev/null
+++ b/update_manager/real_random_provider.h
@@ -0,0 +1,45 @@
+//
+// 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_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
+
+#include <memory>
+
+#include "update_engine/update_manager/random_provider.h"
+
+namespace chromeos_update_manager {
+
+// RandomProvider implementation class.
+class RealRandomProvider : public RandomProvider {
+ public:
+ RealRandomProvider() {}
+
+ Variable<uint64_t>* var_seed() override { return var_seed_.get(); }
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ private:
+ // The seed() scoped variable.
+ std::unique_ptr<Variable<uint64_t>> var_seed_;
+
+ DISALLOW_COPY_AND_ASSIGN(RealRandomProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_RANDOM_PROVIDER_H_
diff --git a/update_manager/real_random_provider_unittest.cc b/update_manager/real_random_provider_unittest.cc
new file mode 100644
index 0000000..ca67da6
--- /dev/null
+++ b/update_manager/real_random_provider_unittest.cc
@@ -0,0 +1,67 @@
+//
+// 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/update_manager/real_random_provider.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+
+#include "update_engine/update_manager/umtest_utils.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealRandomProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // The provider initializes correctly.
+ provider_.reset(new RealRandomProvider());
+ ASSERT_NE(nullptr, provider_.get());
+ ASSERT_TRUE(provider_->Init());
+
+ provider_->var_seed();
+ }
+
+ unique_ptr<RealRandomProvider> provider_;
+};
+
+TEST_F(UmRealRandomProviderTest, InitFinalize) {
+ // The provider initializes all variables with valid objects.
+ EXPECT_NE(nullptr, provider_->var_seed());
+}
+
+TEST_F(UmRealRandomProviderTest, GetRandomValues) {
+ // Should not return the same random seed repeatedly.
+ unique_ptr<const uint64_t> value(
+ provider_->var_seed()->GetValue(UmTestUtils::DefaultTimeout(), nullptr));
+ ASSERT_NE(nullptr, value.get());
+
+ // Test that at least the returned values are different. This test fails,
+ // by design, once every 2^320 runs.
+ bool is_same_value = true;
+ for (int i = 0; i < 5; i++) {
+ unique_ptr<const uint64_t> other_value(
+ provider_->var_seed()->GetValue(UmTestUtils::DefaultTimeout(),
+ nullptr));
+ ASSERT_NE(nullptr, other_value.get());
+ is_same_value = is_same_value && *other_value == *value;
+ }
+ EXPECT_FALSE(is_same_value);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_shill_provider.cc b/update_manager/real_shill_provider.cc
new file mode 100644
index 0000000..7938180
--- /dev/null
+++ b/update_manager/real_shill_provider.cc
@@ -0,0 +1,191 @@
+//
+// 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/update_manager/real_shill_provider.h"
+
+#include <string>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/type_name_undecorate.h>
+#include <shill/dbus-constants.h>
+#include <shill/dbus-proxies.h>
+
+using org::chromium::flimflam::ManagerProxyInterface;
+using org::chromium::flimflam::ServiceProxyInterface;
+using std::string;
+
+namespace chromeos_update_manager {
+
+ConnectionType RealShillProvider::ParseConnectionType(const string& type_str) {
+ if (type_str == shill::kTypeEthernet) {
+ 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;
+ }
+ return ConnectionType::kUnknown;
+}
+
+ConnectionTethering RealShillProvider::ParseConnectionTethering(
+ const string& tethering_str) {
+ if (tethering_str == shill::kTetheringNotDetectedState) {
+ return ConnectionTethering::kNotDetected;
+ } else if (tethering_str == shill::kTetheringSuspectedState) {
+ return ConnectionTethering::kSuspected;
+ } else if (tethering_str == shill::kTetheringConfirmedState) {
+ return ConnectionTethering::kConfirmed;
+ }
+ return ConnectionTethering::kUnknown;
+}
+
+bool RealShillProvider::Init() {
+ ManagerProxyInterface* manager_proxy = shill_proxy_->GetManagerProxy();
+ if (!manager_proxy)
+ return false;
+
+ // Subscribe to the manager's PropertyChanged signal.
+ manager_proxy->RegisterPropertyChangedSignalHandler(
+ base::Bind(&RealShillProvider::OnManagerPropertyChanged,
+ base::Unretained(this)),
+ base::Bind(&RealShillProvider::OnSignalConnected,
+ base::Unretained(this)));
+
+ // Attempt to read initial connection status. Even if this fails because shill
+ // is not responding (e.g. it is down) we'll be notified via "PropertyChanged"
+ // signal as soon as it comes up, so this is not a critical step.
+ brillo::VariantDictionary properties;
+ brillo::ErrorPtr error;
+ if (!manager_proxy->GetProperties(&properties, &error))
+ return true;
+
+ const auto& prop_default_service =
+ properties.find(shill::kDefaultServiceProperty);
+ if (prop_default_service != properties.end()) {
+ OnManagerPropertyChanged(prop_default_service->first,
+ prop_default_service->second);
+ }
+
+ return true;
+}
+
+void RealShillProvider::OnManagerPropertyChanged(const string& name,
+ const brillo::Any& value) {
+ if (name == shill::kDefaultServiceProperty) {
+ dbus::ObjectPath service_path = value.TryGet<dbus::ObjectPath>();
+ if (!service_path.IsValid()) {
+ LOG(WARNING) << "Got an invalid DefaultService path. The property value "
+ "contains a "
+ << value.GetUndecoratedTypeName()
+ << ", read as the object path: '" << service_path.value()
+ << "'";
+ }
+ ProcessDefaultService(service_path);
+ }
+}
+
+void RealShillProvider::OnSignalConnected(const string& interface_name,
+ const string& signal_name,
+ bool successful) {
+ if (!successful) {
+ LOG(ERROR) << "Couldn't connect to the signal " << interface_name << "."
+ << signal_name;
+ }
+}
+
+bool RealShillProvider::ProcessDefaultService(
+ const dbus::ObjectPath& default_service_path) {
+ // We assume that if the service path didn't change, then the connection
+ // type and the tethering status of it also didn't change.
+ if (default_service_path_ == default_service_path)
+ return true;
+
+ // Update the connection status.
+ default_service_path_ = default_service_path;
+ bool is_connected = (default_service_path_.IsValid() &&
+ default_service_path_.value() != "/");
+ var_is_connected_.SetValue(is_connected);
+ var_conn_last_changed_.SetValue(clock_->GetWallclockTime());
+
+ if (!is_connected) {
+ var_conn_type_.UnsetValue();
+ var_conn_tethering_.UnsetValue();
+ return true;
+ }
+
+ // We create and dispose the ServiceProxyInterface on every request.
+ std::unique_ptr<ServiceProxyInterface> service =
+ shill_proxy_->GetServiceForPath(default_service_path_);
+
+ // Get the connection properties synchronously.
+ brillo::VariantDictionary properties;
+ brillo::ErrorPtr error;
+ if (!service->GetProperties(&properties, &error)) {
+ var_conn_type_.UnsetValue();
+ var_conn_tethering_.UnsetValue();
+ return false;
+ }
+
+ // Get the connection tethering mode.
+ const auto& prop_tethering = properties.find(shill::kTetheringProperty);
+ if (prop_tethering == properties.end()) {
+ // Remove the value if not present on the service. This most likely means an
+ // error in shill and the policy will handle it, but we will print a log
+ // message as well for accessing an unused variable.
+ var_conn_tethering_.UnsetValue();
+ LOG(ERROR) << "Could not find connection type (service: "
+ << default_service_path_.value() << ")";
+ } else {
+ // If the property doesn't contain a string value, the empty string will
+ // become kUnknown.
+ var_conn_tethering_.SetValue(
+ ParseConnectionTethering(prop_tethering->second.TryGet<string>()));
+ }
+
+ // Get the connection type.
+ const auto& prop_type = properties.find(shill::kTypeProperty);
+ if (prop_type == properties.end()) {
+ var_conn_type_.UnsetValue();
+ LOG(ERROR) << "Could not find connection tethering mode (service: "
+ << default_service_path_.value() << ")";
+ } else {
+ string type_str = prop_type->second.TryGet<string>();
+ if (type_str == shill::kTypeVPN) {
+ const auto& prop_physical =
+ properties.find(shill::kPhysicalTechnologyProperty);
+ if (prop_physical == properties.end()) {
+ LOG(ERROR) << "No PhysicalTechnology property found for a VPN"
+ << " connection (service: " << default_service_path_.value()
+ << "). Using default kUnknown value.";
+ var_conn_type_.SetValue(ConnectionType::kUnknown);
+ } else {
+ var_conn_type_.SetValue(
+ ParseConnectionType(prop_physical->second.TryGet<string>()));
+ }
+ } else {
+ var_conn_type_.SetValue(ParseConnectionType(type_str));
+ }
+ }
+
+ return true;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_shill_provider.h b/update_manager/real_shill_provider.h
new file mode 100644
index 0000000..dbd6fc5
--- /dev/null
+++ b/update_manager/real_shill_provider.h
@@ -0,0 +1,104 @@
+//
+// 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_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
+
+// TODO(garnold) Much of the functionality in this module was adapted from the
+// update engine's connection_manager. We need to make sure to deprecate use of
+// connection manager when the time comes.
+
+#include <string>
+
+#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/update_manager/generic_variables.h"
+#include "update_engine/update_manager/shill_provider.h"
+
+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) {}
+
+ ~RealShillProvider() override = default;
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ Variable<bool>* var_is_connected() override {
+ return &var_is_connected_;
+ }
+
+ Variable<ConnectionType>* var_conn_type() override {
+ return &var_conn_type_;
+ }
+
+ Variable<ConnectionTethering>* var_conn_tethering() override {
+ return &var_conn_tethering_;
+ }
+
+ Variable<base::Time>* var_conn_last_changed() override {
+ return &var_conn_last_changed_;
+ }
+
+ // Helper methods for converting shill strings into symbolic values.
+ static ConnectionType ParseConnectionType(const std::string& type_str);
+ static ConnectionTethering ParseConnectionTethering(
+ const std::string& tethering_str);
+
+ private:
+ // A handler for ManagerProxy.PropertyChanged signal.
+ void OnManagerPropertyChanged(const std::string& name,
+ const brillo::Any& value);
+
+ // Called when the signal in ManagerProxy.PropertyChanged is connected.
+ void OnSignalConnected(const std::string& interface_name,
+ const std::string& signal_name,
+ bool successful);
+
+ // Get the connection and populate the type and tethering status of the given
+ // default connection.
+ bool ProcessDefaultService(const dbus::ObjectPath& default_service_path);
+
+ // The current default service path, if connected. "/" means not connected.
+ dbus::ObjectPath default_service_path_{"uninitialized"};
+
+ // The mockable interface to access the shill DBus proxies, owned by the
+ // caller.
+ 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<ConnectionType> var_conn_type_{"conn_type"};
+ AsyncCopyVariable<ConnectionTethering> var_conn_tethering_{"conn_tethering"};
+ AsyncCopyVariable<base::Time> var_conn_last_changed_{"conn_last_changed"};
+
+ DISALLOW_COPY_AND_ASSIGN(RealShillProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_SHILL_PROVIDER_H_
diff --git a/update_manager/real_shill_provider_unittest.cc b/update_manager/real_shill_provider_unittest.cc
new file mode 100644
index 0000000..2fa0628
--- /dev/null
+++ b/update_manager/real_shill_provider_unittest.cc
@@ -0,0 +1,530 @@
+//
+// 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/update_manager/real_shill_provider.h"
+
+#include <memory>
+#include <utility>
+
+#include <base/time/time.h>
+#include <brillo/make_unique_ptr.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <shill/dbus-constants.h>
+#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/update_manager/umtest_utils.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::FakeClock;
+using org::chromium::flimflam::ManagerProxyMock;
+using org::chromium::flimflam::ServiceProxyMock;
+using std::unique_ptr;
+using testing::Mock;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+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";
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+class UmRealShillProviderTest : public ::testing::Test {
+ protected:
+ // Initialize the RealShillProvider under test.
+ void SetUp() override {
+ fake_clock_.SetWallclockTime(InitTime());
+ loop_.SetAsCurrent();
+ provider_.reset(new RealShillProvider(&fake_shill_proxy_, &fake_clock_));
+
+ ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_.GetManagerProxy();
+
+ // The PropertyChanged signal should be subscribed to.
+ MOCK_SIGNAL_HANDLER_EXPECT_SIGNAL_HANDLER(
+ manager_property_changed_, *manager_proxy_mock, PropertyChanged);
+ }
+
+ void TearDown() override {
+ provider_.reset();
+ // Check for leaked callbacks on the main loop.
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ // These methods generate fixed timestamps for use in faking the current time.
+ Time InitTime() {
+ Time::Exploded now_exp;
+ now_exp.year = 2014;
+ now_exp.month = 3;
+ now_exp.day_of_week = 2;
+ now_exp.day_of_month = 18;
+ now_exp.hour = 8;
+ now_exp.minute = 5;
+ now_exp.second = 33;
+ now_exp.millisecond = 675;
+ return Time::FromLocalExploded(now_exp);
+ }
+
+ Time ConnChangedTime() {
+ return InitTime() + TimeDelta::FromSeconds(10);
+ }
+
+ // Sets the default_service object path in the response from the
+ // ManagerProxyMock instance.
+ void SetManagerReply(const char* default_service, bool reply_succeeds);
+
+ // Sets the |service_type|, |physical_technology| and |service_tethering|
+ // properties in the mocked service |service_path|. If any of the three
+ // const char* is a nullptr, the corresponding property will not be included
+ // in the response.
+ // Returns the mock object pointer, owned by the |fake_shill_proxy_|.
+ ServiceProxyMock* SetServiceReply(const std::string& service_path,
+ const char* service_type,
+ const char* physical_technology,
+ const char* service_tethering);
+
+ void InitWithDefaultService(const char* default_service) {
+ SetManagerReply(default_service, true);
+ // Check that provider initializes correctly.
+ EXPECT_TRUE(provider_->Init());
+ // RunOnce to notify the signal handler was connected properly.
+ EXPECT_TRUE(loop_.RunOnce(false));
+ }
+
+ // Sends a signal informing the provider about a default connection
+ // |service_path|. Sets the fake connection change time in
+ // |conn_change_time_p| if provided.
+ void SendDefaultServiceSignal(const std::string& service_path,
+ Time* conn_change_time_p) {
+ const Time conn_change_time = ConnChangedTime();
+ 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));
+ if (conn_change_time_p)
+ *conn_change_time_p = conn_change_time;
+ }
+
+ // Sets up expectations for detection of a connection |service_path| with type
+ // |shill_type_str| and tethering mode |shill_tethering_str|. Ensures that the
+ // new connection status and change time are properly detected by the
+ // provider. Writes the fake connection change time to |conn_change_time_p|,
+ // if provided.
+ void SetupConnectionAndAttrs(const std::string& service_path,
+ const char* shill_type,
+ const char* shill_tethering,
+ Time* conn_change_time_p) {
+ SetServiceReply(service_path, shill_type, nullptr, shill_tethering);
+ // Note: We don't setup this |service_path| as the default service path but
+ // we instead send a signal notifying the change since the code won't call
+ // GetProperties on the Manager object at this point.
+
+ // Send a signal about a new default service.
+ Time conn_change_time;
+ SendDefaultServiceSignal(service_path, &conn_change_time);
+
+ // Query the connection status, ensure last change time reported correctly.
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected());
+ UmTestUtils::ExpectVariableHasValue(conn_change_time,
+ provider_->var_conn_last_changed());
+
+ // Write the connection change time to the output argument.
+ if (conn_change_time_p)
+ *conn_change_time_p = conn_change_time;
+ }
+
+ // Sets up a connection and tests that its type is being properly detected by
+ // the provider.
+ void SetupConnectionAndTestType(const char* service_path,
+ const char* shill_type,
+ ConnectionType expected_conn_type) {
+ // Set up and test the connection, record the change time.
+ Time conn_change_time;
+ SetupConnectionAndAttrs(service_path,
+ shill_type,
+ shill::kTetheringNotDetectedState,
+ &conn_change_time);
+
+ // Query the connection type, ensure last change time did not change.
+ UmTestUtils::ExpectVariableHasValue(expected_conn_type,
+ provider_->var_conn_type());
+ UmTestUtils::ExpectVariableHasValue(conn_change_time,
+ provider_->var_conn_last_changed());
+ }
+
+ // Sets up a connection and tests that its tethering mode is being properly
+ // detected by the provider.
+ void SetupConnectionAndTestTethering(
+ const char* service_path,
+ const char* shill_tethering,
+ ConnectionTethering expected_conn_tethering) {
+ // Set up and test the connection, record the change time.
+ Time conn_change_time;
+ SetupConnectionAndAttrs(
+ service_path, shill::kTypeEthernet, shill_tethering, &conn_change_time);
+
+ // Query the connection tethering, ensure last change time did not change.
+ UmTestUtils::ExpectVariableHasValue(expected_conn_tethering,
+ provider_->var_conn_tethering());
+ UmTestUtils::ExpectVariableHasValue(conn_change_time,
+ provider_->var_conn_last_changed());
+ }
+
+ brillo::FakeMessageLoop loop_{nullptr};
+ FakeClock fake_clock_;
+ chromeos_update_engine::FakeShillProxy fake_shill_proxy_;
+
+ // The registered signal handler for the signal Manager.PropertyChanged.
+ chromeos_update_engine::dbus_test_utils::MockSignalHandler<
+ void(const std::string&, const brillo::Any&)> manager_property_changed_;
+
+ unique_ptr<RealShillProvider> provider_;
+};
+
+void UmRealShillProviderTest::SetManagerReply(const char* default_service,
+ bool reply_succeeds) {
+ ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_.GetManagerProxy();
+ if (!reply_succeeds) {
+ EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _))
+ .WillOnce(Return(false));
+ return;
+ }
+
+ // Create a dictionary of properties and optionally include the default
+ // service.
+ brillo::VariantDictionary reply_dict;
+ reply_dict["SomeOtherProperty"] = 0xC0FFEE;
+
+ if (default_service) {
+ reply_dict[shill::kDefaultServiceProperty] =
+ dbus::ObjectPath(default_service);
+ }
+ EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true)));
+}
+
+ServiceProxyMock* UmRealShillProviderTest::SetServiceReply(
+ const std::string& service_path,
+ const char* service_type,
+ const char* physical_technology,
+ const char* service_tethering) {
+ brillo::VariantDictionary reply_dict;
+ reply_dict["SomeOtherProperty"] = 0xC0FFEE;
+
+ if (service_type)
+ reply_dict[shill::kTypeProperty] = std::string(service_type);
+
+ if (physical_technology) {
+ reply_dict[shill::kPhysicalTechnologyProperty] =
+ std::string(physical_technology);
+ }
+
+ if (service_tethering)
+ reply_dict[shill::kTetheringProperty] = std::string(service_tethering);
+
+ ServiceProxyMock* service_proxy_mock = new ServiceProxyMock();
+
+ // Plumb return value into mock object.
+ EXPECT_CALL(*service_proxy_mock, GetProperties(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true)));
+
+ fake_shill_proxy_.SetServiceForPath(
+ dbus::ObjectPath(service_path),
+ brillo::make_unique_ptr(service_proxy_mock));
+ return service_proxy_mock;
+}
+
+
+// Query the connection status, type and time last changed, as they were set
+// during initialization (no signals).
+TEST_F(UmRealShillProviderTest, ReadBaseValues) {
+ InitWithDefaultService("/");
+ // Query the provider variables.
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type());
+ UmTestUtils::ExpectVariableHasValue(InitTime(),
+ provider_->var_conn_last_changed());
+}
+
+// Ensure that invalid DBus paths are ignored.
+TEST_F(UmRealShillProviderTest, InvalidServicePath) {
+ InitWithDefaultService("invalid");
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type());
+ UmTestUtils::ExpectVariableHasValue(InitTime(),
+ provider_->var_conn_last_changed());
+}
+
+// Ensure that a service path property including a different type is ignored.
+TEST_F(UmRealShillProviderTest, InvalidServicePathType) {
+ ManagerProxyMock* manager_proxy_mock = fake_shill_proxy_.GetManagerProxy();
+ brillo::VariantDictionary reply_dict;
+ reply_dict[shill::kDefaultServiceProperty] = "/not/an/object/path";
+ EXPECT_CALL(*manager_proxy_mock, GetProperties(_, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(reply_dict), Return(true)));
+
+ EXPECT_TRUE(provider_->Init());
+ EXPECT_TRUE(loop_.RunOnce(false));
+
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_connected());
+}
+
+// Test that Ethernet connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeEthernet) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestType(kFakeEthernetServicePath,
+ shill::kTypeEthernet,
+ ConnectionType::kEthernet);
+}
+
+// Test that Wifi connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeWifi) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestType(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("/");
+ SetupConnectionAndTestType(kFakeCellularServicePath,
+ shill::kTypeCellular,
+ ConnectionType::kCellular);
+}
+
+// Test that an unknown connection is identified as such.
+TEST_F(UmRealShillProviderTest, ReadConnTypeUnknown) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestType(kFakeUnknownServicePath,
+ "FooConnectionType",
+ ConnectionType::kUnknown);
+}
+
+// Tests that VPN connection is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTypeVpn) {
+ InitWithDefaultService("/");
+ // Mock logic for returning a default service path and its type.
+ SetServiceReply(kFakeVpnServicePath,
+ shill::kTypeVPN,
+ shill::kTypeWifi,
+ shill::kTetheringNotDetectedState);
+
+ // Send a signal about a new default service.
+ Time conn_change_time;
+ SendDefaultServiceSignal(kFakeVpnServicePath, &conn_change_time);
+
+ // Query the connection type, ensure last change time reported correctly.
+ UmTestUtils::ExpectVariableHasValue(ConnectionType::kWifi,
+ provider_->var_conn_type());
+ UmTestUtils::ExpectVariableHasValue(conn_change_time,
+ provider_->var_conn_last_changed());
+}
+
+// Ensure that the connection type is properly cached in the provider through
+// subsequent variable readings.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheUsed) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestType(kFakeEthernetServicePath,
+ shill::kTypeEthernet,
+ ConnectionType::kEthernet);
+
+ UmTestUtils::ExpectVariableHasValue(ConnectionType::kEthernet,
+ provider_->var_conn_type());
+}
+
+// Ensure that the cached connection type remains valid even when a default
+// connection signal occurs but the connection is not changed.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheRemainsValid) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestType(kFakeEthernetServicePath,
+ shill::kTypeEthernet,
+ ConnectionType::kEthernet);
+
+ SendDefaultServiceSignal(kFakeEthernetServicePath, nullptr);
+
+ UmTestUtils::ExpectVariableHasValue(ConnectionType::kEthernet,
+ provider_->var_conn_type());
+}
+
+// Ensure that the cached connection type is invalidated and re-read when the
+// default connection changes.
+TEST_F(UmRealShillProviderTest, ConnTypeCacheInvalidated) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestType(kFakeEthernetServicePath,
+ shill::kTypeEthernet,
+ ConnectionType::kEthernet);
+
+ SetupConnectionAndTestType(kFakeWifiServicePath,
+ shill::kTypeWifi,
+ ConnectionType::kWifi);
+}
+
+// Test that a non-tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringNotDetected) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestTethering(kFakeWifiServicePath,
+ shill::kTetheringNotDetectedState,
+ ConnectionTethering::kNotDetected);
+}
+
+// Test that a suspected tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringSuspected) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestTethering(kFakeWifiServicePath,
+ shill::kTetheringSuspectedState,
+ ConnectionTethering::kSuspected);
+}
+
+// Test that a confirmed tethering mode is identified correctly.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringConfirmed) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestTethering(kFakeWifiServicePath,
+ shill::kTetheringConfirmedState,
+ ConnectionTethering::kConfirmed);
+}
+
+// Test that an unknown tethering mode is identified as such.
+TEST_F(UmRealShillProviderTest, ReadConnTetheringUnknown) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestTethering(kFakeWifiServicePath,
+ "FooConnTethering",
+ ConnectionTethering::kUnknown);
+}
+
+// Ensure that the connection tethering mode is properly cached in the provider.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheUsed) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+ shill::kTetheringNotDetectedState,
+ ConnectionTethering::kNotDetected);
+
+ UmTestUtils::ExpectVariableHasValue(ConnectionTethering::kNotDetected,
+ provider_->var_conn_tethering());
+}
+
+// Ensure that the cached connection tethering mode remains valid even when a
+// default connection signal occurs but the connection is not changed.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheRemainsValid) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+ shill::kTetheringNotDetectedState,
+ ConnectionTethering::kNotDetected);
+
+ SendDefaultServiceSignal(kFakeEthernetServicePath, nullptr);
+
+ UmTestUtils::ExpectVariableHasValue(ConnectionTethering::kNotDetected,
+ provider_->var_conn_tethering());
+}
+
+// Ensure that the cached connection tethering mode is invalidated and re-read
+// when the default connection changes.
+TEST_F(UmRealShillProviderTest, ConnTetheringCacheInvalidated) {
+ InitWithDefaultService("/");
+ SetupConnectionAndTestTethering(kFakeEthernetServicePath,
+ shill::kTetheringNotDetectedState,
+ ConnectionTethering::kNotDetected);
+
+ SetupConnectionAndTestTethering(kFakeWifiServicePath,
+ shill::kTetheringConfirmedState,
+ ConnectionTethering::kConfirmed);
+}
+
+// Fake two DBus signals prompting a default connection change, but otherwise
+// give the same service path. Check connection status and the time it was last
+// changed, making sure that it is the time when the first signal was sent (and
+// not the second).
+TEST_F(UmRealShillProviderTest, ReadLastChangedTimeTwoSignals) {
+ InitWithDefaultService("/");
+ // Send a default service signal twice, advancing the clock in between.
+ Time conn_change_time;
+ SetupConnectionAndAttrs(kFakeEthernetServicePath,
+ shill::kTypeEthernet,
+ shill::kTetheringNotDetectedState,
+ &conn_change_time);
+ // This will set the service path to the same value, so it should not call
+ // GetProperties() again.
+ SendDefaultServiceSignal(kFakeEthernetServicePath, nullptr);
+
+ // Query the connection status, ensure last change time reported as the first
+ // time the signal was sent.
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected());
+ UmTestUtils::ExpectVariableHasValue(conn_change_time,
+ provider_->var_conn_last_changed());
+}
+
+// Make sure that the provider initializes correctly even if shill is not
+// responding, that variables can be obtained, and that they all return a null
+// value (indicating that the underlying values were not set).
+TEST_F(UmRealShillProviderTest, NoInitConnStatusReadBaseValues) {
+ // Initialize the provider, no initial connection status response.
+ SetManagerReply(nullptr, false);
+ EXPECT_TRUE(provider_->Init());
+ EXPECT_TRUE(loop_.RunOnce(false));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_is_connected());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_conn_type());
+ UmTestUtils::ExpectVariableNotSet(provider_->var_conn_last_changed());
+}
+
+// Test that, once a signal is received, the connection status and other info
+// can be read correctly.
+TEST_F(UmRealShillProviderTest, NoInitConnStatusReadConnTypeEthernet) {
+ // Initialize the provider with no initial connection status response.
+ SetManagerReply(nullptr, false);
+ EXPECT_TRUE(provider_->Init());
+ EXPECT_TRUE(loop_.RunOnce(false));
+
+ SetupConnectionAndAttrs(kFakeEthernetServicePath,
+ shill::kTypeEthernet,
+ shill::kTetheringNotDetectedState,
+ nullptr);
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_connected());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_state.h b/update_manager/real_state.h
new file mode 100644
index 0000000..e83c49d
--- /dev/null
+++ b/update_manager/real_state.h
@@ -0,0 +1,82 @@
+//
+// 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_UPDATE_MANAGER_REAL_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_STATE_H_
+
+#include <memory>
+
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// State concrete implementation.
+class RealState : public State {
+ public:
+ ~RealState() override {}
+
+ RealState(ConfigProvider* config_provider,
+ DevicePolicyProvider* device_policy_provider,
+ RandomProvider* random_provider,
+ ShillProvider* shill_provider,
+ SystemProvider* system_provider,
+ TimeProvider* time_provider,
+ UpdaterProvider* updater_provider) :
+ config_provider_(config_provider),
+ device_policy_provider_(device_policy_provider),
+ random_provider_(random_provider),
+ shill_provider_(shill_provider),
+ system_provider_(system_provider),
+ time_provider_(time_provider),
+ updater_provider_(updater_provider) {}
+
+ // These methods return the given provider.
+ ConfigProvider* config_provider() override {
+ return config_provider_.get();
+ }
+ DevicePolicyProvider* device_policy_provider() override {
+ return device_policy_provider_.get();
+ }
+ RandomProvider* random_provider() override {
+ return random_provider_.get();
+ }
+ ShillProvider* shill_provider() override {
+ return shill_provider_.get();
+ }
+ SystemProvider* system_provider() override {
+ return system_provider_.get();
+ }
+ TimeProvider* time_provider() override {
+ return time_provider_.get();
+ }
+ UpdaterProvider* updater_provider() override {
+ return updater_provider_.get();
+ }
+
+ private:
+ // Instances of the providers.
+ std::unique_ptr<ConfigProvider> config_provider_;
+ std::unique_ptr<DevicePolicyProvider> device_policy_provider_;
+ std::unique_ptr<RandomProvider> random_provider_;
+ std::unique_ptr<ShillProvider> shill_provider_;
+ std::unique_ptr<SystemProvider> system_provider_;
+ std::unique_ptr<TimeProvider> time_provider_;
+ std::unique_ptr<UpdaterProvider> updater_provider_;
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_STATE_H_
diff --git a/update_manager/real_system_provider.cc b/update_manager/real_system_provider.cc
new file mode 100644
index 0000000..040f37c
--- /dev/null
+++ b/update_manager/real_system_provider.cc
@@ -0,0 +1,60 @@
+//
+// 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/update_manager/real_system_provider.h"
+
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/update_manager/generic_variables.h"
+
+using std::string;
+
+namespace chromeos_update_manager {
+
+bool RealSystemProvider::Init() {
+ var_is_normal_boot_mode_.reset(
+ new ConstCopyVariable<bool>("is_normal_boot_mode",
+ hardware_->IsNormalBootMode()));
+
+ var_is_official_build_.reset(
+ new ConstCopyVariable<bool>("is_official_build",
+ hardware_->IsOfficialBuild()));
+
+ var_is_oobe_complete_.reset(
+ new CallCopyVariable<bool>(
+ "is_oobe_complete",
+ base::Bind(&chromeos_update_engine::HardwareInterface::IsOOBEComplete,
+ base::Unretained(hardware_), nullptr)));
+
+ var_num_slots_.reset(
+ new ConstCopyVariable<unsigned int>(
+ "num_slots", boot_control_->GetNumSlots()));
+
+ return true;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_system_provider.h b/update_manager/real_system_provider.h
new file mode 100644
index 0000000..0329d74
--- /dev/null
+++ b/update_manager/real_system_provider.h
@@ -0,0 +1,70 @@
+//
+// 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_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
+
+#include <memory>
+#include <string>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/update_manager/system_provider.h"
+
+namespace chromeos_update_manager {
+
+// SystemProvider concrete implementation.
+class RealSystemProvider : public SystemProvider {
+ public:
+ explicit RealSystemProvider(
+ chromeos_update_engine::HardwareInterface* hardware,
+ chromeos_update_engine::BootControlInterface* boot_control)
+ : hardware_(hardware), boot_control_(boot_control) {}
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ Variable<bool>* var_is_normal_boot_mode() override {
+ return var_is_normal_boot_mode_.get();
+ }
+
+ Variable<bool>* var_is_official_build() override {
+ return var_is_official_build_.get();
+ }
+
+ Variable<bool>* var_is_oobe_complete() override {
+ return var_is_oobe_complete_.get();
+ }
+
+ Variable<unsigned int>* var_num_slots() override {
+ return var_num_slots_.get();
+ }
+
+ private:
+ std::unique_ptr<Variable<bool>> var_is_normal_boot_mode_;
+ std::unique_ptr<Variable<bool>> var_is_official_build_;
+ std::unique_ptr<Variable<bool>> var_is_oobe_complete_;
+ std::unique_ptr<Variable<unsigned int>> var_num_slots_;
+
+ chromeos_update_engine::HardwareInterface* hardware_;
+ chromeos_update_engine::BootControlInterface* boot_control_;
+
+ DISALLOW_COPY_AND_ASSIGN(RealSystemProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
diff --git a/update_manager/real_system_provider_unittest.cc b/update_manager/real_system_provider_unittest.cc
new file mode 100644
index 0000000..5ee4137
--- /dev/null
+++ b/update_manager/real_system_provider_unittest.cc
@@ -0,0 +1,61 @@
+//
+// 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/update_manager/real_system_provider.h"
+
+#include <memory>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealSystemProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ provider_.reset(
+ new RealSystemProvider(&fake_hardware_, &fake_boot_control_));
+ EXPECT_TRUE(provider_->Init());
+ }
+
+ chromeos_update_engine::FakeHardware fake_hardware_;
+ chromeos_update_engine::FakeBootControl fake_boot_control_;
+ unique_ptr<RealSystemProvider> provider_;
+};
+
+TEST_F(UmRealSystemProviderTest, InitTest) {
+ EXPECT_NE(nullptr, provider_->var_is_normal_boot_mode());
+ EXPECT_NE(nullptr, provider_->var_is_official_build());
+ EXPECT_NE(nullptr, provider_->var_is_oobe_complete());
+}
+
+TEST_F(UmRealSystemProviderTest, IsOOBECompleteTrue) {
+ fake_hardware_.SetIsOOBEComplete(base::Time());
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_complete());
+}
+
+TEST_F(UmRealSystemProviderTest, IsOOBECompleteFalse) {
+ fake_hardware_.UnsetIsOOBEComplete();
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_complete());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_time_provider.cc b/update_manager/real_time_provider.cc
new file mode 100644
index 0000000..ca3acad
--- /dev/null
+++ b/update_manager/real_time_provider.cc
@@ -0,0 +1,83 @@
+//
+// 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/update_manager/real_time_provider.h"
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ClockInterface;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// A variable returning the current date.
+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) {}
+
+ protected:
+ virtual const Time* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) {
+ Time::Exploded now_exp;
+ clock_->GetWallclockTime().LocalExplode(&now_exp);
+ now_exp.hour = now_exp.minute = now_exp.second = now_exp.millisecond = 0;
+ return new Time(Time::FromLocalExploded(now_exp));
+ }
+
+ private:
+ ClockInterface* clock_;
+
+ DISALLOW_COPY_AND_ASSIGN(CurrDateVariable);
+};
+
+// A variable returning the current hour in local time.
+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) {}
+
+ protected:
+ virtual const int* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) {
+ Time::Exploded exploded;
+ clock_->GetWallclockTime().LocalExplode(&exploded);
+ return new int(exploded.hour);
+ }
+
+ private:
+ ClockInterface* clock_;
+
+ DISALLOW_COPY_AND_ASSIGN(CurrHourVariable);
+};
+
+bool RealTimeProvider::Init() {
+ var_curr_date_.reset(new CurrDateVariable("curr_date", clock_));
+ var_curr_hour_.reset(new CurrHourVariable("curr_hour", clock_));
+ return true;
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_time_provider.h b/update_manager/real_time_provider.h
new file mode 100644
index 0000000..e7cae94
--- /dev/null
+++ b/update_manager/real_time_provider.h
@@ -0,0 +1,58 @@
+//
+// 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_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
+
+#include <memory>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/clock_interface.h"
+#include "update_engine/update_manager/time_provider.h"
+
+namespace chromeos_update_manager {
+
+// TimeProvider concrete implementation.
+class RealTimeProvider : public TimeProvider {
+ public:
+ explicit RealTimeProvider(chromeos_update_engine::ClockInterface* clock)
+ : clock_(clock) {}
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init();
+
+ Variable<base::Time>* var_curr_date() override {
+ return var_curr_date_.get();
+ }
+
+ Variable<int>* var_curr_hour() override {
+ return var_curr_hour_.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_;
+
+ DISALLOW_COPY_AND_ASSIGN(RealTimeProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_TIME_PROVIDER_H_
diff --git a/update_manager/real_time_provider_unittest.cc b/update_manager/real_time_provider_unittest.cc
new file mode 100644
index 0000000..0e1ef34
--- /dev/null
+++ b/update_manager/real_time_provider_unittest.cc
@@ -0,0 +1,84 @@
+//
+// 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/update_manager/real_time_provider.h"
+
+#include <memory>
+
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.h"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Time;
+using chromeos_update_engine::FakeClock;
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+class UmRealTimeProviderTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ // The provider initializes correctly.
+ provider_.reset(new RealTimeProvider(&fake_clock_));
+ ASSERT_NE(nullptr, provider_.get());
+ ASSERT_TRUE(provider_->Init());
+ }
+
+ // Generates a fixed timestamp for use in faking the current time.
+ Time CurrTime() {
+ Time::Exploded now_exp;
+ now_exp.year = 2014;
+ now_exp.month = 3;
+ now_exp.day_of_week = 2;
+ now_exp.day_of_month = 18;
+ now_exp.hour = 8;
+ now_exp.minute = 5;
+ now_exp.second = 33;
+ now_exp.millisecond = 675;
+ return Time::FromLocalExploded(now_exp);
+ }
+
+ FakeClock fake_clock_;
+ unique_ptr<RealTimeProvider> provider_;
+};
+
+TEST_F(UmRealTimeProviderTest, CurrDateValid) {
+ const Time now = CurrTime();
+ Time::Exploded exploded;
+ now.LocalExplode(&exploded);
+ exploded.hour = 0;
+ exploded.minute = 0;
+ exploded.second = 0;
+ exploded.millisecond = 0;
+ const Time expected = Time::FromLocalExploded(exploded);
+
+ fake_clock_.SetWallclockTime(now);
+ UmTestUtils::ExpectVariableHasValue(expected, provider_->var_curr_date());
+}
+
+TEST_F(UmRealTimeProviderTest, CurrHourValid) {
+ const Time now = CurrTime();
+ Time::Exploded expected;
+ now.LocalExplode(&expected);
+ fake_clock_.SetWallclockTime(now);
+ UmTestUtils::ExpectVariableHasValue(expected.hour,
+ provider_->var_curr_hour());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_updater_provider.cc b/update_manager/real_updater_provider.cc
new file mode 100644
index 0000000..1a3e65a
--- /dev/null
+++ b/update_manager/real_updater_provider.cc
@@ -0,0 +1,453 @@
+//
+// 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/update_manager/real_updater_provider.h"
+
+#include <inttypes.h>
+
+#include <string>
+
+#include <base/bind.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+#include <update_engine/dbus-constants.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"
+
+using base::StringPrintf;
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::OmahaRequestParams;
+using chromeos_update_engine::SystemState;
+using std::string;
+
+namespace chromeos_update_manager {
+
+// A templated base class for all update related variables. Provides uniform
+// construction and a system state handle.
+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_;
+};
+
+// 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(
+ &last_checked_time_, &progress_, &update_status_, &new_version_,
+ &payload_size_);
+ if (!is_success_ && errmsg)
+ *errmsg = "Failed to get a status update from the update engine";
+ }
+
+ inline bool is_success() { return is_success_; }
+ inline int64_t last_checked_time() { return last_checked_time_; }
+ inline double progress() { return progress_; }
+ inline const string& update_status() { return update_status_; }
+ inline const string& new_version() { return new_version_; }
+ inline int64_t payload_size() { return payload_size_; }
+
+ private:
+ bool is_success_;
+ int64_t last_checked_time_;
+ double progress_;
+ string update_status_;
+ string new_version_;
+ int64_t payload_size_;
+};
+
+// 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) {}
+
+ private:
+ const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ GetStatusHelper raw(system_state(), errmsg);
+ if (!raw.is_success())
+ return nullptr;
+
+ return new Time(Time::FromTimeT(raw.last_checked_time()));
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(LastCheckedTimeVariable);
+};
+
+// A variable reporting the update (download) progress as a decimal fraction
+// 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) {}
+
+ private:
+ const double* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ GetStatusHelper raw(system_state(), errmsg);
+ if (!raw.is_success())
+ return nullptr;
+
+ if (raw.progress() < 0.0 || raw.progress() > 1.0) {
+ if (errmsg) {
+ *errmsg = StringPrintf("Invalid progress value received: %f",
+ raw.progress());
+ }
+ return nullptr;
+ }
+
+ return new double(raw.progress());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ProgressVariable);
+};
+
+// 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) {}
+
+ private:
+ struct CurrOpStrToStage {
+ const char* str;
+ Stage stage;
+ };
+ static const CurrOpStrToStage curr_op_str_to_stage[];
+
+ // Note: the method is defined outside the class so arraysize can work.
+ const Stage* GetValue(TimeDelta /* timeout */, string* errmsg) override;
+
+ DISALLOW_COPY_AND_ASSIGN(StageVariable);
+};
+
+const StageVariable::CurrOpStrToStage StageVariable::curr_op_str_to_stage[] = {
+ {update_engine::kUpdateStatusIdle, Stage::kIdle},
+ {update_engine::kUpdateStatusCheckingForUpdate, Stage::kCheckingForUpdate},
+ {update_engine::kUpdateStatusUpdateAvailable, Stage::kUpdateAvailable},
+ {update_engine::kUpdateStatusDownloading, Stage::kDownloading},
+ {update_engine::kUpdateStatusVerifying, Stage::kVerifying},
+ {update_engine::kUpdateStatusFinalizing, Stage::kFinalizing},
+ {update_engine::kUpdateStatusUpdatedNeedReboot, Stage::kUpdatedNeedReboot},
+ { // NOLINT(whitespace/braces)
+ update_engine::kUpdateStatusReportingErrorEvent,
+ Stage::kReportingErrorEvent
+ },
+ {update_engine::kUpdateStatusAttemptingRollback, Stage::kAttemptingRollback},
+};
+
+const Stage* StageVariable::GetValue(TimeDelta /* timeout */,
+ string* errmsg) {
+ GetStatusHelper raw(system_state(), errmsg);
+ if (!raw.is_success())
+ return nullptr;
+
+ for (auto& key_val : curr_op_str_to_stage)
+ if (raw.update_status() == key_val.str)
+ return new Stage(key_val.stage);
+
+ if (errmsg)
+ *errmsg = string("Unknown update status: ") + raw.update_status();
+ return nullptr;
+}
+
+// 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) {}
+
+ private:
+ const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ GetStatusHelper raw(system_state(), errmsg);
+ if (!raw.is_success())
+ return nullptr;
+
+ return new string(raw.new_version());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(NewVersionVariable);
+};
+
+// A variable reporting the size of the update being processed in bytes.
+class PayloadSizeVariable : public UpdaterVariableBase<int64_t> {
+ public:
+ PayloadSizeVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<int64_t>(name, kVariableModePoll, system_state) {}
+
+ private:
+ const int64_t* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ GetStatusHelper raw(system_state(), errmsg);
+ if (!raw.is_success())
+ return nullptr;
+
+ if (raw.payload_size() < 0) {
+ if (errmsg)
+ *errmsg = string("Invalid payload size: %" PRId64, raw.payload_size());
+ return nullptr;
+ }
+
+ return new int64_t(raw.payload_size());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(PayloadSizeVariable);
+};
+
+// A variable reporting the point in time an update last completed in the
+// current boot cycle.
+//
+// TODO(garnold) In general, both the current boottime and wallclock time
+// readings should come from the time provider and be moderated by the
+// evaluation context, so that they are uniform throughout the evaluation of a
+// policy request.
+class UpdateCompletedTimeVariable : public UpdaterVariableBase<Time> {
+ public:
+ UpdateCompletedTimeVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<Time>(name, kVariableModePoll, system_state) {}
+
+ private:
+ const Time* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ Time update_boottime;
+ if (!system_state()->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();
+ Time curr_boottime = clock->GetBootTime();
+ if (curr_boottime < update_boottime) {
+ if (errmsg)
+ *errmsg = "Update completed time more recent than current time";
+ return nullptr;
+ }
+ TimeDelta duration_since_update = curr_boottime - update_boottime;
+ return new Time(clock->GetWallclockTime() - duration_since_update);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateCompletedTimeVariable);
+};
+
+// 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) {}
+
+ private:
+ const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ OmahaRequestParams* request_params = system_state()->request_params();
+ string channel = request_params->current_channel();
+ if (channel.empty()) {
+ if (errmsg)
+ *errmsg = "No current channel";
+ return nullptr;
+ }
+ return new string(channel);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(CurrChannelVariable);
+};
+
+// 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) {}
+
+ private:
+ const string* GetValue(TimeDelta /* timeout */, string* errmsg) override {
+ OmahaRequestParams* request_params = system_state()->request_params();
+ string channel = request_params->target_channel();
+ if (channel.empty()) {
+ if (errmsg)
+ *errmsg = "No new channel";
+ return nullptr;
+ }
+ return new string(channel);
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(NewChannelVariable);
+};
+
+// A variable class for reading Boolean prefs values.
+class BooleanPrefVariable
+ : public AsyncCopyVariable<bool>,
+ 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);
+ OnPrefSet(key);
+ }
+ ~BooleanPrefVariable() {
+ 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))
+ result = default_val_;
+ // AsyncCopyVariable will take care of values that didn't change.
+ SetValue(result);
+ }
+
+ 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_;
+
+ DISALLOW_COPY_AND_ASSIGN(BooleanPrefVariable);
+};
+
+// A variable returning the number of consecutive failed update checks.
+class ConsecutiveFailedUpdateChecksVariable
+ : public UpdaterVariableBase<unsigned int> {
+ public:
+ ConsecutiveFailedUpdateChecksVariable(const string& name,
+ SystemState* system_state)
+ : UpdaterVariableBase<unsigned int>(name, kVariableModePoll,
+ system_state) {}
+
+ private:
+ const unsigned int* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) override {
+ return new unsigned int(
+ system_state()->update_attempter()->consecutive_failed_update_checks());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ConsecutiveFailedUpdateChecksVariable);
+};
+
+// A variable returning the server-dictated poll interval.
+class ServerDictatedPollIntervalVariable
+ : public UpdaterVariableBase<unsigned int> {
+ public:
+ ServerDictatedPollIntervalVariable(const string& name,
+ SystemState* system_state)
+ : UpdaterVariableBase<unsigned int>(name, kVariableModePoll,
+ system_state) {}
+
+ private:
+ const unsigned int* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) override {
+ return new unsigned int(
+ system_state()->update_attempter()->server_dictated_poll_interval());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(ServerDictatedPollIntervalVariable);
+};
+
+// An async variable that tracks changes to forced update requests.
+class ForcedUpdateRequestedVariable
+ : public UpdaterVariableBase<UpdateRequestStatus> {
+ public:
+ ForcedUpdateRequestedVariable(const string& name, SystemState* system_state)
+ : UpdaterVariableBase<UpdateRequestStatus>::UpdaterVariableBase(
+ name, kVariableModeAsync, system_state) {
+ system_state->update_attempter()->set_forced_update_pending_callback(
+ new base::Callback<void(bool, bool)>( // NOLINT(readability/function)
+ base::Bind(&ForcedUpdateRequestedVariable::Reset,
+ base::Unretained(this))));
+ }
+
+ private:
+ const UpdateRequestStatus* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) override {
+ return new UpdateRequestStatus(update_request_status_);
+ }
+
+ void Reset(bool forced_update_requested, bool is_interactive) {
+ UpdateRequestStatus new_value = UpdateRequestStatus::kNone;
+ if (forced_update_requested)
+ new_value = (is_interactive ? UpdateRequestStatus::kInteractive :
+ UpdateRequestStatus::kPeriodic);
+ if (update_request_status_ != new_value) {
+ update_request_status_ = new_value;
+ NotifyValueChanged();
+ }
+ }
+
+ UpdateRequestStatus update_request_status_ = UpdateRequestStatus::kNone;
+
+ DISALLOW_COPY_AND_ASSIGN(ForcedUpdateRequestedVariable);
+};
+
+// 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)),
+ 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_)),
+ var_server_dictated_poll_interval_(
+ new ServerDictatedPollIntervalVariable(
+ "server_dictated_poll_interval", system_state_)),
+ var_forced_update_requested_(
+ new ForcedUpdateRequestedVariable(
+ "forced_update_requested", system_state_)) {}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/real_updater_provider.h b/update_manager/real_updater_provider.h
new file mode 100644
index 0000000..b99bcc5
--- /dev/null
+++ b/update_manager/real_updater_provider.h
@@ -0,0 +1,124 @@
+//
+// 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_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
+
+#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"
+
+namespace chromeos_update_manager {
+
+// A concrete UpdaterProvider implementation using local (in-process) bindings.
+class RealUpdaterProvider : public UpdaterProvider {
+ public:
+ // We assume that any other object handle we get from the system state is
+ // "volatile", and so must be re-acquired whenever access is needed; this
+ // 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);
+
+ // Initializes the provider and returns whether it succeeded.
+ bool Init() { return true; }
+
+ Variable<base::Time>* var_updater_started_time() override {
+ return &var_updater_started_time_;
+ }
+
+ Variable<base::Time>* var_last_checked_time() override {
+ return var_last_checked_time_.get();
+ }
+
+ Variable<base::Time>* var_update_completed_time() override {
+ return var_update_completed_time_.get();
+ }
+
+ Variable<double>* var_progress() override {
+ return var_progress_.get();
+ }
+
+ Variable<Stage>* var_stage() override {
+ return var_stage_.get();
+ }
+
+ Variable<std::string>* var_new_version() override {
+ return var_new_version_.get();
+ }
+
+ Variable<int64_t>* var_payload_size() override {
+ return var_payload_size_.get();
+ }
+
+ Variable<std::string>* var_curr_channel() override {
+ return var_curr_channel_.get();
+ }
+
+ Variable<std::string>* var_new_channel() override {
+ return var_new_channel_.get();
+ }
+
+ Variable<bool>* var_p2p_enabled() override {
+ return var_p2p_enabled_.get();
+ }
+
+ Variable<bool>* var_cellular_enabled() override {
+ return var_cellular_enabled_.get();
+ }
+
+ Variable<unsigned int>* var_consecutive_failed_update_checks() override {
+ return var_consecutive_failed_update_checks_.get();
+ }
+
+ Variable<unsigned int>* var_server_dictated_poll_interval() override {
+ return var_server_dictated_poll_interval_.get();
+ }
+
+ Variable<UpdateRequestStatus>* var_forced_update_requested() override {
+ return var_forced_update_requested_.get();
+ }
+
+ private:
+ // A pointer to the update engine's system state aggregator.
+ chromeos_update_engine::SystemState* system_state_;
+
+ // Variable implementations.
+ ConstCopyVariable<base::Time> var_updater_started_time_;
+ std::unique_ptr<Variable<base::Time>> var_last_checked_time_;
+ std::unique_ptr<Variable<base::Time>> var_update_completed_time_;
+ std::unique_ptr<Variable<double>> var_progress_;
+ std::unique_ptr<Variable<Stage>> var_stage_;
+ std::unique_ptr<Variable<std::string>> var_new_version_;
+ std::unique_ptr<Variable<int64_t>> var_payload_size_;
+ std::unique_ptr<Variable<std::string>> var_curr_channel_;
+ std::unique_ptr<Variable<std::string>> var_new_channel_;
+ std::unique_ptr<Variable<bool>> var_p2p_enabled_;
+ std::unique_ptr<Variable<bool>> var_cellular_enabled_;
+ std::unique_ptr<Variable<unsigned int>> var_consecutive_failed_update_checks_;
+ std::unique_ptr<Variable<unsigned int>> var_server_dictated_poll_interval_;
+ std::unique_ptr<Variable<UpdateRequestStatus>> var_forced_update_requested_;
+
+ DISALLOW_COPY_AND_ASSIGN(RealUpdaterProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_UPDATER_PROVIDER_H_
diff --git a/update_manager/real_updater_provider_unittest.cc b/update_manager/real_updater_provider_unittest.cc
new file mode 100644
index 0000000..14eb30b
--- /dev/null
+++ b/update_manager/real_updater_provider_unittest.cc
@@ -0,0 +1,443 @@
+//
+// 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/update_manager/real_updater_provider.h"
+
+#include <memory>
+#include <string>
+
+#include <base/time/time.h>
+#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/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;
+using std::string;
+using std::unique_ptr;
+using testing::Return;
+using testing::SetArgPointee;
+using testing::_;
+
+namespace {
+
+// Generates a fixed timestamp for use in faking the current time.
+Time FixedTime() {
+ Time::Exploded now_exp;
+ now_exp.year = 2014;
+ now_exp.month = 3;
+ now_exp.day_of_week = 2;
+ now_exp.day_of_month = 18;
+ now_exp.hour = 8;
+ now_exp.minute = 5;
+ now_exp.second = 33;
+ now_exp.millisecond = 675;
+ return Time::FromLocalExploded(now_exp);
+}
+
+// Rounds down a timestamp to the nearest second. This is useful when faking
+// times that are converted to time_t (no sub-second resolution).
+Time RoundedToSecond(Time time) {
+ Time::Exploded exp;
+ time.LocalExplode(&exp);
+ exp.millisecond = 0;
+ return Time::FromLocalExploded(exp);
+}
+
+} // namespace
+
+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());
+ // Check that provider initializes correctly.
+ ASSERT_TRUE(provider_->Init());
+ }
+
+ // Sets up mock expectations for testing the update completed time reporting.
+ // |valid| determines whether the returned time is valid. Returns the expected
+ // update completed time value.
+ Time SetupUpdateCompletedTime(bool valid) {
+ const TimeDelta kDurationSinceUpdate = TimeDelta::FromMinutes(7);
+ const Time kUpdateBootTime = Time() + kDurationSinceUpdate * 2;
+ const Time kCurrBootTime = (valid ?
+ kUpdateBootTime + kDurationSinceUpdate :
+ kUpdateBootTime - kDurationSinceUpdate);
+ const Time kCurrWallclockTime = FixedTime();
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetBootTimeAtUpdate(_))
+ .WillOnce(DoAll(SetArgPointee<0>(kUpdateBootTime), Return(true)));
+ fake_clock_->SetBootTime(kCurrBootTime);
+ 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();
+ UmTestUtils::ExpectVariableHasValue(Time::FromDoubleT(123.456),
+ provider_->var_updater_started_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeOkay) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<0>(FixedTime().ToTimeT()), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(RoundedToSecond(FixedTime()),
+ provider_->var_last_checked_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetLastCheckedTimeFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.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(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(0.0), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(0.0, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMid) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(0.3), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(0.3, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressOkayMax) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(1.0), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(1.0, provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.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(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(-2.0), Return(true)));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetProgressFailTooBig) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(2.0), Return(true)));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_progress());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayIdle) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusIdle),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kIdle, provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayCheckingForUpdate) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<2>(update_engine::kUpdateStatusCheckingForUpdate),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kCheckingForUpdate,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdateAvailable) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<2>(update_engine::kUpdateStatusUpdateAvailable),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kUpdateAvailable,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayDownloading) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusDownloading),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kDownloading,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayVerifying) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusVerifying),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kVerifying,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayFinalizing) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(update_engine::kUpdateStatusFinalizing),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kFinalizing,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayUpdatedNeedReboot) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<2>(update_engine::kUpdateStatusUpdatedNeedReboot),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kUpdatedNeedReboot,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayReportingErrorEvent) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<2>(update_engine::kUpdateStatusReportingErrorEvent),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kReportingErrorEvent,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageOkayAttemptingRollback) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(
+ SetArgPointee<2>(update_engine::kUpdateStatusAttemptingRollback),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(Stage::kAttemptingRollback,
+ provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(Return(false));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailUnknown) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>("FooUpdateEngineState"),
+ Return(true)));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetStageFailEmpty) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(""), Return(true)));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_stage());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewVersionOkay) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<3>("1.2.0"), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(string("1.2.0"),
+ provider_->var_new_version());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetNewVersionFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.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(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(0)), Return(true)));
+ UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(0),
+ provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayArbitrary) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(567890)),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(567890),
+ provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeOkayTwoGigabytes) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(1) << 31),
+ Return(true)));
+ UmTestUtils::ExpectVariableHasValue(static_cast<int64_t>(1) << 31,
+ provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(Return(false));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_payload_size());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetPayloadSizeFailNegative) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ GetStatus(_, _, _, _, _))
+ .WillOnce(DoAll(SetArgPointee<4>(static_cast<int64_t>(-1024)),
+ Return(true)));
+ 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);
+ request_params.set_current_channel(kChannelName);
+ fake_sys_state_.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);
+ request_params.set_current_channel("");
+ fake_sys_state_.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);
+ request_params.set_target_channel(kChannelName);
+ fake_sys_state_.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);
+ request_params.set_target_channel("");
+ fake_sys_state_.set_request_params(&request_params);
+ UmTestUtils::ExpectVariableNotSet(provider_->var_new_channel());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefDoesntExist) {
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledOkayPrefReadsFalse) {
+ 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();
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetP2PEnabledUpdated) {
+ fake_prefs_.SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, false);
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+ fake_prefs_.SetBoolean(chromeos_update_engine::kPrefsP2PEnabled, true);
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_p2p_enabled());
+ fake_prefs_.Delete(chromeos_update_engine::kPrefsP2PEnabled);
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_p2p_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefDoesntExist) {
+ UmTestUtils::ExpectVariableHasValue(false, provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetCellularEnabledOkayPrefReadsTrue) {
+ fake_prefs_.SetBoolean(
+ chromeos_update_engine::kPrefsUpdateOverCellularPermission, true);
+ UmTestUtils::ExpectVariableHasValue(true, provider_->var_cellular_enabled());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeOkay) {
+ Time expected = SetupUpdateCompletedTime(true);
+ UmTestUtils::ExpectVariableHasValue(expected,
+ provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeFailNoValue) {
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(), GetBootTimeAtUpdate(_))
+ .WillOnce(Return(false));
+ UmTestUtils::ExpectVariableNotSet(provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetUpdateCompletedTimeFailInvalidValue) {
+ SetupUpdateCompletedTime(false);
+ UmTestUtils::ExpectVariableNotSet(provider_->var_update_completed_time());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetConsecutiveFailedUpdateChecks) {
+ const unsigned int kNumFailedChecks = 3;
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ consecutive_failed_update_checks())
+ .WillRepeatedly(Return(kNumFailedChecks));
+ UmTestUtils::ExpectVariableHasValue(
+ kNumFailedChecks, provider_->var_consecutive_failed_update_checks());
+}
+
+TEST_F(UmRealUpdaterProviderTest, GetServerDictatedPollInterval) {
+ const unsigned int kPollInterval = 2 * 60 * 60; // Two hours.
+ EXPECT_CALL(*fake_sys_state_.mock_update_attempter(),
+ server_dictated_poll_interval())
+ .WillRepeatedly(Return(kPollInterval));
+ UmTestUtils::ExpectVariableHasValue(
+ kPollInterval, provider_->var_server_dictated_poll_interval());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/shill_provider.h b/update_manager/shill_provider.h
new file mode 100644
index 0000000..b40f255
--- /dev/null
+++ b/update_manager/shill_provider.h
@@ -0,0 +1,72 @@
+//
+// 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_UPDATE_MANAGER_SHILL_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_SHILL_PROVIDER_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+enum class ConnectionType {
+ kEthernet,
+ kWifi,
+ kWimax,
+ kBluetooth,
+ kCellular,
+ kUnknown
+};
+
+enum class ConnectionTethering {
+ kNotDetected,
+ kSuspected,
+ kConfirmed,
+ kUnknown,
+};
+
+// Provider for networking related information.
+class ShillProvider : public Provider {
+ public:
+ ~ShillProvider() override {}
+
+ // A variable returning whether we currently have network connectivity.
+ virtual Variable<bool>* var_is_connected() = 0;
+
+ // A variable returning the current network connection type. Unknown if not
+ // connected.
+ virtual Variable<ConnectionType>* var_conn_type() = 0;
+
+ // A variable returning the tethering mode of a network connection. Unknown if
+ // not connected.
+ virtual Variable<ConnectionTethering>* var_conn_tethering() = 0;
+
+ // A variable returning the time when network connection last changed.
+ // Initialized to current time.
+ virtual Variable<base::Time>* var_conn_last_changed() = 0;
+
+ protected:
+ ShillProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ShillProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_SHILL_PROVIDER_H_
diff --git a/update_manager/state.h b/update_manager/state.h
new file mode 100644
index 0000000..d428059
--- /dev/null
+++ b/update_manager/state.h
@@ -0,0 +1,54 @@
+//
+// 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_UPDATE_MANAGER_STATE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_STATE_H_
+
+#include "update_engine/update_manager/config_provider.h"
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/random_provider.h"
+#include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/system_provider.h"
+#include "update_engine/update_manager/time_provider.h"
+#include "update_engine/update_manager/updater_provider.h"
+
+namespace chromeos_update_manager {
+
+// The State class is an interface to the ensemble of providers. This class
+// gives visibility of the state providers to policy implementations.
+class State {
+ public:
+ virtual ~State() {}
+
+ // These methods return the given provider.
+ virtual ConfigProvider* config_provider() = 0;
+ virtual DevicePolicyProvider* device_policy_provider() = 0;
+ virtual RandomProvider* random_provider() = 0;
+ virtual ShillProvider* shill_provider() = 0;
+ virtual SystemProvider* system_provider() = 0;
+ virtual TimeProvider* time_provider() = 0;
+ virtual UpdaterProvider* updater_provider() = 0;
+
+ protected:
+ State() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(State);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_STATE_H_
diff --git a/update_manager/state_factory.cc b/update_manager/state_factory.cc
new file mode 100644
index 0000000..d4f4aa4
--- /dev/null
+++ b/update_manager/state_factory.cc
@@ -0,0 +1,77 @@
+//
+// 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/update_manager/state_factory.h"
+
+#include <memory>
+
+#include <base/logging.h>
+
+#include "update_engine/common/clock_interface.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"
+
+using std::unique_ptr;
+
+namespace chromeos_update_manager {
+
+State* DefaultStateFactory(
+ policy::PolicyProvider* policy_provider,
+ chromeos_update_engine::ShillProxy* shill_proxy,
+ org::chromium::SessionManagerInterfaceProxyInterface* session_manager_proxy,
+ chromeos_update_engine::SystemState* system_state) {
+ chromeos_update_engine::ClockInterface* const clock = system_state->clock();
+ unique_ptr<RealConfigProvider> config_provider(
+ new RealConfigProvider(system_state->hardware()));
+ unique_ptr<RealDevicePolicyProvider> device_policy_provider(
+ new RealDevicePolicyProvider(session_manager_proxy, policy_provider));
+ unique_ptr<RealRandomProvider> random_provider(new RealRandomProvider());
+ unique_ptr<RealShillProvider> shill_provider(
+ new RealShillProvider(shill_proxy, clock));
+ unique_ptr<RealSystemProvider> system_provider(
+ new RealSystemProvider(system_state->hardware(),
+ system_state->boot_control()));
+ unique_ptr<RealTimeProvider> time_provider(new RealTimeProvider(clock));
+ unique_ptr<RealUpdaterProvider> updater_provider(
+ new RealUpdaterProvider(system_state));
+
+ if (!(config_provider->Init() &&
+ device_policy_provider->Init() &&
+ random_provider->Init() &&
+ shill_provider->Init() &&
+ system_provider->Init() &&
+ time_provider->Init() &&
+ updater_provider->Init())) {
+ LOG(ERROR) << "Error initializing providers";
+ return nullptr;
+ }
+
+ return new RealState(config_provider.release(),
+ device_policy_provider.release(),
+ random_provider.release(),
+ shill_provider.release(),
+ system_provider.release(),
+ time_provider.release(),
+ updater_provider.release());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/state_factory.h b/update_manager/state_factory.h
new file mode 100644
index 0000000..f15fd83
--- /dev/null
+++ b/update_manager/state_factory.h
@@ -0,0 +1,41 @@
+//
+// 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_UPDATE_MANAGER_STATE_FACTORY_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_
+
+#include <session_manager/dbus-proxies.h>
+
+#include "update_engine/shill_proxy.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+// Creates and initializes a new UpdateManager State instance containing real
+// providers instantiated using the passed interfaces. The State doesn't take
+// ownership of the passed interfaces, which need to remain available during the
+// life of this instance. Returns null if one of the underlying providers fails
+// to initialize.
+State* DefaultStateFactory(
+ policy::PolicyProvider* policy_provider,
+ chromeos_update_engine::ShillProxy* shill_proxy,
+ org::chromium::SessionManagerInterfaceProxyInterface* session_manager_proxy,
+ chromeos_update_engine::SystemState* system_state);
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_
diff --git a/update_manager/system_provider.h b/update_manager/system_provider.h
new file mode 100644
index 0000000..00fb9af
--- /dev/null
+++ b/update_manager/system_provider.h
@@ -0,0 +1,54 @@
+//
+// 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_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for system information, mostly constant, such as the information
+// reported by crossystem, the kernel boot command line and the partition table.
+class SystemProvider : public Provider {
+ public:
+ ~SystemProvider() override {}
+
+ // Returns true if the boot mode is normal or if it's unable to
+ // determine the boot mode. Returns false if the boot mode is
+ // developer.
+ virtual Variable<bool>* var_is_normal_boot_mode() = 0;
+
+ // Returns whether this is an official Chrome OS build.
+ virtual Variable<bool>* var_is_official_build() = 0;
+
+ // Returns a variable that tells whether OOBE was completed.
+ virtual Variable<bool>* var_is_oobe_complete() = 0;
+
+ // Returns a variable that tells the number of slots in the system.
+ virtual Variable<unsigned int>* var_num_slots() = 0;
+
+ protected:
+ SystemProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(SystemProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
diff --git a/update_manager/time_provider.h b/update_manager/time_provider.h
new file mode 100644
index 0000000..663ec2c
--- /dev/null
+++ b/update_manager/time_provider.h
@@ -0,0 +1,48 @@
+//
+// 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_UPDATE_MANAGER_TIME_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_TIME_PROVIDER_H_
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// Provider for time related information.
+class TimeProvider : public Provider {
+ public:
+ ~TimeProvider() override {}
+
+ // Returns the current date. The time of day component will be zero.
+ virtual Variable<base::Time>* var_curr_date() = 0;
+
+ // Returns the current hour (0 to 23) in local time. The type is int to keep
+ // consistent with base::Time.
+ virtual Variable<int>* var_curr_hour() = 0;
+
+ protected:
+ TimeProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TimeProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_TIME_PROVIDER_H_
diff --git a/update_manager/umtest_utils.cc b/update_manager/umtest_utils.cc
new file mode 100644
index 0000000..aa88141
--- /dev/null
+++ b/update_manager/umtest_utils.cc
@@ -0,0 +1,29 @@
+//
+// 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/update_manager/umtest_utils.h"
+
+#include <base/time/time.h>
+
+namespace chromeos_update_manager {
+
+const unsigned UmTestUtils::kDefaultTimeoutInSeconds = 1;
+
+void PrintTo(const EvalStatus& status, ::std::ostream* os) {
+ *os << ToString(status);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/umtest_utils.h b/update_manager/umtest_utils.h
new file mode 100644
index 0000000..80693db
--- /dev/null
+++ b/update_manager/umtest_utils.h
@@ -0,0 +1,68 @@
+//
+// 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_UPDATE_MANAGER_UMTEST_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UMTEST_UTILS_H_
+
+#include <iostream> // NOLINT(readability/streams)
+#include <memory>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+// A help class with common functionality for use in Update Manager testing.
+class UmTestUtils {
+ public:
+ // A default timeout to use when making various queries.
+ static const base::TimeDelta DefaultTimeout() {
+ return base::TimeDelta::FromSeconds(kDefaultTimeoutInSeconds);
+ }
+
+ // Calls GetValue on |variable| and expects its result to be |expected|.
+ template<typename T>
+ static void ExpectVariableHasValue(const T& expected, Variable<T>* variable) {
+ ASSERT_NE(nullptr, variable);
+ std::unique_ptr<const T> value(
+ variable->GetValue(DefaultTimeout(), nullptr));
+ ASSERT_NE(nullptr, value.get()) << "Variable: " << variable->GetName();
+ EXPECT_EQ(expected, *value) << "Variable: " << variable->GetName();
+ }
+
+ // Calls GetValue on |variable| and expects its result to be null.
+ template<typename T>
+ static void ExpectVariableNotSet(Variable<T>* variable) {
+ ASSERT_NE(nullptr, variable);
+ std::unique_ptr<const T> value(
+ variable->GetValue(DefaultTimeout(), nullptr));
+ EXPECT_EQ(nullptr, value.get()) << "Variable: " << variable->GetName();
+ }
+
+ private:
+ static const unsigned kDefaultTimeoutInSeconds;
+};
+
+// PrintTo() functions are used by gtest to print these values. They need to be
+// defined on the same namespace where the type was defined.
+void PrintTo(const EvalStatus& status, ::std::ostream* os);
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_UMTEST_UTILS_H_
diff --git a/update_manager/update_manager-inl.h b/update_manager/update_manager-inl.h
new file mode 100644
index 0000000..77224cf
--- /dev/null
+++ b/update_manager/update_manager-inl.h
@@ -0,0 +1,165 @@
+//
+// 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_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
+
+#include <memory>
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/update_manager/evaluation_context.h"
+
+namespace chromeos_update_manager {
+
+template<typename R, typename... Args>
+EvalStatus UpdateManager::EvaluatePolicy(
+ EvaluationContext* ec,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ Args...) const,
+ R* result, Args... args) {
+ // If expiration timeout fired, dump the context and reset expiration.
+ // IMPORTANT: We must still proceed with evaluation of the policy in this
+ // case, so that the evaluation time (and corresponding reevaluation timeouts)
+ // are readjusted.
+ if (ec->is_expired()) {
+ LOG(WARNING) << "Request timed out, evaluation context: "
+ << ec->DumpContext();
+ ec->ResetExpiration();
+ }
+
+ // Reset the evaluation context.
+ 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;
+ EvalStatus status = (policy_.get()->*policy_method)(ec, state_.get(), &error,
+ result, args...);
+ // If evaluating the main policy failed, defer to the default policy.
+ if (status == EvalStatus::kFailed) {
+ LOG(WARNING) << "Evaluating policy failed: " << error
+ << "\nEvaluation context: " << ec->DumpContext();
+ error.clear();
+ status = (default_policy_.*policy_method)(ec, state_.get(), &error, result,
+ args...);
+ if (status == EvalStatus::kFailed) {
+ LOG(WARNING) << "Evaluating default policy failed: " << error;
+ } else if (status == EvalStatus::kAskMeAgainLater) {
+ LOG(ERROR)
+ << "Default policy would block; this is a bug, forcing failure.";
+ status = EvalStatus::kFailed;
+ }
+ }
+
+ LOG(INFO) << policy_name << ": END";
+
+ return status;
+}
+
+template<typename R, typename... Args>
+void UpdateManager::OnPolicyReadyToEvaluate(
+ scoped_refptr<EvaluationContext> ec,
+ base::Callback<void(EvalStatus status, const R& result)> callback,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ Args...) const,
+ Args... args) {
+ // Evaluate the policy.
+ R result;
+ EvalStatus status = EvaluatePolicy(ec.get(), policy_method, &result, args...);
+
+ if (status != EvalStatus::kAskMeAgainLater) {
+ // AsyncPolicyRequest finished.
+ callback.Run(status, result);
+ return;
+ }
+
+ // Re-schedule the policy request based on used variables.
+ base::Closure reeval_callback = base::Bind(
+ &UpdateManager::OnPolicyReadyToEvaluate<R, Args...>,
+ base::Unretained(this), ec, callback,
+ policy_method, args...);
+ if (ec->RunOnValueChangeOrTimeout(reeval_callback))
+ return; // Reevaluation scheduled successfully.
+
+ // Scheduling a reevaluation can fail because policy method didn't use any
+ // non-const variable nor there's any time-based event that will change the
+ // status of evaluation. Alternatively, this may indicate an error in the use
+ // of the scheduling interface.
+ LOG(ERROR) << "Failed to schedule a reevaluation of policy "
+ << policy_->PolicyRequestName(policy_method) << "; this is a bug.";
+ callback.Run(status, result);
+}
+
+template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+EvalStatus UpdateManager::PolicyRequest(
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ ExpectedArgs...) const,
+ R* result, ActualArgs... args) {
+ scoped_refptr<EvaluationContext> ec(
+ new EvaluationContext(clock_, 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
+ // explicitly instantiate EvaluatePolicy with the latter in lieu of the
+ // former.
+ EvalStatus ret = EvaluatePolicy<R, ExpectedArgs...>(ec.get(), policy_method,
+ result, args...);
+ // Sync policy requests must not block, if they do then this is an error.
+ DCHECK(EvalStatus::kAskMeAgainLater != ret);
+ LOG_IF(WARNING, EvalStatus::kAskMeAgainLater == ret)
+ << "Sync request used with an async policy; this is a bug";
+ return ret;
+}
+
+template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+void UpdateManager::AsyncPolicyRequest(
+ base::Callback<void(EvalStatus, const R& result)> callback,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ ExpectedArgs...) const,
+ ActualArgs... args) {
+ scoped_refptr<EvaluationContext> ec =
+ new EvaluationContext(
+ clock_, 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) {
+ LOG(ERROR) << "Failed to register evaluation context; this is a bug.";
+ }
+
+ // IMPORTANT: To ensure that ActualArgs can be converted to ExpectedArgs, we
+ // explicitly instantiate UpdateManager::OnPolicyReadyToEvaluate with the
+ // latter in lieu of the former.
+ base::Closure eval_callback = base::Bind(
+ &UpdateManager::OnPolicyReadyToEvaluate<R, ExpectedArgs...>,
+ base::Unretained(this), ec, callback, policy_method, args...);
+ brillo::MessageLoop::current()->PostTask(FROM_HERE, eval_callback);
+}
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_INL_H_
diff --git a/update_manager/update_manager.cc b/update_manager/update_manager.cc
new file mode 100644
index 0000000..8e9b221
--- /dev/null
+++ b/update_manager/update_manager.cc
@@ -0,0 +1,50 @@
+//
+// 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/update_manager/update_manager.h"
+
+#include "update_engine/update_manager/chromeos_policy.h"
+#include "update_engine/update_manager/state.h"
+
+namespace chromeos_update_manager {
+
+UpdateManager::UpdateManager(chromeos_update_engine::ClockInterface* clock,
+ base::TimeDelta evaluation_timeout,
+ base::TimeDelta expiration_timeout, State* state)
+ : default_policy_(clock), state_(state), clock_(clock),
+ evaluation_timeout_(evaluation_timeout),
+ expiration_timeout_(expiration_timeout),
+ weak_ptr_factory_(this) {
+ // TODO(deymo): Make it possible to replace this policy with a different
+ // implementation with a build-time flag.
+ policy_.reset(new ChromeOSPolicy());
+}
+
+UpdateManager::~UpdateManager() {
+ // Remove pending main loop events associated with any of the outstanding
+ // evaluation contexts. This will prevent dangling pending events, causing
+ // these contexts to be destructed once the repo itself is destructed.
+ for (auto& ec : ec_repo_)
+ ec->RemoveObserversAndTimeout();
+}
+
+void UpdateManager::UnregisterEvalContext(EvaluationContext* ec) {
+ if (!ec_repo_.erase(ec)) {
+ LOG(ERROR) << "Unregistering an unknown evaluation context, this is a bug.";
+ }
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/update_manager.conf.example b/update_manager/update_manager.conf.example
new file mode 100644
index 0000000..2d77974
--- /dev/null
+++ b/update_manager/update_manager.conf.example
@@ -0,0 +1,18 @@
+# Configuration file for the update-manager component of update_engine.
+#
+# Normally this file is loaded from /etc/update_manager.conf. If
+# running update_engine in developer mode (and only if running in
+# developer mode), we attempt to load
+#
+# /mnt/stateful_partition/etc/update_manager.conf
+#
+# and use it if it exists. If it doesn't exist, we fall back to
+# /etc/update_manager.conf.
+#
+# Note: changes to this file are not automatically applied. Use the
+# command "restart update-engine" from a root shell to make your
+# changes take effect.
+
+# Set to true if the device supports the concept of OOBE
+# (Out-Of-the-Box-Experience), false if it doesn't.
+is_oobe_enabled=true
diff --git a/update_manager/update_manager.h b/update_manager/update_manager.h
new file mode 100644
index 0000000..a2f35df
--- /dev/null
+++ b/update_manager/update_manager.h
@@ -0,0 +1,178 @@
+//
+// 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_UPDATE_MANAGER_UPDATE_MANAGER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_H_
+
+#include <memory>
+#include <set>
+#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/update_manager/default_policy.h"
+#include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/state.h"
+
+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();
+ }
+};
+
+// The main Update Manager singleton class.
+class UpdateManager {
+ public:
+ // Creates the UpdateManager instance, assuming ownership on the provided
+ // |state|.
+ UpdateManager(chromeos_update_engine::ClockInterface* clock,
+ base::TimeDelta evaluation_timeout,
+ base::TimeDelta expiration_timeout, State* state);
+
+ virtual ~UpdateManager();
+
+ // PolicyRequest() evaluates the given policy with the provided arguments and
+ // returns the result. The |policy_method| is the pointer-to-method of the
+ // Policy class for the policy request to call. The UpdateManager will call
+ // this method on the right policy. The pointer |result| must not be null
+ // and the remaining |args| depend on the arguments required by the passed
+ // |policy_method|.
+ //
+ // When the policy request succeeds, the |result| is set and the method
+ // returns EvalStatus::kSucceeded, otherwise, the |result| may not be set. A
+ // policy called with this method should not block (i.e. return
+ // EvalStatus::kAskMeAgainLater), which is considered a programming error. On
+ // failure, EvalStatus::kFailed is returned.
+ //
+ // An example call to this method is:
+ // um.PolicyRequest(&Policy::SomePolicyMethod, &bool_result, arg1, arg2);
+ template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+ EvalStatus PolicyRequest(
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ ExpectedArgs...) const,
+ R* result, ActualArgs...);
+
+ // Evaluates the given |policy_method| policy with the provided |args|
+ // arguments and calls the |callback| callback with the result when done.
+ //
+ // If the policy implementation should block, returning a
+ // EvalStatus::kAskMeAgainLater status the Update Manager will re-evaluate the
+ // policy until another status is returned. If the policy implementation based
+ // its return value solely on const variables, the callback will be called
+ // with the EvalStatus::kAskMeAgainLater status (which indicates an error).
+ template<typename R, typename... ActualArgs, typename... ExpectedArgs>
+ void AsyncPolicyRequest(
+ base::Callback<void(EvalStatus, const R& result)> callback,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ ExpectedArgs...) const,
+ ActualArgs... args);
+
+ protected:
+ // The UpdateManager receives ownership of the passed Policy instance.
+ void set_policy(const Policy* policy) {
+ policy_.reset(policy);
+ }
+
+ // State getter used for testing.
+ State* state() { return state_.get(); }
+
+ private:
+ FRIEND_TEST(UmUpdateManagerTest, PolicyRequestCallsPolicy);
+ FRIEND_TEST(UmUpdateManagerTest, PolicyRequestCallsDefaultOnError);
+ FRIEND_TEST(UmUpdateManagerTest, PolicyRequestDoesntBlockDeathTest);
+ FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestDelaysEvaluation);
+ FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestTimeoutDoesNotFire);
+ FRIEND_TEST(UmUpdateManagerTest, AsyncPolicyRequestTimesOut);
+
+ // EvaluatePolicy() evaluates the passed |policy_method| method on the current
+ // policy with the given |args| arguments. If the method fails, the default
+ // policy is used instead.
+ template<typename R, typename... Args>
+ EvalStatus EvaluatePolicy(
+ EvaluationContext* ec,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ Args...) const,
+ R* result, Args... args);
+
+ // OnPolicyReadyToEvaluate() is called by the main loop when the evaluation
+ // of the given |policy_method| should be executed. If the evaluation finishes
+ // the |callback| callback is called passing the |result| and the |status|
+ // returned by the policy. If the evaluation returns an
+ // EvalStatus::kAskMeAgainLater state, the |callback| will NOT be called and
+ // the evaluation will be re-scheduled to be called later.
+ template<typename R, typename... Args>
+ void OnPolicyReadyToEvaluate(
+ scoped_refptr<EvaluationContext> ec,
+ base::Callback<void(EvalStatus status, const R& result)> callback,
+ EvalStatus (Policy::*policy_method)(EvaluationContext*, State*,
+ std::string*, R*,
+ Args...) const,
+ Args... args);
+
+ // Unregisters (removes from repo) a previously created EvaluationContext.
+ void UnregisterEvalContext(EvaluationContext* ec);
+
+ // The policy used by the UpdateManager. Note that since it is a const Policy,
+ // policy implementations are not allowed to persist state on this class.
+ std::unique_ptr<const Policy> policy_;
+
+ // A safe default value to the current policy. This policy is used whenever
+ // a policy implementation fails with EvalStatus::kFailed.
+ const DefaultPolicy default_policy_;
+
+ // 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_;
+
+ // Timeout for expiration of the evaluation context, used for async requests.
+ const base::TimeDelta expiration_timeout_;
+
+ // Repository of previously created EvaluationContext objects. These are being
+ // unregistered (and the reference released) when the context is being
+ // 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_;
+
+ base::WeakPtrFactory<UpdateManager> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateManager);
+};
+
+} // namespace chromeos_update_manager
+
+// Include the implementation of the template methods.
+#include "update_engine/update_manager/update_manager-inl.h"
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_MANAGER_H_
diff --git a/update_manager/update_manager_unittest.cc b/update_manager/update_manager_unittest.cc
new file mode 100644
index 0000000..4cc738d
--- /dev/null
+++ b/update_manager/update_manager_unittest.cc
@@ -0,0 +1,325 @@
+//
+// 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/update_manager/update_manager.h"
+
+#include <unistd.h>
+
+#include <algorithm>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/test/simple_test_clock.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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_clock.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"
+#include "update_engine/update_manager/umtest_utils.h"
+
+using base::Bind;
+using base::Callback;
+using base::Time;
+using base::TimeDelta;
+using brillo::MessageLoop;
+using brillo::MessageLoopRunMaxIterations;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::FakeClock;
+using std::pair;
+using std::string;
+using std::tuple;
+using std::unique_ptr;
+using std::vector;
+
+namespace {
+
+// Generates a fixed timestamp for use in faking the current time.
+Time FixedTime() {
+ Time::Exploded now_exp;
+ now_exp.year = 2014;
+ now_exp.month = 3;
+ now_exp.day_of_week = 2;
+ now_exp.day_of_month = 18;
+ now_exp.hour = 8;
+ now_exp.minute = 5;
+ now_exp.second = 33;
+ now_exp.millisecond = 675;
+ return Time::FromLocalExploded(now_exp);
+}
+
+} // namespace
+
+namespace chromeos_update_manager {
+
+class UmUpdateManagerTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ fake_state_ = new FakeState();
+ umut_.reset(new UpdateManager(&fake_clock_, TimeDelta::FromSeconds(5),
+ TimeDelta::FromSeconds(1), fake_state_));
+ }
+
+ void TearDown() override {
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ base::SimpleTestClock test_clock_;
+ brillo::FakeMessageLoop loop_{&test_clock_};
+ FakeState* fake_state_; // Owned by the umut_.
+ FakeClock fake_clock_;
+ unique_ptr<UpdateManager> umut_;
+};
+
+// The FailingPolicy implements a single method and make it always fail. This
+// class extends the DefaultPolicy class to allow extensions of the Policy
+// class without extending nor changing this test.
+class FailingPolicy : public DefaultPolicy {
+ public:
+ explicit FailingPolicy(int* num_called_p) : num_called_p_(num_called_p) {}
+ FailingPolicy() : FailingPolicy(nullptr) {}
+ EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+ string* error,
+ UpdateCheckParams* result) const override {
+ if (num_called_p_)
+ (*num_called_p_)++;
+ *error = "FailingPolicy failed.";
+ return EvalStatus::kFailed;
+ }
+
+ protected:
+ string PolicyName() const override { return "FailingPolicy"; }
+
+ private:
+ int* num_called_p_;
+};
+
+// The LazyPolicy always returns EvalStatus::kAskMeAgainLater.
+class LazyPolicy : public DefaultPolicy {
+ EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+ string* error,
+ UpdateCheckParams* result) const override {
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ protected:
+ string PolicyName() const override { return "LazyPolicy"; }
+};
+
+// A policy that sleeps for a predetermined amount of time, then checks for a
+// wallclock-based time threshold (if given) and returns
+// EvalStatus::kAskMeAgainLater if not passed; otherwise, returns
+// EvalStatus::kSucceeded. Increments a counter every time it is being queried,
+// if a pointer to it is provided.
+class DelayPolicy : public DefaultPolicy {
+ public:
+ DelayPolicy(int sleep_secs, Time time_threshold, int* num_called_p)
+ : sleep_secs_(sleep_secs), time_threshold_(time_threshold),
+ num_called_p_(num_called_p) {}
+ EvalStatus UpdateCheckAllowed(EvaluationContext* ec, State* state,
+ string* error,
+ UpdateCheckParams* result) const override {
+ if (num_called_p_)
+ (*num_called_p_)++;
+
+ // Sleep for a predetermined amount of time.
+ if (sleep_secs_ > 0)
+ sleep(sleep_secs_);
+
+ // Check for a time threshold. This can be used to ensure that the policy
+ // has some non-constant dependency.
+ if (time_threshold_ < Time::Max() &&
+ ec->IsWallclockTimeGreaterThan(time_threshold_))
+ return EvalStatus::kSucceeded;
+
+ return EvalStatus::kAskMeAgainLater;
+ }
+
+ protected:
+ string PolicyName() const override { return "DelayPolicy"; }
+
+ private:
+ int sleep_secs_;
+ Time time_threshold_;
+ int* num_called_p_;
+};
+
+// AccumulateCallsCallback() adds to the passed |acc| accumulator vector pairs
+// of EvalStatus and T instances. This allows to create a callback that keeps
+// track of when it is called and the arguments passed to it, to be used with
+// the UpdateManager::AsyncPolicyRequest().
+template<typename T>
+static void AccumulateCallsCallback(vector<pair<EvalStatus, T>>* acc,
+ EvalStatus status, const T& result) {
+ acc->push_back(std::make_pair(status, result));
+}
+
+// Tests that policy requests are completed successfully. It is important that
+// this tests cover all policy requests as defined in Policy.
+TEST_F(UmUpdateManagerTest, PolicyRequestCallUpdateCheckAllowed) {
+ UpdateCheckParams result;
+ EXPECT_EQ(EvalStatus::kSucceeded, umut_->PolicyRequest(
+ &Policy::UpdateCheckAllowed, &result));
+}
+
+TEST_F(UmUpdateManagerTest, PolicyRequestCallUpdateCanStart) {
+ UpdateState update_state = UpdateState();
+ update_state.is_interactive = true;
+ update_state.is_delta_payload = false;
+ update_state.first_seen = FixedTime();
+ update_state.num_checks = 1;
+ update_state.num_failures = 0;
+ update_state.failures_last_updated = Time();
+ update_state.download_urls = vector<string>{"http://fake/url/"};
+ update_state.download_errors_max = 10;
+ update_state.p2p_downloading_disabled = false;
+ update_state.p2p_sharing_disabled = false;
+ update_state.p2p_num_attempts = 0;
+ update_state.p2p_first_attempted = Time();
+ update_state.last_download_url_idx = -1;
+ update_state.last_download_url_num_errors = 0;
+ update_state.download_errors = vector<tuple<int, ErrorCode, Time>>();
+ update_state.backoff_expiry = Time();
+ update_state.is_backoff_disabled = false;
+ update_state.scatter_wait_period = TimeDelta::FromSeconds(15);
+ update_state.scatter_check_threshold = 4;
+ update_state.scatter_wait_period_max = TimeDelta::FromSeconds(60);
+ update_state.scatter_check_threshold_min = 2;
+ update_state.scatter_check_threshold_max = 8;
+
+ UpdateDownloadParams result;
+ EXPECT_EQ(EvalStatus::kSucceeded,
+ umut_->PolicyRequest(&Policy::UpdateCanStart, &result,
+ update_state));
+}
+
+TEST_F(UmUpdateManagerTest, PolicyRequestCallsDefaultOnError) {
+ umut_->set_policy(new FailingPolicy());
+
+ // Tests that the DefaultPolicy instance is called when the method fails,
+ // which will set this as true.
+ UpdateCheckParams result;
+ result.updates_enabled = false;
+ EvalStatus status = umut_->PolicyRequest(
+ &Policy::UpdateCheckAllowed, &result);
+ EXPECT_EQ(EvalStatus::kSucceeded, status);
+ EXPECT_TRUE(result.updates_enabled);
+}
+
+// This test only applies to debug builds where DCHECK is enabled.
+#if DCHECK_IS_ON
+TEST_F(UmUpdateManagerTest, PolicyRequestDoesntBlockDeathTest) {
+ // The update manager should die (DCHECK) if a policy called synchronously
+ // returns a kAskMeAgainLater value.
+ UpdateCheckParams result;
+ umut_->set_policy(new LazyPolicy());
+ EXPECT_DEATH(umut_->PolicyRequest(&Policy::UpdateCheckAllowed, &result), "");
+}
+#endif // DCHECK_IS_ON
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestDelaysEvaluation) {
+ // To avoid differences in code execution order between an AsyncPolicyRequest
+ // call on a policy that returns AskMeAgainLater the first time and one that
+ // succeeds the first time, we ensure that the passed callback is called from
+ // the main loop in both cases even when we could evaluate it right now.
+ umut_->set_policy(new FailingPolicy());
+
+ vector<pair<EvalStatus, UpdateCheckParams>> calls;
+ Callback<void(EvalStatus, const UpdateCheckParams&)> callback = Bind(
+ AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+ umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+ // The callback should wait until we run the main loop for it to be executed.
+ EXPECT_EQ(0, calls.size());
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(1, calls.size());
+}
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimeoutDoesNotFire) {
+ // Set up an async policy call to return immediately, then wait a little and
+ // ensure that the timeout event does not fire.
+ int num_called = 0;
+ umut_->set_policy(new FailingPolicy(&num_called));
+
+ vector<pair<EvalStatus, UpdateCheckParams>> calls;
+ Callback<void(EvalStatus, const UpdateCheckParams&)> callback =
+ Bind(AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+ umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+ // Run the main loop, ensure that policy was attempted once before deferring
+ // to the default.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(1, num_called);
+ ASSERT_EQ(1, calls.size());
+ EXPECT_EQ(EvalStatus::kSucceeded, calls[0].first);
+ // Wait for the timeout to expire, run the main loop again, ensure that
+ // nothing happened.
+ test_clock_.Advance(TimeDelta::FromSeconds(2));
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ EXPECT_EQ(1, num_called);
+ EXPECT_EQ(1, calls.size());
+}
+
+TEST_F(UmUpdateManagerTest, AsyncPolicyRequestTimesOut) {
+ // 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),
+ &num_called));
+
+ vector<pair<EvalStatus, UpdateCheckParams>> calls;
+ Callback<void(EvalStatus, const UpdateCheckParams&)> callback =
+ Bind(AccumulateCallsCallback<UpdateCheckParams>, &calls);
+
+ umut_->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
+ // Run the main loop, ensure that policy was attempted once but the callback
+ // was not invoked.
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+ EXPECT_EQ(1, num_called);
+ EXPECT_EQ(0, calls.size());
+ // Wait for the expiration timeout to expire, run the main loop again,
+ // 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() +
+ TimeDelta::FromSeconds(2));
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ EXPECT_EQ(2, num_called);
+ EXPECT_EQ(0, calls.size());
+ // 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() +
+ TimeDelta::FromSeconds(2));
+ MessageLoopRunMaxIterations(MessageLoop::current(), 10);
+ EXPECT_EQ(3, num_called);
+ ASSERT_EQ(1, calls.size());
+ EXPECT_EQ(EvalStatus::kSucceeded, calls[0].first);
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_manager/updater_provider.h b/update_manager/updater_provider.h
new file mode 100644
index 0000000..8048d38
--- /dev/null
+++ b/update_manager/updater_provider.h
@@ -0,0 +1,117 @@
+//
+// 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_UPDATE_MANAGER_UPDATER_PROVIDER_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATER_PROVIDER_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/variable.h"
+
+namespace chromeos_update_manager {
+
+enum class Stage {
+ kIdle,
+ kCheckingForUpdate,
+ kUpdateAvailable,
+ kDownloading,
+ kVerifying,
+ kFinalizing,
+ kUpdatedNeedReboot,
+ kReportingErrorEvent,
+ kAttemptingRollback,
+};
+
+enum class UpdateRequestStatus {
+ kNone,
+ kInteractive,
+ kPeriodic,
+};
+
+// Provider for Chrome OS update related information.
+class UpdaterProvider : public Provider {
+ public:
+ ~UpdaterProvider() override {}
+
+ // A variable returning the timestamp when the update engine was started in
+ // wallclock time.
+ virtual Variable<base::Time>* var_updater_started_time() = 0;
+
+ // A variable returning the last update check time.
+ virtual Variable<base::Time>* var_last_checked_time() = 0;
+
+ // A variable reporting the time when an update was last completed in the
+ // current boot cycle. Returns an error if an update completed time could not
+ // be read (e.g. no update was completed in the current boot cycle) or is
+ // invalid.
+ //
+ // IMPORTANT: The time reported is not the wallclock time reading at the time
+ // of the update, rather it is the point in time when the update completed
+ // relative to the current wallclock time reading. Therefore, the gap between
+ // the reported value and the current wallclock time is guaranteed to be
+ // monotonically increasing.
+ virtual Variable<base::Time>* var_update_completed_time() = 0;
+
+ // A variable returning the update progress (0.0 to 1.0).
+ virtual Variable<double>* var_progress() = 0;
+
+ // A variable returning the current update status.
+ virtual Variable<Stage>* var_stage() = 0;
+
+ // A variable returning the update target version.
+ virtual Variable<std::string>* var_new_version() = 0;
+
+ // A variable returning the update payload size. The payload size is
+ // guaranteed to be non-negative.
+ virtual Variable<int64_t>* var_payload_size() = 0;
+
+ // A variable returning the current channel.
+ virtual Variable<std::string>* var_curr_channel() = 0;
+
+ // A variable returning the update target channel.
+ virtual Variable<std::string>* var_new_channel() = 0;
+
+ // A variable indicating whether user settings allow P2P updates.
+ virtual Variable<bool>* var_p2p_enabled() = 0;
+
+ // A variable indicating whether user settings allow updates over a cellular
+ // network.
+ virtual Variable<bool>* var_cellular_enabled() = 0;
+
+ // A variable returning the number of consecutive failed update checks.
+ virtual Variable<unsigned int>* var_consecutive_failed_update_checks() = 0;
+
+ // A server-dictated update check interval in seconds, if one was given.
+ virtual Variable<unsigned int>* var_server_dictated_poll_interval() = 0;
+
+ // A variable denoting whether a forced update was request but no update check
+ // performed yet; also tells whether this request is for an interactive or
+ // scheduled update.
+ virtual Variable<UpdateRequestStatus>* var_forced_update_requested() = 0;
+
+ protected:
+ UpdaterProvider() {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UpdaterProvider);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_UPDATER_PROVIDER_H_
diff --git a/update_manager/variable.h b/update_manager/variable.h
new file mode 100644
index 0000000..98774ef
--- /dev/null
+++ b/update_manager/variable.h
@@ -0,0 +1,217 @@
+//
+// 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_UPDATE_MANAGER_VARIABLE_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_VARIABLE_H_
+
+#include <algorithm>
+#include <list>
+#include <string>
+
+#include <base/bind.h>
+#include <base/location.h>
+#include <base/logging.h>
+#include <base/time/time.h>
+#include <brillo/message_loops/message_loop.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+namespace chromeos_update_manager {
+
+// The VariableMode specifies important behavior of the variable in terms of
+// whether, how and when the value of the variable changes.
+enum VariableMode {
+ // Const variables never changes during the life of a policy request, so the
+ // EvaluationContext caches the value even between different evaluations of
+ // the same policy request.
+ kVariableModeConst,
+
+ // Poll variables, or synchronous variables, represent a variable with a value
+ // that can be queried at any time, but it is not known when the value
+ // changes on the source of information. In order to detect if the value of
+ // the variable changes, it has to be queried again.
+ kVariableModePoll,
+
+ // Async variables are able to produce a signal or callback whenever the
+ // value changes. This means that it's not required to poll the value to
+ // detect when it changes, instead, you should register an observer to get
+ // a notification when that happens.
+ kVariableModeAsync,
+};
+
+// This class is a base class with the common functionality that doesn't
+// depend on the variable's type, implemented by all the variables.
+class BaseVariable {
+ public:
+ // Interface for observing changes on variable value.
+ class ObserverInterface {
+ public:
+ virtual ~ObserverInterface() {}
+
+ // Called when the value on the variable changes.
+ virtual void ValueChanged(BaseVariable* variable) = 0;
+ };
+
+ virtual ~BaseVariable() {
+ if (!observer_list_.empty()) {
+ LOG(WARNING) << "Variable " << name_ << " deleted with "
+ << observer_list_.size() << " observers.";
+ }
+ DCHECK(observer_list_.empty()) << "Don't destroy the variable without "
+ "removing the observers.";
+ }
+
+ // Returns the variable name as a string.
+ const std::string& GetName() const {
+ return name_;
+ }
+
+ // Returns the variable mode.
+ VariableMode GetMode() const {
+ return mode_;
+ }
+
+ // For VariableModePoll variables, it returns the polling interval of this
+ // variable. In other case, it returns 0.
+ base::TimeDelta GetPollInterval() const {
+ return poll_interval_;
+ }
+
+ // 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.
+ virtual void AddObserver(BaseVariable::ObserverInterface* observer) {
+ if (std::find(observer_list_.begin(), observer_list_.end(), observer) ==
+ observer_list_.end()) {
+ observer_list_.push_back(observer);
+ }
+ }
+
+ virtual void RemoveObserver(BaseVariable::ObserverInterface* observer) {
+ observer_list_.remove(observer);
+ }
+
+ protected:
+ // Creates a BaseVariable using the default polling interval (5 minutes).
+ BaseVariable(const std::string& name, VariableMode mode)
+ : BaseVariable(name, mode,
+ base::TimeDelta::FromMinutes(kDefaultPollMinutes)) {}
+
+ // Creates a BaseVariable with mode kVariableModePoll and the provided
+ // polling interval.
+ BaseVariable(const std::string& name, base::TimeDelta poll_interval)
+ : BaseVariable(name, kVariableModePoll, poll_interval) {}
+
+ // Calls ValueChanged on all the observers.
+ void NotifyValueChanged() {
+ // Fire all the observer methods from the main loop as single call. In order
+ // to avoid scheduling these callbacks when it is not needed, we check
+ // first the list of observers.
+ if (!observer_list_.empty()) {
+ brillo::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BaseVariable::OnValueChangedNotification,
+ base::Unretained(this)));
+ }
+ }
+
+ private:
+ friend class UmEvaluationContextTest;
+ FRIEND_TEST(UmBaseVariableTest, RepeatedObserverTest);
+ FRIEND_TEST(UmBaseVariableTest, NotifyValueChangedTest);
+ FRIEND_TEST(UmBaseVariableTest, NotifyValueRemovesObserversTest);
+
+ BaseVariable(const std::string& name, VariableMode mode,
+ base::TimeDelta poll_interval)
+ : name_(name), mode_(mode),
+ poll_interval_(mode == kVariableModePoll ?
+ poll_interval : base::TimeDelta()) {}
+
+ void OnValueChangedNotification() {
+ // A ValueChanged() method can change the list of observers, for example
+ // removing itself and invalidating the iterator, so we create a snapshot
+ // of the observers first. Also, to support the case when *another* observer
+ // is removed, we check for them.
+ std::list<BaseVariable::ObserverInterface*> observer_list_copy(
+ observer_list_);
+
+ for (auto& observer : observer_list_copy) {
+ if (std::find(observer_list_.begin(), observer_list_.end(), observer) !=
+ observer_list_.end()) {
+ observer->ValueChanged(this);
+ }
+ }
+ }
+
+ // The default PollInterval in minutes.
+ static constexpr int kDefaultPollMinutes = 5;
+
+ // The variable's name as a string.
+ const std::string name_;
+
+ // The variable's mode.
+ const VariableMode mode_;
+
+ // The variable's polling interval for VariableModePoll variable and 0 for
+ // other modes.
+ const base::TimeDelta poll_interval_;
+
+ // The list of value changes observers.
+ std::list<BaseVariable::ObserverInterface*> observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(BaseVariable);
+};
+
+// Interface to an Update Manager variable of a given type. Implementation
+// internals are hidden as protected members, since policies should not be
+// using them directly.
+template<typename T>
+class Variable : public BaseVariable {
+ public:
+ ~Variable() override {}
+
+ protected:
+ // Only allow to get values through the EvaluationContext class and not
+ // directly from the variable.
+ friend class EvaluationContext;
+
+ // Needed to be able to verify variable contents during unit testing.
+ friend class UmTestUtils;
+ FRIEND_TEST(UmRealRandomProviderTest, GetRandomValues);
+
+ Variable(const std::string& name, VariableMode mode)
+ : BaseVariable(name, mode) {}
+
+ Variable(const std::string& name, const base::TimeDelta poll_interval)
+ : BaseVariable(name, poll_interval) {}
+
+ // Gets the current value of the variable. The current value is copied to a
+ // new object and returned. The caller of this method owns the object and
+ // should delete it.
+ //
+ // In case of and error getting the current value or the |timeout| timeout is
+ // exceeded, a null value is returned and the |errmsg| is set.
+ //
+ // The caller can pass a null value for |errmsg|, in which case the error
+ // message won't be set.
+ virtual const T* GetValue(base::TimeDelta timeout, std::string* errmsg) = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Variable);
+};
+
+} // namespace chromeos_update_manager
+
+#endif // UPDATE_ENGINE_UPDATE_MANAGER_VARIABLE_H_
diff --git a/update_manager/variable_unittest.cc b/update_manager/variable_unittest.cc
new file mode 100644
index 0000000..13cceb1
--- /dev/null
+++ b/update_manager/variable_unittest.cc
@@ -0,0 +1,181 @@
+//
+// 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/update_manager/variable.h"
+
+#include <vector>
+
+#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>
+
+using base::TimeDelta;
+using brillo::MessageLoop;
+using brillo::MessageLoopRunMaxIterations;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_manager {
+
+// Variable class that returns a value constructed with the default value.
+template <typename T>
+class DefaultVariable : public Variable<T> {
+ public:
+ DefaultVariable(const string& name, VariableMode mode)
+ : Variable<T>(name, mode) {}
+ DefaultVariable(const string& name, const TimeDelta& poll_interval)
+ : Variable<T>(name, poll_interval) {}
+ ~DefaultVariable() override {}
+
+ protected:
+ const T* GetValue(TimeDelta /* timeout */,
+ string* /* errmsg */) override {
+ return new T();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DefaultVariable);
+};
+
+class UmBaseVariableTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ brillo::FakeMessageLoop loop_{nullptr};
+};
+
+TEST_F(UmBaseVariableTest, GetNameTest) {
+ DefaultVariable<int> var("var", kVariableModeConst);
+ EXPECT_EQ(var.GetName(), string("var"));
+}
+
+TEST_F(UmBaseVariableTest, GetModeTest) {
+ DefaultVariable<int> var("var", kVariableModeConst);
+ EXPECT_EQ(var.GetMode(), kVariableModeConst);
+ DefaultVariable<int> other_var("other_var", kVariableModePoll);
+ EXPECT_EQ(other_var.GetMode(), kVariableModePoll);
+}
+
+TEST_F(UmBaseVariableTest, DefaultPollIntervalTest) {
+ DefaultVariable<int> const_var("const_var", kVariableModeConst);
+ EXPECT_EQ(const_var.GetPollInterval(), TimeDelta());
+ DefaultVariable<int> poll_var("poll_var", kVariableModePoll);
+ EXPECT_EQ(poll_var.GetPollInterval(), TimeDelta::FromMinutes(5));
+}
+
+TEST_F(UmBaseVariableTest, GetPollIntervalTest) {
+ DefaultVariable<int> var("var", TimeDelta::FromMinutes(3));
+ EXPECT_EQ(var.GetMode(), kVariableModePoll);
+ EXPECT_EQ(var.GetPollInterval(), TimeDelta::FromMinutes(3));
+}
+
+class BaseVariableObserver : public BaseVariable::ObserverInterface {
+ public:
+ void ValueChanged(BaseVariable* variable) {
+ calls_.push_back(variable);
+ }
+
+ // List of called functions.
+ vector<BaseVariable*> calls_;
+};
+
+TEST_F(UmBaseVariableTest, RepeatedObserverTest) {
+ DefaultVariable<int> var("var", kVariableModeAsync);
+ BaseVariableObserver observer;
+ var.AddObserver(&observer);
+ EXPECT_EQ(var.observer_list_.size(), 1);
+ var.AddObserver(&observer);
+ EXPECT_EQ(var.observer_list_.size(), 1);
+ var.RemoveObserver(&observer);
+ EXPECT_EQ(var.observer_list_.size(), 0);
+ var.RemoveObserver(&observer);
+ EXPECT_EQ(var.observer_list_.size(), 0);
+}
+
+TEST_F(UmBaseVariableTest, NotifyValueChangedTest) {
+ DefaultVariable<int> var("var", kVariableModeAsync);
+ BaseVariableObserver observer1;
+ var.AddObserver(&observer1);
+ // Simulate a value change on the variable's implementation.
+ var.NotifyValueChanged();
+ ASSERT_EQ(0, observer1.calls_.size());
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+ ASSERT_EQ(1, observer1.calls_.size());
+ // Check that the observer is called with the right argument.
+ EXPECT_EQ(&var, observer1.calls_[0]);
+
+ BaseVariableObserver observer2;
+ var.AddObserver(&observer2);
+ var.NotifyValueChanged();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+ // Check that all the observers are called.
+ EXPECT_EQ(2, observer1.calls_.size());
+ EXPECT_EQ(1, observer2.calls_.size());
+
+ var.RemoveObserver(&observer1);
+ var.RemoveObserver(&observer2);
+}
+
+class BaseVariableObserverRemover : public BaseVariable::ObserverInterface {
+ public:
+ BaseVariableObserverRemover() : calls_(0) {}
+
+ void ValueChanged(BaseVariable* variable) override {
+ for (auto& observer : remove_observers_) {
+ variable->RemoveObserver(observer);
+ }
+ calls_++;
+ }
+
+ void OnCallRemoveObserver(BaseVariable::ObserverInterface* observer) {
+ remove_observers_.push_back(observer);
+ }
+
+ int get_calls() { return calls_; }
+
+ private:
+ vector<BaseVariable::ObserverInterface*> remove_observers_;
+ int calls_;
+};
+
+// Tests that we can remove an observer from a Variable on the ValueChanged()
+// call to that observer.
+TEST_F(UmBaseVariableTest, NotifyValueRemovesObserversTest) {
+ DefaultVariable<int> var("var", kVariableModeAsync);
+ BaseVariableObserverRemover observer1;
+ BaseVariableObserverRemover observer2;
+
+ var.AddObserver(&observer1);
+ var.AddObserver(&observer2);
+
+ // Make each observer remove both observers on ValueChanged.
+ observer1.OnCallRemoveObserver(&observer1);
+ observer1.OnCallRemoveObserver(&observer2);
+ observer2.OnCallRemoveObserver(&observer1);
+ observer2.OnCallRemoveObserver(&observer2);
+
+ var.NotifyValueChanged();
+ MessageLoopRunMaxIterations(MessageLoop::current(), 100);
+
+ EXPECT_EQ(1, observer1.get_calls() + observer2.get_calls());
+}
+
+} // namespace chromeos_update_manager
diff --git a/update_metadata.proto b/update_metadata.proto
new file mode 100644
index 0000000..c856405
--- /dev/null
+++ b/update_metadata.proto
@@ -0,0 +1,277 @@
+//
+// 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.
+//
+
+// Update file format: A delta update file contains all the deltas needed
+// to update a system from one specific version to another specific
+// version. The update format is represented by this struct pseudocode:
+// struct delta_update_file {
+// char magic[4] = "CrAU";
+// uint64 file_format_version;
+// uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
+//
+// // Only present if format_version > 1:
+// uint32 metadata_signature_size;
+//
+// // The Bzip2 compressed DeltaArchiveManifest
+// char manifest[];
+//
+// // The signature of the metadata (from the beginning of the payload up to
+// // this location, not including the signature itself). This is a serialized
+// // Signatures message.
+// char medatada_signature_message[metadata_signature_size];
+//
+// // Data blobs for files, no specific format. The specific offset
+// // and length of each data blob is recorded in the DeltaArchiveManifest.
+// struct {
+// char data[];
+// } blobs[];
+//
+// // These two are not signed:
+// uint64 payload_signatures_message_size;
+// char payload_signatures_message[];
+//
+// };
+
+// The DeltaArchiveManifest protobuf is an ordered list of InstallOperation
+// objects. These objects are stored in a linear array in the
+// DeltaArchiveManifest. Each operation is applied in order by the client.
+
+// The DeltaArchiveManifest also contains the initial and final
+// checksums for the device.
+
+// The client will perform each InstallOperation in order, beginning even
+// before the entire delta file is downloaded (but after at least the
+// protobuf is downloaded). The types of operations are explained:
+// - REPLACE: Replace the dst_extents on the drive with the attached data,
+// zero padding out to block size.
+// - REPLACE_BZ: bzip2-uncompress the attached data and write it into
+// dst_extents on the drive, zero padding to block size.
+// - MOVE: Copy the data in src_extents to dst_extents. Extents may overlap,
+// so it may be desirable to read all src_extents data into memory before
+// writing it out.
+// - SOURCE_COPY: Copy the data in src_extents in the old partition to
+// dst_extents in the new partition. There's no overlapping of data because
+// the extents are in different partitions.
+// - BSDIFF: Read src_length bytes from src_extents into memory, perform
+// bspatch with attached data, write new data to dst_extents, zero padding
+// to block size.
+// - SOURCE_BSDIFF: Read the data in src_extents in the old partition, perform
+// bspatch with the attached data and write the new data to dst_extents in the
+// new partition.
+// - 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.
+// - 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.
+//
+// The operations allowed in the payload (supported by the client) depend on the
+// major and minor version. See InstallOperation.Type bellow for details.
+
+package chromeos_update_engine;
+option optimize_for = LITE_RUNTIME;
+
+// Data is packed into blocks on disk, always starting from the beginning
+// of the block. If a file's data is too large for one block, it overflows
+// into another block, which may or may not be the following block on the
+// physical partition. An ordered list of extents is another
+// representation of an ordered list of blocks. For example, a file stored
+// in blocks 9, 10, 11, 2, 18, 12 (in that order) would be stored in
+// extents { {9, 3}, {2, 1}, {18, 1}, {12, 1} } (in that order).
+// In general, files are stored sequentially on disk, so it's more efficient
+// to use extents to encode the block lists (this is effectively
+// run-length encoding).
+// A sentinel value (kuint64max) as the start block denotes a sparse-hole
+// in a file whose block-length is specified by num_blocks.
+
+// Signatures: Updates may be signed by the OS vendor. The client verifies
+// an update's signature by hashing the entire download. The section of the
+// download that contains the signature is at the end of the file, so when
+// signing a file, only the part up to the signature part is signed.
+// Then, the client looks inside the download's Signatures message for a
+// Signature message that it knows how to handle. Generally, a client will
+// only know how to handle one type of signature, but an update may contain
+// many signatures to support many different types of client. Then client
+// selects a Signature message and uses that, along with a known public key,
+// to verify the download. The public key is expected to be part of the
+// client.
+
+message Extent {
+ optional uint64 start_block = 1;
+ optional uint64 num_blocks = 2;
+}
+
+message Signatures {
+ message Signature {
+ optional uint32 version = 1;
+ optional bytes data = 2;
+ }
+ repeated Signature signatures = 1;
+}
+
+message PartitionInfo {
+ optional uint64 size = 1;
+ optional bytes hash = 2;
+}
+
+// Describe an image we are based on in a human friendly way.
+// Examples:
+// dev-channel, x86-alex, 1.2.3, mp-v3
+// nplusone-channel, x86-alex, 1.2.4, mp-v3, dev-channel, 1.2.3
+//
+// 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;
+
+ // 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;
+}
+
+message InstallOperation {
+ enum Type {
+ REPLACE = 0; // Replace destination extents w/ attached data
+ REPLACE_BZ = 1; // Replace destination extents w/ attached bzipped data
+ MOVE = 2; // Move source extents to destination extents
+ BSDIFF = 3; // 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
+
+ // On minor version 3 or newer and on major version 2 or newer, these
+ // operations are supported:
+ ZERO = 6; // Write zeros in the destination.
+ DISCARD = 7; // Discard the destination blocks, reading as undefined.
+ REPLACE_XZ = 8; // Replace destination extents w/ attached xz data.
+ }
+ required Type type = 1;
+ // The offset into the delta file (after the protobuf)
+ // where the data (if any) is stored
+ optional uint32 data_offset = 2;
+ // The length of the data in the delta file
+ optional uint32 data_length = 3;
+
+ // Ordered list of extents that are read from (if any) and written to.
+ repeated Extent src_extents = 4;
+ // Byte length of src, equal to the number of blocks in src_extents *
+ // block_size. It is used for BSDIFF, because we need to pass that
+ // external program the number of bytes to read from the blocks we pass it.
+ // This is not used in any other operation.
+ optional uint64 src_length = 5;
+
+ repeated Extent dst_extents = 6;
+ // Byte length of dst, equal to the number of blocks in dst_extents *
+ // block_size. Used for BSDIFF, but not in any other operation.
+ optional uint64 dst_length = 7;
+
+ // Optional SHA 256 hash of the blob associated with this operation.
+ // This is used as a primary validation for http-based downloads and
+ // as a defense-in-depth validation for https-based downloads. If
+ // the operation doesn't refer to any blob, this field will have
+ // zero bytes.
+ optional bytes data_sha256_hash = 8;
+
+ // Indicates the SHA 256 hash of the source data referenced in src_extents at
+ // the time of applying the operation. If present, the update_engine daemon
+ // MUST read and verify the source data before applying the operation.
+ optional bytes src_sha256_hash = 9;
+}
+
+// Describes the update to apply to a single partition.
+message PartitionUpdate {
+ // A platform-specific name to identify the partition set being updated. For
+ // example, in Chrome OS this could be "ROOT" or "KERNEL".
+ required string partition_name = 1;
+
+ // Whether this partition carries a filesystem with post-install program that
+ // must be run to finalize the update process. See also |postinstall_path| and
+ // |filesystem_type|.
+ optional bool run_postinstall = 2;
+
+ // The path of the executable program to run during the post-install step,
+ // relative to the root of this filesystem. If not set, the default "postinst"
+ // will be used. This setting is only used when |run_postinstall| is set and
+ // true.
+ optional string postinstall_path = 3;
+
+ // The filesystem type as passed to the mount(2) syscall when mounting the new
+ // filesystem to run the post-install program. If not set, a fixed list of
+ // filesystems will be attempted. This setting is only used if
+ // |run_postinstall| is set and true.
+ optional string filesystem_type = 4;
+
+ // If present, a list of signatures of the new_partition_info.hash signed with
+ // different keys. If the update_engine daemon requires vendor-signed images
+ // and has its public key installed, one of the signatures should be valid
+ // for /postinstall to run.
+ repeated Signatures.Signature new_partition_signature = 5;
+
+ optional PartitionInfo old_partition_info = 6;
+ optional PartitionInfo new_partition_info = 7;
+
+ // The list of operations to be performed to apply this PartitionUpdate. The
+ // associated operation blobs (in operations[i].data_offset, data_length)
+ // should be stored contiguously and in the same order.
+ repeated InstallOperation operations = 8;
+}
+
+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;
+
+ // (At time of writing) usually 4096
+ optional uint32 block_size = 3 [default = 4096];
+
+ // If signatures are present, the offset into the blobs, generally
+ // tacked onto the end of the file, and the length. We use an offset
+ // rather than a bool to allow for more flexibility in future file formats.
+ // If either is absent, it means signatures aren't supported in this
+ // file.
+ optional uint64 signatures_offset = 4;
+ optional uint64 signatures_size = 5;
+
+ // 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;
+
+ // old_image_info will only be present for delta images.
+ optional ImageInfo old_image_info = 10;
+
+ optional ImageInfo new_image_info = 11;
+
+ // The minor version, also referred as "delta version", of the payload.
+ optional uint32 minor_version = 12 [default = 0];
+
+ // Only present in major version >= 2. List of partitions that will be
+ // updated, in the order they will be updated. This field replaces the
+ // |install_operations|, |kernel_install_operations| and the
+ // |{old,new}_{kernel,rootfs}_info| fields used in major version = 1. This
+ // array can have more than two partitions if needed, and they are identified
+ // by the partition name.
+ repeated PartitionUpdate partitions = 13;
+}
diff --git a/update_payload_key/README b/update_payload_key/README
new file mode 100644
index 0000000..cf87148
--- /dev/null
+++ b/update_payload_key/README
@@ -0,0 +1,2 @@
+This directory contains the public half of the payload signing key. This is
+baked into the system image as part of update_engine install.
diff --git a/update_payload_key/brillo-update-payload-key.pub.pem b/update_payload_key/brillo-update-payload-key.pub.pem
new file mode 100644
index 0000000..7e5164c
--- /dev/null
+++ b/update_payload_key/brillo-update-payload-key.pub.pem
@@ -0,0 +1,9 @@
+-----BEGIN PUBLIC KEY-----
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxAxPqfII4vIe3cqKzdvl
+gwjBhj9kyF+6ig73yZq0o4wLOq3nsRUToaIOtQmcjr1G+hhSXBU3WTbfZLlm07Fb
+B535o2zhYghs8Br7xobjX+gikEnxnFuTtB2sB4Gpan4hKwU+BuZhJDSl1oZwUJJ4
+eiGJpH5xJswbyO/bA81BCMjU3rm+G6SzOLQTK0YEnhn7bB69UucM57GM7l+dCl8r
+RhKjbpP7E1fVtgX++BGs6pKciPLxYfXVup0MgH0h8VdSDMiHkshIXYvcCV1KOBFX
+9GrYvXLtq41Hm5hC5l48mwLi0ALdIfbPQ5oHLl2u+etLmGwbMpzhybTCZQA/SgEl
+HwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/update_status_utils.cc b/update_status_utils.cc
new file mode 100644
index 0000000..ff039b8
--- /dev/null
+++ b/update_status_utils.cc
@@ -0,0 +1,89 @@
+//
+// 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/update_status_utils.h"
+
+#include <base/logging.h>
+#include <update_engine/dbus-constants.h>
+
+using update_engine::UpdateStatus;
+
+namespace chromeos_update_engine {
+
+const char* UpdateStatusToString(const UpdateStatus& status) {
+ switch (status) {
+ case UpdateStatus::IDLE:
+ return update_engine::kUpdateStatusIdle;
+ case UpdateStatus::CHECKING_FOR_UPDATE:
+ return update_engine::kUpdateStatusCheckingForUpdate;
+ case UpdateStatus::UPDATE_AVAILABLE:
+ return update_engine::kUpdateStatusUpdateAvailable;
+ case UpdateStatus::DOWNLOADING:
+ return update_engine::kUpdateStatusDownloading;
+ case UpdateStatus::VERIFYING:
+ return update_engine::kUpdateStatusVerifying;
+ case UpdateStatus::FINALIZING:
+ return update_engine::kUpdateStatusFinalizing;
+ case UpdateStatus::UPDATED_NEED_REBOOT:
+ return update_engine::kUpdateStatusUpdatedNeedReboot;
+ case UpdateStatus::REPORTING_ERROR_EVENT:
+ return update_engine::kUpdateStatusReportingErrorEvent;
+ case UpdateStatus::ATTEMPTING_ROLLBACK:
+ return update_engine::kUpdateStatusAttemptingRollback;
+ case UpdateStatus::DISABLED:
+ return update_engine::kUpdateStatusDisabled;
+ }
+
+ NOTREACHED();
+ 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::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;
+ }
+ return false;
+}
+
+} // namespace chromeos_update_engine
diff --git a/update_status_utils.h b/update_status_utils.h
new file mode 100644
index 0000000..30ae53b
--- /dev/null
+++ b/update_status_utils.h
@@ -0,0 +1,33 @@
+//
+// 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_UPDATE_STATUS_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_STATUS_UTILS_H_
+
+#include <string>
+
+#include "update_engine/client_library/include/update_engine/update_status.h"
+
+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);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_UPDATE_STATUS_UTILS_H_