Project import
diff --git a/update_engine/.clang-format b/update_engine/.clang-format new file mode 120000 index 0000000..f412743 --- /dev/null +++ b/update_engine/.clang-format
@@ -0,0 +1 @@ +../../build/tools/brillo-clang-format \ No newline at end of file
diff --git a/update_engine/Android.mk b/update_engine/Android.mk new file mode 100644 index 0000000..1cba534 --- /dev/null +++ b/update_engine/Android.mk
@@ -0,0 +1,1152 @@ +# +# 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 +# by setting BRILLO_USE_* values. Note that we define local variables like +# local_use_* to prevent leaking our default setting for other packages. +local_use_binder := $(if $(BRILLO_USE_BINDER),$(BRILLO_USE_BINDER),1) +local_use_dbus := $(if $(BRILLO_USE_DBUS),$(BRILLO_USE_DBUS),0) +local_use_hwid_override := \ + $(if $(BRILLO_USE_HWID_OVERRIDE),$(BRILLO_USE_HWID_OVERRIDE),0) +# "libcros" gates the LibCrosService exposed by the Chrome OS' chrome browser to +# the system layer. +local_use_libcros := $(if $(BRILLO_USE_LIBCROS),$(BRILLO_USE_LIBCROS),0) +local_use_mtd := $(if $(BRILLO_USE_MTD),$(BRILLO_USE_MTD),0) +local_use_omaha := $(if $(BRILLO_USE_OMAHA),$(BRILLO_USE_OMAHA),0) +local_use_shill := $(if $(BRILLO_USE_SHILL),$(BRILLO_USE_SHILL),0) +local_use_weave := $(if $(BRILLO_USE_WEAVE),$(BRILLO_USE_WEAVE),0) +local_use_nestlabs := $(if $(BRILLO_USE_NESTLABS),$(BRILLO_USE_NESTLABS),0) + +ifeq ($(local_use_shill),1) +ifneq ($(local_use_dbus),1) +$(error USE_SHILL depends on USE_DBUS.) +endif # local_use_dbus != 1 +endif # local_use_shill == 1 + +ue_common_cflags := \ + -DUSE_BINDER=$(local_use_binder) \ + -DUSE_DBUS=$(local_use_dbus) \ + -DUSE_HWID_OVERRIDE=$(local_use_hwid_override) \ + -DUSE_LIBCROS=$(local_use_libcros) \ + -DUSE_MTD=$(local_use_mtd) \ + -DUSE_OMAHA=$(local_use_omaha) \ + -DUSE_SHILL=$(local_use_shill) \ + -DUSE_WEAVE=$(local_use_weave) \ + -DUSE_NESTLABS=$(local_use_nestlabs) \ + -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 +ue_common_ldflags := \ + -Wl,--gc-sections +ue_common_c_includes := \ + $(LOCAL_PATH)/client_library/include \ + system +ue_common_shared_libraries := \ + libbrillo-stream \ + libbrillo \ + libchrome \ + libbase +ue_common_static_libraries := \ + libgtest_prod \ + +ifeq ($(local_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 +ifeq ($(local_use_nestlabs),1) +LOCAL_SRC_FILES += \ + dbus_bindings/dbus-service-config-nestlabs.json \ + dbus_bindings/com.nestlabs.UpdateEngineInterface.dbus-xml +else +LOCAL_SRC_FILES += \ + dbus_bindings/dbus-service-config.json \ + dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml +endif +LOCAL_DBUS_PROXY_PREFIX := update_engine +include $(BUILD_STATIC_LIBRARY) + +endif # local_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 + +ue_update_metadata_protos_src_files := \ + update_metadata.proto + +# Build for the host. +include $(CLEAR_VARS) +LOCAL_MODULE := update_metadata-protos +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_IS_HOST_MODULE := true +generated_sources_dir := $(call local-generated-sources-dir) +LOCAL_EXPORT_C_INCLUDE_DIRS := $(generated_sources_dir)/proto/system +LOCAL_SRC_FILES := $(ue_update_metadata_protos_src_files) +include $(BUILD_HOST_STATIC_LIBRARY) + +# Build for the target. +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 := $(ue_update_metadata_protos_src_files) +include $(BUILD_STATIC_LIBRARY) + +ifeq ($(local_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 +ifeq ($(local_use_nestlabs),1) +LOCAL_SRC_FILES := \ + dbus_bindings/com.nestlabs.UpdateEngineInterface.dbus-xml +else +LOCAL_SRC_FILES := \ + dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml +endif +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 # local_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-host \ + libbz \ + libimgpatch \ + libz \ + $(ue_update_metadata_protos_exported_static_libraries) +ue_libpayload_consumer_exported_shared_libraries := \ + libcrypto \ + $(ue_update_metadata_protos_exported_shared_libraries) + +ue_libpayload_consumer_src_files := \ + common/action_processor.cc \ + common/boot_control_stub.cc \ + common/clock.cc \ + common/constants.cc \ + common/cpu_limiter.cc \ + common/error_code_utils.cc \ + common/file_fetcher.cc \ + common/hash_calculator.cc \ + common/http_common.cc \ + common/http_fetcher.cc \ + common/hwid_override.cc \ + common/multi_range_http_fetcher.cc \ + common/prefs.cc \ + common/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 +ifeq ($(local_use_nestlabs),1) +ue_libpayload_consumer_src_files += \ + common/platform_constants_nestlabs.cc +else # local_use_nestlabs == 1 +ue_libpayload_consumer_src_files += \ + common/platform_constants_android.cc +endif # local_use_nestlabs != 1 + +ifeq ($(HOST_OS),linux) +# Build for the host. +include $(CLEAR_VARS) +LOCAL_MODULE := libpayload_consumer +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +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) +LOCAL_STATIC_LIBRARIES := \ + update_metadata-protos \ + $(ue_common_static_libraries) \ + $(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 := $(ue_libpayload_consumer_src_files) +include $(BUILD_HOST_STATIC_LIBRARY) +endif # HOST_OS == linux + +# Build for the target. +include $(CLEAR_VARS) +LOCAL_MODULE := libpayload_consumer +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +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) +LOCAL_STATIC_LIBRARIES := \ + update_metadata-protos \ + $(ue_common_static_libraries) \ + $(ue_libpayload_consumer_exported_static_libraries:-host=) \ + $(ue_update_metadata_protos_exported_static_libraries) +LOCAL_SHARED_LIBRARIES := \ + $(ue_common_shared_libraries) \ + $(ue_libpayload_consumer_exported_shared_libraries:-host=) \ + $(ue_update_metadata_protos_exported_shared_libraries) +LOCAL_SRC_FILES := $(ue_libpayload_consumer_src_files) +include $(BUILD_STATIC_LIBRARY) + +ifeq ($(local_use_omaha),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 \ + libbz \ + libfs_mgr \ + $(ue_libpayload_consumer_exported_static_libraries) \ + $(ue_update_metadata_protos_exported_static_libraries) +ue_libupdate_engine_exported_shared_libraries := \ + libmetrics \ + libexpat \ + libbrillo-policy \ + libhardware \ + libcurl \ + libcutils \ + libssl \ + $(ue_libpayload_consumer_exported_shared_libraries) \ + $(ue_update_metadata_protos_exported_shared_libraries) +ifeq ($(local_use_dbus),1) +ue_libupdate_engine_exported_static_libraries += \ + update_engine-dbus-adaptor \ + update_engine-dbus-libcros-client \ + update_engine_client-dbus-proxies +ue_libupdate_engine_exported_shared_libraries += \ + libdbus \ + libbrillo-dbus \ + libchrome-dbus +endif # local_use_dbus == 1 +ifeq ($(local_use_shill),1) +ue_libupdate_engine_exported_shared_libraries += \ + libshill-client +endif # local_use_shill == 1 +ifeq ($(local_use_binder),1) +ue_libupdate_engine_exported_shared_libraries += \ + libbinder \ + libbinderwrapper \ + libbrillo-binder \ + libutils +endif # local_use_binder == 1 +ifeq ($(local_use_weave),1) +ue_libupdate_engine_exported_shared_libraries += \ + libbinderwrapper \ + libbrillo-binder \ + libweaved +endif # local_use_weave == 1 + +include $(CLEAR_VARS) +LOCAL_MODULE := libupdate_engine +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +LOCAL_CPP_EXTENSION := .cc +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) \ + bootable/recovery +LOCAL_STATIC_LIBRARIES := \ + libpayload_consumer \ + update_metadata-protos \ + $(ue_common_static_libraries) \ + $(ue_libupdate_engine_exported_static_libraries:-host=) \ + $(ue_libpayload_consumer_exported_static_libraries:-host=) \ + $(ue_update_metadata_protos_exported_static_libraries) +LOCAL_SHARED_LIBRARIES := \ + $(ue_common_shared_libraries) \ + $(ue_libupdate_engine_exported_shared_libraries:-host=) \ + $(ue_libpayload_consumer_exported_shared_libraries:-host=) \ + $(ue_update_metadata_protos_exported_shared_libraries) +LOCAL_SRC_FILES := \ + boot_control_android.cc \ + certificate_checker.cc \ + common_service.cc \ + connection_utils.cc \ + daemon.cc \ + hardware_android.cc \ + image_properties_android.cc \ + libcurl_http_fetcher.cc \ + metrics.cc \ + metrics_utils.cc \ + p2p_manager.cc \ + payload_state.cc \ + power_manager_android.cc \ + proxy_resolver.cc \ + real_system_state.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_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 \ + utils_android.cc \ + weave_service_factory.cc +ifeq ($(local_use_nestlabs),1) +LOCAL_SRC_FILES += \ + omaha_request_params.cc \ + omaha_utils.cc \ + payload_properties_request_action.cc \ + payload_properties_handler_action.cc \ + update_attempter_nestlabs.cc +else +LOCAL_SRC_FILES += \ + omaha_request_action.cc \ + omaha_response_handler_action.cc \ + update_attempter.cc +endif # local_use_nestlabs == 1 +ifeq ($(local_use_dbus),1) +LOCAL_SRC_FILES += \ + dbus_connection.cc \ + libcros_proxy.cc +ifeq ($(local_use_nestlabs),1) +LOCAL_SRC_FILES += \ + dbus_service_nestlabs.cc +else +LOCAL_SRC_FILES += \ + dbus_service.cc +endif +endif # local_use_dbus == 1 +ifeq ($(local_use_shill),1) +LOCAL_SRC_FILES += \ + connection_manager.cc \ + shill_proxy.cc \ + update_manager/real_shill_provider.cc +else # local_use_shill != 1 +LOCAL_SRC_FILES += \ + connection_manager_android.cc +endif # local_use_shill == 1 +ifeq ($(local_use_binder),1) +LOCAL_AIDL_INCLUDES += $(LOCAL_PATH)/binder_bindings +LOCAL_SRC_FILES += \ + binder_bindings/android/brillo/IUpdateEngine.aidl \ + binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl \ + binder_service_brillo.cc \ + parcelable_update_engine_status.cc +endif # local_use_binder == 1 +ifeq ($(local_use_weave),1) +LOCAL_SRC_FILES += \ + weave_service.cc +endif # local_use_weave == 1 +ifeq ($(local_use_libcros),1) +LOCAL_SRC_FILES += \ + chrome_browser_proxy_resolver.cc +endif # local_use_libcros == 1 +include $(BUILD_STATIC_LIBRARY) + +else # local_use_omaha == 1 + +ifneq ($(local_use_binder),1) +$(error USE_BINDER is disabled but is required in non-Brillo devices.) +endif # local_use_binder == 1 + +# libupdate_engine_android (type: static_library) +# ======================================================== +# The main daemon static_library used in Android (non-Brillo). This only has a +# loop to apply payloads provided by the upper layer via a Binder interface. +ue_libupdate_engine_android_exported_static_libraries := \ + libpayload_consumer \ + libfs_mgr \ + $(ue_libpayload_consumer_exported_static_libraries) +ue_libupdate_engine_android_exported_shared_libraries := \ + $(ue_libpayload_consumer_exported_shared_libraries) \ + libandroid \ + libbinder \ + libbinderwrapper \ + libbrillo-binder \ + libcutils \ + libcurl \ + libhardware \ + libssl \ + libutils + +include $(CLEAR_VARS) +LOCAL_MODULE := libupdate_engine_android +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +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) \ + bootable/recovery +#TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved +# out of the DBus interface. +LOCAL_C_INCLUDES += \ + external/cros/system_api/dbus +LOCAL_STATIC_LIBRARIES := \ + $(ue_common_static_libraries) \ + $(ue_libupdate_engine_android_exported_static_libraries:-host=) +LOCAL_SHARED_LIBRARIES += \ + $(ue_common_shared_libraries) \ + $(ue_libupdate_engine_android_exported_shared_libraries:-host=) +LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/binder_bindings +LOCAL_SRC_FILES += \ + binder_bindings/android/os/IUpdateEngine.aidl \ + binder_bindings/android/os/IUpdateEngineCallback.aidl \ + binder_service_android.cc \ + boot_control_android.cc \ + certificate_checker.cc \ + daemon.cc \ + daemon_state_android.cc \ + hardware_android.cc \ + libcurl_http_fetcher.cc \ + network_selector_android.cc \ + proxy_resolver.cc \ + update_attempter_android.cc \ + update_status_utils.cc \ + utils_android.cc +include $(BUILD_STATIC_LIBRARY) + +endif # local_use_omaha == 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 +ifeq ($(local_use_weave),1) +LOCAL_REQUIRED_MODULES += updater.json +endif # local_use_weave == 1 +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) +LOCAL_SHARED_LIBRARIES := \ + $(ue_common_shared_libraries) +LOCAL_STATIC_LIBRARIES := \ + $(ue_common_static_libraries) +LOCAL_SRC_FILES := \ + main.cc + +ifeq ($(local_use_omaha),1) +LOCAL_C_INCLUDES += \ + $(ue_libupdate_engine_exported_c_includes) +LOCAL_STATIC_LIBRARIES += \ + libupdate_engine \ + $(ue_libupdate_engine_exported_static_libraries:-host=) +LOCAL_SHARED_LIBRARIES += \ + $(ue_libupdate_engine_exported_shared_libraries:-host=) +else # local_use_omaha == 1 +LOCAL_STATIC_LIBRARIES += \ + libupdate_engine_android \ + $(ue_libupdate_engine_android_exported_static_libraries:-host=) +LOCAL_SHARED_LIBRARIES += \ + $(ue_libupdate_engine_android_exported_shared_libraries:-host=) +endif # local_use_omaha == 1 + +LOCAL_INIT_RC := update_engine.rc +include $(BUILD_EXECUTABLE) + +# update_engine_sideload (type: executable) +# ======================================================== +# A static binary equivalent to update_engine daemon that installs an update +# from a local file directly instead of running in the background. +include $(CLEAR_VARS) +LOCAL_MODULE := update_engine_sideload +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_REQUIRED_MODULES := \ + bspatch_recovery +LOCAL_CPP_EXTENSION := .cc +LOCAL_CLANG := true +LOCAL_CFLAGS := \ + $(ue_common_cflags) \ + -D_UE_SIDELOAD +LOCAL_CPPFLAGS := $(ue_common_cppflags) +LOCAL_LDFLAGS := $(ue_common_ldflags) +LOCAL_C_INCLUDES := \ + $(ue_common_c_includes) \ + bootable/recovery +#TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved +# out of the DBus interface. +LOCAL_C_INCLUDES += \ + external/cros/system_api/dbus +LOCAL_SRC_FILES := \ + boot_control_android.cc \ + hardware_android.cc \ + network_selector_stub.cc \ + proxy_resolver.cc \ + sideload_main.cc \ + update_attempter_android.cc \ + update_status_utils.cc \ + utils_android.cc +LOCAL_STATIC_LIBRARIES := \ + libfs_mgr \ + libpayload_consumer \ + update_metadata-protos \ + libmodpb64 \ + libgtest_prod \ + $(ue_common_static_libraries) \ + $(ue_libpayload_consumer_exported_static_libraries:-host=) \ + $(ue_update_metadata_protos_exported_static_libraries) +LOCAL_SHARED_LIBRARIES += \ + $(ue_common_shared_libraries) \ + libcutils \ + liblog \ + libevent \ + libprotobuf-cpp-lite \ + +LOCAL_SHARED_LIBRARIES += \ + $(ue_libupdate_engine_exported_shared_libraries:-host=) + +include $(BUILD_EXECUTABLE) + +# libupdate_engine_client (type: shared_library) +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libupdate_engine_client +LOCAL_CFLAGS := \ + -Wall \ + -Werror \ + -Wno-unused-parameter \ + -DUSE_DBUS=$(local_use_dbus) \ + -DUSE_BINDER=$(local_use_binder) \ + -DUSE_NESTLABS=$(local_use_nestlabs) +LOCAL_CLANG := true +LOCAL_CPP_EXTENSION := .cc +# TODO(deymo): Remove "external/cros/system_api/dbus" when dbus is not used. +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH)/client_library/include \ + external/cros/system_api/dbus \ + system +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/client_library/include +LOCAL_SHARED_LIBRARIES := \ + libchrome \ + libbrillo +LOCAL_SRC_FILES := \ + client_library/client.cc \ + update_status_utils.cc + +# We can only compile support for one IPC mechanism. If both "binder" and "dbus" +# are defined, we prefer binder. +ifeq ($(local_use_binder),1) +LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/binder_bindings +LOCAL_SHARED_LIBRARIES += \ + libbinder \ + libbrillo-binder \ + libutils +LOCAL_SRC_FILES += \ + binder_bindings/android/brillo/IUpdateEngine.aidl \ + binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl \ + client_library/client_binder.cc \ + parcelable_update_engine_status.cc +else # local_use_binder != 1 +LOCAL_STATIC_LIBRARIES := \ + update_engine_client-dbus-proxies +LOCAL_SHARED_LIBRARIES += \ + libchrome-dbus \ + libbrillo-dbus +ifeq ($(local_use_nestlabs),1) +LOCAL_SRC_FILES += \ + client_library/client_dbus_nestlabs.cc +else +LOCAL_SRC_FILES += \ + client_library/client_dbus.cc +endif +endif # local_use_binder == 1 + +include $(BUILD_SHARED_LIBRARY) + +# 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_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_SHARED_LIBRARIES := $(ue_common_shared_libraries) +LOCAL_STATIC_LIBRARIES := $(ue_common_static_libraries) +ifeq ($(local_use_omaha),1) +LOCAL_SHARED_LIBRARIES += \ + libupdate_engine_client +LOCAL_SRC_FILES := \ + update_engine_client.cc \ + common/error_code_utils.cc \ + omaha_utils.cc +else # local_use_omaha == 1 +#TODO(deymo): Remove external/cros/system_api/dbus once the strings are moved +# out of the DBus interface. +LOCAL_C_INCLUDES += \ + external/cros/system_api/dbus +LOCAL_SHARED_LIBRARIES += \ + libbinder \ + libbinderwrapper \ + libbrillo-binder \ + libutils +LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/binder_bindings +LOCAL_SRC_FILES := \ + binder_bindings/android/os/IUpdateEngine.aidl \ + binder_bindings/android/os/IUpdateEngineCallback.aidl \ + common/error_code_utils.cc \ + update_engine_client_android.cc \ + update_status_utils.cc +endif # local_use_omaha == 1 +include $(BUILD_EXECUTABLE) + +# 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 \ + liblzma \ + $(ue_libpayload_consumer_exported_static_libraries) \ + $(ue_update_metadata_protos_exported_static_libraries) +ue_libpayload_generator_exported_shared_libraries := \ + libext2fs-host \ + $(ue_libpayload_consumer_exported_shared_libraries) \ + $(ue_update_metadata_protos_exported_shared_libraries) + +ue_libpayload_generator_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 \ + payload_generator/xz_android.cc + +ifeq ($(HOST_OS),linux) +# Build for the host. +include $(CLEAR_VARS) +LOCAL_MODULE := libpayload_generator +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +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) +LOCAL_STATIC_LIBRARIES := \ + libpayload_consumer \ + update_metadata-protos \ + liblzma \ + $(ue_common_static_libraries) \ + $(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 := $(ue_libpayload_generator_src_files) +include $(BUILD_HOST_STATIC_LIBRARY) +endif # HOST_OS == linux + +# Build for the target. +include $(CLEAR_VARS) +LOCAL_MODULE := libpayload_generator +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +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) +LOCAL_STATIC_LIBRARIES := \ + libpayload_consumer \ + update_metadata-protos \ + liblzma \ + $(ue_common_static_libraries) \ + $(ue_libpayload_consumer_exported_static_libraries:-host=) \ + $(ue_update_metadata_protos_exported_static_libraries) +LOCAL_SHARED_LIBRARIES := \ + $(ue_common_shared_libraries) \ + $(ue_libpayload_generator_exported_shared_libraries:-host=) \ + $(ue_libpayload_consumer_exported_shared_libraries:-host=) \ + $(ue_update_metadata_protos_exported_shared_libraries) +LOCAL_SRC_FILES := $(ue_libpayload_generator_src_files) +include $(BUILD_STATIC_LIBRARY) + +# delta_generator (type: executable) +# ======================================================== +# server-side delta generator. +ue_delta_generator_src_files := \ + payload_generator/generate_delta_main.cc + +ifeq ($(HOST_OS),linux) +# Build for the host. +include $(CLEAR_VARS) +LOCAL_MODULE := delta_generator +LOCAL_REQUIRED_MODULES := \ + bsdiff \ + imgdiff +LOCAL_MODULE_CLASS := EXECUTABLES +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) +LOCAL_STATIC_LIBRARIES := \ + libpayload_consumer \ + libpayload_generator \ + $(ue_common_static_libraries) \ + $(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 := $(ue_delta_generator_src_files) +include $(BUILD_HOST_EXECUTABLE) +endif # HOST_OS == linux + +# Build for the target. +include $(CLEAR_VARS) +LOCAL_MODULE := ue_unittest_delta_generator +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests +LOCAL_MODULE_STEM := delta_generator +LOCAL_MODULE_CLASS := EXECUTABLES +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) +LOCAL_STATIC_LIBRARIES := \ + libpayload_consumer \ + libpayload_generator \ + $(ue_common_static_libraries) \ + $(ue_libpayload_consumer_exported_static_libraries:-host=) \ + $(ue_libpayload_generator_exported_static_libraries:-host=) +LOCAL_SHARED_LIBRARIES := \ + $(ue_common_shared_libraries) \ + $(ue_libpayload_consumer_exported_shared_libraries:-host=) \ + $(ue_libpayload_generator_exported_shared_libraries:-host=) +LOCAL_SRC_FILES := $(ue_delta_generator_src_files) +include $(BUILD_EXECUTABLE) + +# Private and public keys for unittests. +# ======================================================== +# Generate a module that installs a prebuilt private key and a module that +# installs a public key generated from the private key. +# +# $(1): The path to the private key in pem format. +define ue-unittest-keys + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_MODULE := ue_$(1).pem) \ + $(eval LOCAL_MODULE_CLASS := ETC) \ + $(eval $(ifeq $(BRILLO), 1, LOCAL_MODULE_TAGS := eng)) \ + $(eval LOCAL_SRC_FILES := $(1).pem) \ + $(eval LOCAL_MODULE_PATH := \ + $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests) \ + $(eval LOCAL_MODULE_STEM := $(1).pem) \ + $(eval include $(BUILD_PREBUILT)) \ + \ + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_MODULE := ue_$(1).pub.pem) \ + $(eval LOCAL_MODULE_CLASS := ETC) \ + $(eval $(ifeq $(BRILLO), 1, LOCAL_MODULE_TAGS := eng)) \ + $(eval LOCAL_MODULE_PATH := \ + $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests) \ + $(eval LOCAL_MODULE_STEM := $(1).pub.pem) \ + $(eval include $(BUILD_SYSTEM)/base_rules.mk) \ + $(eval $(LOCAL_BUILT_MODULE) : $(LOCAL_PATH)/$(1).pem ; \ + openssl rsa -in $$< -pubout -out $$@) +endef + +$(call ue-unittest-keys,unittest_key) +$(call ue-unittest-keys,unittest_key2) + +# Sample images for unittests. +# ======================================================== +# Generate a prebuilt module that installs a sample image from the compressed +# sample_images.tar.bz2 file used by the unittests. +# +# $(1): The filename in the sample_images.tar.bz2 +define ue-unittest-sample-image + $(eval include $(CLEAR_VARS)) \ + $(eval LOCAL_MODULE := ue_unittest_$(1)) \ + $(eval LOCAL_MODULE_CLASS := EXECUTABLES) \ + $(eval $(ifeq $(BRILLO), 1, LOCAL_MODULE_TAGS := eng)) \ + $(eval LOCAL_MODULE_PATH := \ + $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests/gen) \ + $(eval LOCAL_MODULE_STEM := $(1)) \ + $(eval include $(BUILD_SYSTEM)/base_rules.mk) \ + $(eval $(LOCAL_BUILT_MODULE) : \ + $(LOCAL_PATH)/sample_images/sample_images.tar.bz2 ; \ + tar -jxf $$< -C $$(dir $$@) $$(notdir $$@) && touch $$@) +endef + +$(call ue-unittest-sample-image,disk_ext2_1k.img) +$(call ue-unittest-sample-image,disk_ext2_4k.img) +$(call ue-unittest-sample-image,disk_ext2_4k_empty.img) +$(call ue-unittest-sample-image,disk_ext2_unittest.img) + +# Zlib Fingerprint +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := zlib_fingerprint +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests +LOCAL_PREBUILT_MODULE_FILE := $(TARGET_OUT_COMMON_GEN)/zlib_fingerprint +include $(BUILD_PREBUILT) + +# update_engine.conf +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := ue_unittest_update_engine.conf +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests +LOCAL_MODULE_STEM := update_engine.conf +LOCAL_SRC_FILES := update_engine.conf +include $(BUILD_PREBUILT) + +# test_http_server (type: executable) +# ======================================================== +# Test HTTP Server. +include $(CLEAR_VARS) +LOCAL_MODULE := test_http_server +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests +LOCAL_MODULE_CLASS := EXECUTABLES +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) +LOCAL_SHARED_LIBRARIES := $(ue_common_shared_libraries) +LOCAL_STATIC_LIBRARIES := $(ue_common_static_libraries) +LOCAL_SRC_FILES := \ + common/http_common.cc \ + test_http_server.cc +include $(BUILD_EXECUTABLE) + +# bsdiff (type: executable) +# ======================================================== +# We need bsdiff in the update_engine_unittests directory, so we build it here. +include $(CLEAR_VARS) +LOCAL_MODULE := ue_unittest_bsdiff +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests +LOCAL_MODULE_STEM := bsdiff +LOCAL_CPP_EXTENSION := .cc +LOCAL_SRC_FILES := ../../external/bsdiff/bsdiff_main.cc +LOCAL_CFLAGS := \ + -D_FILE_OFFSET_BITS=64 \ + -Wall \ + -Werror \ + -Wextra \ + -Wno-unused-parameter +LOCAL_STATIC_LIBRARIES := \ + libbsdiff \ + libbz \ + libdivsufsort64 \ + libdivsufsort +include $(BUILD_EXECUTABLE) + +# test_subprocess (type: executable) +# ======================================================== +# Test helper subprocess program. +include $(CLEAR_VARS) +LOCAL_MODULE := test_subprocess +LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_NATIVE_TESTS)/update_engine_unittests +LOCAL_MODULE_CLASS := EXECUTABLES +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) +LOCAL_SHARED_LIBRARIES := $(ue_common_shared_libraries) +LOCAL_STATIC_LIBRARIES := $(ue_common_static_libraries) +LOCAL_SRC_FILES := test_subprocess.cc +include $(BUILD_EXECUTABLE) + +# update_engine_unittests (type: executable) +# ======================================================== +# Main unittest file. +include $(CLEAR_VARS) +LOCAL_MODULE := update_engine_unittests +LOCAL_REQUIRED_MODULES := \ + test_http_server \ + test_subprocess \ + ue_unittest_bsdiff \ + ue_unittest_delta_generator \ + ue_unittest_disk_ext2_1k.img \ + ue_unittest_disk_ext2_4k.img \ + ue_unittest_disk_ext2_4k_empty.img \ + ue_unittest_disk_ext2_unittest.img \ + ue_unittest_key.pem \ + ue_unittest_key.pub.pem \ + ue_unittest_key2.pem \ + ue_unittest_key2.pub.pem \ + ue_unittest_update_engine.conf \ + zlib_fingerprint +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) \ + $(ue_libupdate_engine_exported_c_includes) +LOCAL_STATIC_LIBRARIES := \ + libpayload_generator \ + libbrillo-test-helpers \ + libgmock \ + libchrome_test_helpers \ + $(ue_common_static_libraries) \ + $(ue_libpayload_generator_exported_static_libraries:-host=) +LOCAL_SHARED_LIBRARIES := \ + $(ue_common_shared_libraries) \ + $(ue_libpayload_generator_exported_shared_libraries:-host=) +LOCAL_SRC_FILES := \ + certificate_checker_unittest.cc \ + common/action_pipe_unittest.cc \ + common/action_processor_unittest.cc \ + common/action_unittest.cc \ + common/cpu_limiter_unittest.cc \ + common/fake_prefs.cc \ + common/file_fetcher_unittest.cc \ + common/hash_calculator_unittest.cc \ + common/http_fetcher_unittest.cc \ + common/hwid_override_unittest.cc \ + common/mock_http_fetcher.cc \ + common/prefs_unittest.cc \ + common/subprocess_unittest.cc \ + common/terminator_unittest.cc \ + common/test_utils.cc \ + common/utils_unittest.cc \ + payload_consumer/bzip_extent_writer_unittest.cc \ + payload_consumer/delta_performer_integration_test.cc \ + payload_consumer/delta_performer_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 \ + testrunner.cc +ifeq ($(local_use_omaha),1) +LOCAL_C_INCLUDES += \ + $(ue_libupdate_engine_exported_c_includes) +LOCAL_STATIC_LIBRARIES += \ + libupdate_engine \ + $(ue_libupdate_engine_exported_static_libraries:-host=) +LOCAL_SHARED_LIBRARIES += \ + $(ue_libupdate_engine_exported_shared_libraries:-host=) +LOCAL_SRC_FILES += \ + common_service_unittest.cc \ + fake_system_state.cc \ + metrics_utils_unittest.cc +ifneq ($(local_use_nestlabs),1) +LOCAL_SRC_FILES += \ + omaha_request_action_unittest.cc \ + omaha_request_params_unittest.cc \ + omaha_response_handler_action_unittest.cc \ + omaha_utils_unittest.cc +endif # local_use_nestlabs != 1 +LOCAL_SRC_FILES += \ + p2p_manager_unittest.cc \ + payload_consumer/download_action_unittest.cc \ + payload_state_unittest.cc \ + update_manager/boxed_value_unittest.cc \ + update_manager/chromeos_policy_unittest.cc \ + update_manager/evaluation_context_unittest.cc \ + update_manager/generic_variables_unittest.cc \ + update_manager/prng_unittest.cc \ + update_manager/real_device_policy_provider_unittest.cc \ + update_manager/real_random_provider_unittest.cc \ + update_manager/real_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 +else # local_use_omaha == 1 +LOCAL_STATIC_LIBRARIES += \ + libupdate_engine_android \ + $(ue_libupdate_engine_android_exported_static_libraries:-host=) +LOCAL_SHARED_LIBRARIES += \ + $(ue_libupdate_engine_android_exported_shared_libraries:-host=) +endif # local_use_omaha == 1 +ifeq ($(local_use_shill),1) +LOCAL_SRC_FILES += \ + connection_manager_unittest.cc \ + fake_shill_proxy.cc \ + update_manager/real_shill_provider_unittest.cc +endif # local_use_shill == 1 +ifeq ($(local_use_libcros),1) +LOCAL_SRC_FILES += \ + chrome_browser_proxy_resolver_unittest.cc +endif # local_use_libcros == 1 +include $(BUILD_NATIVE_TEST) + +# Weave schema files +# ======================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := updater.json +LOCAL_MODULE_CLASS := ETC +LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/weaved/traits +LOCAL_SRC_FILES := weaved/traits/$(LOCAL_MODULE) +include $(BUILD_PREBUILT) + +# Update payload signing public key. +# ======================================================== +ifneq ($(local_use_nestlabs),1) +ifdef BRILLO +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) +endif # BRILLO +endif # local_use_nestlabs != 1 + +# Brillo update payload generation script +# ======================================================== +ifeq ($(HOST_OS),linux) +include $(CLEAR_VARS) +LOCAL_SRC_FILES := scripts/brillo_update_payload +LOCAL_MODULE := brillo_update_payload +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_IS_HOST_MODULE := true +LOCAL_MODULE_TAGS := optional +LOCAL_REQUIRED_MODULES := \ + delta_generator \ + shflags +include $(BUILD_PREBUILT) +endif # HOST_OS == linux + +include $(CLEAR_VARS) +LOCAL_SRC_FILES := scripts/payload_apply.sh +LOCAL_MODULE := payload_apply.sh +LOCAL_MODULE_CLASS := EXECUTABLES +LOCAL_MODULE_TAGS := optional +LOCAL_REQUIRED_MODULES := \ + update_engine_sideload +include $(BUILD_PREBUILT)
diff --git a/update_engine/MODULE_LICENSE_APACHE2 b/update_engine/MODULE_LICENSE_APACHE2 new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/update_engine/MODULE_LICENSE_APACHE2
diff --git a/update_engine/NOTICE b/update_engine/NOTICE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/update_engine/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/update_engine/OWNERS b/update_engine/OWNERS new file mode 100644 index 0000000..fafbecc --- /dev/null +++ b/update_engine/OWNERS
@@ -0,0 +1,4 @@ +set noparent +deymo@chromium.org +garnold@chromium.org +zeuthen@chromium.org
diff --git a/update_engine/PRESUBMIT.cfg b/update_engine/PRESUBMIT.cfg new file mode 100644 index 0000000..087dfa3 --- /dev/null +++ b/update_engine/PRESUBMIT.cfg
@@ -0,0 +1,3 @@ +[Hook Overrides] +cros_license_check: false +aosp_license_check: true
diff --git a/update_engine/UpdateEngine.conf b/update_engine/UpdateEngine.conf new file mode 100644 index 0000000..58cca09 --- /dev/null +++ b/update_engine/UpdateEngine.conf
@@ -0,0 +1,76 @@ +<!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="SetCohortHint"/> + <allow send_destination="org.chromium.UpdateEngine" + send_interface="org.chromium.UpdateEngineInterface" + send_member="GetCohortHint"/> + <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_destination="org.chromium.UpdateEngine" + send_interface="org.chromium.UpdateEngineInterface" + send_member="GetLastAttemptError"/> + <allow send_destination="org.chromium.UpdateEngine" + send_interface="org.chromium.UpdateEngineInterface" + send_member="GetEolStatus"/> + <allow send_interface="org.chromium.UpdateEngineLibcrosProxyResolvedInterface" /> + </policy> + <policy user="power"> + <allow send_destination="org.chromium.UpdateEngine" + send_interface="org.chromium.UpdateEngineInterface" + send_member="GetStatus"/> + </policy> +</busconfig>
diff --git a/update_engine/WATCHLISTS b/update_engine/WATCHLISTS new file mode 100644 index 0000000..bcce0de --- /dev/null +++ b/update_engine/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/update_engine/binder_bindings/android/brillo/IUpdateEngine.aidl b/update_engine/binder_bindings/android/brillo/IUpdateEngine.aidl new file mode 100644 index 0000000..b1a1b4f --- /dev/null +++ b/update_engine/binder_bindings/android/brillo/IUpdateEngine.aidl
@@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.brillo; + +import android.brillo.IUpdateEngineStatusCallback; +import android.brillo.ParcelableUpdateEngineStatus; + +interface IUpdateEngine { + void AttemptUpdate(in String app_version, in String omaha_url, in int flags); + void AttemptRollback(in boolean powerwash); + boolean CanRollback(); + void ResetStatus(); + ParcelableUpdateEngineStatus GetStatus(); + void RebootIfNeeded(); + void SetChannel(in String target_channel, in boolean powewash); + String GetChannel(in boolean get_current_channel); + void SetCohortHint(in String cohort_hint); + String GetCohortHint(); + void SetP2PUpdatePermission(in boolean enabled); + boolean GetP2PUpdatePermission(); + void SetUpdateOverCellularPermission(in boolean enabled); + boolean GetUpdateOverCellularPermission(); + long GetDurationSinceUpdate(); + String GetPrevVersion(); + String GetRollbackPartition(); + void RegisterStatusCallback(in IUpdateEngineStatusCallback callback); + int GetLastAttemptError(); + int GetEolStatus(); +}
diff --git a/update_engine/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl b/update_engine/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl new file mode 100644 index 0000000..42b6438 --- /dev/null +++ b/update_engine/binder_bindings/android/brillo/IUpdateEngineStatusCallback.aidl
@@ -0,0 +1,23 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.brillo; + +interface IUpdateEngineStatusCallback { + oneway + void HandleStatusUpdate(in long last_checked_time, in double progress, + in String current_operation, in String new_version, in long new_size); +}
diff --git a/update_engine/binder_bindings/android/brillo/ParcelableUpdateEngineStatus.aidl b/update_engine/binder_bindings/android/brillo/ParcelableUpdateEngineStatus.aidl new file mode 100644 index 0000000..fc10505 --- /dev/null +++ b/update_engine/binder_bindings/android/brillo/ParcelableUpdateEngineStatus.aidl
@@ -0,0 +1,20 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.brillo; + +parcelable ParcelableUpdateEngineStatus cpp_header + "update_engine/parcelable_update_engine_status.h";
diff --git a/update_engine/binder_bindings/android/os/IUpdateEngine.aidl b/update_engine/binder_bindings/android/os/IUpdateEngine.aidl new file mode 100644 index 0000000..67f828a --- /dev/null +++ b/update_engine/binder_bindings/android/os/IUpdateEngine.aidl
@@ -0,0 +1,38 @@ +/* + * 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.IUpdateEngineCallback; + +/** @hide */ +interface IUpdateEngine { + /** @hide */ + void applyPayload(String url, + in long payload_offset, + in long payload_size, + in String[] headerKeyValuePairs); + /** @hide */ + boolean bind(IUpdateEngineCallback callback); + /** @hide */ + void suspend(); + /** @hide */ + void resume(); + /** @hide */ + void cancel(); + /** @hide */ + void resetStatus(); +}
diff --git a/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl b/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl new file mode 100644 index 0000000..ee15c8b --- /dev/null +++ b/update_engine/binder_bindings/android/os/IUpdateEngineCallback.aidl
@@ -0,0 +1,25 @@ +/* + * 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; + +/** @hide */ +oneway interface IUpdateEngineCallback { + /** @hide */ + void onStatusUpdate(int status_code, float percentage); + /** @hide */ + void onPayloadApplicationComplete(int error_code); +}
diff --git a/update_engine/binder_service_android.cc b/update_engine/binder_service_android.cc new file mode 100644 index 0000000..872f64c --- /dev/null +++ b/update_engine/binder_service_android.cc
@@ -0,0 +1,146 @@ +// +// 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_android.h" + +#include <base/bind.h> +#include <base/logging.h> +#include <binderwrapper/binder_wrapper.h> +#include <brillo/errors/error.h> +#include <utils/String8.h> + +using android::binder::Status; +using android::os::IUpdateEngineCallback; + +namespace { +Status ErrorPtrToStatus(const brillo::ErrorPtr& error) { + return Status::fromServiceSpecificError( + 1, android::String8{error->GetMessage().c_str()}); +} +} // namespace + +namespace chromeos_update_engine { + +BinderUpdateEngineAndroidService::BinderUpdateEngineAndroidService( + ServiceDelegateAndroidInterface* service_delegate) + : service_delegate_(service_delegate) { +} + +void BinderUpdateEngineAndroidService::SendStatusUpdate( + int64_t /* last_checked_time */, + double progress, + update_engine::UpdateStatus status, + const std::string& /* new_version */, + int64_t /* new_size */) { + last_status_ = static_cast<int>(status); + last_progress_ = progress; + for (auto& callback : callbacks_) { + callback->onStatusUpdate(last_status_, last_progress_); + } +} + +void BinderUpdateEngineAndroidService::SendPayloadApplicationComplete( + ErrorCode error_code) { + for (auto& callback : callbacks_) { + callback->onPayloadApplicationComplete(static_cast<int>(error_code)); + } +} + +Status BinderUpdateEngineAndroidService::bind( + const android::sp<IUpdateEngineCallback>& callback, bool* return_value) { + callbacks_.emplace_back(callback); + + auto binder_wrapper = android::BinderWrapper::Get(); + binder_wrapper->RegisterForDeathNotifications( + IUpdateEngineCallback::asBinder(callback), + base::Bind(&BinderUpdateEngineAndroidService::UnbindCallback, + base::Unretained(this), + base::Unretained(callback.get()))); + + // Send an status update on connection (except when no update sent so far), + // since the status update is oneway and we don't need to wait for the + // response. + if (last_status_ != -1) + callback->onStatusUpdate(last_status_, last_progress_); + + *return_value = true; + return Status::ok(); +} + +Status BinderUpdateEngineAndroidService::applyPayload( + const android::String16& url, + int64_t payload_offset, + int64_t payload_size, + const std::vector<android::String16>& header_kv_pairs) { + const std::string payload_url{android::String8{url}.string()}; + std::vector<std::string> str_headers; + str_headers.reserve(header_kv_pairs.size()); + for (const auto& header : header_kv_pairs) { + str_headers.emplace_back(android::String8{header}.string()); + } + + brillo::ErrorPtr error; + if (!service_delegate_->ApplyPayload( + payload_url, payload_offset, payload_size, str_headers, &error)) { + return ErrorPtrToStatus(error); + } + return Status::ok(); +} + +Status BinderUpdateEngineAndroidService::suspend() { + brillo::ErrorPtr error; + if (!service_delegate_->SuspendUpdate(&error)) + return ErrorPtrToStatus(error); + return Status::ok(); +} + +Status BinderUpdateEngineAndroidService::resume() { + brillo::ErrorPtr error; + if (!service_delegate_->ResumeUpdate(&error)) + return ErrorPtrToStatus(error); + return Status::ok(); +} + +Status BinderUpdateEngineAndroidService::cancel() { + brillo::ErrorPtr error; + if (!service_delegate_->CancelUpdate(&error)) + return ErrorPtrToStatus(error); + return Status::ok(); +} + +Status BinderUpdateEngineAndroidService::resetStatus() { + brillo::ErrorPtr error; + if (!service_delegate_->ResetStatus(&error)) + return ErrorPtrToStatus(error); + return Status::ok(); +} + +void BinderUpdateEngineAndroidService::UnbindCallback( + IUpdateEngineCallback* callback) { + auto it = + std::find_if(callbacks_.begin(), + callbacks_.end(), + [&callback](const android::sp<IUpdateEngineCallback>& elem) { + return elem.get() == callback; + }); + if (it == callbacks_.end()) { + LOG(ERROR) << "Got death notification for unknown callback."; + return; + } + callbacks_.erase(it); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/binder_service_android.h b/update_engine/binder_service_android.h new file mode 100644 index 0000000..3fb38bc --- /dev/null +++ b/update_engine/binder_service_android.h
@@ -0,0 +1,90 @@ +// +// 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_ANDROID_H_ +#define UPDATE_ENGINE_BINDER_SERVICE_ANDROID_H_ + +#include <stdint.h> + +#include <string> +#include <vector> + +#include <utils/Errors.h> +#include <utils/String16.h> +#include <utils/StrongPointer.h> + +#include "android/os/BnUpdateEngine.h" +#include "android/os/IUpdateEngineCallback.h" +#include "update_engine/service_delegate_android_interface.h" +#include "update_engine/service_observer_interface.h" + +namespace chromeos_update_engine { + +class BinderUpdateEngineAndroidService : public android::os::BnUpdateEngine, + public ServiceObserverInterface { + public: + explicit BinderUpdateEngineAndroidService( + ServiceDelegateAndroidInterface* service_delegate); + ~BinderUpdateEngineAndroidService() override = default; + + const char* ServiceName() const { + return "android.os.UpdateEngineService"; + } + + // ServiceObserverInterface overrides. + void SendStatusUpdate(int64_t last_checked_time, + double progress, + update_engine::UpdateStatus status, + const std::string& new_version, + int64_t new_size) override; + void SendPayloadApplicationComplete(ErrorCode error_code) override; + + // Channel tracking changes are ignored. + void SendChannelChangeUpdate(const std::string& tracking_channel) override {} + + // android::os::BnUpdateEngine overrides. + android::binder::Status applyPayload( + const android::String16& url, + int64_t payload_offset, + int64_t payload_size, + const std::vector<android::String16>& header_kv_pairs) override; + android::binder::Status bind( + const android::sp<android::os::IUpdateEngineCallback>& callback, + bool* return_value) override; + android::binder::Status suspend() override; + android::binder::Status resume() override; + android::binder::Status cancel() override; + android::binder::Status resetStatus() override; + + private: + // Remove the passed |callback| from the list of registered callbacks. Called + // whenever the callback object is destroyed. + void UnbindCallback(android::os::IUpdateEngineCallback* callback); + + // List of currently bound callbacks. + std::vector<android::sp<android::os::IUpdateEngineCallback>> callbacks_; + + // Cached copy of the last status update sent. Used to send an initial + // notification when bind() is called from the client. + int last_status_{-1}; + double last_progress_{0.0}; + + ServiceDelegateAndroidInterface* service_delegate_; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_BINDER_SERVICE_ANDROID_H_
diff --git a/update_engine/binder_service_brillo.cc b/update_engine/binder_service_brillo.cc new file mode 100644 index 0000000..5e74159 --- /dev/null +++ b/update_engine/binder_service_brillo.cc
@@ -0,0 +1,246 @@ +// +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/binder_service_brillo.h" + +#include <base/bind.h> + +#include <binderwrapper/binder_wrapper.h> + +#include <utils/String16.h> +#include <utils/StrongPointer.h> + +#include "update_engine/update_status_utils.h" + +using android::String16; +using android::String8; +using android::binder::Status; +using android::brillo::IUpdateEngineStatusCallback; +using android::brillo::ParcelableUpdateEngineStatus; +using android::sp; +using brillo::ErrorPtr; +using std::string; + +namespace chromeos_update_engine { + +namespace { +string NormalString(const String16& in) { + return string{String8{in}.string()}; +} + +Status ToStatus(ErrorPtr* error) { + return Status::fromServiceSpecificError( + 1, String8{error->get()->GetMessage().c_str()}); +} +} // namespace + +template <typename... Parameters, typename... Arguments> +Status BinderUpdateEngineBrilloService::CallCommonHandler( + bool (UpdateEngineService::*Handler)(ErrorPtr*, Parameters...), + Arguments... arguments) { + ErrorPtr error; + if (((common_.get())->*Handler)(&error, arguments...)) + return Status::ok(); + return ToStatus(&error); +} + +Status BinderUpdateEngineBrilloService::AttemptUpdate( + const String16& app_version, const String16& omaha_url, int flags) { + return CallCommonHandler(&UpdateEngineService::AttemptUpdate, + NormalString(app_version), + NormalString(omaha_url), + flags); +} + +Status BinderUpdateEngineBrilloService::AttemptRollback(bool powerwash) { + return CallCommonHandler(&UpdateEngineService::AttemptRollback, powerwash); +} + +Status BinderUpdateEngineBrilloService::CanRollback(bool* out_can_rollback) { + return CallCommonHandler(&UpdateEngineService::CanRollback, out_can_rollback); +} + +Status BinderUpdateEngineBrilloService::ResetStatus() { + return CallCommonHandler(&UpdateEngineService::ResetStatus); +} + +Status BinderUpdateEngineBrilloService::GetStatus( + ParcelableUpdateEngineStatus* status) { + string current_op; + string new_version; + + auto ret = CallCommonHandler(&UpdateEngineService::GetStatus, + &status->last_checked_time_, + &status->progress_, + ¤t_op, + &new_version, + &status->new_size_); + + if (ret.isOk()) { + status->current_operation_ = String16{current_op.c_str()}; + status->new_version_ = String16{new_version.c_str()}; + } + + return ret; +} + +Status BinderUpdateEngineBrilloService::RebootIfNeeded() { + return CallCommonHandler(&UpdateEngineService::RebootIfNeeded); +} + +Status BinderUpdateEngineBrilloService::SetChannel( + const String16& target_channel, bool powerwash) { + return CallCommonHandler(&UpdateEngineService::SetChannel, + NormalString(target_channel), + powerwash); +} + +Status BinderUpdateEngineBrilloService::GetChannel(bool get_current_channel, + String16* out_channel) { + string channel_string; + auto ret = CallCommonHandler( + &UpdateEngineService::GetChannel, get_current_channel, &channel_string); + + *out_channel = String16(channel_string.c_str()); + return ret; +} + +Status BinderUpdateEngineBrilloService::SetCohortHint( + const String16& in_cohort_hint) { + return CallCommonHandler(&UpdateEngineService::SetCohortHint, + NormalString(in_cohort_hint)); +} + +Status BinderUpdateEngineBrilloService::GetCohortHint( + String16* out_cohort_hint) { + string cohort_hint; + auto ret = + CallCommonHandler(&UpdateEngineService::GetCohortHint, &cohort_hint); + + *out_cohort_hint = String16(cohort_hint.c_str()); + return ret; +} + +Status BinderUpdateEngineBrilloService::SetP2PUpdatePermission(bool enabled) { + return CallCommonHandler(&UpdateEngineService::SetP2PUpdatePermission, + enabled); +} + +Status BinderUpdateEngineBrilloService::GetP2PUpdatePermission( + bool* out_p2p_permission) { + return CallCommonHandler(&UpdateEngineService::GetP2PUpdatePermission, + out_p2p_permission); +} + +Status BinderUpdateEngineBrilloService::SetUpdateOverCellularPermission( + bool enabled) { + return CallCommonHandler( + &UpdateEngineService::SetUpdateOverCellularPermission, enabled); +} + +Status BinderUpdateEngineBrilloService::GetUpdateOverCellularPermission( + bool* out_cellular_permission) { + return CallCommonHandler( + &UpdateEngineService::GetUpdateOverCellularPermission, + out_cellular_permission); +} + +Status BinderUpdateEngineBrilloService::GetDurationSinceUpdate( + int64_t* out_duration) { + return CallCommonHandler(&UpdateEngineService::GetDurationSinceUpdate, + out_duration); +} + +Status BinderUpdateEngineBrilloService::GetPrevVersion( + String16* out_prev_version) { + string version_string; + auto ret = + CallCommonHandler(&UpdateEngineService::GetPrevVersion, &version_string); + + *out_prev_version = String16(version_string.c_str()); + return ret; +} + +Status BinderUpdateEngineBrilloService::GetRollbackPartition( + String16* out_rollback_partition) { + string partition_string; + auto ret = CallCommonHandler(&UpdateEngineService::GetRollbackPartition, + &partition_string); + + if (ret.isOk()) { + *out_rollback_partition = String16(partition_string.c_str()); + } + + return ret; +} + +Status BinderUpdateEngineBrilloService::RegisterStatusCallback( + const sp<IUpdateEngineStatusCallback>& callback) { + callbacks_.emplace_back(callback); + + auto binder_wrapper = android::BinderWrapper::Get(); + + binder_wrapper->RegisterForDeathNotifications( + IUpdateEngineStatusCallback::asBinder(callback), + base::Bind(&BinderUpdateEngineBrilloService::UnregisterStatusCallback, + base::Unretained(this), + base::Unretained(callback.get()))); + + return Status::ok(); +} + +Status BinderUpdateEngineBrilloService::GetLastAttemptError( + int* out_last_attempt_error) { + return CallCommonHandler(&UpdateEngineService::GetLastAttemptError, + out_last_attempt_error); +} + +Status BinderUpdateEngineBrilloService::GetEolStatus(int* out_eol_status) { + return CallCommonHandler(&UpdateEngineService::GetEolStatus, out_eol_status); +} + +void BinderUpdateEngineBrilloService::UnregisterStatusCallback( + IUpdateEngineStatusCallback* callback) { + auto it = callbacks_.begin(); + while (it != callbacks_.end() && it->get() != callback) + it++; + + if (it == callbacks_.end()) { + LOG(ERROR) << "Got death notification for unknown callback."; + return; + } + + LOG(INFO) << "Erasing orphan callback"; + callbacks_.erase(it); +} + +void BinderUpdateEngineBrilloService::SendStatusUpdate( + int64_t last_checked_time, + double progress, + update_engine::UpdateStatus status, + const string& new_version, + int64_t new_size) { + const string str_status = UpdateStatusToString(status); + for (auto& callback : callbacks_) { + callback->HandleStatusUpdate(last_checked_time, + progress, + String16{str_status.c_str()}, + String16{new_version.c_str()}, + new_size); + } +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/binder_service_brillo.h b/update_engine/binder_service_brillo.h new file mode 100644 index 0000000..fad0f8c --- /dev/null +++ b/update_engine/binder_service_brillo.h
@@ -0,0 +1,115 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_BINDER_SERVICE_BRILLO_H_ +#define UPDATE_ENGINE_BINDER_SERVICE_BRILLO_H_ + +#include <utils/Errors.h> + +#include <memory> +#include <string> +#include <vector> + +#include <utils/RefBase.h> + +#include "update_engine/common_service.h" +#include "update_engine/parcelable_update_engine_status.h" +#include "update_engine/service_observer_interface.h" + +#include "android/brillo/BnUpdateEngine.h" +#include "android/brillo/IUpdateEngineStatusCallback.h" + +namespace chromeos_update_engine { + +class BinderUpdateEngineBrilloService : public android::brillo::BnUpdateEngine, + public ServiceObserverInterface { + public: + explicit BinderUpdateEngineBrilloService(SystemState* system_state) + : common_(new UpdateEngineService(system_state)) {} + virtual ~BinderUpdateEngineBrilloService() = default; + + const char* ServiceName() const { + return "android.brillo.UpdateEngineService"; + } + + // ServiceObserverInterface overrides. + void SendStatusUpdate(int64_t last_checked_time, + double progress, + update_engine::UpdateStatus status, + const std::string& new_version, + int64_t new_size) override; + void SendPayloadApplicationComplete(ErrorCode error_code) override {} + // Channel tracking changes are ignored. + void SendChannelChangeUpdate(const std::string& tracking_channel) override {} + + // android::brillo::BnUpdateEngine overrides. + android::binder::Status AttemptUpdate(const android::String16& app_version, + const android::String16& omaha_url, + int flags) override; + android::binder::Status AttemptRollback(bool powerwash) override; + android::binder::Status CanRollback(bool* out_can_rollback) override; + android::binder::Status ResetStatus() override; + android::binder::Status GetStatus( + android::brillo::ParcelableUpdateEngineStatus* status); + android::binder::Status RebootIfNeeded() override; + android::binder::Status SetChannel(const android::String16& target_channel, + bool powerwash) override; + android::binder::Status GetChannel(bool get_current_channel, + android::String16* out_channel) override; + android::binder::Status SetCohortHint( + const android::String16& cohort_hint) override; + android::binder::Status GetCohortHint( + android::String16* out_cohort_hint) override; + android::binder::Status SetP2PUpdatePermission(bool enabled) override; + android::binder::Status GetP2PUpdatePermission( + bool* out_p2p_permission) override; + android::binder::Status SetUpdateOverCellularPermission( + bool enabled) override; + android::binder::Status GetUpdateOverCellularPermission( + bool* out_cellular_permission) override; + android::binder::Status GetDurationSinceUpdate( + int64_t* out_duration) override; + android::binder::Status GetPrevVersion( + android::String16* out_prev_version) override; + android::binder::Status GetRollbackPartition( + android::String16* out_rollback_partition) override; + android::binder::Status RegisterStatusCallback( + const android::sp<android::brillo::IUpdateEngineStatusCallback>& callback) + override; + android::binder::Status GetLastAttemptError( + int* out_last_attempt_error) override; + android::binder::Status GetEolStatus(int* out_eol_status) override; + + private: + // Generic function for dispatching to the common service. + template <typename... Parameters, typename... Arguments> + android::binder::Status CallCommonHandler( + bool (UpdateEngineService::*Handler)(brillo::ErrorPtr*, Parameters...), + Arguments... arguments); + + // To be used as a death notification handler only. + void UnregisterStatusCallback( + android::brillo::IUpdateEngineStatusCallback* callback); + + std::unique_ptr<UpdateEngineService> common_; + + std::vector<android::sp<android::brillo::IUpdateEngineStatusCallback>> + callbacks_; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_BINDER_SERVICE_BRILLO_H_
diff --git a/update_engine/boot_control_android.cc b/update_engine/boot_control_android.cc new file mode 100644 index 0000000..b3b7630 --- /dev/null +++ b/update_engine/boot_control_android.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/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 "update_engine/common/utils.h" +#include "update_engine/utils_android.h" + +using std::string; + +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 std::move(boot_control); +} + +} // 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 { + // 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. + + base::FilePath misc_device; + if (!utils::DeviceForMountPoint("/misc", &misc_device)) + return false; + + if (!utils::IsSymlink(misc_device.value().c_str())) { + LOG(ERROR) << "Device file " << misc_device.value() << " for /misc " + << "is not a symlink."; + 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/update_engine/boot_control_android.h b/update_engine/boot_control_android.h new file mode 100644 index 0000000..a5a6255 --- /dev/null +++ b/update_engine/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/update_engine/boot_control_chromeos.cc b/update_engine/boot_control_chromeos.cc new file mode 100644 index 0000000..e9ad698 --- /dev/null +++ b/update_engine/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 std::move(boot_control_chromeos); +} + +} // 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::ToLowerASCII(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/update_engine/boot_control_chromeos.h b/update_engine/boot_control_chromeos.h new file mode 100644 index 0000000..a1d57fe --- /dev/null +++ b/update_engine/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/update_engine/boot_control_chromeos_unittest.cc b/update_engine/boot_control_chromeos_unittest.cc new file mode 100644 index 0000000..6a60009 --- /dev/null +++ b/update_engine/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/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/update_engine/boot_control_recovery_stub.cc b/update_engine/boot_control_recovery_stub.cc new file mode 100644 index 0000000..129c5d0 --- /dev/null +++ b/update_engine/boot_control_recovery_stub.cc
@@ -0,0 +1,21 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <hardware/hardware.h> + +hw_module_t HAL_MODULE_INFO_SYM = { + .id = "stub", +};
diff --git a/update_engine/certificate_checker.cc b/update_engine/certificate_checker.cc new file mode 100644 index 0000000..6e886e7 --- /dev/null +++ b/update_engine/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/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/update_engine/certificate_checker.h b/update_engine/certificate_checker.h new file mode 100644 index 0000000..5d0b5ba --- /dev/null +++ b/update_engine/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_CERTIFICATE_CHECKER_H_ +#define UPDATE_ENGINE_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_CERTIFICATE_CHECKER_H_
diff --git a/update_engine/certificate_checker_unittest.cc b/update_engine/certificate_checker_unittest.cc new file mode 100644 index 0000000..20efce9 --- /dev/null +++ b/update_engine/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/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_prefs.h" +#include "update_engine/mock_certificate_checker.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/update_engine/chrome_browser_proxy_resolver.cc b/update_engine/chrome_browser_proxy_resolver.cc new file mode 100644 index 0000000..da57e1d --- /dev/null +++ b/update_engine/chrome_browser_proxy_resolver.cc
@@ -0,0 +1,193 @@ +// +// 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 (base::IsAsciiWhitespace(*space)) { + break; + } + } + + string scheme = base::ToLowerASCII(string(token.begin(), space)); + // 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/update_engine/chrome_browser_proxy_resolver.h b/update_engine/chrome_browser_proxy_resolver.h new file mode 100644 index 0000000..84b0c28 --- /dev/null +++ b/update_engine/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/update_engine/chrome_browser_proxy_resolver_unittest.cc b/update_engine/chrome_browser_proxy_resolver_unittest.cc new file mode 100644 index 0000000..bb5193e --- /dev/null +++ b/update_engine/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(2U, 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(1U, 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/update_engine/client_library/client.cc b/update_engine/client_library/client.cc new file mode 100644 index 0000000..348219f --- /dev/null +++ b/update_engine/client_library/client.cc
@@ -0,0 +1,54 @@ +// +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/client_library/include/update_engine/client.h" + +#include <memory> + +#if USE_BINDER +#include "update_engine/client_library/client_binder.h" +#else // !USE_BINDER +#ifdef USE_NESTLABS +#include "update_engine/client_library/client_dbus_nestlabs.h" +#else +#include "update_engine/client_library/client_dbus.h" +#endif +#endif // USE_BINDER + +using std::unique_ptr; + +namespace update_engine { + +unique_ptr<UpdateEngineClient> UpdateEngineClient::CreateInstance() { +#if USE_BINDER + auto update_engine_client_impl = new internal::BinderUpdateEngineClient{}; +#else // !USE_BINDER +#if USE_NESTLABS + auto update_engine_client_impl = new internal::DBusUpdateEngineNestlabsClient{}; +#else + auto update_engine_client_impl = new internal::DBusUpdateEngineClient{}; +#endif +#endif // USE_BINDER + auto ret = unique_ptr<UpdateEngineClient>{update_engine_client_impl}; + + if (!update_engine_client_impl->Init()) { + ret.reset(); + } + + return ret; +} + +} // namespace update_engine
diff --git a/update_engine/client_library/client_binder.cc b/update_engine/client_library/client_binder.cc new file mode 100644 index 0000000..e98c225 --- /dev/null +++ b/update_engine/client_library/client_binder.cc
@@ -0,0 +1,251 @@ +// +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/client_library/client_binder.h" + +#include <binder/IServiceManager.h> + +#include <base/message_loop/message_loop.h> +#include <utils/String8.h> + +#include "update_engine/common_service.h" +#include "update_engine/parcelable_update_engine_status.h" +#include "update_engine/update_status_utils.h" + +using android::OK; +using android::String16; +using android::String8; +using android::binder::Status; +using android::brillo::ParcelableUpdateEngineStatus; +using android::getService; +using chromeos_update_engine::StringToUpdateStatus; +using chromeos_update_engine::UpdateEngineService; +using std::string; + +namespace update_engine { +namespace internal { + +bool BinderUpdateEngineClient::Init() { + if (!binder_watcher_.Init()) return false; + + return getService(String16{"android.brillo.UpdateEngineService"}, + &service_) == OK; +} + +bool BinderUpdateEngineClient::AttemptUpdate(const string& in_app_version, + const string& in_omaha_url, + bool at_user_request) { + return service_->AttemptUpdate(String16{in_app_version.c_str()}, + String16{in_omaha_url.c_str()}, + at_user_request ? 0 : + UpdateEngineService::kAttemptUpdateFlagNonInteractive).isOk(); +} + +bool BinderUpdateEngineClient::GetStatus(int64_t* out_last_checked_time, + double* out_progress, + UpdateStatus* out_update_status, + string* out_new_version, + int64_t* out_new_size) const { + ParcelableUpdateEngineStatus status; + + if (!service_->GetStatus(&status).isOk()) + return false; + + *out_last_checked_time = status.last_checked_time_; + *out_progress = status.progress_; + StringToUpdateStatus(String8{status.current_operation_}.string(), + out_update_status); + *out_new_version = String8{status.new_version_}.string(); + *out_new_size = status.new_size_; + return true; +} + +bool BinderUpdateEngineClient::SetCohortHint(const string& in_cohort_hint) { + return service_->SetCohortHint(String16{in_cohort_hint.c_str()}).isOk(); +} + +bool BinderUpdateEngineClient::GetCohortHint(string* out_cohort_hint) const { + String16 out_as_string16; + + if (!service_->GetCohortHint(&out_as_string16).isOk()) + return false; + + *out_cohort_hint = String8{out_as_string16}.string(); + return true; +} + +bool BinderUpdateEngineClient::SetUpdateOverCellularPermission(bool allowed) { + return service_->SetUpdateOverCellularPermission(allowed).isOk(); +} + +bool BinderUpdateEngineClient::GetUpdateOverCellularPermission( + bool* allowed) const { + return service_->GetUpdateOverCellularPermission(allowed).isOk(); +} + +bool BinderUpdateEngineClient::SetP2PUpdatePermission(bool enabled) { + return service_->SetP2PUpdatePermission(enabled).isOk(); +} + +bool BinderUpdateEngineClient::GetP2PUpdatePermission(bool* enabled) const { + return service_->GetP2PUpdatePermission(enabled).isOk(); +} + +bool BinderUpdateEngineClient::Rollback(bool powerwash) { + return service_->AttemptRollback(powerwash).isOk(); +} + +bool BinderUpdateEngineClient::GetRollbackPartition( + string* rollback_partition) const { + String16 out_as_string16; + + if (!service_->GetRollbackPartition(&out_as_string16).isOk()) + return false; + + *rollback_partition = String8{out_as_string16}.string(); + return true; +} + +bool BinderUpdateEngineClient::GetPrevVersion(string* prev_version) const { + String16 out_as_string16; + + if (!service_->GetPrevVersion(&out_as_string16).isOk()) + return false; + + *prev_version = String8{out_as_string16}.string(); + return true; +} + +void BinderUpdateEngineClient::RebootIfNeeded() { + if (!service_->RebootIfNeeded().isOk()) { + // Reboot error code doesn't necessarily mean that a reboot + // failed. For example, D-Bus may be shutdown before we receive the + // result. + LOG(INFO) << "RebootIfNeeded() failure ignored."; + } +} + +bool BinderUpdateEngineClient::ResetStatus() { + return service_->ResetStatus().isOk(); +} + +Status BinderUpdateEngineClient::StatusUpdateCallback::HandleStatusUpdate( + int64_t last_checked_time, + double progress, + const String16& current_operation, + const String16& new_version, + int64_t new_size) { + UpdateStatus update_status; + + StringToUpdateStatus(String8{current_operation}.string(), &update_status); + + for (auto& handler : client_->handlers_) { + handler->HandleStatusUpdate(last_checked_time, progress, update_status, + String8{new_version}.string(), new_size); + } + + return Status::ok(); +} + +bool BinderUpdateEngineClient::RegisterStatusUpdateHandler( + StatusUpdateHandler* handler) { + if (!status_callback_.get()) { + status_callback_ = + new BinderUpdateEngineClient::StatusUpdateCallback(this); + if (!service_->RegisterStatusCallback(status_callback_).isOk()) { + return false; + } + } + + handlers_.push_back(handler); + + int64_t last_checked_time; + double progress; + UpdateStatus update_status; + string new_version; + int64_t new_size; + + if (!GetStatus(&last_checked_time, &progress, &update_status, + &new_version, &new_size)) { + handler->IPCError("Could not get status from binder service"); + } + + handler->HandleStatusUpdate(last_checked_time, progress, update_status, + new_version, new_size); + + return true; +} + +bool BinderUpdateEngineClient::UnregisterStatusUpdateHandler( + StatusUpdateHandler* handler) { + auto it = std::find(handlers_.begin(), handlers_.end(), handler); + if (it != handlers_.end()) { + handlers_.erase(it); + return true; + } + + return false; +} + +bool BinderUpdateEngineClient::SetTargetChannel(const string& in_target_channel, + bool allow_powerwash) { + return service_->SetChannel(String16{in_target_channel.c_str()}, + allow_powerwash).isOk(); +} + +bool BinderUpdateEngineClient::GetTargetChannel(string* out_channel) const { + String16 out_as_string16; + + if (!service_->GetChannel(false, &out_as_string16).isOk()) + return false; + + *out_channel = String8{out_as_string16}.string(); + return true; +} + +bool BinderUpdateEngineClient::GetChannel(string* out_channel) const { + String16 out_as_string16; + + if (!service_->GetChannel(true, &out_as_string16).isOk()) + return false; + + *out_channel = String8{out_as_string16}.string(); + return true; +} + +bool BinderUpdateEngineClient::GetLastAttemptError( + int32_t* last_attempt_error) const { + int out_as_int; + + if (!service_->GetLastAttemptError(&out_as_int).isOk()) + return false; + + *last_attempt_error = out_as_int; + return true; +} + +bool BinderUpdateEngineClient::GetEolStatus(int32_t* eol_status) const { + int out_as_int; + + if (!service_->GetEolStatus(&out_as_int).isOk()) + return false; + + *eol_status = out_as_int; + return true; +} + +} // namespace internal +} // namespace update_engine
diff --git a/update_engine/client_library/client_binder.h b/update_engine/client_library/client_binder.h new file mode 100644 index 0000000..b1b34da --- /dev/null +++ b/update_engine/client_library/client_binder.h
@@ -0,0 +1,118 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_BINDER_H_ +#define UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_BINDER_H_ + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +#include <base/macros.h> +#include <utils/String16.h> +#include <utils/StrongPointer.h> + +#include <brillo/binder_watcher.h> + +#include "android/brillo/BnUpdateEngineStatusCallback.h" +#include "android/brillo/IUpdateEngine.h" + +#include "update_engine/client_library/include/update_engine/client.h" + +namespace update_engine { +namespace internal { + +class BinderUpdateEngineClient : public UpdateEngineClient { + public: + BinderUpdateEngineClient() = default; + bool Init(); + + virtual ~BinderUpdateEngineClient() = default; + + bool AttemptUpdate(const std::string& app_version, + const std::string& omaha_url, + bool at_user_request) override; + + bool GetStatus(int64_t* out_last_checked_time, + double* out_progress, + UpdateStatus* out_update_status, + std::string* out_new_version, + int64_t* out_new_size) const override; + + bool SetCohortHint(const std::string& in_cohort_hint) override; + bool GetCohortHint(std::string* out_cohort_hint) const override; + + bool SetUpdateOverCellularPermission(bool allowed) override; + bool GetUpdateOverCellularPermission(bool* allowed) const override; + + bool SetP2PUpdatePermission(bool enabled) override; + bool GetP2PUpdatePermission(bool* enabled) const override; + + bool Rollback(bool powerwash) override; + + bool GetRollbackPartition(std::string* rollback_partition) const override; + + void RebootIfNeeded() override; + + bool GetPrevVersion(std::string* prev_version) const override; + + bool ResetStatus() override; + + bool SetTargetChannel(const std::string& target_channel, + bool allow_powerwash) override; + + bool GetTargetChannel(std::string* out_channel) const override; + + bool GetChannel(std::string* out_channel) const override; + + bool RegisterStatusUpdateHandler(StatusUpdateHandler* handler) override; + bool UnregisterStatusUpdateHandler(StatusUpdateHandler* handler) override; + + bool GetLastAttemptError(int32_t* last_attempt_error) const override; + + bool GetEolStatus(int32_t* eol_status) const override; + + private: + class StatusUpdateCallback : + public android::brillo::BnUpdateEngineStatusCallback { + public: + explicit StatusUpdateCallback(BinderUpdateEngineClient* client) + : client_(client) {} + + android::binder::Status HandleStatusUpdate( + int64_t last_checked_time, + double progress, + const android::String16& current_operation, + const android::String16& new_version, + int64_t new_size) override; + + private: + BinderUpdateEngineClient* client_; + }; + + android::sp<android::brillo::IUpdateEngine> service_; + android::sp<android::brillo::IUpdateEngineStatusCallback> status_callback_; + std::vector<update_engine::StatusUpdateHandler*> handlers_; + brillo::BinderWatcher binder_watcher_; + + DISALLOW_COPY_AND_ASSIGN(BinderUpdateEngineClient); +}; // class BinderUpdateEngineClient + +} // namespace internal +} // namespace update_engine + +#endif // UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_BINDER_H_
diff --git a/update_engine/client_library/client_dbus.cc b/update_engine/client_library/client_dbus.cc new file mode 100644 index 0000000..bebea6a --- /dev/null +++ b/update_engine/client_library/client_dbus.cc
@@ -0,0 +1,249 @@ +// +// 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_dbus.h" + +#include <base/message_loop/message_loop.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 { + +bool DBusUpdateEngineClient::Init() { + Bus::Options options; + options.bus_type = Bus::SYSTEM; + scoped_refptr<Bus> bus{new Bus{options}}; + + if (!bus->Connect()) + return false; + + proxy_.reset(new UpdateEngineInterfaceProxy{bus}); + return true; +} + +bool DBusUpdateEngineClient::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 DBusUpdateEngineClient::GetStatus(int64_t* out_last_checked_time, + double* out_progress, + UpdateStatus* out_update_status, + string* out_new_version, + int64_t* out_new_size) const { + string status_as_string; + const bool success = proxy_->GetStatus(out_last_checked_time, + out_progress, + &status_as_string, + out_new_version, + out_new_size, + nullptr); + if (!success) { + return false; + } + + return StringToUpdateStatus(status_as_string, out_update_status); +} + +bool DBusUpdateEngineClient::SetCohortHint(const string& cohort_hint) { + return proxy_->SetCohortHint(cohort_hint, nullptr); +} + +bool DBusUpdateEngineClient::GetCohortHint(string* cohort_hint) const { + return proxy_->GetCohortHint(cohort_hint, nullptr); +} + +bool DBusUpdateEngineClient::SetUpdateOverCellularPermission(bool allowed) { + return proxy_->SetUpdateOverCellularPermission(allowed, nullptr); +} + +bool DBusUpdateEngineClient::GetUpdateOverCellularPermission( + bool* allowed) const { + return proxy_->GetUpdateOverCellularPermission(allowed, nullptr); +} + +bool DBusUpdateEngineClient::SetP2PUpdatePermission(bool enabled) { + return proxy_->SetP2PUpdatePermission(enabled, nullptr); +} + +bool DBusUpdateEngineClient::GetP2PUpdatePermission(bool* enabled) const { + return proxy_->GetP2PUpdatePermission(enabled, nullptr); +} + +bool DBusUpdateEngineClient::Rollback(bool powerwash) { + return proxy_->AttemptRollback(powerwash, nullptr); +} + +bool DBusUpdateEngineClient::GetRollbackPartition( + string* rollback_partition) const { + return proxy_->GetRollbackPartition(rollback_partition, nullptr); +} + +bool DBusUpdateEngineClient::GetPrevVersion(string* prev_version) const { + return proxy_->GetPrevVersion(prev_version, nullptr); +} + +void DBusUpdateEngineClient::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."; + } +} + +bool DBusUpdateEngineClient::ResetStatus() { + return proxy_->ResetStatus(nullptr); +} + +void DBusUpdateEngineClient::DBusStatusHandlersRegistered( + const string& interface, + const string& signal_name, + bool success) const { + if (!success) { + for (auto handler : handlers_) { + handler->IPCError("Could not connect to" + signal_name + + " on " + interface); + } + } else { + StatusUpdateHandlersRegistered(nullptr); + } +} + +void DBusUpdateEngineClient::StatusUpdateHandlersRegistered( + StatusUpdateHandler* handler) const { + int64_t last_checked_time; + double progress; + UpdateStatus update_status; + string new_version; + int64_t new_size; + + if (!GetStatus(&last_checked_time, + &progress, + &update_status, + &new_version, + &new_size)) { + handler->IPCError("Could not query current status"); + return; + } + + std::vector<update_engine::StatusUpdateHandler*> just_handler = {handler}; + for (auto h : handler ? just_handler : handlers_) { + h->HandleStatusUpdate( + last_checked_time, progress, update_status, new_version, new_size); + } +} + +void DBusUpdateEngineClient::RunStatusUpdateHandlers( + int64_t last_checked_time, + double progress, + const string& current_operation, + const string& new_version, + int64_t new_size) { + UpdateStatus status; + StringToUpdateStatus(current_operation, &status); + + for (auto handler : handlers_) { + handler->HandleStatusUpdate( + last_checked_time, progress, status, new_version, new_size); + } +} + +bool DBusUpdateEngineClient::UnregisterStatusUpdateHandler( + StatusUpdateHandler* handler) { + auto it = std::find(handlers_.begin(), handlers_.end(), handler); + if (it != handlers_.end()) { + handlers_.erase(it); + return true; + } + + return false; +} + +bool DBusUpdateEngineClient::RegisterStatusUpdateHandler( + StatusUpdateHandler* handler) { + if (!base::MessageLoopForIO::current()) { + LOG(FATAL) << "Cannot get UpdateEngineClient outside of message loop."; + return false; + } + + handlers_.push_back(handler); + + if (dbus_handler_registered_) { + StatusUpdateHandlersRegistered(handler); + return true; + } + + proxy_->RegisterStatusUpdateSignalHandler( + base::Bind(&DBusUpdateEngineClient::RunStatusUpdateHandlers, + base::Unretained(this)), + base::Bind(&DBusUpdateEngineClient::DBusStatusHandlersRegistered, + base::Unretained(this))); + + dbus_handler_registered_ = true; + + return true; +} + +bool DBusUpdateEngineClient::SetTargetChannel(const string& in_target_channel, + bool allow_powerwash) { + return proxy_->SetChannel(in_target_channel, allow_powerwash, nullptr); +} + +bool DBusUpdateEngineClient::GetTargetChannel(string* out_channel) const { + return proxy_->GetChannel(false, // Get the target channel. + out_channel, + nullptr); +} + +bool DBusUpdateEngineClient::GetChannel(string* out_channel) const { + return proxy_->GetChannel(true, // Get the current channel. + out_channel, + nullptr); +} + +bool DBusUpdateEngineClient::GetLastAttemptError( + int32_t* last_attempt_error) const { + return proxy_->GetLastAttemptError(last_attempt_error, nullptr); +} + +bool DBusUpdateEngineClient::GetEolStatus(int32_t* eol_status) const { + return proxy_->GetEolStatus(eol_status, nullptr); +} + +#ifdef USE_NESTLABS +bool DBusUpdateEngineClient::MarkBootSuccessful() const { + return proxy_->MarkBootSuccessful(nullptr); +} +#endif // USE_NESTLABS + +} // namespace internal +} // namespace update_engine
diff --git a/update_engine/client_library/client_dbus.h b/update_engine/client_library/client_dbus.h new file mode 100644 index 0000000..cec1665 --- /dev/null +++ b/update_engine/client_library/client_dbus.h
@@ -0,0 +1,109 @@ +// +// 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_DBUS_H_ +#define UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_DBUS_H_ + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +#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 DBusUpdateEngineClient : public UpdateEngineClient { + public: + DBusUpdateEngineClient() = default; + bool Init(); + + virtual ~DBusUpdateEngineClient() = 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) const override; + + bool SetCohortHint(const std::string& cohort_hint) override; + bool GetCohortHint(std::string* cohort_hint) const override; + + bool SetUpdateOverCellularPermission(bool allowed) override; + bool GetUpdateOverCellularPermission(bool* allowed) const override; + + bool SetP2PUpdatePermission(bool enabled) override; + bool GetP2PUpdatePermission(bool* enabled) const override; + + bool Rollback(bool powerwash) override; + + bool GetRollbackPartition(std::string* rollback_partition) const override; + + void RebootIfNeeded() override; + + bool GetPrevVersion(std::string* prev_version) const override; + + bool ResetStatus() override; + + bool SetTargetChannel(const std::string& target_channel, + bool allow_powerwash) override; + + bool GetTargetChannel(std::string* out_channel) const override; + + bool GetChannel(std::string* out_channel) const override; + + bool RegisterStatusUpdateHandler(StatusUpdateHandler* handler) override; + bool UnregisterStatusUpdateHandler(StatusUpdateHandler* handler) override; + + bool GetLastAttemptError(int32_t* last_attempt_error) const override; + + bool GetEolStatus(int32_t* eol_status) const override; + + private: + void DBusStatusHandlersRegistered(const std::string& interface, + const std::string& signal_name, + bool success) const; + + // Send an initial event to new StatusUpdateHandlers. If the handler argument + // is not nullptr, only that handler receives the event. Otherwise all + // registered handlers receive the event. + void StatusUpdateHandlersRegistered(StatusUpdateHandler* handler) const; + + void RunStatusUpdateHandlers(int64_t last_checked_time, + double progress, + const std::string& current_operation, + const std::string& new_version, + int64_t new_size); + + std::unique_ptr<org::chromium::UpdateEngineInterfaceProxy> proxy_; + std::vector<update_engine::StatusUpdateHandler*> handlers_; + bool dbus_handler_registered_{false}; + + DISALLOW_COPY_AND_ASSIGN(DBusUpdateEngineClient); +}; // class DBusUpdateEngineClient + +} // namespace internal +} // namespace update_engine + +#endif // UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_DBUS_H_
diff --git a/update_engine/client_library/client_dbus_nestlabs.cc b/update_engine/client_library/client_dbus_nestlabs.cc new file mode 100644 index 0000000..64ede0e --- /dev/null +++ b/update_engine/client_library/client_dbus_nestlabs.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/client_library/client_dbus_nestlabs.h" + +#include <base/message_loop/message_loop.h> + +#include <dbus/bus.h> +#include <update_engine/dbus-constants-nestlabs.h> + +#include "update_engine/update_status_utils.h" + +using chromeos_update_engine::StringToUpdateStatus; +using dbus::Bus; +using com::nestlabs::UpdateEngineInterfaceProxy; +using std::string; + +namespace update_engine { +namespace internal { + +bool DBusUpdateEngineNestlabsClient::Init() { + Bus::Options options; + options.bus_type = Bus::SYSTEM; + scoped_refptr<Bus> bus{new Bus{options}}; + + if (!bus->Connect()) + return false; + + proxy_.reset(new UpdateEngineInterfaceProxy{bus, update_engine::kUpdateEngineServiceName}); + return true; +} + +bool DBusUpdateEngineNestlabsClient::AttemptUpdate(const string& in_app_version, + const string& in_url, + bool at_user_request) { + return proxy_->AttemptUpdate( + in_app_version, + in_url, + nullptr); +} + +bool DBusUpdateEngineNestlabsClient::GetStatus(int64_t* out_last_checked_time, + double* out_progress, + UpdateStatus* out_update_status, + string* out_new_version, + int64_t* out_new_size) const { + string status_as_string; + const bool success = proxy_->GetStatus(out_last_checked_time, + out_progress, + &status_as_string, + out_new_version, + out_new_size, + nullptr); + if (!success) { + return false; + } + + return StringToUpdateStatus(status_as_string, out_update_status); +} + +bool DBusUpdateEngineNestlabsClient::Rollback(bool powerwash) { + return proxy_->AttemptRollback(powerwash, nullptr); +} + +bool DBusUpdateEngineNestlabsClient::GetRollbackPartition( + string* rollback_partition) const { + return proxy_->GetRollbackPartition(rollback_partition, nullptr); +} + +bool DBusUpdateEngineNestlabsClient::GetPrevVersion(string* prev_version) const { + return proxy_->GetPrevVersion(prev_version, nullptr); +} + +void DBusUpdateEngineNestlabsClient::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."; + } +} + +bool DBusUpdateEngineNestlabsClient::ResetStatus() { + return proxy_->ResetStatus(nullptr); +} + +void DBusUpdateEngineNestlabsClient::DBusStatusHandlersRegistered( + const string& interface, + const string& signal_name, + bool success) const { + if (!success) { + for (auto handler : handlers_) { + handler->IPCError("Could not connect to" + signal_name + + " on " + interface); + } + } else { + StatusUpdateHandlersRegistered(nullptr); + } +} + +void DBusUpdateEngineNestlabsClient::StatusUpdateHandlersRegistered( + StatusUpdateHandler* handler) const { + int64_t last_checked_time; + double progress; + UpdateStatus update_status; + string new_version; + int64_t new_size; + + if (!GetStatus(&last_checked_time, + &progress, + &update_status, + &new_version, + &new_size)) { + handler->IPCError("Could not query current status"); + return; + } + + std::vector<update_engine::StatusUpdateHandler*> just_handler = {handler}; + for (auto h : handler ? just_handler : handlers_) { + h->HandleStatusUpdate( + last_checked_time, progress, update_status, new_version, new_size); + } +} + +void DBusUpdateEngineNestlabsClient::RunStatusUpdateHandlers( + int64_t last_checked_time, + double progress, + const string& current_operation, + const string& new_version, + int64_t new_size) { + UpdateStatus status; + StringToUpdateStatus(current_operation, &status); + + for (auto handler : handlers_) { + handler->HandleStatusUpdate( + last_checked_time, progress, status, new_version, new_size); + } +} + +bool DBusUpdateEngineNestlabsClient::UnregisterStatusUpdateHandler( + StatusUpdateHandler* handler) { + auto it = std::find(handlers_.begin(), handlers_.end(), handler); + if (it != handlers_.end()) { + handlers_.erase(it); + return true; + } + + return false; +} + +bool DBusUpdateEngineNestlabsClient::RegisterStatusUpdateHandler( + StatusUpdateHandler* handler) { + if (!base::MessageLoopForIO::current()) { + LOG(FATAL) << "Cannot get UpdateEngineClient outside of message loop."; + return false; + } + + handlers_.push_back(handler); + + if (dbus_handler_registered_) { + StatusUpdateHandlersRegistered(handler); + return true; + } + + proxy_->RegisterStatusUpdateSignalHandler( + base::Bind(&DBusUpdateEngineNestlabsClient::RunStatusUpdateHandlers, + base::Unretained(this)), + base::Bind(&DBusUpdateEngineNestlabsClient::DBusStatusHandlersRegistered, + base::Unretained(this))); + + dbus_handler_registered_ = true; + + return true; +} + +bool DBusUpdateEngineNestlabsClient::GetLastAttemptError( + int32_t* last_attempt_error) const { + return proxy_->GetLastAttemptError(last_attempt_error, nullptr); +} + +bool DBusUpdateEngineNestlabsClient::MarkBootSuccessful() const { + return proxy_->MarkBootSuccessful(nullptr); +} + +} // namespace internal +} // namespace update_engine
diff --git a/update_engine/client_library/client_dbus_nestlabs.h b/update_engine/client_library/client_dbus_nestlabs.h new file mode 100644 index 0000000..5d3fc1b --- /dev/null +++ b/update_engine/client_library/client_dbus_nestlabs.h
@@ -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. +// + +#ifndef UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_DBUS_NESTLABS_H_ +#define UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_DBUS_NESTLABS_H_ + +#include <cstdint> +#include <memory> +#include <string> +#include <vector> + +#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 DBusUpdateEngineNestlabsClient : public UpdateEngineClient { + public: + DBusUpdateEngineNestlabsClient() = default; + bool Init(); + + virtual ~DBusUpdateEngineNestlabsClient() = default; + + bool AttemptUpdate(const std::string& app_version, + const std::string& 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) const override; + + bool Rollback(bool powerwash) override; + + bool GetRollbackPartition(std::string* rollback_partition) const override; + + void RebootIfNeeded() override; + + bool GetPrevVersion(std::string* prev_version) const override; + + bool ResetStatus() override; + + bool RegisterStatusUpdateHandler(StatusUpdateHandler* handler) override; + bool UnregisterStatusUpdateHandler(StatusUpdateHandler* handler) override; + + bool GetLastAttemptError(int32_t* last_attempt_error) const override; + + // not supported API + bool SetCohortHint(const std::string& cohort_hint) override { return false; }; + bool GetCohortHint(std::string* cohort_hint) const override { return false; }; + + bool SetUpdateOverCellularPermission(bool allowed) override { return false; }; + bool GetUpdateOverCellularPermission(bool* allowed) const override { return false; }; + + bool SetP2PUpdatePermission(bool enabled) override { return false; }; + bool GetP2PUpdatePermission(bool* enabled) const override { return false; }; + + + bool SetTargetChannel(const std::string& target_channel, + bool allow_powerwash) override { return false; }; + + bool GetTargetChannel(std::string* out_channel) const override { return false; }; + + bool GetChannel(std::string* out_channel) const override { return false; }; + + bool GetEolStatus(int32_t* eol_status) const override { return false; }; + + bool MarkBootSuccessful() const override; + + private: + void DBusStatusHandlersRegistered(const std::string& interface, + const std::string& signal_name, + bool success) const; + + // Send an initial event to new StatusUpdateHandlers. If the handler argument + // is not nullptr, only that handler receives the event. Otherwise all + // registered handlers receive the event. + void StatusUpdateHandlersRegistered(StatusUpdateHandler* handler) const; + + void RunStatusUpdateHandlers(int64_t last_checked_time, + double progress, + const std::string& current_operation, + const std::string& new_version, + int64_t new_size); + + std::unique_ptr<com::nestlabs::UpdateEngineInterfaceProxy> proxy_; + std::vector<update_engine::StatusUpdateHandler*> handlers_; + bool dbus_handler_registered_{false}; + + DISALLOW_COPY_AND_ASSIGN(DBusUpdateEngineNestlabsClient); +}; // class DBusUpdateEngineNestlabsClient + +} // namespace internal +} // namespace update_engine + +#endif // UPDATE_ENGINE_CLIENT_LIBRARY_CLIENT_DBUS_NESTLABS_H_
diff --git a/update_engine/client_library/include/update_engine/client.h b/update_engine/client_library/include/update_engine/client.h new file mode 100644 index 0000000..7fe946e --- /dev/null +++ b/update_engine/client_library/include/update_engine/client.h
@@ -0,0 +1,140 @@ +// +// 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/status_update_handler.h" +#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) const = 0; + + // Getter and setter for the cohort hint. + virtual bool SetCohortHint(const std::string& cohort_hint) = 0; + virtual bool GetCohortHint(std::string* cohort_hint) const = 0; + + // Getter and setter for the updates over cellular connections. + virtual bool SetUpdateOverCellularPermission(bool allowed) = 0; + virtual bool GetUpdateOverCellularPermission(bool* allowed) const = 0; + + // Getter and setter for the updates from P2P permission. + virtual bool SetP2PUpdatePermission(bool enabled) = 0; + virtual bool GetP2PUpdatePermission(bool* enabled) const = 0; + + // Attempt a rollback. Set 'powerwash' to reset the device while rolling + // back. + virtual bool Rollback(bool powerwash) = 0; + + // Get the rollback partition if available. Gives empty string if not. + virtual bool GetRollbackPartition(std::string* rollback_partition) const = 0; + + // Reboot the system if needed. + virtual void RebootIfNeeded() = 0; + + // Get the previous version + virtual bool GetPrevVersion(std::string* prev_version) const = 0; + + // Resets the status of the Update Engine + virtual bool ResetStatus() = 0; + + // Changes the current channel of the device to the target channel. + virtual bool SetTargetChannel(const std::string& target_channel, + bool allow_powerwash) = 0; + + // Get the channel the device will switch to on reboot. + virtual bool GetTargetChannel(std::string* out_channel) const = 0; + + // Get the channel the device is currently on. + virtual bool GetChannel(std::string* out_channel) const = 0; + + // Handle status updates. The handler must exist until the client is + // destroyed or UnregisterStatusUpdateHandler is called for it. Its IPCError + // method will be called if the handler could not be registered. Otherwise + // its HandleStatusUpdate method will be called every time update_engine's + // status changes. Will always report the status on registration to prevent + // race conditions. + virtual bool RegisterStatusUpdateHandler(StatusUpdateHandler* handler) = 0; + + // Unregister a status update handler + virtual bool UnregisterStatusUpdateHandler(StatusUpdateHandler* handler) = 0; + + // Get the last UpdateAttempt error code. + virtual bool GetLastAttemptError(int32_t* last_attempt_error) const = 0; + + // Get the current end-of-life status code. See EolStatus enum for details. + virtual bool GetEolStatus(int32_t* eol_status) const = 0; + +#ifdef USE_NESTLABS + virtual bool MarkBootSuccessful() const = 0; +#endif // USE_NESTLABS + + 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/update_engine/client_library/include/update_engine/status_update_handler.h b/update_engine/client_library/include/update_engine/status_update_handler.h new file mode 100644 index 0000000..d5b8cdb --- /dev/null +++ b/update_engine/client_library/include/update_engine/status_update_handler.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_CLIENT_LIBRARY_INCLUDE_STATUS_UPDATE_HANDLER_H_ +#define UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_STATUS_UPDATE_HANDLER_H_ + +#include <string> + +#include "update_engine/client.h" +#include "update_engine/update_status.h" + +namespace update_engine { + +// Handles update_engine status changes. An instance of this class can be +// registered with UpdateEngineClient and will respond to any update_engine +// status changes. +class StatusUpdateHandler { + public: + virtual ~StatusUpdateHandler() = default; + + // Runs when we fail to register the handler due to an IPC error. + virtual void IPCError(const std::string& error) = 0; + + // Runs every time update_engine reports a status change. + virtual void HandleStatusUpdate(int64_t last_checked_time, + double progress, + UpdateStatus current_operation, + const std::string& new_version, + int64_t new_size) = 0; +}; + +} // namespace update_engine + +#endif // UPDATE_ENGINE_CLIENT_LIBRARY_INCLUDE_STATUS_UPDATE_HANDLER_H_
diff --git a/update_engine/client_library/include/update_engine/update_status.h b/update_engine/client_library/include/update_engine/update_status.h new file mode 100644 index 0000000..3e9af5b --- /dev/null +++ b/update_engine/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/update_engine/common/action.h b/update_engine/common/action.h new file mode 100644 index 0000000..6c88216 --- /dev/null +++ b/update_engine/common/action.h
@@ -0,0 +1,227 @@ +// +// 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() {} + + // Called on asynchronous actions if the processing is suspended and resumed, + // respectively. These methods are called by the ActionProcessor and should + // not be explicitly called. + // The action may still call ActionCompleted() once the action is completed + // while the processing is suspended, for example if suspend/resume is not + // implemented for the given action. + virtual void SuspendAction() {} + virtual void ResumeAction() {} + + // 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/update_engine/common/action_pipe.h b/update_engine/common/action_pipe.h new file mode 100644 index 0000000..376c2f1 --- /dev/null +++ b/update_engine/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) { + static_assert( + std::is_same<typename FromAction::OutputObjectType, + typename ToAction::InputObjectType>::value, + "FromAction::OutputObjectType doesn't match ToAction::InputObjectType"); + ActionPipe<typename FromAction::OutputObjectType>::Bond(from, to); +} + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_ACTION_PIPE_H_
diff --git a/update_engine/common/action_pipe_unittest.cc b/update_engine/common/action_pipe_unittest.cc new file mode 100644 index 0000000..9bfbc83 --- /dev/null +++ b/update_engine/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/update_engine/common/action_processor.cc b/update_engine/common/action_processor.cc new file mode 100644 index 0000000..3549e08 --- /dev/null +++ b/update_engine/common/action_processor.cc
@@ -0,0 +1,147 @@ +// +// 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" +#include "update_engine/common/error_code_utils.h" + +using std::string; + +namespace chromeos_update_engine { + +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: starting " << current_action_->Type(); + actions_.pop_front(); + current_action_->PerformAction(); + } +} + +void ActionProcessor::StopProcessing() { + CHECK(IsRunning()); + if (current_action_) { + current_action_->TerminateProcessing(); + current_action_->SetProcessor(nullptr); + } + LOG(INFO) << "ActionProcessor: aborted " + << (current_action_ ? current_action_->Type() : "") + << (suspended_ ? " while suspended" : ""); + current_action_ = nullptr; + suspended_ = false; + // Delete all the actions before calling the delegate. + for (auto action : actions_) + action->SetProcessor(nullptr); + actions_.clear(); + if (delegate_) + delegate_->ProcessingStopped(this); +} + +void ActionProcessor::SuspendProcessing() { + // No current_action_ when not suspended means that the action processor was + // never started or already finished. + if (suspended_ || !current_action_) { + LOG(WARNING) << "Called SuspendProcessing while not processing."; + return; + } + suspended_ = true; + + // If there's a current action we should notify it that it should suspend, but + // the action can ignore that and terminate at any point. + LOG(INFO) << "ActionProcessor: suspending " << current_action_->Type(); + current_action_->SuspendAction(); +} + +void ActionProcessor::ResumeProcessing() { + if (!suspended_) { + LOG(WARNING) << "Called ResumeProcessing while not suspended."; + return; + } + suspended_ = false; + if (current_action_) { + // The current_action_ did not call ActionComplete while suspended, so we + // should notify it of the resume operation. + LOG(INFO) << "ActionProcessor: resuming " << current_action_->Type(); + current_action_->ResumeAction(); + } else { + // The last action called ActionComplete while suspended, so there is + // already a log message with the type of the finished action. We simply + // state that we are resuming processing and the next function will log the + // start of the next action or processing completion. + LOG(INFO) << "ActionProcessor: resuming processing"; + StartNextActionOrFinish(suspended_error_code_); + } +} + +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; + LOG(INFO) << "ActionProcessor: finished " + << (actions_.empty() ? "last action " : "") << old_type + << (suspended_ ? " while suspended" : "") + << " with code " << utils::ErrorCodeToString(code); + if (!actions_.empty() && code != ErrorCode::kSuccess) { + LOG(INFO) << "ActionProcessor: Aborting processing due to failure."; + actions_.clear(); + } + if (suspended_) { + // If an action finished while suspended we don't start the next action (or + // terminate the processing) until the processor is resumed. This condition + // will be flagged by a nullptr current_action_ while suspended_ is true. + suspended_error_code_ = code; + return; + } + StartNextActionOrFinish(code); +} + +void ActionProcessor::StartNextActionOrFinish(ErrorCode code) { + if (actions_.empty()) { + if (delegate_) { + delegate_->ProcessingDone(this, code); + } + return; + } + current_action_ = actions_.front(); + actions_.pop_front(); + LOG(INFO) << "ActionProcessor: starting " << current_action_->Type(); + current_action_->PerformAction(); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/common/action_processor.h b/update_engine/common/action_processor.h new file mode 100644 index 0000000..c9c179e --- /dev/null +++ b/update_engine/common/action_processor.h
@@ -0,0 +1,144 @@ +// +// 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 <brillo/errors/error.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() = default; + + 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 and all the + // remaining actions will be lost and must be re-enqueued if this Processor is + // to use it. + void StopProcessing(); + + // Suspend the processing. If an Action is running, it will have the + // SuspendProcessing() called on it, and it should suspend operations until + // ResumeProcessing() is called on this class to continue. While suspended, + // no new actions will be started. Calling SuspendProcessing while the + // processing is suspended or not running this method performs no action. + void SuspendProcessing(); + + // Resume the suspended processing. If the ActionProcessor is not suspended + // or not running in the first place this method performs no action. + void ResumeProcessing(); + + // Returns true iff the processing was started but not yet completed nor + // stopped. + bool IsRunning() const { return current_action_ != nullptr || suspended_; } + + // 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: + // Continue processing actions (if any) after the last action terminated with + // the passed error code. If there are no more actions to process, the + // processing will terminate. + void StartNextActionOrFinish(ErrorCode code); + + // 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_{nullptr}; + + // The ErrorCode reported by an action that was suspended but finished while + // being suspended. This error code is stored here to be reported back to the + // delegate once the processor is resumed. + ErrorCode suspended_error_code_{ErrorCode::kSuccess}; + + // Whether the action processor is or should be suspended. + bool suspended_{false}; + + // A pointer to the delegate, or null if none. + ActionProcessorDelegate* delegate_{nullptr}; + + 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/update_engine/common/action_processor_unittest.cc b/update_engine/common/action_processor_unittest.cc new file mode 100644 index 0000000..631e42d --- /dev/null +++ b/update_engine/common/action_processor_unittest.cc
@@ -0,0 +1,264 @@ +// +// 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 <gtest/gtest.h> + +#include "update_engine/common/action.h" +#include "update_engine/common/mock_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"; } +}; + +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 + +class ActionProcessorTest : public ::testing::Test { + void SetUp() override { + action_processor_.set_delegate(&delegate_); + // Silence Type() calls used for logging. + EXPECT_CALL(mock_action_, Type()).Times(testing::AnyNumber()); + } + + void TearDown() override { + action_processor_.set_delegate(nullptr); + } + + protected: + // The ActionProcessor under test. + ActionProcessor action_processor_; + + MyActionProcessorDelegate delegate_{&action_processor_}; + + // Common actions used during most tests. + testing::StrictMock<MockAction> mock_action_; + ActionProcessorTestAction action_; +}; + +TEST_F(ActionProcessorTest, SimpleTest) { + 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()); +} + +TEST_F(ActionProcessorTest, DelegateTest) { + action_processor_.EnqueueAction(&action_); + action_processor_.StartProcessing(); + action_.CompleteAction(); + EXPECT_TRUE(delegate_.processing_done_called_); + EXPECT_TRUE(delegate_.action_completed_called_); +} + +TEST_F(ActionProcessorTest, StopProcessingTest) { + action_processor_.EnqueueAction(&action_); + action_processor_.StartProcessing(); + action_processor_.StopProcessing(); + 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_F(ActionProcessorTest, ChainActionsTest) { + // This test doesn't use a delegate since it terminates several actions. + action_processor_.set_delegate(nullptr); + + ActionProcessorTestAction action1, action2; + 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_F(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_F(ActionProcessorTest, DefaultDelegateTest) { + // Just make sure it doesn't crash + action_processor_.EnqueueAction(&action_); + action_processor_.StartProcessing(); + action_.CompleteAction(); + + action_processor_.EnqueueAction(&action_); + action_processor_.StartProcessing(); + action_processor_.StopProcessing(); +} + +// This test suspends and resume the action processor while running one action_. +TEST_F(ActionProcessorTest, SuspendResumeTest) { + action_processor_.EnqueueAction(&mock_action_); + + testing::InSequence s; + EXPECT_CALL(mock_action_, PerformAction()); + action_processor_.StartProcessing(); + + EXPECT_CALL(mock_action_, SuspendAction()); + action_processor_.SuspendProcessing(); + // Suspending the processor twice should not suspend the action twice. + action_processor_.SuspendProcessing(); + + // IsRunning should return whether there's is an action doing some work, even + // if it is suspended. + EXPECT_TRUE(action_processor_.IsRunning()); + EXPECT_EQ(&mock_action_, action_processor_.current_action()); + + EXPECT_CALL(mock_action_, ResumeAction()); + action_processor_.ResumeProcessing(); + + // Calling ResumeProcessing twice should not affect the action_. + action_processor_.ResumeProcessing(); + + action_processor_.ActionComplete(&mock_action_, ErrorCode::kSuccess); +} + +// This test suspends an action that presumably doesn't support suspend/resume +// and it finished before being resumed. +TEST_F(ActionProcessorTest, ActionCompletedWhileSuspendedTest) { + action_processor_.EnqueueAction(&mock_action_); + + testing::InSequence s; + EXPECT_CALL(mock_action_, PerformAction()); + action_processor_.StartProcessing(); + + EXPECT_CALL(mock_action_, SuspendAction()); + action_processor_.SuspendProcessing(); + + // Simulate the action completion while suspended. No other call to + // |mock_action_| is expected at this point. + action_processor_.ActionComplete(&mock_action_, ErrorCode::kSuccess); + + // The processing should not be done since the ActionProcessor is suspended + // and the processing is considered to be still running until resumed. + EXPECT_FALSE(delegate_.processing_done_called_); + EXPECT_TRUE(action_processor_.IsRunning()); + + action_processor_.ResumeProcessing(); + EXPECT_TRUE(delegate_.processing_done_called_); + EXPECT_FALSE(delegate_.processing_stopped_called_); +} + +TEST_F(ActionProcessorTest, StoppedWhileSuspendedTest) { + action_processor_.EnqueueAction(&mock_action_); + + testing::InSequence s; + EXPECT_CALL(mock_action_, PerformAction()); + action_processor_.StartProcessing(); + EXPECT_CALL(mock_action_, SuspendAction()); + action_processor_.SuspendProcessing(); + + EXPECT_CALL(mock_action_, TerminateProcessing()); + action_processor_.StopProcessing(); + // Stopping the processing should abort the current execution no matter what. + EXPECT_TRUE(delegate_.processing_stopped_called_); + EXPECT_FALSE(delegate_.processing_done_called_); + EXPECT_FALSE(delegate_.action_completed_called_); + EXPECT_FALSE(action_processor_.IsRunning()); + EXPECT_EQ(nullptr, action_processor_.current_action()); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/common/action_unittest.cc b/update_engine/common/action_unittest.cc new file mode 100644 index 0000000..dcdce17 --- /dev/null +++ b/update_engine/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/update_engine/common/boot_control.h b/update_engine/common/boot_control.h new file mode 100644 index 0000000..1463015 --- /dev/null +++ b/update_engine/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/update_engine/common/boot_control_interface.h b/update_engine/common/boot_control_interface.h new file mode 100644 index 0000000..659b388 --- /dev/null +++ b/update_engine/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/update_engine/common/boot_control_stub.cc b/update_engine/common/boot_control_stub.cc new file mode 100644 index 0000000..2de0c82 --- /dev/null +++ b/update_engine/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/update_engine/common/boot_control_stub.h b/update_engine/common/boot_control_stub.h new file mode 100644 index 0000000..7832adc --- /dev/null +++ b/update_engine/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/update_engine/common/clock.cc b/update_engine/common/clock.cc new file mode 100644 index 0000000..f0eff44 --- /dev/null +++ b/update_engine/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/update_engine/common/clock.h b/update_engine/common/clock.h new file mode 100644 index 0000000..2f373a7 --- /dev/null +++ b/update_engine/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/update_engine/common/clock_interface.h b/update_engine/common/clock_interface.h new file mode 100644 index 0000000..2228983 --- /dev/null +++ b/update_engine/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/update_engine/common/constants.cc b/update_engine/common/constants.cc new file mode 100644 index 0000000..324bdc5 --- /dev/null +++ b/update_engine/common/constants.cc
@@ -0,0 +1,98 @@ +// +// 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 kPowerwashSafePrefsSubDirectory[] = "update_engine/prefs"; + +const char kPrefsSubDirectory[] = "prefs"; + +const char kStatefulPartition[] = "/mnt/stateful_partition"; + +const char kPostinstallDefaultScript[] = "postinst"; + +// Constants defining keys for the persisted state of update engine. +const char kPrefsAttemptInProgress[] = "attempt-in-progress"; +const char kPrefsBackoffExpiryTime[] = "backoff-expiry-time"; +const char kPrefsBootId[] = "boot-id"; +const char kPrefsCurrentBytesDownloaded[] = "current-bytes-downloaded"; +const char kPrefsCurrentResponseSignature[] = "current-response-signature"; +const char kPrefsCurrentUrlFailureCount[] = "current-url-failure-count"; +const char kPrefsCurrentUrlIndex[] = "current-url-index"; +const char kPrefsDailyMetricsLastReportedAt[] = + "daily-metrics-last-reported-at"; +const char kPrefsDeltaUpdateFailures[] = "delta-update-failures"; +const char kPrefsFullPayloadAttemptNumber[] = "full-payload-attempt-number"; +const char kPrefsInstallDateDays[] = "install-date-days"; +const char kPrefsLastActivePingDay[] = "last-active-ping-day"; +const char kPrefsLastRollCallPingDay[] = "last-roll-call-ping-day"; +const char kPrefsManifestMetadataSize[] = "manifest-metadata-size"; +const char kPrefsManifestSignatureSize[] = "manifest-signature-size"; +const char kPrefsMetricsAttemptLastReportingTime[] = + "metrics-attempt-last-reporting-time"; +const char kPrefsMetricsCheckLastReportingTime[] = + "metrics-check-last-reporting-time"; +const char 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 kPrefsOmahaEolStatus[] = "omaha-eol-status"; +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"; + +const char kPayloadPropertyFileSize[] = "FILE_SIZE"; +const char kPayloadPropertyFileHash[] = "FILE_HASH"; +const char kPayloadPropertyMetadataSize[] = "METADATA_SIZE"; +const char kPayloadPropertyMetadataHash[] = "METADATA_HASH"; +const char kPayloadPropertyAuthorization[] = "AUTHORIZATION"; +const char kPayloadPropertyUserAgent[] = "USER_AGENT"; +const char kPayloadPropertyPowerwash[] = "POWERWASH"; +const char kPayloadPropertyNetworkId[] = "NETWORK_ID"; + +} // namespace chromeos_update_engine
diff --git a/update_engine/common/constants.h b/update_engine/common/constants.h new file mode 100644 index 0000000..ab66921 --- /dev/null +++ b/update_engine/common/constants.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_COMMON_CONSTANTS_H_ +#define UPDATE_ENGINE_COMMON_CONSTANTS_H_ + +namespace chromeos_update_engine { + +// 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 post install command, relative to the partition. +extern const char kPostinstallDefaultScript[]; + +// 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 kPrefsManifestSignatureSize[]; +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 kPrefsOmahaEolStatus[]; +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[]; + +// Keys used when storing and loading payload properties. +extern const char kPayloadPropertyFileSize[]; +extern const char kPayloadPropertyFileHash[]; +extern const char kPayloadPropertyMetadataSize[]; +extern const char kPayloadPropertyMetadataHash[]; +extern const char kPayloadPropertyAuthorization[]; +extern const char kPayloadPropertyUserAgent[]; +extern const char kPayloadPropertyPowerwash[]; +extern const char kPayloadPropertyNetworkId[]; + +// 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/update_engine/common/cpu_limiter.cc b/update_engine/common/cpu_limiter.cc new file mode 100644 index 0000000..32068d4 --- /dev/null +++ b/update_engine/common/cpu_limiter.cc
@@ -0,0 +1,92 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/common/cpu_limiter.h" + +#include <string> + +#include <base/bind.h> +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <base/time/time.h> + +#include "update_engine/common/utils.h" + +namespace { + +// Cgroup container is created in update-engine's upstart script located at +// /etc/init/update-engine.conf. +#ifdef USE_NESTLABS +const char kCGroupSharesPath[] = "/dev/cpuctl/update_engine/cpu.shares"; +#else +const char kCGroupSharesPath[] = "/sys/fs/cgroup/cpu/update-engine/cpu.shares"; +#endif + +} // namespace + +namespace chromeos_update_engine { + +CPULimiter::~CPULimiter() { + // Set everything back to normal on destruction. + CPULimiter::SetCpuShares(CpuShares::kNormal); +} + +void CPULimiter::StartLimiter() { + if (manage_shares_id_ != brillo::MessageLoop::kTaskIdNull) { + LOG(ERROR) << "Cpu shares timeout source hasn't been destroyed."; + StopLimiter(); + } + manage_shares_id_ = brillo::MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&CPULimiter::StopLimiterCallback, base::Unretained(this)), + base::TimeDelta::FromHours(2)); + SetCpuShares(CpuShares::kLow); +} + +void CPULimiter::StopLimiter() { + if (manage_shares_id_ != brillo::MessageLoop::kTaskIdNull) { + // If the shares were never set and there isn't a message loop instance, + // we avoid calling CancelTask(), which otherwise would have been a no-op. + brillo::MessageLoop::current()->CancelTask(manage_shares_id_); + manage_shares_id_ = brillo::MessageLoop::kTaskIdNull; + } + SetCpuShares(CpuShares::kNormal); +} + +bool CPULimiter::SetCpuShares(CpuShares shares) { + // Short-circuit to avoid re-setting the shares. + if (shares_ == shares) + return true; + + std::string string_shares = base::IntToString(static_cast<int>(shares)); + LOG(INFO) << "Setting cgroup cpu shares to " << string_shares; + if (!utils::WriteFile( + kCGroupSharesPath, string_shares.c_str(), string_shares.size())) { + LOG(ERROR) << "Failed to change cgroup cpu shares to " << string_shares + << " using " << kCGroupSharesPath; + return false; + } + shares_ = shares; + LOG(INFO) << "CPU shares = " << static_cast<int>(shares_); + return true; +} + +void CPULimiter::StopLimiterCallback() { + SetCpuShares(CpuShares::kNormal); + manage_shares_id_ = brillo::MessageLoop::kTaskIdNull; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/common/cpu_limiter.h b/update_engine/common/cpu_limiter.h new file mode 100644 index 0000000..c7add89 --- /dev/null +++ b/update_engine/common/cpu_limiter.h
@@ -0,0 +1,67 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_COMMON_CPU_LIMITER_H_ +#define UPDATE_ENGINE_COMMON_CPU_LIMITER_H_ + +#include <brillo/message_loops/message_loop.h> + +namespace chromeos_update_engine { + +// 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 class CpuShares : int { + kHigh = 2048, + kNormal = 1024, + kLow = 2, +}; + +// Sets the current process shares to |shares|. Returns true on +// success, false otherwise. +bool SetCpuShares(CpuShares shares); + +class CPULimiter { + public: + CPULimiter() = default; + ~CPULimiter(); + + // Sets the cpu shares to low and sets up timeout events to stop the limiter. + void StartLimiter(); + + // Resets the cpu shares to normal and destroys any scheduled timeout sources. + void StopLimiter(); + + // Sets the cpu shares to |shares|. This method can be user at any time, but + // if the limiter is not running, the shares won't be reset to normal. + bool SetCpuShares(CpuShares shares); + + private: + // The cpu shares timeout source callback sets the current cpu shares to + // normal. + void StopLimiterCallback(); + + // Current cpu shares. + CpuShares shares_ = CpuShares::kNormal; + + // The cpu shares management timeout task id. + brillo::MessageLoop::TaskId manage_shares_id_{ + brillo::MessageLoop::kTaskIdNull}; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_CPU_LIMITER_H_
diff --git a/update_engine/common/cpu_limiter_unittest.cc b/update_engine/common/cpu_limiter_unittest.cc new file mode 100644 index 0000000..d549b4c --- /dev/null +++ b/update_engine/common/cpu_limiter_unittest.cc
@@ -0,0 +1,42 @@ +// +// 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/cpu_limiter.h" + +#include <gtest/gtest.h> + +namespace chromeos_update_engine { + +class CPULimiterTest : public ::testing::Test {}; + +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(CpuShares shares_lhs, 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(CPULimiterTest, CompareCpuSharesTest) { + EXPECT_LT(CompareCpuShares(CpuShares::kLow, CpuShares::kNormal), 0); + EXPECT_GT(CompareCpuShares(CpuShares::kNormal, CpuShares::kLow), 0); + EXPECT_EQ(CompareCpuShares(CpuShares::kNormal, CpuShares::kNormal), 0); + EXPECT_GT(CompareCpuShares(CpuShares::kHigh, CpuShares::kNormal), 0); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/common/error_code.h b/update_engine/common/error_code.h new file mode 100644 index 0000000..bc50589 --- /dev/null +++ b/update_engine/common/error_code.h
@@ -0,0 +1,138 @@ +// +// 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, + kUserCanceled = 48, + kNonCriticalUpdateInOOBE = 49, + + // 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 + kHTTPResponseBase = kOmahaRequestHTTPResponseBase, // alias for kOmahaRequestHTTPResponseBase + + // 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/update_engine/common/error_code_utils.cc b/update_engine/common/error_code_utils.cc new file mode 100644 index 0000000..ad4a00f --- /dev/null +++ b/update_engine/common/error_code_utils.cc
@@ -0,0 +1,163 @@ +// +// 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/error_code_utils.h" + +#include <base/strings/string_number_conversions.h> + +using std::string; + +namespace chromeos_update_engine { +namespace utils { + +string ErrorCodeToString(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: +#ifdef USE_NESTLABS + return "ErrorCode::kErrorInHTTPResponse"; +#else + return "ErrorCode::kOmahaErrorInHTTPResponse"; +#endif + 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: +#ifdef USE_NESTLABS + return "ErrorCode::kRequestHTTPResponseBase"; +#else + return "ErrorCode::kOmahaRequestHTTPResponseBase"; +#endif + 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"; + case ErrorCode::kUserCanceled: + return "ErrorCode::kUserCanceled"; + case ErrorCode::kNonCriticalUpdateInOOBE: + return "ErrorCode::kNonCriticalUpdateInOOBE"; + // 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)); +} + +} // namespace utils +} // namespace chromeos_update_engine
diff --git a/update_engine/common/error_code_utils.h b/update_engine/common/error_code_utils.h new file mode 100644 index 0000000..ae3958e --- /dev/null +++ b/update_engine/common/error_code_utils.h
@@ -0,0 +1,34 @@ +// +// 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_ERROR_CODE_UTILS_H_ +#define UPDATE_ENGINE_COMMON_ERROR_CODE_UTILS_H_ + +#include <string> + +#include "update_engine/common/error_code.h" + +namespace chromeos_update_engine { +namespace utils { + +// Returns a string representation of the ErrorCodes (either the base +// error codes or the bit flags) for logging purposes. +std::string ErrorCodeToString(ErrorCode code); + +} // namespace utils +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_ERROR_CODE_UTILS_H_
diff --git a/update_engine/common/fake_boot_control.h b/update_engine/common/fake_boot_control.h new file mode 100644 index 0000000..3eccc80 --- /dev/null +++ b/update_engine/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/update_engine/common/fake_clock.h b/update_engine/common/fake_clock.h new file mode 100644 index 0000000..3d3bad8 --- /dev/null +++ b/update_engine/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/update_engine/common/fake_hardware.h b/update_engine/common/fake_hardware.h new file mode 100644 index 0000000..5d0fca3 --- /dev/null +++ b/update_engine/common/fake_hardware.h
@@ -0,0 +1,146 @@ +// +// 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() = default; + + // HardwareInterface methods. + bool IsOfficialBuild() const override { return is_official_build_; } + + bool IsNormalBootMode() const override { return is_normal_boot_mode_; } + + bool AreDevFeaturesEnabled() const override { + return are_dev_features_enabled_; + } + + bool IsOOBEEnabled() const override { return is_oobe_enabled_; } + + 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 SchedulePowerwash() override { + powerwash_scheduled_ = true; + return true; + } + + bool CancelPowerwash() override { + powerwash_scheduled_ = false; + return true; + } + + bool IsPowerwashScheduled() { return powerwash_scheduled_; } + + 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; + } + + void SetAreDevFeaturesEnabled(bool are_dev_features_enabled) { + are_dev_features_enabled_ = are_dev_features_enabled; + } + + // Sets the SetIsOOBEEnabled to |is_oobe_enabled|. + void SetIsOOBEEnabled(bool is_oobe_enabled) { + is_oobe_enabled_ = is_oobe_enabled; + } + + // Sets the IsOOBEComplete to True with the given timestamp. + void SetIsOOBEComplete(base::Time oobe_timestamp) { + is_oobe_complete_ = true; + oobe_timestamp_ = oobe_timestamp; + } + + void UnsetIsOOBEComplete() { + is_oobe_complete_ = false; + } + + void SetHardwareClass(const std::string& hardware_class) { + hardware_class_ = hardware_class; + } + + void SetFirmwareVersion(const std::string& firmware_version) { + firmware_version_ = firmware_version; + } + + void SetECVersion(const std::string& ec_version) { + ec_version_ = ec_version; + } + + void SetPowerwashCount(int powerwash_count) { + powerwash_count_ = powerwash_count; + } + + private: + bool is_official_build_{true}; + bool is_normal_boot_mode_{true}; + bool are_dev_features_enabled_{false}; + bool is_oobe_enabled_{true}; + bool is_oobe_complete_{true}; + base::Time oobe_timestamp_{base::Time::FromTimeT(1169280000)}; // Jan 20, 2007 + std::string hardware_class_{"Fake HWID BLAH-1234"}; + std::string firmware_version_{"Fake Firmware v1.0.1"}; + std::string ec_version_{"Fake EC v1.0a"}; + int powerwash_count_{kPowerwashCountNotSet}; + bool powerwash_scheduled_{false}; + + DISALLOW_COPY_AND_ASSIGN(FakeHardware); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_FAKE_HARDWARE_H_
diff --git a/update_engine/common/fake_prefs.cc b/update_engine/common/fake_prefs.cc new file mode 100644 index 0000000..5a0a3af --- /dev/null +++ b/update_engine/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/update_engine/common/fake_prefs.h b/update_engine/common/fake_prefs.h new file mode 100644 index 0000000..d194060 --- /dev/null +++ b/update_engine/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/update_engine/common/file_fetcher.cc b/update_engine/common/file_fetcher.cc new file mode 100644 index 0000000..d0a109b --- /dev/null +++ b/update_engine/common/file_fetcher.cc
@@ -0,0 +1,186 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/common/file_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 <brillo/streams/file_stream.h> + +#include "update_engine/common/hardware_interface.h" +#include "update_engine/common/platform_constants.h" + +using std::string; + +namespace { + +size_t kReadBufferSize = 16 * 1024; + +} // namespace + +namespace chromeos_update_engine { + +// static +bool FileFetcher::SupportedUrl(const string& url) { + // Note that we require the file path to start with a "/". + return base::StartsWith( + url, "file:///", base::CompareCase::INSENSITIVE_ASCII); +} + +FileFetcher::~FileFetcher() { + LOG_IF(ERROR, transfer_in_progress_) + << "Destroying the fetcher while a transfer is in progress."; + CleanUp(); +} + +// Begins the transfer, which must not have already been started. +void FileFetcher::BeginTransfer(const string& url) { + CHECK(!transfer_in_progress_); + + if (!SupportedUrl(url)) { + LOG(ERROR) << "Unsupported file URL: " << url; + // No HTTP error code when the URL is not supported. + http_response_code_ = 0; + CleanUp(); + if (delegate_) + delegate_->TransferComplete(this, false); + return; + } + + string file_path = url.substr(strlen("file://")); + stream_ = + brillo::FileStream::Open(base::FilePath(file_path), + brillo::Stream::AccessMode::READ, + brillo::FileStream::Disposition::OPEN_EXISTING, + nullptr); + + if (!stream_) { + LOG(ERROR) << "Couldn't open " << file_path; + http_response_code_ = kHttpResponseNotFound; + CleanUp(); + if (delegate_) + delegate_->TransferComplete(this, false); + return; + } + http_response_code_ = kHttpResponseOk; + + if (offset_) + stream_->SetPosition(offset_, nullptr); + bytes_copied_ = 0; + transfer_in_progress_ = true; + ScheduleRead(); +} + +void FileFetcher::TerminateTransfer() { + CleanUp(); + if (delegate_) { + // Note that after the callback returns this object may be destroyed. + delegate_->TransferTerminated(this); + } +} + +void FileFetcher::ScheduleRead() { + if (transfer_paused_ || ongoing_read_ || !transfer_in_progress_) + return; + + buffer_.resize(kReadBufferSize); + size_t bytes_to_read = buffer_.size(); + if (data_length_ >= 0) { + bytes_to_read = std::min(static_cast<uint64_t>(bytes_to_read), + data_length_ - bytes_copied_); + } + + if (!bytes_to_read) { + OnReadDoneCallback(0); + return; + } + + ongoing_read_ = stream_->ReadAsync( + buffer_.data(), + bytes_to_read, + base::Bind(&FileFetcher::OnReadDoneCallback, base::Unretained(this)), + base::Bind(&FileFetcher::OnReadErrorCallback, base::Unretained(this)), + nullptr); + + if (!ongoing_read_) { + LOG(ERROR) << "Unable to schedule an asynchronous read from the stream."; + CleanUp(); + if (delegate_) + delegate_->TransferComplete(this, false); + } +} + +void FileFetcher::OnReadDoneCallback(size_t bytes_read) { + ongoing_read_ = false; + if (bytes_read == 0) { + CleanUp(); + if (delegate_) + delegate_->TransferComplete(this, true); + } else { + bytes_copied_ += bytes_read; + if (delegate_) + delegate_->ReceivedBytes(this, buffer_.data(), bytes_read); + ScheduleRead(); + } +} + +void FileFetcher::OnReadErrorCallback(const brillo::Error* error) { + LOG(ERROR) << "Asynchronous read failed: " << error->GetMessage(); + CleanUp(); + if (delegate_) + delegate_->TransferComplete(this, false); +} + +void FileFetcher::Pause() { + if (transfer_paused_) { + LOG(ERROR) << "Fetcher already paused."; + return; + } + transfer_paused_ = true; +} + +void FileFetcher::Unpause() { + if (!transfer_paused_) { + LOG(ERROR) << "Resume attempted when fetcher not paused."; + return; + } + transfer_paused_ = false; + ScheduleRead(); +} + +void FileFetcher::CleanUp() { + if (stream_) { + stream_->CancelPendingAsyncOperations(); + stream_->CloseBlocking(nullptr); + stream_.reset(); + } + // Destroying the |stream_| releases the callback, so we don't have any + // ongoing read at this point. + ongoing_read_ = false; + buffer_ = brillo::Blob(); + + transfer_in_progress_ = false; + transfer_paused_ = false; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/common/file_fetcher.h b/update_engine/common/file_fetcher.h new file mode 100644 index 0000000..2368b1d --- /dev/null +++ b/update_engine/common/file_fetcher.h
@@ -0,0 +1,119 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_COMMON_FILE_FETCHER_H_ +#define UPDATE_ENGINE_COMMON_FILE_FETCHER_H_ + +#include <memory> +#include <string> +#include <utility> + +#include <base/logging.h> +#include <base/macros.h> +#include <brillo/message_loops/message_loop.h> +#include <brillo/streams/stream.h> + +#include "update_engine/common/http_fetcher.h" + +// This is a concrete implementation of HttpFetcher that reads files +// asynchronously. + +namespace chromeos_update_engine { + +class FileFetcher : public HttpFetcher { + public: + // Returns whether the passed url is supported. + static bool SupportedUrl(const std::string& url); + + FileFetcher() : HttpFetcher(nullptr) {} + + // Cleans up all internal state. Does not notify delegate. + ~FileFetcher() override; + + // HttpFetcher overrides. + void SetOffset(off_t offset) override { offset_ = offset; } + void SetLength(size_t length) override { data_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; + + // Ignore all extra headers for files. + void SetHeader(const std::string& header_name, + const std::string& header_value) override {}; + + // Suspend the asynchronous file read. + void Pause() override; + + // Resume the suspended file read. + void Unpause() override; + + size_t GetBytesDownloaded() override { + return static_cast<size_t>(bytes_copied_); + } + + // Ignore all the time limits for files. + 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 {} + + private: + // Cleans up the fetcher, resetting its status to a newly constructed one. + void CleanUp(); + + // Schedule a new asynchronous read if the stream is not paused and no other + // read is in process. This method can be called at any point. + void ScheduleRead(); + + // Called from the main loop when a single read from |stream_| succeeds or + // fails, calling OnReadDoneCallback() and OnReadErrorCallback() respectively. + void OnReadDoneCallback(size_t bytes_read); + void OnReadErrorCallback(const brillo::Error* error); + + // Whether the transfer was started and didn't finish yet. + bool transfer_in_progress_{false}; + + // Whether the transfer is paused. + bool transfer_paused_{false}; + + // Whether there's an ongoing asynchronous read. When this value is true, the + // the |buffer_| is being used by the |stream_|. + bool ongoing_read_{false}; + + // Total number of bytes copied. + uint64_t bytes_copied_{0}; + + // The offset inside the file where the read should start. + uint64_t offset_{0}; + + // The length of the data or -1 if unknown (will read until EOF). + int64_t data_length_{-1}; + + brillo::StreamPtr stream_; + + // The buffer used for reading from the stream. + brillo::Blob buffer_; + + DISALLOW_COPY_AND_ASSIGN(FileFetcher); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_FILE_FETCHER_H_
diff --git a/update_engine/common/file_fetcher_unittest.cc b/update_engine/common/file_fetcher_unittest.cc new file mode 100644 index 0000000..9c6b0ec --- /dev/null +++ b/update_engine/common/file_fetcher_unittest.cc
@@ -0,0 +1,37 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/common/file_fetcher.h" + +#include <string> + +#include <gtest/gtest.h> + +#include "update_engine/common/test_utils.h" + +namespace chromeos_update_engine { + +class FileFetcherUnitTest : public ::testing::Test {}; + +TEST_F(FileFetcherUnitTest, SupporterUrlsTest) { + EXPECT_TRUE(FileFetcher::SupportedUrl("file:///path/to/somewhere.bin")); + EXPECT_TRUE(FileFetcher::SupportedUrl("FILE:///I/LIKE/TO/SHOUT")); + + EXPECT_FALSE(FileFetcher::SupportedUrl("file://relative")); + EXPECT_FALSE(FileFetcher::SupportedUrl("http:///no_http_here")); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/common/hardware.h b/update_engine/common/hardware.h new file mode 100644 index 0000000..f1365e0 --- /dev/null +++ b/update_engine/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/update_engine/common/hardware_interface.h b/update_engine/common/hardware_interface.h new file mode 100644 index 0000000..316ad3d --- /dev/null +++ b/update_engine/common/hardware_interface.h
@@ -0,0 +1,96 @@ +// +// 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 whether the developer features are enabled. + virtual bool AreDevFeaturesEnabled() const = 0; + + // Returns whether the device has an OOBE flow that the user must go through + // before getting non-critical updates. Use IsOOBEComplete() to determine if + // that flow is complete. + virtual bool IsOOBEEnabled() 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; + + // Signals that a powerwash (stateful partition wipe) should be performed + // after reboot. + virtual bool SchedulePowerwash() = 0; + + // Cancel the powerwash operation scheduled to be performed on next boot. + virtual bool CancelPowerwash() = 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/update_engine/common/hash_calculator.cc b/update_engine/common/hash_calculator.cc new file mode 100644 index 0000000..de6e0f9 --- /dev/null +++ b/update_engine/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/update_engine/common/hash_calculator.h b/update_engine/common/hash_calculator.h new file mode 100644 index 0000000..f749585 --- /dev/null +++ b/update_engine/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/update_engine/common/hash_calculator_unittest.cc b/update_engine/common/hash_calculator_unittest.cc new file mode 100644 index 0000000..436e6a7 --- /dev/null +++ b/update_engine/common/hash_calculator_unittest.cc
@@ -0,0 +1,173 @@ +// +// 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/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/update_engine/common/http_common.cc b/update_engine/common/http_common.cc new file mode 100644 index 0000000..d07ced3 --- /dev/null +++ b/update_engine/common/http_common.cc
@@ -0,0 +1,88 @@ +// +// 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 <cstdlib> + +#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/update_engine/common/http_common.h b/update_engine/common/http_common.h new file mode 100644 index 0000000..6d444ed --- /dev/null +++ b/update_engine/common/http_common.h
@@ -0,0 +1,72 @@ +// +// 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_ + +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/update_engine/common/http_fetcher.cc b/update_engine/common/http_fetcher.cc new file mode 100644 index 0000000..400b43c --- /dev/null +++ b/update_engine/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/update_engine/common/http_fetcher.h b/update_engine/common/http_fetcher.h new file mode 100644 index 0000000..d2499eb --- /dev/null +++ b/update_engine/common/http_fetcher.h
@@ -0,0 +1,203 @@ +// +// 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. + explicit 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; + + // Add or update a custom header to be sent with every request. If the same + // |header_name| is passed twice, the second |header_value| would override the + // previous value. + virtual void SetHeader(const std::string& header_name, + const std::string& header_value) = 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/update_engine/common/http_fetcher_unittest.cc b/update_engine/common/http_fetcher_unittest.cc new file mode 100644 index 0000000..0f34475 --- /dev/null +++ b/update_engine/common/http_fetcher_unittest.cc
@@ -0,0 +1,1240 @@ +// +// 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/file_fetcher.h" +#include "update_engine/common/http_common.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/libcurl_http_fetcher.h" +#include "update_engine/mock_proxy_resolver.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; +using testing::DoAll; +using testing::Return; +using testing::SaveArg; +using testing::_; + +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()); + http_server->AddArg(test_utils::GetBuildArtifactsPath("test_http_server")); + 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(ProxyResolver* proxy_resolver) = 0; + HttpFetcher* NewLargeFetcher(size_t num_proxies) { + proxy_resolver_.set_num_proxies(num_proxies); + return NewLargeFetcher(&proxy_resolver_); + } + HttpFetcher* NewLargeFetcher() { + return NewLargeFetcher(1); + } + + virtual HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) = 0; + HttpFetcher* NewSmallFetcher() { + proxy_resolver_.set_num_proxies(1); + return NewSmallFetcher(&proxy_resolver_); + } + + 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 bool IsHttpSupported() 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(ProxyResolver* proxy_resolver) override { + brillo::Blob big_data(1000000); + return new MockHttpFetcher( + big_data.data(), big_data.size(), proxy_resolver); + } + + // Necessary to unhide the definition in the base class. + using AnyHttpFetcherTest::NewSmallFetcher; + HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override { + return new MockHttpFetcher("x", 1, proxy_resolver); + } + + bool IsMock() const override { return true; } + bool IsMulti() const override { return false; } + bool IsHttpSupported() const override { return true; } + + 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(ProxyResolver* proxy_resolver) override { + LibcurlHttpFetcher* ret = + new LibcurlHttpFetcher(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(ProxyResolver* proxy_resolver) override { + return NewLargeFetcher(proxy_resolver); + } + + 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; } + bool IsHttpSupported() const override { return true; } + + 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(ProxyResolver* proxy_resolver) override { + MultiRangeHttpFetcher* ret = new MultiRangeHttpFetcher( + new LibcurlHttpFetcher(proxy_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(ProxyResolver* proxy_resolver) override { + return NewLargeFetcher(proxy_resolver); + } + + bool IsMulti() const override { return true; } +}; + +class FileFetcherTest : public AnyHttpFetcherTest { + public: + // Necessary to unhide the definition in the base class. + using AnyHttpFetcherTest::NewLargeFetcher; + HttpFetcher* NewLargeFetcher(ProxyResolver* /* proxy_resolver */) override { + return new FileFetcher(); + } + + // Necessary to unhide the definition in the base class. + using AnyHttpFetcherTest::NewSmallFetcher; + HttpFetcher* NewSmallFetcher(ProxyResolver* proxy_resolver) override { + return NewLargeFetcher(proxy_resolver); + } + + string BigUrl(in_port_t port) const override { + return "file://" + temp_file_.path(); + } + string SmallUrl(in_port_t port) const override { + test_utils::WriteFileString(temp_file_.path(), "small contents"); + return "file://" + temp_file_.path(); + } + string ErrorUrl(in_port_t port) const override { + return "file:///path/to/non-existing-file"; + } + + bool IsMock() const override { return false; } + bool IsMulti() const override { return false; } + bool IsHttpSupported() const override { return false; } + + void IgnoreServerAborting(HttpServer* server) const override {} + + HttpServer* CreateServer() override { return new NullHttpServer; } + + private: + test_utils::ScopedTempFile temp_file_{"ue_file_fetcher.XXXXXX"}; +}; + +// +// 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, + FileFetcherTest> + HttpFetcherTestTypes; +TYPED_TEST_CASE(HttpFetcherTest, HttpFetcherTestTypes); + + +namespace { +class HttpFetcherTestDelegate : public HttpFetcherDelegate { + public: + HttpFetcherTestDelegate() = default; + + void ReceivedBytes(HttpFetcher* /* fetcher */, + const void* bytes, + size_t length) override { + data.append(reinterpret_cast<const char*>(bytes), length); + // 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_++; + MessageLoop::current()->BreakLoop(); + } + + // Are we expecting an error response? (default: no) + bool is_expect_error_{false}; + + // Counters for callback invocations. + int times_transfer_complete_called_{0}; + int times_transfer_terminated_called_{0}; + int times_received_bytes_called_{0}; + + // The received data bytes. + string data; +}; + + +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); +} + +TYPED_TEST(HttpFetcherTest, ExtraHeadersInRequestTest) { + if (this->test_.IsMock() || !this->test_.IsHttpSupported()) + return; + + HttpFetcherTestDelegate delegate; + unique_ptr<HttpFetcher> fetcher(this->test_.NewSmallFetcher()); + fetcher->set_delegate(&delegate); + fetcher->SetHeader("User-Agent", "MyTest"); + fetcher->SetHeader("user-agent", "Override that header"); + fetcher->SetHeader("Authorization", "Basic user:passwd"); + + // Invalid headers. + fetcher->SetHeader("X-Foo", "Invalid\nHeader\nIgnored"); + fetcher->SetHeader("X-Bar: ", "I do not know how to parse"); + + // Hide Accept header normally added by default. + fetcher->SetHeader("Accept", ""); + + PythonHttpServer server; + int port = server.GetPort(); + ASSERT_TRUE(server.started_); + + this->loop_.PostTask( + FROM_HERE, + base::Bind(StartTransfer, + fetcher.get(), + LocalServerUrlForPath(port, "/echo-headers"))); + this->loop_.Run(); + + EXPECT_NE(string::npos, + delegate.data.find("user-agent: Override that header\r\n")); + EXPECT_NE(string::npos, + delegate.data.find("Authorization: Basic user:passwd\r\n")); + + EXPECT_EQ(string::npos, delegate.data.find("\nAccept:")); + EXPECT_EQ(string::npos, delegate.data.find("X-Foo: Invalid")); + EXPECT_EQ(string::npos, delegate.data.find("X-Bar: I do not")); +} + +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)); +} + +// This test will pause the fetcher while the download is not yet started +// because it is waiting for the proxy to be resolved. +TYPED_TEST(HttpFetcherTest, PauseWhileResolvingProxyTest) { + if (this->test_.IsMock() || !this->test_.IsHttpSupported()) + return; + MockProxyResolver mock_resolver; + unique_ptr<HttpFetcher> fetcher(this->test_.NewLargeFetcher(&mock_resolver)); + + // Saved arguments from the proxy call. + ProxiesResolvedFn proxy_callback = nullptr; + void* proxy_data = nullptr; + + EXPECT_CALL(mock_resolver, GetProxiesForUrl("http://fake_url", _, _)) + .WillOnce(DoAll( + SaveArg<1>(&proxy_callback), SaveArg<2>(&proxy_data), Return(true))); + fetcher->BeginTransfer("http://fake_url"); + testing::Mock::VerifyAndClearExpectations(&mock_resolver); + + // Pausing and unpausing while resolving the proxy should not affect anything. + fetcher->Pause(); + fetcher->Unpause(); + fetcher->Pause(); + // Proxy resolver comes back after we paused the fetcher. + ASSERT_TRUE(proxy_callback); + (*proxy_callback)({1, kNoProxy}, proxy_data); +} + +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() || !this->test_.IsHttpSupported()) + 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, static_cast<int>(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([](bool* timeout) { *timeout = true; }, + base::Unretained(&timeout)); + 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(static_cast<size_t>(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() || !this->test_.IsHttpSupported()) + 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() || !this->test_.IsHttpSupported()) + 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() || !this->test_.IsHttpSupported()) + 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, + size_t expected_size, + HttpResponseCode expected_response_code) { + MultiHttpFetcherTestDelegate delegate(expected_response_code); + delegate.fetcher_.reset(fetcher_in); + + MultiRangeHttpFetcher* multi_fetcher = + static_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/update_engine/common/hwid_override.cc b/update_engine/common/hwid_override.cc new file mode 100644 index 0000000..8800e94 --- /dev/null +++ b/update_engine/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/update_engine/common/hwid_override.h b/update_engine/common/hwid_override.h new file mode 100644 index 0000000..d39b572 --- /dev/null +++ b/update_engine/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/update_engine/common/hwid_override_unittest.cc b/update_engine/common/hwid_override_unittest.cc new file mode 100644 index 0000000..26ef30a --- /dev/null +++ b/update_engine/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()), + static_cast<int>(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()), + static_cast<int>(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/update_engine/common/mock_action.h b/update_engine/common/mock_action.h new file mode 100644 index 0000000..06acad1 --- /dev/null +++ b/update_engine/common/mock_action.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_COMMON_MOCK_ACTION_H_ +#define UPDATE_ENGINE_COMMON_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: + MockAction() { + ON_CALL(*this, Type()).WillByDefault(testing::Return("MockAction")); + } + + MOCK_METHOD0(PerformAction, void()); + MOCK_METHOD0(TerminateProcessing, void()); + MOCK_METHOD0(SuspendAction, void()); + MOCK_METHOD0(ResumeAction, void()); + MOCK_CONST_METHOD0(Type, std::string()); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_MOCK_ACTION_H_
diff --git a/update_engine/common/mock_action_processor.h b/update_engine/common/mock_action_processor.h new file mode 100644 index 0000000..04275c1 --- /dev/null +++ b/update_engine/common/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_COMMON_MOCK_ACTION_PROCESSOR_H_ +#define UPDATE_ENGINE_COMMON_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_COMMON_MOCK_ACTION_PROCESSOR_H_
diff --git a/update_engine/common/mock_hardware.h b/update_engine/common/mock_hardware.h new file mode 100644 index 0000000..1c4253a --- /dev/null +++ b/update_engine/common/mock_hardware.h
@@ -0,0 +1,97 @@ +// +// 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, AreDevFeaturesEnabled()) + .WillByDefault(testing::Invoke(&fake_, + &FakeHardware::AreDevFeaturesEnabled)); + ON_CALL(*this, IsOOBEEnabled()) + .WillByDefault(testing::Invoke(&fake_, + &FakeHardware::IsOOBEEnabled)); + 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_METHOD0(IsOOBEEnabled, bool()); + MOCK_CONST_METHOD1(IsOOBEComplete, bool(base::Time* out_time_of_oobe)); + MOCK_CONST_METHOD0(GetHardwareClass, std::string()); + MOCK_CONST_METHOD0(GetFirmwareVersion, std::string()); + MOCK_CONST_METHOD0(GetECVersion, std::string()); + MOCK_CONST_METHOD0(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/update_engine/common/mock_http_fetcher.cc b/update_engine/common/mock_http_fetcher.cc new file mode 100644 index 0000000..f1ae72a --- /dev/null +++ b/update_engine/common/mock_http_fetcher.cc
@@ -0,0 +1,162 @@ +// +// 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/strings/string_util.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::SetHeader(const std::string& header_name, + const std::string& header_value) { + extra_headers_[base::ToLowerASCII(header_name)] = header_value; +} + +std::string MockHttpFetcher::GetHeader(const std::string& header_name) const { + const auto it = extra_headers_.find(base::ToLowerASCII(header_name)); + if (it == extra_headers_.end()) + return ""; + return it->second; +} + +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/update_engine/common/mock_http_fetcher.h b/update_engine/common/mock_http_fetcher.h new file mode 100644 index 0000000..367802e --- /dev/null +++ b/update_engine/common/mock_http_fetcher.h
@@ -0,0 +1,157 @@ +// +// 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 <map> +#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; + + void SetHeader(const std::string& header_name, + const std::string& header_value) override; + + // Return the value of the header |header_name| or the empty string if not + // set. + std::string GetHeader(const std::string& header_name) const; + + // 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 extra headers set. + std::map<std::string, std::string> extra_headers_; + + // 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/update_engine/common/mock_prefs.h b/update_engine/common/mock_prefs.h new file mode 100644 index 0000000..0e639a2 --- /dev/null +++ b/update_engine/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/update_engine/common/multi_range_http_fetcher.cc b/update_engine/common/multi_range_http_fetcher.cc new file mode 100644 index 0000000..0a97b6e --- /dev/null +++ b/update_engine/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/update_engine/common/multi_range_http_fetcher.h b/update_engine/common/multi_range_http_fetcher.h new file mode 100644 index 0000000..8a91ead --- /dev/null +++ b/update_engine/common/multi_range_http_fetcher.h
@@ -0,0 +1,184 @@ +// +// 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 SetHeader(const std::string& header_name, + const std::string& header_value) override { + base_fetcher_->SetHeader(header_name, header_value); + } + + 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/update_engine/common/platform_constants.h b/update_engine/common/platform_constants.h new file mode 100644 index 0000000..419f322 --- /dev/null +++ b/update_engine/common/platform_constants.h
@@ -0,0 +1,64 @@ +// +// 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[]; + +extern const char kCACertificatesFilePath[]; +extern const char kSSLCertPath[]; +extern const char kSSLKeyPath[]; + +// 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[]; + +// Options passed to the filesystem when mounting the new partition during +// postinstall. +extern const char kPostinstallMountOptions[]; + +} // namespace constants +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_PLATFORM_CONSTANTS_H_
diff --git a/update_engine/common/platform_constants_android.cc b/update_engine/common/platform_constants_android.cc new file mode 100644 index 0000000..371fe26 --- /dev/null +++ b/update_engine/common/platform_constants_android.cc
@@ -0,0 +1,38 @@ +// +// 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"; +const char kPostinstallMountOptions[] = + "context=u:object_r:postinstall_file:s0"; + +} // namespace constants +} // namespace chromeos_update_engine
diff --git a/update_engine/common/platform_constants_chromeos.cc b/update_engine/common/platform_constants_chromeos.cc new file mode 100644 index 0000000..3ebcf8a --- /dev/null +++ b/update_engine/common/platform_constants_chromeos.cc
@@ -0,0 +1,38 @@ +// +// 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"; +const char kPostinstallMountOptions[] = ""; + +} // namespace constants +} // namespace chromeos_update_engine
diff --git a/update_engine/common/platform_constants_nestlabs.cc b/update_engine/common/platform_constants_nestlabs.cc new file mode 100644 index 0000000..42cd2f5 --- /dev/null +++ b/update_engine/common/platform_constants_nestlabs.cc
@@ -0,0 +1,40 @@ +// +// 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[] = ""; +const char kOmahaDefaultAUTestURL[] = ""; +const char kOmahaUpdaterID[] = ""; +const char kOmahaPlatformName[] = ""; +const char kOmahaResponseDeadlineFile[] = ""; + +const char kUpdatePayloadPublicKeyPath[] = + "/etc/update_engine/update-payload-key.pub.pem"; +const char kCACertificatesPath[] = "/system/etc/security/cacerts_google"; +const char kCACertificatesFilePath[] = "/etc/dropcam_calist.pem"; +const char kSSLCertPath[] = "/data/misc/certs/device_cert.pem"; +const char kSSLKeyPath[] = "/data/misc/certs/device_cert.key"; +// This directory is wiped during powerwash. +const char kNonVolatileDirectory[] = "/data/misc/update_engine"; +const char kPostinstallMountOptions[] = + "context=u:object_r:postinstall_file:s0"; + +} // namespace constants +} // namespace chromeos_update_engine
diff --git a/update_engine/common/prefs.cc b/update_engine/common/prefs.cc new file mode 100644 index 0000000..12d06c0 --- /dev/null +++ b/update_engine/common/prefs.cc
@@ -0,0 +1,196 @@ +// +// 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 PrefsBase::GetString(const string& key, string* value) const { + return storage_->GetKey(key, value); +} + +bool PrefsBase::SetString(const string& key, const string& value) { + TEST_AND_RETURN_FALSE(storage_->SetKey(key, 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); + } + return true; +} + +bool PrefsBase::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 PrefsBase::SetInt64(const string& key, const int64_t value) { + return SetString(key, base::Int64ToString(value)); +} + +bool PrefsBase::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 PrefsBase::SetBoolean(const string& key, const bool value) { + return SetString(key, value ? "true" : "false"); +} + +bool PrefsBase::Exists(const string& key) const { + return storage_->KeyExists(key); +} + +bool PrefsBase::Delete(const string& key) { + TEST_AND_RETURN_FALSE(storage_->DeleteKey(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; +} + +void PrefsBase::AddObserver(const string& key, ObserverInterface* observer) { + observers_[key].push_back(observer); +} + +void PrefsBase::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); +} + +// Prefs + +bool Prefs::Init(const base::FilePath& prefs_dir) { + return file_storage_.Init(prefs_dir); +} + +bool Prefs::FileStorage::Init(const base::FilePath& prefs_dir) { + prefs_dir_ = prefs_dir; + return true; +} + +bool Prefs::FileStorage::GetKey(const string& key, string* value) const { + base::FilePath filename; + TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename)); + if (!base::ReadFileToString(filename, value)) { + LOG(INFO) << key << " not present in " << prefs_dir_.value(); + return false; + } + return true; +} + +bool Prefs::FileStorage::SetKey(const string& key, const string& value) { + 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())); + return true; +} + +bool Prefs::FileStorage::KeyExists(const string& key) const { + base::FilePath filename; + TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename)); + return base::PathExists(filename); +} + +bool Prefs::FileStorage::DeleteKey(const string& key) { + base::FilePath filename; + TEST_AND_RETURN_FALSE(GetFileNameForKey(key, &filename)); + TEST_AND_RETURN_FALSE(base::DeleteFile(filename, false)); + return true; +} + +bool Prefs::FileStorage::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(base::IsAsciiAlpha(c) || base::IsAsciiDigit(c) || + c == '_' || c == '-'); + } + *filename = prefs_dir_.Append(key); + return true; +} + +// MemoryPrefs + +bool MemoryPrefs::MemoryStorage::GetKey(const string& key, + string* value) const { + auto it = values_.find(key); + if (it == values_.end()) + return false; + *value = it->second; + return true; +} + +bool MemoryPrefs::MemoryStorage::SetKey(const string& key, + const string& value) { + values_[key] = value; + return true; +} + +bool MemoryPrefs::MemoryStorage::KeyExists(const string& key) const { + return values_.find(key) != values_.end(); +} + +bool MemoryPrefs::MemoryStorage::DeleteKey(const string& key) { + auto it = values_.find(key); + if (it == values_.end()) + return false; + values_.erase(it); + return true; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/common/prefs.h b/update_engine/common/prefs.h new file mode 100644 index 0000000..0116454 --- /dev/null +++ b/update_engine/common/prefs.h
@@ -0,0 +1,168 @@ +// +// 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 given storage passed during construction. +class PrefsBase : public PrefsInterface { + public: + // Storage interface used to set and retrieve keys. + class StorageInterface { + public: + StorageInterface() = default; + virtual ~StorageInterface() = default; + + // Get the key named |key| and store its value in the referenced |value|. + // Returns whether the operation succeeded. + virtual bool GetKey(const std::string& key, std::string* value) const = 0; + + // Set the value of the key named |key| to |value| regardless of the + // previous value. Returns whether the operation succeeded. + virtual bool SetKey(const std::string& key, const std::string& value) = 0; + + // Returns whether the key named |key| exists. + virtual bool KeyExists(const std::string& key) const = 0; + + // Deletes the value associated with the key name |key|. Returns whether the + // key was deleted. + virtual bool DeleteKey(const std::string& key) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(StorageInterface); + }; + + explicit PrefsBase(StorageInterface* storage) : storage_(storage) {} + + // PrefsInterface methods. + bool GetString(const std::string& key, std::string* value) const override; + bool SetString(const std::string& key, 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: + // The registered observers watching for changes. + std::map<std::string, std::vector<ObserverInterface*>> observers_; + + // The concrete implementation of the storage used for the keys. + StorageInterface* storage_; + + DISALLOW_COPY_AND_ASSIGN(PrefsBase); +}; + +// 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 PrefsBase { + public: + Prefs() : PrefsBase(&file_storage_) {} + + // 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); + + private: + FRIEND_TEST(PrefsTest, GetFileNameForKey); + FRIEND_TEST(PrefsTest, GetFileNameForKeyBadCharacter); + FRIEND_TEST(PrefsTest, GetFileNameForKeyEmpty); + + class FileStorage : public PrefsBase::StorageInterface { + public: + FileStorage() = default; + + bool Init(const base::FilePath& prefs_dir); + + // PrefsBase::StorageInterface overrides. + bool GetKey(const std::string& key, std::string* value) const override; + bool SetKey(const std::string& key, const std::string& value) override; + bool KeyExists(const std::string& key) const override; + bool DeleteKey(const std::string& key) 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 concrete file storage implementation. + FileStorage file_storage_; + + DISALLOW_COPY_AND_ASSIGN(Prefs); +}; + +// Implements a preference store in memory. The stored values are lost when the +// object is destroyed. + +class MemoryPrefs : public PrefsBase { + public: + MemoryPrefs() : PrefsBase(&mem_storage_) {} + + private: + class MemoryStorage : public PrefsBase::StorageInterface { + public: + MemoryStorage() = default; + + // PrefsBase::StorageInterface overrides. + bool GetKey(const std::string& key, std::string* value) const override; + bool SetKey(const std::string& key, const std::string& value) override; + bool KeyExists(const std::string& key) const override; + bool DeleteKey(const std::string& key) override; + + private: + // The std::map holding the values in memory. + std::map<std::string, std::string> values_; + }; + + // The concrete memory storage implementation. + MemoryStorage mem_storage_; + + DISALLOW_COPY_AND_ASSIGN(MemoryPrefs); +}; +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_PREFS_H_
diff --git a/update_engine/common/prefs_interface.h b/update_engine/common/prefs_interface.h new file mode 100644 index 0000000..03ae3ec --- /dev/null +++ b/update_engine/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/update_engine/common/prefs_unittest.cc b/update_engine/common/prefs_unittest.cc new file mode 100644 index 0000000..73ceb00 --- /dev/null +++ b/update_engine/common/prefs_unittest.cc
@@ -0,0 +1,361 @@ +// +// 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 <limits> +#include <string> + +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.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(temp_dir_.CreateUniqueTempDir()); + prefs_dir_ = temp_dir_.path(); + ASSERT_TRUE(prefs_.Init(prefs_dir_)); + } + + 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::ScopedTempDir temp_dir_; + base::FilePath prefs_dir_; + Prefs prefs_; +}; + +TEST_F(PrefsTest, GetFileNameForKey) { + const char kAllvalidCharsKey[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-"; + base::FilePath path; + EXPECT_TRUE(prefs_.file_storage_.GetFileNameForKey(kAllvalidCharsKey, &path)); + EXPECT_EQ(prefs_dir_.Append(kAllvalidCharsKey).value(), path.value()); +} + +TEST_F(PrefsTest, GetFileNameForKeyBadCharacter) { + base::FilePath path; + EXPECT_FALSE(prefs_.file_storage_.GetFileNameForKey("ABC abc", &path)); +} + +TEST_F(PrefsTest, GetFileNameForKeyEmpty) { + base::FilePath path; + EXPECT_FALSE(prefs_.file_storage_.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, std::numeric_limits<int64_t>::max()))); + int64_t value; + EXPECT_TRUE(prefs_.GetInt64(kKey, &value)); + EXPECT_EQ(std::numeric_limits<int64_t>::max(), value); +} + +TEST_F(PrefsTest, GetInt64Min) { + ASSERT_TRUE(SetValue(kKey, base::StringPrintf( + "%" PRIi64, std::numeric_limits<int64_t>::min()))); + int64_t value; + EXPECT_TRUE(prefs_.GetInt64(kKey, &value)); + EXPECT_EQ(std::numeric_limits<int64_t>::min(), 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, std::numeric_limits<int64_t>::max())); + string value; + EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value)); + EXPECT_EQ(base::StringPrintf("%" PRIi64, std::numeric_limits<int64_t>::max()), + value); +} + +TEST_F(PrefsTest, SetInt64Min) { + EXPECT_TRUE(prefs_.SetInt64(kKey, std::numeric_limits<int64_t>::min())); + string value; + EXPECT_TRUE(base::ReadFileToString(prefs_dir_.Append(kKey), &value)); + EXPECT_EQ(base::StringPrintf("%" PRIi64, std::numeric_limits<int64_t>::min()), + 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); +} + +class MemoryPrefsTest : public ::testing::Test { + protected: + MemoryPrefs prefs_; +}; + +TEST_F(MemoryPrefsTest, BasicTest) { + EXPECT_FALSE(prefs_.Exists(kKey)); + int64_t value = 0; + EXPECT_FALSE(prefs_.GetInt64(kKey, &value)); + + EXPECT_TRUE(prefs_.SetInt64(kKey, 1234)); + EXPECT_TRUE(prefs_.Exists(kKey)); + EXPECT_TRUE(prefs_.GetInt64(kKey, &value)); + EXPECT_EQ(1234, value); + + EXPECT_TRUE(prefs_.Delete(kKey)); + EXPECT_FALSE(prefs_.Exists(kKey)); + EXPECT_FALSE(prefs_.Delete(kKey)); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/common/subprocess.cc b/update_engine/common/subprocess.cc new file mode 100644 index 0000000..4e6d352 --- /dev/null +++ b/update_engine/common/subprocess.cc
@@ -0,0 +1,294 @@ +// +// 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> + +#include "update_engine/common/utils.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, + const vector<int>& output_pipes, + 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); + } + + for (const int fd : output_pipes) { + proc->RedirectUsingPipe(fd, false); + } + proc->SetCloseUnusedFileDescriptors(true); + 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]; + size_t bytes_read; + do { + bytes_read = 0; + bool eof; + bool ok = utils::ReadAll( + record->stdout_fd, buf, arraysize(buf), &bytes_read, &eof); + record->stdout.append(buf, bytes_read); + if (!ok || eof) { + // There was either an error or an EOF condition, so we are done watching + // the file descriptor. + MessageLoop::current()->CancelTask(record->stdout_task_id); + record->stdout_task_id = MessageLoop::kTaskIdNull; + return; + } + } while (bytes_read); +} + +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; + + // 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); + } + // Release and close all the pipes after calling the callback so our + // redirected pipes are still alive. Releasing the process first makes + // Reset(0) not attempt to kill the process, which is already a zombie at this + // point. + record->proc.Release(); + record->proc.Reset(0); + + 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 vector<int>& output_pipes, + const ExecCallback& callback) { + unique_ptr<SubprocessRecord> record(new SubprocessRecord(callback)); + + if (!LaunchProcess(cmd, flags, output_pipes, &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] = std::move(record); + 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(); + // We don't care about output/return code, so we use SIGKILL here to ensure it + // will be killed, SIGTERM might lead to leaked subprocess. + if (kill(pid, SIGKILL) != 0) { + PLOG(WARNING) << "Error sending SIGKILL to " << pid; + } + // Release the pid now so we don't try to kill it if Subprocess is destroyed + // before the corresponding ChildExitedCallback() is called. + pid_record->second->proc.Release(); +} + +int Subprocess::GetPipeFd(pid_t pid, int fd) const { + auto pid_record = subprocess_records_.find(pid); + if (pid_record == subprocess_records_.end()) + return -1; + return pid_record->second->proc.GetPipe(fd); +} + +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; + // It doesn't make sense to redirect some pipes in the synchronous case + // because we won't be reading on our end, so we don't expose the output_pipes + // in this case. + if (!LaunchProcess(cmd, flags, {}, &proc)) { + 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/update_engine/common/subprocess.h b/update_engine/common/subprocess.h new file mode 100644 index 0000000..b655fb7 --- /dev/null +++ b/update_engine/common/subprocess.h
@@ -0,0 +1,152 @@ +// +// 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. The file descriptors specified in |output_pipes| will + // be available in the child as the writer end of a pipe. Use GetPipeFd() to + // know the reader end in the parent. Only stdin, stdout, stderr and the file + // descriptors in |output_pipes| will be open in the child. + // 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 std::vector<int>& output_pipes, + const ExecCallback& callback); + + // Kills the running process with SIGTERM and ignores the callback. + void KillExec(pid_t pid); + + // Return the parent end of the pipe mapped onto |fd| in the child |pid|. This + // file descriptor is available until the callback for the child |pid| + // returns. After that the file descriptor will be closed. The passed |fd| + // must be one of the file descriptors passed to ExecFlags() in + // |output_pipes|, otherwise returns -1. + int GetPipeFd(pid_t pid, int fd) const; + + // 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/update_engine/common/subprocess_unittest.cc b/update_engine/common/subprocess_unittest.cc new file mode 100644 index 0000000..7dbdf98 --- /dev/null +++ b/update_engine/common/subprocess_unittest.cc
@@ -0,0 +1,273 @@ +// +// 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 <poll.h> +#include <sys/types.h> +#include <unistd.h> + +#include <set> +#include <string> +#include <vector> + +#include <base/bind.h> +#include <base/files/scoped_temp_dir.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 <brillo/unittest_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 { + +#ifdef __ANDROID__ +#define kBinPath "/system/bin" +#define kUsrBinPath "/system/bin" +#else +#define kBinPath "/bin" +#define kUsrBinPath "/usr/bin" +#endif // __ANDROID__ + +} // namespace + +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 { + +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 (const 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(); +} + +void ExpectedDataOnPipe(const Subprocess* subprocess, + pid_t* pid, + int child_fd, + const string& child_fd_data, + int expected_return_code, + int return_code, + const string& /* output */) { + EXPECT_EQ(expected_return_code, return_code); + + // Verify that we can read the data from our end of |child_fd|. + int fd = subprocess->GetPipeFd(*pid, child_fd); + EXPECT_NE(-1, fd); + vector<char> buf(child_fd_data.size() + 1); + EXPECT_EQ(static_cast<ssize_t>(child_fd_data.size()), + HANDLE_EINTR(read(fd, buf.data(), buf.size()))); + EXPECT_EQ(child_fd_data, + string(buf.begin(), buf.begin() + child_fd_data.size())); + + 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({kBinPath "/false"}, + base::Bind(&ExpectedResults, 1, ""))); + loop_.Run(); +} + +TEST_F(SubprocessTest, EchoTest) { + EXPECT_TRUE(subprocess_.Exec( + {kBinPath "/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( + {kBinPath "/sh", "-c", "echo on stdout; echo on stderr >&2"}, + 0, + {}, + base::Bind(&ExpectedResults, 0, "on stdout\n"))); + loop_.Run(); +} + +TEST_F(SubprocessTest, PipeRedirectFdTest) { + pid_t pid; + pid = subprocess_.ExecFlags( + {kBinPath "/sh", "-c", "echo on pipe >&3"}, + 0, + {3}, + base::Bind(&ExpectedDataOnPipe, &subprocess_, &pid, 3, "on pipe\n", 0)); + EXPECT_NE(0, pid); + + // Wrong file descriptor values should return -1. + EXPECT_EQ(-1, subprocess_.GetPipeFd(pid, 123)); + loop_.Run(); + // Calling GetPipeFd() after the callback runs is invalid. + EXPECT_EQ(-1, subprocess_.GetPipeFd(pid, 3)); +} + +// Test that a pipe file descriptor open in the parent is not open in the child. +TEST_F(SubprocessTest, PipeClosedWhenNotRedirectedTest) { + brillo::ScopedPipe pipe; + + // test_subprocess will return with the errno of fstat, which should be EBADF + // if the passed file descriptor is closed in the child. + const vector<string> cmd = { + test_utils::GetBuildArtifactsPath("test_subprocess"), + "fstat", + std::to_string(pipe.writer)}; + EXPECT_TRUE(subprocess_.ExecFlags( + cmd, 0, {}, base::Bind(&ExpectedResults, EBADF, ""))); + loop_.Run(); +} + +TEST_F(SubprocessTest, EnvVarsAreFiltered) { + EXPECT_TRUE( + subprocess_.Exec({kUsrBinPath "/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 = { + kBinPath "/sh", + "-c", + "echo -n stdout-here; echo -n stderr-there >&2"}; + 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( + {kBinPath "/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."; +} +} // namespace + +// Test that you can cancel a program that's already running. +TEST_F(SubprocessTest, CancelTest) { + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + string fifo_path = tempdir.path().Append("fifo").value(); + EXPECT_EQ(0, mkfifo(fifo_path.c_str(), 0666)); + + // Start a process, make sure it is running and try to cancel it. We write + // two bytes to the fifo, the first one marks that the program is running and + // the second one marks that the process waited for a timeout and was not + // killed. We should read the first byte but not the second one. + vector<string> cmd = { + kBinPath "/sh", + "-c", + base::StringPrintf( + "echo -n X >\"%s\"; sleep 60; echo -n Y >\"%s\"; exit 1", + fifo_path.c_str(), + fifo_path.c_str())}; + uint32_t tag = Subprocess::Get().Exec(cmd, base::Bind(&CallbackBad)); + EXPECT_NE(0U, tag); + + int fifo_fd = HANDLE_EINTR(open(fifo_path.c_str(), O_RDONLY)); + EXPECT_GE(fifo_fd, 0); + + loop_.WatchFileDescriptor(FROM_HERE, + fifo_fd, + MessageLoop::WatchMode::kWatchRead, + false, + base::Bind([](int fifo_fd, uint32_t tag) { + char c; + EXPECT_EQ(1, HANDLE_EINTR(read(fifo_fd, &c, 1))); + EXPECT_EQ('X', c); + LOG(INFO) << "Killing tag " << tag; + Subprocess::Get().KillExec(tag); + }, fifo_fd, tag)); + + // This test would leak a callback that runs when the child process exits + // unless we wait for it to run. + brillo::MessageLoopRunUntil( + &loop_, + TimeDelta::FromSeconds(120), + base::Bind([] { return Subprocess::Get().subprocess_records_.empty(); })); + EXPECT_TRUE(Subprocess::Get().subprocess_records_.empty()); + // Check that there isn't anything else to read from the pipe. + char c; + EXPECT_EQ(0, HANDLE_EINTR(read(fifo_fd, &c, 1))); + IGNORE_EINTR(close(fifo_fd)); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/common/terminator.cc b/update_engine/common/terminator.cc new file mode 100644 index 0000000..62adafd --- /dev/null +++ b/update_engine/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/update_engine/common/terminator.h b/update_engine/common/terminator.h new file mode 100644 index 0000000..20616f6 --- /dev/null +++ b/update_engine/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/update_engine/common/terminator_unittest.cc b/update_engine/common/terminator_unittest.cc new file mode 100644 index 0000000..5e8302f --- /dev/null +++ b/update_engine/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/update_engine/common/test_utils.cc b/update_engine/common/test_utils.cc new file mode 100644 index 0000000..dfdc6b8 --- /dev/null +++ b/update_engine/common/test_utils.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/test_utils.h" + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/loop.h> +#include <linux/major.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/ioctl.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/error_code_utils.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() << ")"; +} + +void PrintTo(const ErrorCode& error_code, ::std::ostream* os) { + *os << utils::ErrorCodeToString(error_code); +} + +namespace test_utils { + +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, +}; + +string Readlink(const string& path) { + vector<char> buf(PATH_MAX + 1); + ssize_t r = readlink(path.c_str(), buf.data(), buf.size()); + if (r < 0) + return ""; + CHECK_LT(r, static_cast<ssize_t>(buf.size())); + return string(buf.begin(), buf.begin() + r); +} + +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()); +} + +bool BindToUnusedLoopDevice(const string& filename, + bool writable, + string* out_lo_dev_name) { + CHECK(out_lo_dev_name); + // Get the next available loop-device. + int control_fd = + HANDLE_EINTR(open("/dev/loop-control", O_RDWR | O_LARGEFILE)); + TEST_AND_RETURN_FALSE_ERRNO(control_fd >= 0); + int loop_number = ioctl(control_fd, LOOP_CTL_GET_FREE); + IGNORE_EINTR(close(control_fd)); + *out_lo_dev_name = StringPrintf("/dev/loop%d", loop_number); + + // Double check that the loop exists and is free. + int loop_device_fd = + HANDLE_EINTR(open(out_lo_dev_name->c_str(), O_RDWR | O_LARGEFILE)); + if (loop_device_fd == -1 && errno == ENOENT) { + // Workaround the case when the loop device doesn't exist. + TEST_AND_RETURN_FALSE_ERRNO(mknod(out_lo_dev_name->c_str(), + S_IFBLK | 0660, + makedev(LOOP_MAJOR, loop_number)) == 0); + loop_device_fd = + HANDLE_EINTR(open(out_lo_dev_name->c_str(), O_RDWR | O_LARGEFILE)); + } + TEST_AND_RETURN_FALSE_ERRNO(loop_device_fd != -1); + ScopedFdCloser loop_device_fd_closer(&loop_device_fd); + + struct loop_info64 device_info; + if (ioctl(loop_device_fd, LOOP_GET_STATUS64, &device_info) != -1 || + errno != ENXIO) { + PLOG(ERROR) << "Loop device " << out_lo_dev_name->c_str() + << " already in use"; + return false; + } + + // Open our data file and assign it to the loop device. + int data_fd = open(filename.c_str(), + (writable ? O_RDWR : O_RDONLY) | O_LARGEFILE | O_CLOEXEC); + TEST_AND_RETURN_FALSE_ERRNO(data_fd >= 0); + ScopedFdCloser data_fd_closer(&data_fd); + TEST_AND_RETURN_FALSE_ERRNO(ioctl(loop_device_fd, LOOP_SET_FD, data_fd) == 0); + + memset(&device_info, 0, sizeof(device_info)); + device_info.lo_offset = 0; + device_info.lo_sizelimit = 0; // 0 means whole file. + device_info.lo_flags = (writable ? 0 : LO_FLAGS_READ_ONLY); + device_info.lo_number = loop_number; + strncpy(reinterpret_cast<char*>(device_info.lo_file_name), + base::FilePath(filename).BaseName().value().c_str(), + LO_NAME_SIZE - 1); + device_info.lo_file_name[LO_NAME_SIZE - 1] = '\0'; + TEST_AND_RETURN_FALSE_ERRNO( + ioctl(loop_device_fd, LOOP_SET_STATUS64, &device_info) == 0); + return true; +} + +bool UnbindLoopDevice(const string& lo_dev_name) { + int loop_device_fd = + HANDLE_EINTR(open(lo_dev_name.c_str(), O_RDWR | O_LARGEFILE)); + if (loop_device_fd == -1 && errno == ENOENT) + return true; + TEST_AND_RETURN_FALSE_ERRNO(loop_device_fd != -1); + ScopedFdCloser loop_device_fd_closer(&loop_device_fd); + + struct loop_info64 device_info; + // Check if the device is bound before trying to unbind it. + int get_stat_err = ioctl(loop_device_fd, LOOP_GET_STATUS64, &device_info); + if (get_stat_err == -1 && errno == ENXIO) + return true; + + TEST_AND_RETURN_FALSE_ERRNO(ioctl(loop_device_fd, LOOP_CLR_FD) == 0); + 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); + } +} + +ScopedLoopMounter::ScopedLoopMounter(const string& file_path, + string* mnt_path, + unsigned long flags) { // NOLINT - long + EXPECT_TRUE(temp_dir_.CreateUniqueTempDir()); + *mnt_path = temp_dir_.path().value(); + + string loop_dev; + loop_binder_.reset( + new ScopedLoopbackDeviceBinder(file_path, true, &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(); +} + +string GetBuildArtifactsPath(const string& relative_path) { + return GetBuildArtifactsPath().Append(relative_path).value(); +} + +} // namespace test_utils +} // namespace chromeos_update_engine
diff --git a/update_engine/common/test_utils.h b/update_engine/common/test_utils.h new file mode 100644 index 0000000..ba9f5f2 --- /dev/null +++ b/update_engine/common/test_utils.h
@@ -0,0 +1,272 @@ +// +// 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 <base/files/scoped_temp_dir.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); +void PrintTo(const ErrorCode& error_code, ::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); + +// Binds provided |filename| to an unused loopback device, whose name is written +// to the string pointed to by |out_lo_dev_name|. The new loop device will be +// read-only unless |writable| is set to true. Returns true on success, false +// otherwise (along with corresponding test failures), in which case the content +// of |out_lo_dev_name| is unknown. +bool BindToUnusedLoopDevice(const std::string& filename, + bool writable, + std::string* out_lo_dev_name); +bool UnbindLoopDevice(const std::string& lo_dev_name); + +// 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()); +} + +// Reads a symlink from disk. Returns empty string on failure. +std::string Readlink(const std::string& path); + +// 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); + +// 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, + bool writable, + std::string* dev) { + is_bound_ = BindToUnusedLoopDevice(file, writable, &dev_); + EXPECT_TRUE(is_bound_); + + if (is_bound_ && dev) + *dev = dev_; + } + + ~ScopedLoopbackDeviceBinder() { + if (!is_bound_) + return; + + for (int retry = 0; retry < 5; retry++) { + if (UnbindLoopDevice(dev_)) + 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() : ScopedTempFile("update_engine_test_temp_file.XXXXXX") {} + + explicit ScopedTempFile(const std::string& pattern) { + EXPECT_TRUE(utils::MakeTempFile(pattern, &path_, nullptr)); + unlinker_.reset(new ScopedPathUnlinker(path_)); + } + + const std::string& path() const { return path_; } + + private: + std::string path_; + std::unique_ptr<ScopedPathUnlinker> unlinker_; +}; + +class ScopedLoopMounter { + public: + explicit ScopedLoopMounter(const std::string& file_path, + 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) + base::ScopedTempDir temp_dir_; + 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(); +// Returns the path of the build artifact specified in |relative_path|. +std::string GetBuildArtifactsPath(const std::string& relative_path); + +} // 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/update_engine/common/utils.cc b/update_engine/common/utils.cc new file mode 100644 index 0000000..8e2e6ae --- /dev/null +++ b/update_engine/common/utils.cc
@@ -0,0 +1,1060 @@ +// +// 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 <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 <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 "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" +#include "update_engine/payload_consumer/payload_constants.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"; + +// A pointer to a null-terminated string containing the root directory where all +// the temporary files should be created. If null, the system default is used +// instead. +const char* root_temp_dir = nullptr; + +// Return true if |disk_name| is an MTD or a UBI device. Note that this test is +// simply based on the name of the device. +bool IsMtdDeviceName(const string& disk_name) { + return base::StartsWith(disk_name, "/dev/ubi", + base::CompareCase::SENSITIVE) || + base::StartsWith(disk_name, "/dev/mtd", base::CompareCase::SENSITIVE); +} + +// Return the device name for the corresponding partition on a NAND device. +// WARNING: This function returns device names that are not mountable. +string MakeNandPartitionName(int partition_num) { + switch (partition_num) { + case 2: + case 4: + case 6: { + return base::StringPrintf("/dev/mtd%d", partition_num); + } + default: { + return base::StringPrintf("/dev/ubi%d_0", partition_num); + } + } +} + +// Return the device name for the corresponding partition on a NAND device that +// may be mountable (but may not be writable). +string MakeNandPartitionNameForMount(int partition_num) { + switch (partition_num) { + case 2: + case 4: + case 6: { + return base::StringPrintf("/dev/mtd%d", partition_num); + } + case 3: + case 5: + case 7: { + return base::StringPrintf("/dev/ubiblock%d_0", partition_num); + } + default: { + return base::StringPrintf("/dev/ubi%d_0", partition_num); + } + } +} + +// If |path| is absolute, or explicit relative to the current working directory, +// leaves it as is. Otherwise, uses the system's temp directory, as defined by +// base::GetTempDir() and prepends it to |path|. On success stores the full +// temporary path in |template_path| and returns true. +bool GetTempName(const string& path, base::FilePath* template_path) { + if (path[0] == '/' || + base::StartsWith(path, "./", base::CompareCase::SENSITIVE) || + base::StartsWith(path, "../", base::CompareCase::SENSITIVE)) { + *template_path = base::FilePath(path); + return true; + } + + base::FilePath temp_dir; + if (root_temp_dir) { + temp_dir = base::FilePath(root_temp_dir); + } else { +#ifdef __ANDROID__ + temp_dir = base::FilePath(constants::kNonVolatileDirectory).Append("tmp"); +#else + TEST_AND_RETURN_FALSE(base::GetTempDir(&temp_dir)); +#endif // __ANDROID__ + } + if (!base::PathExists(temp_dir)) + TEST_AND_RETURN_FALSE(base::CreateDirectory(temp_dir)); + *template_path = temp_dir.Append(path); + return true; +} + +} // namespace + +namespace utils { + +void SetRootTempDir(const char* new_root_temp_dir) { + root_temp_dir = new_root_temp_dir; +} + +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 ReadAll( + int fd, void* buf, size_t count, size_t* out_bytes_read, bool* eof) { + char* c_buf = static_cast<char*>(buf); + size_t bytes_read = 0; + *eof = false; + while (bytes_read < count) { + ssize_t rc = HANDLE_EINTR(read(fd, c_buf + bytes_read, count - bytes_read)); + if (rc < 0) { + // EAGAIN and EWOULDBLOCK are normal return values when there's no more + // input and we are in non-blocking mode. + if (errno != EWOULDBLOCK && errno != EAGAIN) { + PLOG(ERROR) << "Error reading fd " << fd; + *out_bytes_read = bytes_read; + return false; + } + break; + } else if (rc == 0) { + // A value of 0 means that we reached EOF and there is nothing else to + // read from this fd. + *eof = true; + break; + } else { + bytes_read += rc; + } + } + *out_bytes_read = bytes_read; + 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(const 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(const FileDescriptorPtr& fd, + const void* buf, + size_t count, + off_t offset) { + TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) != + static_cast<off_t>(-1)); + return WriteAll(fd, buf, count); +} + +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(const FileDescriptorPtr& fd, void* buf, size_t count, off_t offset, + ssize_t* out_bytes_read) { + TEST_AND_RETURN_FALSE_ERRNO(fd->Seek(offset, SEEK_SET) != + static_cast<off_t>(-1)); + char* c_buf = static_cast<char*>(buf); + 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::StartsWith(partition_name, "/dev/", + base::CompareCase::SENSITIVE)) { + 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::StartsWith(disk_name, "/dev/", base::CompareCase::SENSITIVE)) { + 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 SetBlockDeviceReadOnly(const string& device, bool read_only) { + int fd = HANDLE_EINTR(open(device.c_str(), O_RDONLY | O_CLOEXEC)); + if (fd < 0) { + PLOG(ERROR) << "Opening block device " << device; + return false; + } + ScopedFdCloser fd_closer(&fd); + // We take no action if not needed. + int read_only_flag; + int expected_flag = read_only ? 1 : 0; + int rc = ioctl(fd, BLKROGET, &read_only_flag); + // In case of failure reading the setting we will try to set it anyway. + if (rc == 0 && read_only_flag == expected_flag) + return true; + + rc = ioctl(fd, BLKROSET, &expected_flag); + if (rc != 0) { + PLOG(ERROR) << "Marking block device " << device << " as read_only=" + << expected_flag; + return false; + } + return true; +} + +bool MountFilesystem(const string& device, + const string& mountpoint, + unsigned long mountflags, // NOLINT(runtime/int) + const string& type, + const string& fs_mount_options) { + vector<const char*> fstypes; + if (type.empty()) { + fstypes = {"ext2", "ext3", "ext4", "squashfs"}; + } else { + fstypes = {type.c_str()}; + } + for (const char* fstype : fstypes) { + int rc = mount(device.c_str(), mountpoint.c_str(), fstype, mountflags, + fs_mount_options.c_str()); + if (rc == 0) + return true; + + PLOG(WARNING) << "Unable to mount destination device " << device + << " on " << mountpoint << " as " << fstype; + } + if (!type.empty()) { + LOG(ERROR) << "Unable to mount " << device << " with any supported type"; + } + return false; +} + +bool UnmountFilesystem(const string& mountpoint) { + int num_retries = 1; + for (;; ++num_retries) { + if (umount(mountpoint.c_str()) == 0) + return true; + if (errno != EBUSY || num_retries >= kUnmountMaxNumOfRetries) + break; + usleep(kUnmountRetryIntervalInMicroseconds); + } + if (errno == EINVAL) { + LOG(INFO) << "Not a mountpoint: " << mountpoint; + return false; + } + PLOG(WARNING) << "Error unmounting " << mountpoint << " after " << num_retries + << " attempts. Lazy unmounting instead, error was"; + if (umount2(mountpoint.c_str(), MNT_DETACH) != 0) { + PLOG(ERROR) << "Lazy unmount failed"; + return false; + } + return true; +} + +// 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"; +} + +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 +#ifdef USE_NESTLABS + << " to ErrorCode::kErrorInHTTPResponse"; +#else + << " to ErrorCode::kOmahaErrorInHTTPResponse"; +#endif + base_code = ErrorCode::kOmahaErrorInHTTPResponse; + } + + return base_code; +} + +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 IsZlibCompatible(const string& fingerprint) { + if (fingerprint.size() != sizeof(kCompatibleZlibFingerprint[0]) - 1) { + LOG(ERROR) << "Invalid fingerprint: " << fingerprint; + return false; + } + for (auto& f : kCompatibleZlibFingerprint) { + if (base::CompareCaseInsensitiveASCII(fingerprint, f) == 0) { + 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/update_engine/common/utils.h b/update_engine/common/utils.h new file mode 100644 index 0000000..3cffcdd --- /dev/null +++ b/update_engine/common/utils.h
@@ -0,0 +1,438 @@ +// +// 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(const FileDescriptorPtr& fd, const void* buf, size_t count); +bool PWriteAll(const FileDescriptorPtr& fd, + const void* buf, + size_t count, + off_t offset); + +// Calls read() repeatedly until |count| bytes are read or EOF or EWOULDBLOCK +// is reached. Returns whether all read() calls succeeded (including EWOULDBLOCK +// as a success case), sets |eof| to whether the eof was reached and sets +// |out_bytes_read| to the actual number of bytes read regardless of the return +// value. +bool ReadAll( + int fd, void* buf, size_t count, size_t* out_bytes_read, bool* eof); + +// 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(const 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); + +// Setup the directory |new_root_temp_dir| to be used as the root directory for +// temporary files instead of the system's default. If the directory doesn't +// exists, it will be created when first used. +// NOTE: The memory pointed by |new_root_temp_dir| must be available until this +// function is called again with a different value. +void SetRootTempDir(const char* new_root_temp_dir); + +// 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); + +// 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); + +// Set the read-only attribute on the block device |device| to the value passed +// in |read_only|. Return whether the operation succeeded. +bool SetBlockDeviceReadOnly(const std::string& device, bool read_only); + +// Synchronously mount or unmount a filesystem. Return true on success. +// When mounting, it will attempt to mount the device as the passed filesystem +// type |type|, with the passed |flags| options. If |type| is empty, "ext2", +// "ext3", "ext4" and "squashfs" will be tried. +bool MountFilesystem(const std::string& device, + const std::string& mountpoint, + unsigned long flags, // NOLINT(runtime/int) + const std::string& type, + const std::string& fs_mount_options); +bool UnmountFilesystem(const std::string& mountpoint); + +// 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); + +// 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; + } +} + +// 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); + +// 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); + +// Returns whether zlib |fingerprint| is compatible with zlib we are using. +bool IsZlibCompatible(const std::string& fingerprint); + +// 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); +}; + +// 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/update_engine/common/utils_unittest.cc b/update_engine/common/utils_unittest.cc new file mode 100644 index 0000000..634de01 --- /dev/null +++ b/update_engine/common/utils_unittest.cc
@@ -0,0 +1,479 @@ +// +// 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 <fcntl.h> +#include <stdint.h> +#include <sys/mount.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <string> +#include <vector> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "update_engine/common/test_utils.h" + +using 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) { + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + string temp_file = temp_dir.path().Append("temp-file").value(); + EXPECT_TRUE(utils::WriteFile(temp_file.c_str(), "", 0)); + string temp_symlink = temp_dir.path().Append("temp-symlink").value(); + EXPECT_EQ(0, symlink(temp_file.c_str(), temp_symlink.c_str())); + EXPECT_FALSE(utils::IsSymlink(temp_dir.path().value().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")); +} + +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")); +} + +TEST(UtilsTest, FuzzIntTest) { + static const uint32_t kRanges[] = { 0, 1, 2, 20 }; + for (uint32_t range : kRanges) { + const int kValue = 50; + for (int tries = 0; tries < 100; ++tries) { + uint32_t value = utils::FuzzInt(kValue, range); + EXPECT_GE(value, kValue - range / 2); + EXPECT_LE(value, kValue + range - range / 2); + } + } +} + +namespace { +void GetFileFormatTester(const string& expected, + const vector<uint8_t>& contents) { + test_utils::ScopedTempFile file; + ASSERT_TRUE(utils::WriteFile(file.path().c_str(), + reinterpret_cast<const char*>(contents.data()), + contents.size())); + EXPECT_EQ(expected, utils::GetFileFormat(file.path())); +} +} // 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, 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(static_cast<off_t>(expected_contents.size()), + utils::FileSize(path.value())); +} + +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(123U, minor_version); +} + +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()); +} + +TEST(UtilsTest, RunAsRootUnmountFilesystemFailureTest) { + EXPECT_FALSE(utils::UnmountFilesystem("/path/to/non-existing-dir")); +} + +TEST(UtilsTest, RunAsRootUnmountFilesystemBusyFailureTest) { + string tmp_image; + EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &tmp_image, nullptr)); + ScopedPathUnlinker tmp_image_unlinker(tmp_image); + + EXPECT_TRUE(base::CopyFile( + test_utils::GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"), + base::FilePath(tmp_image))); + + base::ScopedTempDir mnt_dir; + EXPECT_TRUE(mnt_dir.CreateUniqueTempDir()); + + string loop_dev; + test_utils::ScopedLoopbackDeviceBinder loop_binder( + tmp_image, true, &loop_dev); + + // This is the actual test part. While we hold a file descriptor open for the + // mounted filesystem, umount should still succeed. + EXPECT_TRUE(utils::MountFilesystem( + loop_dev, mnt_dir.path().value(), MS_RDONLY, "ext4", "")); + string target_file = mnt_dir.path().Append("empty-file").value(); + int fd = HANDLE_EINTR(open(target_file.c_str(), O_RDONLY)); + EXPECT_GE(fd, 0); + EXPECT_TRUE(utils::UnmountFilesystem(mnt_dir.path().value())); + IGNORE_EINTR(close(fd)); + // The filesystem was already unmounted so this call should fail. + EXPECT_FALSE(utils::UnmountFilesystem(mnt_dir.path().value())); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/common_service.cc b/update_engine/common_service.cc new file mode 100644 index 0000000..33cfabc --- /dev/null +++ b/update_engine/common_service.cc
@@ -0,0 +1,392 @@ +// +// 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_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/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/omaha_utils.h" +#include "update_engine/p2p_manager.h" +#include "update_engine/payload_state_interface.h" +#if USE_NESTLABS +#include "update_engine/update_attempter_nestlabs.h" +#else +#include "update_engine/update_attempter.h" +#endif + +using base::StringPrintf; +using brillo::ErrorPtr; +using brillo::string_utils::ToString; +using std::set; +using std::string; + +namespace chromeos_update_engine { + +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, + UpdateEngineService::kErrorDomain, + UpdateEngineService::kErrorFailed, + reason); + LOG(ERROR) << "Sending Update Engine Failure: " << location.ToString() << ": " + << reason; +} +} // namespace + +const char* const UpdateEngineService::kErrorDomain = "update_engine"; +#ifdef USE_NESTLABS +const char* const UpdateEngineService::kErrorFailed = + "com.nestlabs.UpdateEngine.Error.Failed"; +#else +const char* const UpdateEngineService::kErrorFailed = + "org.chromium.UpdateEngine.Error.Failed"; +#endif + +UpdateEngineService::UpdateEngineService(SystemState* system_state) + : system_state_(system_state) { +} + +// UpdateEngineInterfaceInterface methods implementation. + +bool UpdateEngineService::AttemptUpdate(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; + } + // Update the weave state because updated the target channel. + system_state_->update_attempter()->BroadcastChannel(); + 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::SetCohortHint(ErrorPtr* error, + string in_cohort_hint) { + PrefsInterface* prefs = system_state_->prefs(); + + // It is ok to override the cohort hint with an invalid value since it is + // stored in stateful partition. The code reading it should sanitize it + // anyway. + if (!prefs->SetString(kPrefsOmahaCohortHint, in_cohort_hint)) { + LogAndSetError( + error, + FROM_HERE, + StringPrintf("Error setting the cohort hint value to \"%s\".", + in_cohort_hint.c_str())); + return false; + } + return true; +} + +bool UpdateEngineService::GetCohortHint(ErrorPtr* error, + string* out_cohort_hint) { + PrefsInterface* prefs = system_state_->prefs(); + + *out_cohort_hint = ""; + if (prefs->Exists(kPrefsOmahaCohortHint) && + !prefs->GetString(kPrefsOmahaCohortHint, out_cohort_hint)) { + LogAndSetError(error, FROM_HERE, "Error getting the cohort hint."); + return false; + } + 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(ConnectionType::kCellular, + ConnectionTethering::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; +} + +bool UpdateEngineService::GetLastAttemptError(ErrorPtr* /* error */, + int32_t* out_last_attempt_error) { + ErrorCode error_code = system_state_->payload_state()->GetAttemptErrorCode(); + *out_last_attempt_error = static_cast<int>(error_code); + return true; +} + +bool UpdateEngineService::GetEolStatus(ErrorPtr* error, + int32_t* out_eol_status) { + PrefsInterface* prefs = system_state_->prefs(); + + string str_eol_status; + if (prefs->Exists(kPrefsOmahaEolStatus) && + !prefs->GetString(kPrefsOmahaEolStatus, &str_eol_status)) { + LogAndSetError(error, FROM_HERE, "Error getting the end-of-life status."); + return false; + } + + // StringToEolStatus will return kSupported for invalid values. + *out_eol_status = static_cast<int32_t>(StringToEolStatus(str_eol_status)); + return true; +} + +#ifdef USE_NESTLABS +bool UpdateEngineService::MarkBootSuccessful(brillo::ErrorPtr* /* error */) { + system_state_->update_attempter()->UpdateBootFlags(); + return true; +} +#endif // USE_NESTLABS + +} // namespace chromeos_update_engine
diff --git a/update_engine/common_service.h b/update_engine/common_service.h new file mode 100644 index 0000000..d83a29e --- /dev/null +++ b/update_engine/common_service.h
@@ -0,0 +1,157 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_COMMON_SERVICE_H_ +#define UPDATE_ENGINE_COMMON_SERVICE_H_ + +#include <inttypes.h> + +#include <string> + +#include <base/memory/ref_counted.h> +#include <brillo/errors/error.h> + +#include "update_engine/system_state.h" + +namespace chromeos_update_engine { + +class UpdateEngineService { + public: + // Flags used in the AttemptUpdateWithFlags() D-Bus method. + typedef enum { + kAttemptUpdateFlagNonInteractive = (1<<0) + } AttemptUpdateFlags; + + // Error domain for all the service errors. + static const char* const kErrorDomain; + + // Generic service error. + static const char* const kErrorFailed; + + explicit UpdateEngineService(SystemState* system_state); + virtual ~UpdateEngineService() = default; + + bool AttemptUpdate(brillo::ErrorPtr* error, + const std::string& in_app_version, + const std::string& in_omaha_url, + int32_t in_flags_as_int); + + bool AttemptRollback(brillo::ErrorPtr* error, bool in_powerwash); + + // 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); + + // Resets the status of the update_engine to idle, ignoring any applied + // update. This is used for development only. + bool ResetStatus(brillo::ErrorPtr* error); + + // 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); + + // Reboots the device if an update is applied and a reboot is required. + bool RebootIfNeeded(brillo::ErrorPtr* error); + + // 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); + + // 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); + + // Sets the current "cohort hint" value to |in_cohort_hint|. The cohort hint + // is sent back to Omaha on every request and can be used as a hint of what + // cohort should we be put on. + bool SetCohortHint(brillo::ErrorPtr* error, std::string in_cohort_hint); + + // Return the current cohort hint. This value can be set with SetCohortHint() + // and can also be updated from Omaha on every update check request. + bool GetCohortHint(brillo::ErrorPtr* error, std::string* out_cohort_hint); + + // 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); + + // 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); + + // 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); + + // 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); + + // 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); + + // 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); + + // Returns the name of kernel partition that can be rolled back into. + bool GetRollbackPartition(brillo::ErrorPtr* error, + std::string* out_rollback_partition_name); + + // Returns the last UpdateAttempt error. + bool GetLastAttemptError(brillo::ErrorPtr* error, + int32_t* out_last_attempt_error); + + // Returns the current end-of-life status of the device. This value is updated + // on every update check and persisted on disk across reboots. + bool GetEolStatus(brillo::ErrorPtr* error, int32_t* out_eol_status); + +#ifdef USE_NESTLABS + bool MarkBootSuccessful(brillo::ErrorPtr* error); +#endif // USE_NESTLABS + + private: + SystemState* system_state_; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_COMMON_SERVICE_H_
diff --git a/update_engine/common_service_unittest.cc b/update_engine/common_service_unittest.cc new file mode 100644 index 0000000..0a7bfc3 --- /dev/null +++ b/update_engine/common_service_unittest.cc
@@ -0,0 +1,151 @@ +// +// 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_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/common/fake_prefs.h" +#include "update_engine/fake_system_state.h" +#include "update_engine/omaha_utils.h" + +using std::string; +using testing::Return; +using testing::SetArgumentPointee; +using testing::_; + +namespace chromeos_update_engine { + +class UpdateEngineServiceTest : public ::testing::Test { + protected: + UpdateEngineServiceTest() + : mock_update_attempter_(fake_system_state_.mock_update_attempter()), + common_service_(&fake_system_state_) {} + + 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 common_service_; +}; + +TEST_F(UpdateEngineServiceTest, AttemptUpdate) { + 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(common_service_.AttemptUpdate( + &error_, "app_ver", "url", + UpdateEngineService::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(common_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(common_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(common_service_.SetChannel(&error_, "foo-channel", true)); + ASSERT_NE(nullptr, error_); + EXPECT_TRUE(error_->HasError(UpdateEngineService::kErrorDomain, + UpdateEngineService::kErrorFailed)); +} + +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(common_service_.GetChannel( + &error_, true /* get_current_channel */, &channel)); + EXPECT_EQ(nullptr, error_); + EXPECT_EQ("current", channel); + + EXPECT_TRUE(common_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(common_service_.ResetStatus(&error_)); + EXPECT_EQ(nullptr, error_); +} + +TEST_F(UpdateEngineServiceTest, ResetStatusFails) { + EXPECT_CALL(*mock_update_attempter_, ResetStatus()).WillOnce(Return(false)); + EXPECT_FALSE(common_service_.ResetStatus(&error_)); + ASSERT_NE(nullptr, error_); + EXPECT_TRUE(error_->HasError(UpdateEngineService::kErrorDomain, + UpdateEngineService::kErrorFailed)); +} + +TEST_F(UpdateEngineServiceTest, GetEolStatusTest) { + FakePrefs fake_prefs; + fake_system_state_.set_prefs(&fake_prefs); + // The default value should be "supported". + int32_t eol_status = static_cast<int32_t>(EolStatus::kEol); + EXPECT_TRUE(common_service_.GetEolStatus(&error_, &eol_status)); + EXPECT_EQ(nullptr, error_); + EXPECT_EQ(EolStatus::kSupported, static_cast<EolStatus>(eol_status)); + + fake_prefs.SetString(kPrefsOmahaEolStatus, "security-only"); + EXPECT_TRUE(common_service_.GetEolStatus(&error_, &eol_status)); + EXPECT_EQ(nullptr, error_); + EXPECT_EQ(EolStatus::kSecurityOnly, static_cast<EolStatus>(eol_status)); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/connection_manager.cc b/update_engine/connection_manager.cc new file mode 100644 index 0000000..f72d9e8 --- /dev/null +++ b/update_engine/connection_manager.cc
@@ -0,0 +1,205 @@ +// +// 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/connection_utils.h" +#include "update_engine/shill_proxy.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 connection_manager { +std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager( + SystemState* system_state) { + return std::unique_ptr<ConnectionManagerInterface>( + new ConnectionManager(new ShillProxy(), system_state)); +} +} + +ConnectionManager::ConnectionManager(ShillProxyInterface* shill_proxy, + SystemState* system_state) + : shill_proxy_(shill_proxy), system_state_(system_state) {} + +bool ConnectionManager::IsUpdateAllowedOver( + ConnectionType type, ConnectionTethering tethering) const { + switch (type) { + case ConnectionType::kBluetooth: + return false; + + case ConnectionType::kCellular: { + set<string> allowed_types; + const policy::DevicePolicy* device_policy = + system_state_->device_policy(); + + // 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 == ConnectionTethering::kConfirmed) { + // Treat this connection as if it is a cellular connection. + LOG(INFO) << "Current connection is confirmed tethered, using Cellular " + "setting."; + return IsUpdateAllowedOver(ConnectionType::kCellular, + ConnectionTethering::kUnknown); + } + return true; + } +} + +bool ConnectionManager::GetConnectionProperties( + ConnectionType* out_type, ConnectionTethering* 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, + ConnectionType* out_type, + ConnectionTethering* 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 = ConnectionTethering::kUnknown; + } else { + // If the property doesn't contain a string value, the empty string will + // become kUnknown. + *out_tethering = connection_utils::ParseConnectionTethering( + 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 = ConnectionType::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 = ConnectionType::kUnknown; + } else { + *out_type = connection_utils::ParseConnectionType( + prop_physical->second.TryGet<string>()); + } + } else { + *out_type = connection_utils::ParseConnectionType(type_str); + } + return true; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/connection_manager.h b/update_engine/connection_manager.h new file mode 100644 index 0000000..e5a9d49 --- /dev/null +++ b/update_engine/connection_manager.h
@@ -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. +// + +#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 { + +// 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: + // 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(ConnectionType* out_type, + ConnectionTethering* out_tethering) override; + bool IsUpdateAllowedOver(ConnectionType type, + ConnectionTethering 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, + ConnectionType* out_type, + ConnectionTethering* out_tethering); + + // The mockable interface to access the shill DBus proxies. + std::unique_ptr<ShillProxyInterface> shill_proxy_; + + // The global context for update_engine. + SystemState* system_state_; + + DISALLOW_COPY_AND_ASSIGN(ConnectionManager); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_CONNECTION_MANAGER_H_
diff --git a/update_engine/connection_manager_android.cc b/update_engine/connection_manager_android.cc new file mode 100644 index 0000000..2dd824a --- /dev/null +++ b/update_engine/connection_manager_android.cc
@@ -0,0 +1,38 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/connection_manager_android.h" + +namespace chromeos_update_engine { + +namespace connection_manager { +std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager( + SystemState* system_state) { + return std::unique_ptr<ConnectionManagerInterface>( + new ConnectionManagerAndroid()); +} +} + +bool ConnectionManagerAndroid::GetConnectionProperties( + ConnectionType* out_type, ConnectionTethering* out_tethering) { + return false; +} +bool ConnectionManagerAndroid::IsUpdateAllowedOver( + ConnectionType type, ConnectionTethering tethering) const { + return true; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/connection_manager_android.h b/update_engine/connection_manager_android.h new file mode 100644 index 0000000..0cd5e73 --- /dev/null +++ b/update_engine/connection_manager_android.h
@@ -0,0 +1,43 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_CONNECTION_MANAGER_ANDROID_H_ +#define UPDATE_ENGINE_CONNECTION_MANAGER_ANDROID_H_ + +#include <base/macros.h> + +#include "update_engine/connection_manager_interface.h" + +namespace chromeos_update_engine { + +// TODO(senj): Remove this class and use ShillProvider from the UpdateManager. +class ConnectionManagerAndroid : public ConnectionManagerInterface { + public: + ConnectionManagerAndroid() = default; + ~ConnectionManagerAndroid() override = default; + + // ConnectionManagerInterface overrides. + bool GetConnectionProperties(ConnectionType* out_type, + ConnectionTethering* out_tethering) override; + bool IsUpdateAllowedOver(ConnectionType type, + ConnectionTethering tethering) const override; + + DISALLOW_COPY_AND_ASSIGN(ConnectionManagerAndroid); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_CONNECTION_MANAGER_ANDROID_H_
diff --git a/update_engine/connection_manager_interface.h b/update_engine/connection_manager_interface.h new file mode 100644 index 0000000..df8eb4b --- /dev/null +++ b/update_engine/connection_manager_interface.h
@@ -0,0 +1,64 @@ +// +// 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 <memory> + +#include <base/macros.h> + +#include "update_engine/connection_utils.h" + +namespace chromeos_update_engine { + +class SystemState; + +// This class exposes a generic interface to the connection manager +// (e.g FlimFlam, Shill, etc.) to consolidate all connection-related +// logic in update_engine. +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(ConnectionType* out_type, + ConnectionTethering* 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(ConnectionType type, + ConnectionTethering tethering) const = 0; + + protected: + ConnectionManagerInterface() = default; + + private: + DISALLOW_COPY_AND_ASSIGN(ConnectionManagerInterface); +}; + +namespace connection_manager { +// Factory function which creates a ConnectionManager. +std::unique_ptr<ConnectionManagerInterface> CreateConnectionManager( + SystemState* system_state); +} + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_CONNECTION_MANAGER_INTERFACE_H_
diff --git a/update_engine/connection_manager_unittest.cc b/update_engine/connection_manager_unittest.cc new file mode 100644 index 0000000..0bb5547 --- /dev/null +++ b/update_engine/connection_manager_unittest.cc
@@ -0,0 +1,396 @@ +// +// 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 chromeos_update_engine::connection_utils::StringForConnectionType; +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: + ConnectionManagerTest() : fake_shill_proxy_(new FakeShillProxy()) {} + + 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, + ConnectionType expected_type); + void TestWithServiceTethering( + const char* service_tethering, + ConnectionTethering expected_tethering); + + brillo::FakeMessageLoop loop_{nullptr}; + FakeSystemState fake_system_state_; + FakeShillProxy* fake_shill_proxy_; + + // ConnectionManager under test. + ConnectionManager cmut_{fake_shill_proxy_, &fake_system_state_}; +}; + +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, + ConnectionType expected_type) { + SetManagerReply("/service/guest/network", true); + SetServiceReply("/service/guest/network", + service_type, + physical_technology, + shill::kTetheringNotDetectedState); + + ConnectionType type; + ConnectionTethering 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, + ConnectionTethering expected_tethering) { + SetManagerReply("/service/guest/network", true); + SetServiceReply( + "/service/guest/network", shill::kTypeWifi, nullptr, service_tethering); + + ConnectionType type; + ConnectionTethering 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, ConnectionType::kEthernet); + TestWithServiceType(shill::kTypeWifi, nullptr, ConnectionType::kWifi); + TestWithServiceType(shill::kTypeWimax, nullptr, ConnectionType::kWimax); + TestWithServiceType( + shill::kTypeBluetooth, nullptr, ConnectionType::kBluetooth); + TestWithServiceType(shill::kTypeCellular, nullptr, ConnectionType::kCellular); +} + +TEST_F(ConnectionManagerTest, PhysicalTechnologyTest) { + TestWithServiceType(shill::kTypeVPN, nullptr, ConnectionType::kUnknown); + TestWithServiceType( + shill::kTypeVPN, shill::kTypeVPN, ConnectionType::kUnknown); + TestWithServiceType(shill::kTypeVPN, shill::kTypeWifi, ConnectionType::kWifi); + TestWithServiceType( + shill::kTypeVPN, shill::kTypeWimax, ConnectionType::kWimax); +} + +TEST_F(ConnectionManagerTest, TetheringTest) { + TestWithServiceTethering(shill::kTetheringConfirmedState, + ConnectionTethering::kConfirmed); + TestWithServiceTethering(shill::kTetheringNotDetectedState, + ConnectionTethering::kNotDetected); + TestWithServiceTethering(shill::kTetheringSuspectedState, + ConnectionTethering::kSuspected); + TestWithServiceTethering("I'm not a valid property value =)", + ConnectionTethering::kUnknown); +} + +TEST_F(ConnectionManagerTest, UnknownTest) { + TestWithServiceType("foo", nullptr, ConnectionType::kUnknown); +} + +TEST_F(ConnectionManagerTest, AllowUpdatesOverEthernetTest) { + // Updates over Ethernet are allowed even if there's no policy. + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet, + ConnectionTethering::kUnknown)); +} + +TEST_F(ConnectionManagerTest, AllowUpdatesOverWifiTest) { + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi, + ConnectionTethering::kUnknown)); +} + +TEST_F(ConnectionManagerTest, AllowUpdatesOverWimaxTest) { + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWimax, + ConnectionTethering::kUnknown)); +} + +TEST_F(ConnectionManagerTest, BlockUpdatesOverBluetoothTest) { + EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kBluetooth, + ConnectionTethering::kUnknown)); +} + +TEST_F(ConnectionManagerTest, AllowUpdatesOnlyOver3GPerPolicyTest) { + policy::MockDevicePolicy allow_3g_policy; + + fake_system_state_.set_device_policy(&allow_3g_policy); + + // This test tests cellular (3G) being the only connection type being allowed. + set<string> allowed_set; + allowed_set.insert(StringForConnectionType(ConnectionType::kCellular)); + + EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<0>(allowed_set), Return(true))); + + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular, + ConnectionTethering::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(StringForConnectionType(ConnectionType::kCellular)); + allowed_set.insert(StringForConnectionType(ConnectionType::kBluetooth)); + + EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_)) + .Times(3) + .WillRepeatedly(DoAll(SetArgPointee<0>(allowed_set), Return(true))); + + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet, + ConnectionTethering::kUnknown)); + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet, + ConnectionTethering::kNotDetected)); + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular, + ConnectionTethering::kUnknown)); + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi, + ConnectionTethering::kUnknown)); + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWimax, + ConnectionTethering::kUnknown)); + EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kBluetooth, + ConnectionTethering::kUnknown)); + + // Tethered networks are treated in the same way as Cellular networks and + // thus allowed. + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet, + ConnectionTethering::kConfirmed)); + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi, + ConnectionTethering::kConfirmed)); +} + +TEST_F(ConnectionManagerTest, BlockUpdatesOverCellularByDefaultTest) { + EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular, + ConnectionTethering::kUnknown)); +} + +TEST_F(ConnectionManagerTest, BlockUpdatesOverTetheredNetworkByDefaultTest) { + EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi, + ConnectionTethering::kConfirmed)); + EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet, + ConnectionTethering::kConfirmed)); + EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi, + ConnectionTethering::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(StringForConnectionType(ConnectionType::kEthernet)); + allowed_set.insert(StringForConnectionType(ConnectionType::kWifi)); + allowed_set.insert(StringForConnectionType(ConnectionType::kWimax)); + + EXPECT_CALL(block_3g_policy, GetAllowedConnectionTypesForUpdate(_)) + .Times(1) + .WillOnce(DoAll(SetArgPointee<0>(allowed_set), Return(true))); + + EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular, + ConnectionTethering::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(StringForConnectionType(ConnectionType::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(ConnectionType::kCellular, + ConnectionTethering::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(ConnectionType::kCellular, + ConnectionTethering::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(ConnectionType::kCellular, + ConnectionTethering::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(ConnectionType::kCellular, + ConnectionTethering::kUnknown)); +} + +TEST_F(ConnectionManagerTest, StringForConnectionTypeTest) { + EXPECT_STREQ(shill::kTypeEthernet, + StringForConnectionType(ConnectionType::kEthernet)); + EXPECT_STREQ(shill::kTypeWifi, + StringForConnectionType(ConnectionType::kWifi)); + EXPECT_STREQ(shill::kTypeWimax, + StringForConnectionType(ConnectionType::kWimax)); + EXPECT_STREQ(shill::kTypeBluetooth, + StringForConnectionType(ConnectionType::kBluetooth)); + EXPECT_STREQ(shill::kTypeCellular, + StringForConnectionType(ConnectionType::kCellular)); + EXPECT_STREQ("Unknown", StringForConnectionType(ConnectionType::kUnknown)); + EXPECT_STREQ("Unknown", + StringForConnectionType(static_cast<ConnectionType>(999999))); +} + +TEST_F(ConnectionManagerTest, MalformedServiceList) { + SetManagerReply("/service/guest/network", false); + + ConnectionType type; + ConnectionTethering tethering; + EXPECT_FALSE(cmut_.GetConnectionProperties(&type, &tethering)); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/connection_utils.cc b/update_engine/connection_utils.cc new file mode 100644 index 0000000..9b6b526 --- /dev/null +++ b/update_engine/connection_utils.cc
@@ -0,0 +1,70 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/connection_utils.h" + +#include <shill/dbus-constants.h> + +namespace chromeos_update_engine { +namespace connection_utils { + +ConnectionType ParseConnectionType(const std::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 ParseConnectionTethering(const std::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; +} + +const char* StringForConnectionType(ConnectionType type) { + switch (type) { + case ConnectionType::kEthernet: + return shill::kTypeEthernet; + case ConnectionType::kWifi: + return shill::kTypeWifi; + case ConnectionType::kWimax: + return shill::kTypeWimax; + case ConnectionType::kBluetooth: + return shill::kTypeBluetooth; + case ConnectionType::kCellular: + return shill::kTypeCellular; + case ConnectionType::kUnknown: + return "Unknown"; + } + return "Unknown"; +} + +} // namespace connection_utils + +} // namespace chromeos_update_engine
diff --git a/update_engine/connection_utils.h b/update_engine/connection_utils.h new file mode 100644 index 0000000..e385517 --- /dev/null +++ b/update_engine/connection_utils.h
@@ -0,0 +1,51 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_CONNECTION_UTILS_H_ +#define UPDATE_ENGINE_CONNECTION_UTILS_H_ + +#include <string> + +namespace chromeos_update_engine { + +enum class ConnectionType { + kEthernet, + kWifi, + kWimax, + kBluetooth, + kCellular, + kUnknown +}; + +enum class ConnectionTethering { + kNotDetected, + kSuspected, + kConfirmed, + kUnknown, +}; + +namespace connection_utils { +// Helper methods for converting shill strings into symbolic values. +ConnectionType ParseConnectionType(const std::string& type_str); +ConnectionTethering ParseConnectionTethering(const std::string& tethering_str); + +// Returns the string representation corresponding to the given connection type. +const char* StringForConnectionType(ConnectionType type); +} // namespace connection_utils + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_CONNECTION_UTILS_H_
diff --git a/update_engine/daemon.cc b/update_engine/daemon.cc new file mode 100644 index 0000000..4155243 --- /dev/null +++ b/update_engine/daemon.cc
@@ -0,0 +1,117 @@ +// +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/daemon.h" + +#include <sysexits.h> + +#include <base/bind.h> +#include <base/location.h> +#if USE_WEAVE || USE_BINDER +#include <binderwrapper/binder_wrapper.h> +#endif // USE_WEAVE || USE_BINDER + +#if USE_OMAHA +#include "update_engine/real_system_state.h" +#else // !USE_OMAHA +#include "update_engine/daemon_state_android.h" +#endif // USE_OMAHA + +namespace chromeos_update_engine { + +int UpdateEngineDaemon::OnInit() { + // Register the |subprocess_| singleton with this Daemon as the signal + // handler. + subprocess_.Init(this); + + int exit_code = Daemon::OnInit(); + if (exit_code != EX_OK) + return exit_code; + +#if USE_WEAVE || USE_BINDER + android::BinderWrapper::Create(); + binder_watcher_.Init(); +#endif // USE_WEAVE || USE_BINDER + +#if USE_OMAHA + // Initialize update engine global state but continue if something fails. + // TODO(deymo): Move the daemon_state_ initialization to a factory method + // avoiding the explicit re-usage of the |bus| instance, shared between + // D-Bus service and D-Bus client calls. + RealSystemState* real_system_state = new RealSystemState(); + daemon_state_.reset(real_system_state); + LOG_IF(ERROR, !real_system_state->Initialize()) + << "Failed to initialize system state."; +#else // !USE_OMAHA + DaemonStateAndroid* daemon_state_android = new DaemonStateAndroid(); + daemon_state_.reset(daemon_state_android); + LOG_IF(ERROR, !daemon_state_android->Initialize()) + << "Failed to initialize system state."; +#endif // USE_OMAHA + +#if USE_BINDER + // Create the Binder Service. +#if USE_OMAHA + binder_service_ = new BinderUpdateEngineBrilloService{real_system_state}; +#else // !USE_OMAHA + binder_service_ = new BinderUpdateEngineAndroidService{ + daemon_state_android->service_delegate()}; +#endif // USE_OMAHA + auto binder_wrapper = android::BinderWrapper::Get(); + if (!binder_wrapper->RegisterService(binder_service_->ServiceName(), + binder_service_)) { + LOG(ERROR) << "Failed to register binder service."; + } + + daemon_state_->AddObserver(binder_service_.get()); +#endif // USE_BINDER + +#if USE_DBUS + // Create the DBus service. + dbus_adaptor_.reset(new UpdateEngineAdaptor(real_system_state)); + daemon_state_->AddObserver(dbus_adaptor_.get()); + + dbus_adaptor_->RegisterAsync(base::Bind(&UpdateEngineDaemon::OnDBusRegistered, + base::Unretained(this))); + LOG(INFO) << "Waiting for DBus object to be registered."; +#else // !USE_DBUS + daemon_state_->StartUpdater(); +#endif // USE_DBUS + return EX_OK; +} + +#if USE_DBUS +void UpdateEngineDaemon::OnDBusRegistered(bool succeeded) { + if (!succeeded) { + LOG(ERROR) << "Registering the UpdateEngineAdaptor"; + QuitWithExitCode(1); + return; + } + + // Take ownership of the service now that everything is initialized. We need + // to this now and not before to avoid exposing a well known DBus service + // path that doesn't have the service it is supposed to implement. + if (!dbus_adaptor_->RequestOwnership()) { + LOG(ERROR) << "Unable to take ownership of the DBus service, is there " + << "other update_engine daemon running?"; + QuitWithExitCode(1); + return; + } + daemon_state_->StartUpdater(); +} +#endif // USE_DBUS + +} // namespace chromeos_update_engine
diff --git a/update_engine/daemon.h b/update_engine/daemon.h new file mode 100644 index 0000000..15351b4 --- /dev/null +++ b/update_engine/daemon.h
@@ -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. +// + +#ifndef UPDATE_ENGINE_DAEMON_H_ +#define UPDATE_ENGINE_DAEMON_H_ + +#include <memory> +#include <string> + +#if USE_WEAVE || USE_BINDER +#include <brillo/binder_watcher.h> +#endif // USE_WEAVE || USE_BINDER +#include <brillo/daemons/daemon.h> + +#if USE_BINDER +#if USE_OMAHA +#include "update_engine/binder_service_brillo.h" +#else // !USE_OMAHA +#include "update_engine/binder_service_android.h" +#endif // USE_OMAHA +#endif // USE_BINDER +#include "update_engine/common/subprocess.h" +#include "update_engine/daemon_state_interface.h" +#if USE_DBUS +#ifdef USE_NESTLABS +#include "update_engine/dbus_service_nestlabs.h" +#else +#include "update_engine/dbus_service.h" +#endif +#endif // USE_DBUS + +namespace chromeos_update_engine { + +class UpdateEngineDaemon : public brillo::Daemon { + public: + UpdateEngineDaemon() = default; + + protected: + int OnInit() override; + + private: +#if USE_DBUS + // Run from the main loop when the |dbus_adaptor_| object is registered. At + // this point we can request ownership of the DBus service name and continue + // initialization. + void OnDBusRegistered(bool succeeded); + + // Main D-Bus service adaptor. + std::unique_ptr<UpdateEngineAdaptor> dbus_adaptor_; +#endif // USE_DBUS + + // The Subprocess singleton class requires a brillo::MessageLoop in the + // current thread, so we need to initialize it from this class instead of + // the main() function. + Subprocess subprocess_; + +#if USE_WEAVE || USE_BINDER + brillo::BinderWatcher binder_watcher_; +#endif // USE_WEAVE || USE_BINDER + +#if USE_BINDER +#if USE_OMAHA + android::sp<BinderUpdateEngineBrilloService> binder_service_; +#else // !USE_OMAHA + android::sp<BinderUpdateEngineAndroidService> binder_service_; +#endif // USE_OMAHA +#endif // USE_BINDER + + // The daemon state with all the required daemon classes for the configured + // platform. + std::unique_ptr<DaemonStateInterface> daemon_state_; + + DISALLOW_COPY_AND_ASSIGN(UpdateEngineDaemon); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_DAEMON_H_
diff --git a/update_engine/daemon_state_android.cc b/update_engine/daemon_state_android.cc new file mode 100644 index 0000000..0960b1a --- /dev/null +++ b/update_engine/daemon_state_android.cc
@@ -0,0 +1,92 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/daemon_state_android.h" + +#include <base/logging.h> + +#include "update_engine/common/boot_control.h" +#include "update_engine/common/boot_control_stub.h" +#include "update_engine/common/hardware.h" +#include "update_engine/common/prefs.h" +#include "update_engine/update_attempter_android.h" + +namespace chromeos_update_engine { + +bool DaemonStateAndroid::Initialize() { + 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_.reset(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."; + + // Initialize prefs. + base::FilePath non_volatile_path; + // TODO(deymo): Fall back to in-memory prefs if there's no physical directory + // available. + if (!hardware_->GetNonVolatileDirectory(&non_volatile_path)) { + LOG(ERROR) << "Failed to get a non-volatile directory."; + return false; + } + Prefs* prefs = new Prefs(); + prefs_.reset(prefs); + if (!prefs->Init(non_volatile_path.Append(kPrefsSubDirectory))) { + LOG(ERROR) << "Failed to initialize preferences."; + return false; + } + + // The CertificateChecker singleton is used by the update attempter. + certificate_checker_.reset( + new CertificateChecker(prefs_.get(), &openssl_wrapper_)); + certificate_checker_->Init(); + + // Initialize the UpdateAttempter before the UpdateManager. + update_attempter_.reset(new UpdateAttempterAndroid( + this, prefs_.get(), boot_control_.get(), hardware_.get())); + + return true; +} + +bool DaemonStateAndroid::StartUpdater() { + // The DaemonState in Android is a passive daemon. It will only start applying + // an update when instructed to do so from the exposed binder API. + update_attempter_->Init(); + return true; +} + +void DaemonStateAndroid::AddObserver(ServiceObserverInterface* observer) { + service_observers_.insert(observer); +} + +void DaemonStateAndroid::RemoveObserver(ServiceObserverInterface* observer) { + service_observers_.erase(observer); +} + +ServiceDelegateAndroidInterface* DaemonStateAndroid::service_delegate() { + return update_attempter_.get(); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/daemon_state_android.h b/update_engine/daemon_state_android.h new file mode 100644 index 0000000..928a14e --- /dev/null +++ b/update_engine/daemon_state_android.h
@@ -0,0 +1,76 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_DAEMON_STATE_ANDROID_H_ +#define UPDATE_ENGINE_DAEMON_STATE_ANDROID_H_ + +#include <memory> +#include <set> + +#include "update_engine/certificate_checker.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/hardware_interface.h" +#include "update_engine/common/prefs_interface.h" +#include "update_engine/daemon_state_interface.h" +#include "update_engine/service_delegate_android_interface.h" +#include "update_engine/service_observer_interface.h" +#include "update_engine/update_attempter_android.h" + +namespace chromeos_update_engine { + +class DaemonStateAndroid : public DaemonStateInterface { + public: + DaemonStateAndroid() = default; + ~DaemonStateAndroid() override = default; + + bool Initialize(); + + // DaemonStateInterface overrides. + bool StartUpdater() override; + void AddObserver(ServiceObserverInterface* observer) override; + void RemoveObserver(ServiceObserverInterface* observer) override; + + const std::set<ServiceObserverInterface*>& service_observers() override { + return service_observers_; + } + + // Return a pointer to the service delegate. + ServiceDelegateAndroidInterface* service_delegate(); + + protected: + std::set<ServiceObserverInterface*> service_observers_; + + // Interface for the boot control functions. + std::unique_ptr<BootControlInterface> boot_control_; + + // Interface for the hardware functions. + std::unique_ptr<HardwareInterface> hardware_; + + // Interface for persisted store. + std::unique_ptr<PrefsInterface> prefs_; + + // The main class handling the updates. + std::unique_ptr<UpdateAttempterAndroid> update_attempter_; + + // OpenSSLWrapper and CertificateChecker used for checking changes in SSL + // certificates. + OpenSSLWrapper openssl_wrapper_; + std::unique_ptr<CertificateChecker> certificate_checker_; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_DAEMON_STATE_ANDROID_H_
diff --git a/update_engine/daemon_state_interface.h b/update_engine/daemon_state_interface.h new file mode 100644 index 0000000..2356816 --- /dev/null +++ b/update_engine/daemon_state_interface.h
@@ -0,0 +1,49 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_DAEMON_STATE_INTERFACE_H_ +#define UPDATE_ENGINE_DAEMON_STATE_INTERFACE_H_ + +#include "update_engine/service_observer_interface.h" + +#include <memory> +#include <set> + +namespace chromeos_update_engine { + +class DaemonStateInterface { + public: + virtual ~DaemonStateInterface() = default; + + // Start the daemon loop. Should be called only once to start the daemon's + // main functionality. + virtual bool StartUpdater() = 0; + + // Add and remove an observer. All the registered observers will be called + // whenever there's a new status to update. + virtual void AddObserver(ServiceObserverInterface* observer) = 0; + virtual void RemoveObserver(ServiceObserverInterface* observer) = 0; + + // Return the set of current observers. + virtual const std::set<ServiceObserverInterface*>& service_observers() = 0; + + protected: + DaemonStateInterface() = default; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_DAEMON_STATE_INTERFACE_H_
diff --git a/update_engine/dbus-constants-nestlabs.h b/update_engine/dbus-constants-nestlabs.h new file mode 100644 index 0000000..04bc658 --- /dev/null +++ b/update_engine/dbus-constants-nestlabs.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_UPDATE_ENGINE_DBUS_CONSTANTS_NESTLABS_H_ +#define SYSTEM_API_DBUS_UPDATE_ENGINE_DBUS_CONSTANTS_NESTLABS_H_ + +// Original location for a upstream version of this file is +// external/cros/system_api/dbus/update_engine/dbus-constants.h +// This is a customized copy for Nestlabs implementation of UpdateAttempter + +namespace update_engine { +const char kUpdateEngineInterface[] = "com.nestlabs.UpdateEngineInterface"; +const char kUpdateEngineServicePath[] = "/com/nestlabs/UpdateEngine"; +const char kUpdateEngineServiceName[] = "com.nestlabs.UpdateEngine"; + +// Generic UpdateEngine D-Bus error. +const char kUpdateEngineServiceErrorFailed[] = + "com.nestlabs.UpdateEngine.Error.Failed"; + +// Methods. +const char kAttemptUpdate[] = "AttemptUpdate"; +const char kGetLastAttemptError[] = "GetLastAttemptError"; +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_NESTLABS_H_
diff --git a/update_engine/dbus_bindings/com.nestlabs.UpdateEngineInterface.dbus-xml b/update_engine/dbus_bindings/com.nestlabs.UpdateEngineInterface.dbus-xml new file mode 100644 index 0000000..190e3d4 --- /dev/null +++ b/update_engine/dbus_bindings/com.nestlabs.UpdateEngineInterface.dbus-xml
@@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8" ?> +<node name="/com/nestlabs/UpdateEngine"> + <interface name="com.nestlabs.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="url" 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="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> + <method name="GetLastAttemptError"> + <arg type="i" name="last_attempt_error" direction="out" /> + </method> + <method name="MarkBootSuccessful" /> + </interface> +</node>
diff --git a/update_engine/dbus_bindings/dbus-service-config-nestlabs.json b/update_engine/dbus_bindings/dbus-service-config-nestlabs.json new file mode 100644 index 0000000..f69a372 --- /dev/null +++ b/update_engine/dbus_bindings/dbus-service-config-nestlabs.json
@@ -0,0 +1,3 @@ +{ + "service_name": "com.nestlabs.UpdateEngine" +}
diff --git a/update_engine/dbus_bindings/dbus-service-config.json b/update_engine/dbus_bindings/dbus-service-config.json new file mode 100644 index 0000000..fdae3ba --- /dev/null +++ b/update_engine/dbus_bindings/dbus-service-config.json
@@ -0,0 +1,3 @@ +{ + "service_name": "org.chromium.UpdateEngine" +}
diff --git a/update_engine/dbus_bindings/org.chromium.LibCrosService.dbus-xml b/update_engine/dbus_bindings/org.chromium.LibCrosService.dbus-xml new file mode 100644 index 0000000..2da1929 --- /dev/null +++ b/update_engine/dbus_bindings/org.chromium.LibCrosService.dbus-xml
@@ -0,0 +1,23 @@ +<?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> + <method name="GetKioskAppRequiredPlatformVersion"> + <arg name="required_platform_version" type="s" direction="out" /> + </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/update_engine/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml b/update_engine/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml new file mode 100644 index 0000000..848f775 --- /dev/null +++ b/update_engine/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
@@ -0,0 +1,96 @@ +<?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="SetCohortHint"> + <arg type="s" name="cohort_hint" direction="in" /> + </method> + <method name="GetCohortHint"> + <arg type="s" name="cohort_hint" 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> + <method name="GetLastAttemptError"> + <arg type="i" name="last_attempt_error" direction="out" /> + </method> + <method name="GetEolStatus"> + <arg type="i" name="eol_status" direction="out" /> + </method> + </interface> +</node>
diff --git a/update_engine/dbus_connection.cc b/update_engine/dbus_connection.cc new file mode 100644 index 0000000..cf17ec9 --- /dev/null +++ b/update_engine/dbus_connection.cc
@@ -0,0 +1,55 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/dbus_connection.h" + +#include <base/time/time.h> + +namespace chromeos_update_engine { + +namespace { +const int kDBusSystemMaxWaitSeconds = 2 * 60; + +DBusConnection* dbus_connection_singleton = nullptr; +} // namespace + +DBusConnection::DBusConnection() { + // We 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. + bus_ = dbus_connection_.ConnectWithTimeout( + base::TimeDelta::FromSeconds(kDBusSystemMaxWaitSeconds)); + + if (!bus_) { + // TODO(deymo): Make it possible to run update_engine even if dbus-daemon + // is not running or constantly crashing. + LOG(FATAL) << "Failed to initialize DBus, aborting."; + } + + CHECK(bus_->SetUpAsyncOperations()); +} + +const scoped_refptr<dbus::Bus>& DBusConnection::GetDBus() { + CHECK(bus_); + return bus_; +} + +DBusConnection* DBusConnection::Get() { + if (!dbus_connection_singleton) + dbus_connection_singleton = new DBusConnection(); + return dbus_connection_singleton; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/dbus_connection.h b/update_engine/dbus_connection.h new file mode 100644 index 0000000..c3205ba --- /dev/null +++ b/update_engine/dbus_connection.h
@@ -0,0 +1,44 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_DBUS_CONNECTION_H_ +#define UPDATE_ENGINE_DBUS_CONNECTION_H_ + +#include <base/memory/ref_counted.h> +#include <brillo/dbus/dbus_connection.h> +#include <dbus/bus.h> + +namespace chromeos_update_engine { + +class DBusConnection { + public: + DBusConnection(); + + const scoped_refptr<dbus::Bus>& GetDBus(); + + static DBusConnection* Get(); + + private: + scoped_refptr<dbus::Bus> bus_; + + brillo::DBusConnection dbus_connection_; + + DISALLOW_COPY_AND_ASSIGN(DBusConnection); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_DBUS_CONNECTION_H_
diff --git a/update_engine/dbus_service.cc b/update_engine/dbus_service.cc new file mode 100644 index 0000000..0a7ad5b --- /dev/null +++ b/update_engine/dbus_service.cc
@@ -0,0 +1,185 @@ +// +// 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 "update_engine/dbus-constants.h" +#include "update_engine/dbus_connection.h" +#include "update_engine/update_status_utils.h" + +namespace chromeos_update_engine { + +using brillo::ErrorPtr; +using chromeos_update_engine::UpdateEngineService; +using std::string; + +DBusUpdateEngineService::DBusUpdateEngineService(SystemState* system_state) + : common_(new UpdateEngineService{system_state}) { +} + +// org::chromium::UpdateEngineInterfaceInterface methods implementation. + +bool DBusUpdateEngineService::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 DBusUpdateEngineService::AttemptUpdateWithFlags( + ErrorPtr* error, + const string& in_app_version, + const string& in_omaha_url, + int32_t in_flags_as_int) { + update_engine::AttemptUpdateFlags flags = + static_cast<update_engine::AttemptUpdateFlags>(in_flags_as_int); + bool interactive = !(flags & + update_engine::kAttemptUpdateFlagNonInteractive); + + return common_->AttemptUpdate( + error, in_app_version, in_omaha_url, + interactive ? 0 : UpdateEngineService::kAttemptUpdateFlagNonInteractive); +} + +bool DBusUpdateEngineService::AttemptRollback(ErrorPtr* error, + bool in_powerwash) { + return common_->AttemptRollback(error, in_powerwash); +} + +bool DBusUpdateEngineService::CanRollback(ErrorPtr* error, + bool* out_can_rollback) { + return common_->CanRollback(error, out_can_rollback); +} + +bool DBusUpdateEngineService::ResetStatus(ErrorPtr* error) { + return common_->ResetStatus(error); +} + +bool DBusUpdateEngineService::GetStatus(ErrorPtr* error, + int64_t* out_last_checked_time, + double* out_progress, + string* out_current_operation, + string* out_new_version, + int64_t* out_new_size) { + return common_->GetStatus(error, + out_last_checked_time, + out_progress, + out_current_operation, + out_new_version, + out_new_size); +} + +bool DBusUpdateEngineService::RebootIfNeeded(ErrorPtr* error) { + return common_->RebootIfNeeded(error); +} + +bool DBusUpdateEngineService::SetChannel(ErrorPtr* error, + const string& in_target_channel, + bool in_is_powerwash_allowed) { + return common_->SetChannel(error, in_target_channel, in_is_powerwash_allowed); +} + +bool DBusUpdateEngineService::GetChannel(ErrorPtr* error, + bool in_get_current_channel, + string* out_channel) { + return common_->GetChannel(error, in_get_current_channel, out_channel); +} + +bool DBusUpdateEngineService::GetCohortHint(ErrorPtr* error, + string* out_cohort_hint) { + return common_->GetCohortHint(error, out_cohort_hint); +} + +bool DBusUpdateEngineService::SetCohortHint(ErrorPtr* error, + const string& in_cohort_hint) { + return common_->SetCohortHint(error, in_cohort_hint); +} + +bool DBusUpdateEngineService::SetP2PUpdatePermission(ErrorPtr* error, + bool in_enabled) { + return common_->SetP2PUpdatePermission(error, in_enabled); +} + +bool DBusUpdateEngineService::GetP2PUpdatePermission(ErrorPtr* error, + bool* out_enabled) { + return common_->GetP2PUpdatePermission(error, out_enabled); +} + +bool DBusUpdateEngineService::SetUpdateOverCellularPermission(ErrorPtr* error, + bool in_allowed) { + return common_->SetUpdateOverCellularPermission(error, in_allowed); +} + +bool DBusUpdateEngineService::GetUpdateOverCellularPermission( + ErrorPtr* error, bool* out_allowed) { + return common_->GetUpdateOverCellularPermission(error, out_allowed); +} + +bool DBusUpdateEngineService::GetDurationSinceUpdate( + ErrorPtr* error, int64_t* out_usec_wallclock) { + return common_->GetDurationSinceUpdate(error, out_usec_wallclock); +} + +bool DBusUpdateEngineService::GetPrevVersion(ErrorPtr* error, + string* out_prev_version) { + return common_->GetPrevVersion(error, out_prev_version); +} + +bool DBusUpdateEngineService::GetRollbackPartition( + ErrorPtr* error, string* out_rollback_partition_name) { + return common_->GetRollbackPartition(error, out_rollback_partition_name); +} + +bool DBusUpdateEngineService::GetLastAttemptError( + ErrorPtr* error, int32_t* out_last_attempt_error) { + return common_->GetLastAttemptError(error, out_last_attempt_error); +} + +bool DBusUpdateEngineService::GetEolStatus(ErrorPtr* error, + int32_t* out_eol_status) { + return common_->GetEolStatus(error, out_eol_status); +} + +UpdateEngineAdaptor::UpdateEngineAdaptor(SystemState* system_state) + : org::chromium::UpdateEngineInterfaceAdaptor(&dbus_service_), + bus_(DBusConnection::Get()->GetDBus()), + 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); +} + +void UpdateEngineAdaptor::SendStatusUpdate(int64_t last_checked_time, + double progress, + update_engine::UpdateStatus status, + const string& new_version, + int64_t new_size) { + const string str_status = UpdateStatusToString(status); + SendStatusUpdateSignal( + last_checked_time, progress, str_status, new_version, new_size); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/dbus_service.h b/update_engine/dbus_service.h new file mode 100644 index 0000000..d18aa16 --- /dev/null +++ b/update_engine/dbus_service.h
@@ -0,0 +1,193 @@ +// +// 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 <memory> +#include <string> + +#include <base/memory/ref_counted.h> +#include <brillo/errors/error.h> + +#include "update_engine/common_service.h" +#include "update_engine/service_observer_interface.h" +#if USE_NESTLABS +#include "update_engine/update_attempter_nestlabs.h" +#else +#include "update_engine/update_attempter.h" +#endif + +#include "dbus_bindings/org.chromium.UpdateEngineInterface.h" + +namespace chromeos_update_engine { + +class DBusUpdateEngineService + : public org::chromium::UpdateEngineInterfaceInterface { + public: + explicit DBusUpdateEngineService(SystemState* system_state); + virtual ~DBusUpdateEngineService() = 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; + + bool SetCohortHint(brillo::ErrorPtr* error, + const std::string& in_cohort_hint) override; + + bool GetCohortHint(brillo::ErrorPtr* error, + std::string* out_cohort_hint) 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; + + // Returns the last UpdateAttempt error. If not updated yet, default success + // ErrorCode will be returned. + bool GetLastAttemptError(brillo::ErrorPtr* error, + int32_t* out_last_attempt_error) override; + + // Returns the current end-of-life status of the device in |out_eol_status|. + bool GetEolStatus(brillo::ErrorPtr* error, int32_t* out_eol_status) override; + + private: + std::unique_ptr<UpdateEngineService> common_; +}; + +// 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 ServiceObserverInterface { + public: + UpdateEngineAdaptor(SystemState* system_state); + ~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(); + + // ServiceObserverInterface overrides. + void SendStatusUpdate(int64_t last_checked_time, + double progress, + update_engine::UpdateStatus status, + const std::string& new_version, + int64_t new_size) override; + + void SendPayloadApplicationComplete(ErrorCode error_code) override {} + + // Channel tracking changes are ignored. + void SendChannelChangeUpdate(const std::string& tracking_channel) override {} + + private: + scoped_refptr<dbus::Bus> bus_; + DBusUpdateEngineService dbus_service_; + brillo::dbus_utils::DBusObject dbus_object_; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_DBUS_SERVICE_H_
diff --git a/update_engine/dbus_service_nestlabs.cc b/update_engine/dbus_service_nestlabs.cc new file mode 100644 index 0000000..6833322 --- /dev/null +++ b/update_engine/dbus_service_nestlabs.cc
@@ -0,0 +1,127 @@ +// +// 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_nestlabs.h" + +#include "update_engine/dbus-constants-nestlabs.h" +#include "update_engine/dbus_connection.h" +#include "update_engine/update_status_utils.h" + +namespace chromeos_update_engine { + +using brillo::ErrorPtr; +using chromeos_update_engine::UpdateEngineService; +using std::string; + +DBusUpdateEngineService::DBusUpdateEngineService(SystemState* system_state) + : common_(new UpdateEngineService{system_state}) { +} + +// com::nestlabs::UpdateEngineInterfaceInterface methods implementation. + +bool DBusUpdateEngineService::AttemptUpdate(ErrorPtr* error, + const string& in_app_version, + const string& in_url) { + return common_->AttemptUpdate( + error, in_app_version, in_url, false); +} + +bool DBusUpdateEngineService::AttemptRollback(ErrorPtr* error, + bool in_powerwash) { + return common_->AttemptRollback(error, in_powerwash); +} + +bool DBusUpdateEngineService::CanRollback(ErrorPtr* error, + bool* out_can_rollback) { + return common_->CanRollback(error, out_can_rollback); +} + +bool DBusUpdateEngineService::ResetStatus(ErrorPtr* error) { + return common_->ResetStatus(error); +} + +bool DBusUpdateEngineService::GetStatus(ErrorPtr* error, + int64_t* out_last_checked_time, + double* out_progress, + string* out_current_operation, + string* out_new_version, + int64_t* out_new_size) { + return common_->GetStatus(error, + out_last_checked_time, + out_progress, + out_current_operation, + out_new_version, + out_new_size); +} + +bool DBusUpdateEngineService::RebootIfNeeded(ErrorPtr* error) { + return common_->RebootIfNeeded(error); +} + +bool DBusUpdateEngineService::GetDurationSinceUpdate( + ErrorPtr* error, int64_t* out_usec_wallclock) { + return common_->GetDurationSinceUpdate(error, out_usec_wallclock); +} + +bool DBusUpdateEngineService::GetPrevVersion(ErrorPtr* error, + string* out_prev_version) { + return common_->GetPrevVersion(error, out_prev_version); +} + +bool DBusUpdateEngineService::GetRollbackPartition( + ErrorPtr* error, string* out_rollback_partition_name) { + return common_->GetRollbackPartition(error, out_rollback_partition_name); +} + +bool DBusUpdateEngineService::GetLastAttemptError( + ErrorPtr* error, int32_t* out_last_attempt_error) { + return common_->GetLastAttemptError(error, out_last_attempt_error); +} + +bool DBusUpdateEngineService::MarkBootSuccessful(brillo::ErrorPtr* error) { + return common_->MarkBootSuccessful(error); +} + +UpdateEngineAdaptor::UpdateEngineAdaptor(SystemState* system_state) + : com::nestlabs::UpdateEngineInterfaceAdaptor(&dbus_service_), + bus_(DBusConnection::Get()->GetDBus()), + 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); +} + +void UpdateEngineAdaptor::SendStatusUpdate(int64_t last_checked_time, + double progress, + update_engine::UpdateStatus status, + const string& new_version, + int64_t new_size) { + const string str_status = UpdateStatusToString(status); + SendStatusUpdateSignal( + last_checked_time, progress, str_status, new_version, new_size); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/dbus_service_nestlabs.h b/update_engine/dbus_service_nestlabs.h new file mode 100644 index 0000000..0a6e6cb --- /dev/null +++ b/update_engine/dbus_service_nestlabs.h
@@ -0,0 +1,138 @@ +// +// 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/common_service.h" +#include "update_engine/service_observer_interface.h" +#if USE_NESTLABS +#include "update_engine/update_attempter_nestlabs.h" +#else +#include "update_engine/update_attempter.h" +#endif + +#include "dbus_bindings/com.nestlabs.UpdateEngineInterface.h" + +namespace chromeos_update_engine { + +class DBusUpdateEngineService + : public com::nestlabs::UpdateEngineInterfaceInterface { + public: + explicit DBusUpdateEngineService(SystemState* system_state); + virtual ~DBusUpdateEngineService() = default; + + // Implementation of com::nestlabs::UpdateEngineInterfaceInterface. + bool AttemptUpdate(brillo::ErrorPtr* error, + const std::string& in_app_version, + const std::string& in_url) 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; + + // 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; + + // Returns the last UpdateAttempt error. If not updated yet, default success + // ErrorCode will be returned. + bool GetLastAttemptError(brillo::ErrorPtr* error, + int32_t* out_last_attempt_error) override; + + bool MarkBootSuccessful(brillo::ErrorPtr* error) override; + + private: + std::unique_ptr<UpdateEngineService> common_; +}; + +// 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 com::nestlabs::UpdateEngineInterfaceAdaptor, + public ServiceObserverInterface { + public: + UpdateEngineAdaptor(SystemState* system_state); + ~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(); + + // ServiceObserverInterface overrides. + void SendStatusUpdate(int64_t last_checked_time, + double progress, + update_engine::UpdateStatus status, + const std::string& new_version, + int64_t new_size) override; + + void SendPayloadApplicationComplete(ErrorCode error_code) override {} + + // Channel tracking changes are ignored. + void SendChannelChangeUpdate(const std::string& tracking_channel) override {} + + private: + scoped_refptr<dbus::Bus> bus_; + DBusUpdateEngineService dbus_service_; + brillo::dbus_utils::DBusObject dbus_object_; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_DBUS_SERVICE_H_
diff --git a/update_engine/dbus_test_utils.h b/update_engine/dbus_test_utils.h new file mode 100644 index 0000000..b3748ce --- /dev/null +++ b/update_engine/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/update_engine/fake_file_writer.h b/update_engine/fake_file_writer.h new file mode 100644 index 0000000..43b71c7 --- /dev/null +++ b/update_engine/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/update_engine/fake_p2p_manager.h b/update_engine/fake_p2p_manager.h new file mode 100644 index 0000000..a8cf4ea --- /dev/null +++ b/update_engine/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/update_engine/fake_p2p_manager_configuration.h b/update_engine/fake_p2p_manager_configuration.h new file mode 100644 index 0000000..1bc1dc8 --- /dev/null +++ b/update_engine/fake_p2p_manager_configuration.h
@@ -0,0 +1,106 @@ +// +// 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/p2p_manager.h" + +#include <string> +#include <vector> + +#include <base/files/scoped_temp_dir.h> +#include <base/strings/string_util.h> +#include <gtest/gtest.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(p2p_dir_.CreateUniqueTempDir()); + } + + // P2PManager::Configuration override + base::FilePath GetP2PDir() override { + return p2p_dir_.path(); + } + + // 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) { + base::ReplaceSubstringsAfterOffset(&arg, 0, "{file_id}", file_id); + base::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. + base::ScopedTempDir 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/update_engine/fake_shill_proxy.cc b/update_engine/fake_shill_proxy.cc new file mode 100644 index 0000000..17698cd --- /dev/null +++ b/update_engine/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/update_engine/fake_shill_proxy.h b/update_engine/fake_shill_proxy.h new file mode 100644 index 0000000..ae17eaa --- /dev/null +++ b/update_engine/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/update_engine/fake_system_state.cc b/update_engine/fake_system_state.cc new file mode 100644 index 0000000..d51f775 --- /dev/null +++ b/update_engine/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), + 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/update_engine/fake_system_state.h b/update_engine/fake_system_state.h new file mode 100644 index 0000000..030cb07 --- /dev/null +++ b/update_engine/fake_system_state.h
@@ -0,0 +1,273 @@ +// +// 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 "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_power_manager.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 WeaveServiceInterface* weave_service() override { return nullptr; } + + 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 PowerManagerInterface* power_manager() override { + return power_manager_; + } + + 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_; + testing::NiceMock<MockPowerManager> 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_; + PowerManagerInterface* power_manager_{&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/update_engine/generate_pc_file.sh b/update_engine/generate_pc_file.sh new file mode 100755 index 0000000..ab101f4 --- /dev/null +++ b/update_engine/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/update_engine/hardware_android.cc b/update_engine/hardware_android.cc new file mode 100644 index 0000000..653ccf9 --- /dev/null +++ b/update_engine/hardware_android.cc
@@ -0,0 +1,185 @@ +// +// 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 <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <algorithm> + +#include <bootloader.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" +#include "update_engine/common/utils.h" +#include "update_engine/utils_android.h" + +using std::string; + +namespace chromeos_update_engine { + +namespace { + +// The powerwash arguments passed to recovery. Arguments are separated by \n. +const char kAndroidRecoveryPowerwashCommand[] = + "recovery\n" + "--wipe_data\n" + "--reason=wipe_data_from_ota\n"; + +// Write a recovery command line |message| to the BCB. The arguments to recovery +// must be separated by '\n'. An empty string will erase the BCB. +bool WriteBootloaderRecoveryMessage(const string& message) { + base::FilePath misc_device; + if (!utils::DeviceForMountPoint("/misc", &misc_device)) + return false; + + // Setup a bootloader_message with just the command and recovery fields set. + bootloader_message boot = {}; + if (!message.empty()) { + strncpy(boot.command, "boot-recovery", sizeof(boot.command) - 1); + memcpy(boot.recovery, + message.data(), + std::min(message.size(), sizeof(boot.recovery) - 1)); + } + + int fd = + HANDLE_EINTR(open(misc_device.value().c_str(), O_WRONLY | O_SYNC, 0600)); + if (fd < 0) { + PLOG(ERROR) << "Opening misc"; + return false; + } + ScopedFdCloser fd_closer(&fd); + // We only re-write the first part of the bootloader_message, up to and + // including the recovery message. + size_t boot_size = + offsetof(bootloader_message, recovery) + sizeof(boot.recovery); + if (!utils::WriteAll(fd, &boot, boot_size)) { + PLOG(ERROR) << "Writing recovery command to misc"; + return false; + } + return true; +} + +} // namespace + +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::AreDevFeaturesEnabled() const { + return !IsNormalBootMode(); +} + +bool HardwareAndroid::IsOOBEEnabled() const { + // No OOBE flow blocking updates for Android-based boards. + return false; +} + +bool HardwareAndroid::IsOOBEComplete(base::Time* out_time_of_oobe) const { + LOG(WARNING) << "OOBE is not enabled but IsOOBEComplete() called."; + 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::SchedulePowerwash() { + LOG(INFO) << "Scheduling a powerwash to BCB."; + return WriteBootloaderRecoveryMessage(kAndroidRecoveryPowerwashCommand); +} + +bool HardwareAndroid::CancelPowerwash() { + return WriteBootloaderRecoveryMessage(""); +} + +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/update_engine/hardware_android.h b/update_engine/hardware_android.h new file mode 100644 index 0000000..78af871 --- /dev/null +++ b/update_engine/hardware_android.h
@@ -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. +// + +#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 AreDevFeaturesEnabled() const override; + bool IsOOBEEnabled() const override; + bool IsOOBEComplete(base::Time* out_time_of_oobe) const override; + std::string GetHardwareClass() const override; + std::string GetFirmwareVersion() const override; + std::string GetECVersion() const override; + int GetPowerwashCount() const override; + bool SchedulePowerwash() override; + bool CancelPowerwash() 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/update_engine/hardware_chromeos.cc b/update_engine/hardware_chromeos.cc new file mode 100644 index 0000000..4b0b82f --- /dev/null +++ b/update_engine/hardware_chromeos.cc
@@ -0,0 +1,251 @@ +// +// 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_path.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/key_value_store.h> +#include <brillo/make_unique_ptr.h> +#include <debugd/dbus-constants.h> +#include <vboot/crossystem.h> + +extern "C" { +#include "vboot/vboot_host.h" +} + +#include "update_engine/common/constants.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" +#include "update_engine/dbus_connection.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"; + +// The name of the marker file used to trigger powerwash when post-install +// completes successfully so that the device is powerwashed on next reboot. +const char kPowerwashMarkerFile[] = + "/mnt/stateful_partition/factory_install_reset"; + +// The contents of the powerwash marker file. +const char kPowerwashCommand[] = "safe fast keepimg reason=update_engine\n"; + +// UpdateManager config path. +const char* kConfigFilePath = "/etc/update_manager.conf"; + +// UpdateManager config options: +const char* kConfigOptsIsOOBEEnabled = "is_oobe_enabled"; + +} // namespace + +namespace chromeos_update_engine { + +namespace hardware { + +// Factory defined in hardware.h. +std::unique_ptr<HardwareInterface> CreateHardware() { + std::unique_ptr<HardwareChromeOS> hardware(new HardwareChromeOS()); + hardware->Init(); + return std::move(hardware); +} + +} // namespace hardware + +void HardwareChromeOS::Init() { + LoadConfig("" /* root_prefix */, IsNormalBootMode()); + debugd_proxy_.reset( + new org::chromium::debugdProxy(DBusConnection::Get()->GetDBus())); +} + +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::AreDevFeaturesEnabled() const { + // 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 (IsNormalBootMode()) + return false; + + int32_t dev_features = debugd::DEV_FEATURES_DISABLED; + brillo::ErrorPtr error; + // Some boards may not include debugd so it's expected that this may fail, + // in which case we treat it as disabled. + if (debugd_proxy_ && debugd_proxy_->QueryDevFeatures(&dev_features, &error) && + !(dev_features & debugd::DEV_FEATURES_DISABLED)) { + LOG(INFO) << "Debugd dev tools enabled."; + return true; + } + return false; +} + +bool HardwareChromeOS::IsOOBEEnabled() const { + return is_oobe_enabled_; +} + +bool HardwareChromeOS::IsOOBEComplete(base::Time* out_time_of_oobe) const { + if (!is_oobe_enabled_) { + LOG(WARNING) << "OOBE is not enabled but IsOOBEComplete() was called"; + } + 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::SchedulePowerwash() { + bool result = utils::WriteFile( + kPowerwashMarkerFile, kPowerwashCommand, strlen(kPowerwashCommand)); + if (result) { + LOG(INFO) << "Created " << kPowerwashMarkerFile + << " to powerwash on next reboot"; + } else { + PLOG(ERROR) << "Error in creating powerwash marker file: " + << kPowerwashMarkerFile; + } + + return result; +} + +bool HardwareChromeOS::CancelPowerwash() { + bool result = base::DeleteFile(base::FilePath(kPowerwashMarkerFile), false); + + if (result) { + LOG(INFO) << "Successfully deleted the powerwash marker file : " + << kPowerwashMarkerFile; + } else { + PLOG(ERROR) << "Could not delete the powerwash marker file : " + << kPowerwashMarkerFile; + } + + return result; +} + +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; +} + +void HardwareChromeOS::LoadConfig(const string& root_prefix, bool normal_mode) { + brillo::KeyValueStore store; + + if (normal_mode) { + store.Load(base::FilePath(root_prefix + kConfigFilePath)); + } else { + if (store.Load(base::FilePath(root_prefix + kStatefulPartition + + kConfigFilePath))) { + LOG(INFO) << "UpdateManager Config loaded from stateful partition."; + } else { + store.Load(base::FilePath(root_prefix + kConfigFilePath)); + } + } + + if (!store.GetBoolean(kConfigOptsIsOOBEEnabled, &is_oobe_enabled_)) + is_oobe_enabled_ = true; // Default value. +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/hardware_chromeos.h b/update_engine/hardware_chromeos.h new file mode 100644 index 0000000..03ad750 --- /dev/null +++ b/update_engine/hardware_chromeos.h
@@ -0,0 +1,72 @@ +// +// 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 <debugd/dbus-proxies.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; + + void Init(); + + // HardwareInterface methods. + bool IsOfficialBuild() const override; + bool IsNormalBootMode() const override; + bool AreDevFeaturesEnabled() const override; + bool IsOOBEEnabled() const override; + bool IsOOBEComplete(base::Time* out_time_of_oobe) const override; + std::string GetHardwareClass() const override; + std::string GetFirmwareVersion() const override; + std::string GetECVersion() const override; + int GetPowerwashCount() const override; + bool SchedulePowerwash() override; + bool CancelPowerwash() override; + bool GetNonVolatileDirectory(base::FilePath* path) const override; + bool GetPowerwashSafeDirectory(base::FilePath* path) const override; + + private: + friend class HardwareChromeOSTest; + + // Load the update manager config flags (is_oobe_enabled flag) from the + // appropriate location based on whether we are in a normal mode boot (as + // passed in |normal_mode|) prefixing the paths with |root_prefix|. + void LoadConfig(const std::string& root_prefix, bool normal_mode); + + bool is_oobe_enabled_; + + std::unique_ptr<org::chromium::debugdProxyInterface> debugd_proxy_; + + DISALLOW_COPY_AND_ASSIGN(HardwareChromeOS); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_HARDWARE_CHROMEOS_H_
diff --git a/update_engine/hardware_chromeos_unittest.cc b/update_engine/hardware_chromeos_unittest.cc new file mode 100644 index 0000000..a6bad54 --- /dev/null +++ b/update_engine/hardware_chromeos_unittest.cc
@@ -0,0 +1,89 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/hardware_chromeos.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 chromeos_update_engine::test_utils::WriteFileString; +using std::string; + +namespace chromeos_update_engine { + +class HardwareChromeOSTest : public ::testing::Test { + protected: + void SetUp() override { ASSERT_TRUE(root_dir_.CreateUniqueTempDir()); } + + void WriteStatefulConfig(const string& config) { + base::FilePath kFile(root_dir_.path().value() + 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)); + } + + // Helper method to call HardwareChromeOS::LoadConfig with the test directory. + void CallLoadConfig(bool normal_mode) { + hardware_.LoadConfig(root_dir_.path().value(), normal_mode); + } + + HardwareChromeOS hardware_; + base::ScopedTempDir root_dir_; +}; + +TEST_F(HardwareChromeOSTest, NoFileFoundReturnsDefault) { + CallLoadConfig(true /* normal_mode */); + EXPECT_TRUE(hardware_.IsOOBEEnabled()); +} + +TEST_F(HardwareChromeOSTest, DontReadStatefulInNormalMode) { + WriteStatefulConfig("is_oobe_enabled=false"); + + CallLoadConfig(true /* normal_mode */); + EXPECT_TRUE(hardware_.IsOOBEEnabled()); +} + +TEST_F(HardwareChromeOSTest, ReadStatefulInDevMode) { + WriteRootfsConfig("is_oobe_enabled=true"); + // Since the stateful is present, we should read that one. + WriteStatefulConfig("is_oobe_enabled=false"); + + CallLoadConfig(false /* normal_mode */); + EXPECT_FALSE(hardware_.IsOOBEEnabled()); +} + +TEST_F(HardwareChromeOSTest, ReadRootfsIfStatefulNotFound) { + WriteRootfsConfig("is_oobe_enabled=false"); + + CallLoadConfig(false /* normal_mode */); + EXPECT_FALSE(hardware_.IsOOBEEnabled()); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/image_properties.h b/update_engine/image_properties.h new file mode 100644 index 0000000..6026c2e --- /dev/null +++ b/update_engine/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/update_engine/image_properties_android.cc b/update_engine/image_properties_android.cc new file mode 100644 index 0000000..00822da --- /dev/null +++ b/update_engine/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/update_engine/image_properties_chromeos.cc b/update_engine/image_properties_chromeos.cc new file mode 100644 index 0000000..501e662 --- /dev/null +++ b/update_engine/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/update_engine/image_properties_chromeos_unittest.cc b/update_engine/image_properties_chromeos_unittest.cc new file mode 100644 index 0000000..12c2039 --- /dev/null +++ b/update_engine/image_properties_chromeos_unittest.cc
@@ -0,0 +1,166 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/image_properties.h" + +#include <string> + +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "update_engine/common/constants.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/fake_system_state.h" + +using chromeos_update_engine::test_utils::WriteFileString; +using std::string; + +namespace chromeos_update_engine { + +class ImagePropertiesTest : public ::testing::Test { + protected: + void SetUp() override { + // Create a uniquely named test directory. + ASSERT_TRUE(tempdir_.CreateUniqueTempDir()); + EXPECT_TRUE(base::CreateDirectory(tempdir_.path().Append("etc"))); + EXPECT_TRUE(base::CreateDirectory( + base::FilePath(tempdir_.path().value() + kStatefulPartition + "/etc"))); + test::SetImagePropertiesRootPrefix(tempdir_.path().value().c_str()); + SetLockDown(false); + } + + void SetLockDown(bool locked_down) { + fake_system_state_.fake_hardware()->SetIsOfficialBuild(locked_down); + fake_system_state_.fake_hardware()->SetIsNormalBootMode(locked_down); + } + + FakeSystemState fake_system_state_; + + base::ScopedTempDir tempdir_; +}; + +TEST_F(ImagePropertiesTest, SimpleTest) { + ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(), + "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")); + ImageProperties props = LoadImageProperties(&fake_system_state_); + EXPECT_EQ("arm-generic", props.board); + EXPECT_EQ("{87efface-864d-49a5-9bb3-4b050a7c227a}", props.product_id); + EXPECT_EQ("0.2.2.3", props.version); + EXPECT_EQ("dev-channel", props.current_channel); + EXPECT_EQ("http://www.google.com", props.omaha_url); +} + +TEST_F(ImagePropertiesTest, AppIDTest) { + ASSERT_TRUE(WriteFileString( + tempdir_.path().Append("etc/lsb-release").value(), + "CHROMEOS_RELEASE_APPID={58c35cef-9d30-476e-9098-ce20377d535d}")); + ImageProperties props = LoadImageProperties(&fake_system_state_); + EXPECT_EQ("{58c35cef-9d30-476e-9098-ce20377d535d}", props.product_id); +} + +TEST_F(ImagePropertiesTest, ConfusingReleaseTest) { + ASSERT_TRUE( + WriteFileString(tempdir_.path().Append("etc/lsb-release").value(), + "CHROMEOS_RELEASE_FOO=CHROMEOS_RELEASE_VERSION=1.2.3.4\n" + "CHROMEOS_RELEASE_VERSION=0.2.2.3")); + ImageProperties props = LoadImageProperties(&fake_system_state_); + EXPECT_EQ("0.2.2.3", props.version); +} + +TEST_F(ImagePropertiesTest, MissingVersionTest) { + ImageProperties props = LoadImageProperties(&fake_system_state_); + EXPECT_EQ("", props.version); +} + +TEST_F(ImagePropertiesTest, OverrideTest) { + ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(), + "CHROMEOS_RELEASE_BOARD=arm-generic\n" + "CHROMEOS_RELEASE_FOO=bar\n" + "CHROMEOS_RELEASE_TRACK=dev-channel\n" + "CHROMEOS_AUSERVER=http://www.google.com")); + ASSERT_TRUE(WriteFileString( + tempdir_.path().value() + kStatefulPartition + "/etc/lsb-release", + "CHROMEOS_RELEASE_BOARD=x86-generic\n" + "CHROMEOS_RELEASE_TRACK=beta-channel\n" + "CHROMEOS_AUSERVER=https://www.google.com")); + ImageProperties props = LoadImageProperties(&fake_system_state_); + EXPECT_EQ("x86-generic", props.board); + EXPECT_EQ("dev-channel", props.current_channel); + EXPECT_EQ("https://www.google.com", props.omaha_url); + MutableImageProperties mutable_props = + LoadMutableImageProperties(&fake_system_state_); + EXPECT_EQ("beta-channel", mutable_props.target_channel); +} + +TEST_F(ImagePropertiesTest, OverrideLockDownTest) { + ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(), + "CHROMEOS_RELEASE_BOARD=arm-generic\n" + "CHROMEOS_RELEASE_FOO=bar\n" + "CHROMEOS_RELEASE_TRACK=dev-channel\n" + "CHROMEOS_AUSERVER=https://www.google.com")); + ASSERT_TRUE(WriteFileString( + tempdir_.path().value() + kStatefulPartition + "/etc/lsb-release", + "CHROMEOS_RELEASE_BOARD=x86-generic\n" + "CHROMEOS_RELEASE_TRACK=stable-channel\n" + "CHROMEOS_AUSERVER=http://www.google.com")); + SetLockDown(true); + ImageProperties props = LoadImageProperties(&fake_system_state_); + EXPECT_EQ("arm-generic", props.board); + EXPECT_EQ("dev-channel", props.current_channel); + EXPECT_EQ("https://www.google.com", props.omaha_url); + MutableImageProperties mutable_props = + LoadMutableImageProperties(&fake_system_state_); + EXPECT_EQ("stable-channel", mutable_props.target_channel); +} + +TEST_F(ImagePropertiesTest, BoardAppIdUsedForNonCanaryChannelTest) { + ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(), + "CHROMEOS_RELEASE_APPID=r\n" + "CHROMEOS_BOARD_APPID=b\n" + "CHROMEOS_CANARY_APPID=c\n" + "CHROMEOS_RELEASE_TRACK=stable-channel\n")); + ImageProperties props = LoadImageProperties(&fake_system_state_); + EXPECT_EQ("stable-channel", props.current_channel); + EXPECT_EQ("b", props.product_id); +} + +TEST_F(ImagePropertiesTest, CanaryAppIdUsedForCanaryChannelTest) { + ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(), + "CHROMEOS_RELEASE_APPID=r\n" + "CHROMEOS_BOARD_APPID=b\n" + "CHROMEOS_CANARY_APPID=c\n" + "CHROMEOS_RELEASE_TRACK=canary-channel\n")); + ImageProperties props = LoadImageProperties(&fake_system_state_); + EXPECT_EQ("canary-channel", props.current_channel); + EXPECT_EQ("c", props.canary_product_id); +} + +TEST_F(ImagePropertiesTest, ReleaseAppIdUsedAsDefaultTest) { + ASSERT_TRUE(WriteFileString(tempdir_.path().Append("etc/lsb-release").value(), + "CHROMEOS_RELEASE_APPID=r\n" + "CHROMEOS_CANARY_APPID=c\n" + "CHROMEOS_RELEASE_TRACK=stable-channel\n")); + ImageProperties props = LoadImageProperties(&fake_system_state_); + EXPECT_EQ("stable-channel", props.current_channel); + EXPECT_EQ("r", props.product_id); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/include/debugd/dbus-proxies.h b/update_engine/include/debugd/dbus-proxies.h new file mode 100644 index 0000000..a528480 --- /dev/null +++ b/update_engine/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/update_engine/include/debugd/dbus-proxy-mocks.h b/update_engine/include/debugd/dbus-proxy-mocks.h new file mode 100644 index 0000000..042a9fd --- /dev/null +++ b/update_engine/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/update_engine/include/libcros/dbus-proxy-mocks.h b/update_engine/include/libcros/dbus-proxy-mocks.h new file mode 100644 index 0000000..16790bd --- /dev/null +++ b/update_engine/include/libcros/dbus-proxy-mocks.h
@@ -0,0 +1,77 @@ +// Automatic generation of D-Bus interface mock proxies for: +// - org.chromium.LibCrosServiceInterface +// - org.chromium.UpdateEngineLibcrosProxyResolvedInterface +#ifndef ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_LIBCROS_DBUS_PROXY_MOCKS_H +#define ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_LIBCROS_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 "libcros/dbus-proxies.h" + +namespace org { +namespace chromium { + +// Mock object for LibCrosServiceInterfaceProxyInterface. +class LibCrosServiceInterfaceProxyMock : public LibCrosServiceInterfaceProxyInterface { + public: + LibCrosServiceInterfaceProxyMock() = default; + + MOCK_METHOD5(ResolveNetworkProxy, + bool(const std::string& /*in_source_url*/, + const std::string& /*in_signal_interface*/, + const std::string& /*in_signal_name*/, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD6(ResolveNetworkProxyAsync, + void(const std::string& /*in_source_url*/, + const std::string& /*in_signal_interface*/, + const std::string& /*in_signal_name*/, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetKioskAppRequiredPlatformVersion, + bool(std::string* /*out_required_platform_version*/, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetKioskAppRequiredPlatformVersionAsync, + void(const base::Callback<void(const std::string& /*required_platform_version*/)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_CONST_METHOD0(GetObjectPath, const dbus::ObjectPath&()); + + private: + DISALLOW_COPY_AND_ASSIGN(LibCrosServiceInterfaceProxyMock); +}; +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Mock object for UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface. +class UpdateEngineLibcrosProxyResolvedInterfaceProxyMock : public UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface { + public: + UpdateEngineLibcrosProxyResolvedInterfaceProxyMock() = default; + + MOCK_METHOD2(RegisterProxyResolvedSignalHandler, + void(const base::Callback<void(const std::string&, + const std::string&, + const std::string&)>& /*signal_callback*/, + dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/)); + MOCK_CONST_METHOD0(GetObjectPath, const dbus::ObjectPath&()); + + private: + DISALLOW_COPY_AND_ASSIGN(UpdateEngineLibcrosProxyResolvedInterfaceProxyMock); +}; +} // namespace chromium +} // namespace org + +#endif // ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_LIBCROS_DBUS_PROXY_MOCKS_H
diff --git a/update_engine/include/power_manager/dbus-proxies.h b/update_engine/include/power_manager/dbus-proxies.h new file mode 100644 index 0000000..e66848d --- /dev/null +++ b/update_engine/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/update_engine/include/power_manager/dbus-proxy-mocks.h b/update_engine/include/power_manager/dbus-proxy-mocks.h new file mode 100644 index 0000000..d4e3dd0 --- /dev/null +++ b/update_engine/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/update_engine/include/session_manager/dbus-proxies.h b/update_engine/include/session_manager/dbus-proxies.h new file mode 100644 index 0000000..2ca0128 --- /dev/null +++ b/update_engine/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/update_engine/include/session_manager/dbus-proxy-mocks.h b/update_engine/include/session_manager/dbus-proxy-mocks.h new file mode 100644 index 0000000..2b6ce4d --- /dev/null +++ b/update_engine/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/update_engine/include/shill/dbus-proxy-mocks.h b/update_engine/include/shill/dbus-proxy-mocks.h new file mode 100644 index 0000000..e5d52f7 --- /dev/null +++ b/update_engine/include/shill/dbus-proxy-mocks.h
@@ -0,0 +1,549 @@ +// Automatic generation of D-Bus interface mock proxies for: +// - org.chromium.flimflam.Manager +// - org.chromium.flimflam.Service +#ifndef ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_SHILL_DBUS_PROXY_MOCKS_H +#define ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_SHILL_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 "shill/dbus-proxies.h" + +namespace org { +namespace chromium { +namespace flimflam { + +// Mock object for ManagerProxyInterface. +class ManagerProxyMock : public ManagerProxyInterface { + public: + ManagerProxyMock() = default; + + MOCK_METHOD3(GetProperties, + bool(brillo::VariantDictionary*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetPropertiesAsync, + void(const base::Callback<void(const brillo::VariantDictionary&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(SetProperty, + bool(const std::string&, + const brillo::Any&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD5(SetPropertyAsync, + void(const std::string&, + const brillo::Any&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetState, + bool(std::string*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetStateAsync, + void(const base::Callback<void(const std::string&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(CreateProfile, + bool(const std::string&, + dbus::ObjectPath*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(CreateProfileAsync, + void(const std::string&, + const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(RemoveProfile, + bool(const std::string&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(RemoveProfileAsync, + void(const std::string&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(PushProfile, + bool(const std::string&, + dbus::ObjectPath*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(PushProfileAsync, + void(const std::string&, + const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD5(InsertUserProfile, + bool(const std::string&, + const std::string&, + dbus::ObjectPath*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD5(InsertUserProfileAsync, + void(const std::string&, + const std::string&, + const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(PopProfile, + bool(const std::string&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(PopProfileAsync, + void(const std::string&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(PopAnyProfile, + bool(brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(PopAnyProfileAsync, + void(const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(PopAllUserProfiles, + bool(brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(PopAllUserProfilesAsync, + void(const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(RecheckPortal, + bool(brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(RecheckPortalAsync, + void(const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(RequestScan, + bool(const std::string&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(RequestScanAsync, + void(const std::string&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(EnableTechnology, + bool(const std::string&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(EnableTechnologyAsync, + void(const std::string&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(DisableTechnology, + bool(const std::string&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(DisableTechnologyAsync, + void(const std::string&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(GetService, + bool(const brillo::VariantDictionary&, + dbus::ObjectPath*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(GetServiceAsync, + void(const brillo::VariantDictionary&, + const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(GetWifiService, + bool(const brillo::VariantDictionary&, + dbus::ObjectPath*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(GetWifiServiceAsync, + void(const brillo::VariantDictionary&, + const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(ConfigureService, + bool(const brillo::VariantDictionary&, + dbus::ObjectPath*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(ConfigureServiceAsync, + void(const brillo::VariantDictionary&, + const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD5(ConfigureServiceForProfile, + bool(const dbus::ObjectPath&, + const brillo::VariantDictionary&, + dbus::ObjectPath*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD5(ConfigureServiceForProfileAsync, + void(const dbus::ObjectPath&, + const brillo::VariantDictionary&, + const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(FindMatchingService, + bool(const brillo::VariantDictionary&, + dbus::ObjectPath*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(FindMatchingServiceAsync, + void(const brillo::VariantDictionary&, + const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(GetVPNService, + bool(const brillo::VariantDictionary&, + dbus::ObjectPath*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(GetVPNServiceAsync, + void(const brillo::VariantDictionary&, + const base::Callback<void(const dbus::ObjectPath&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetDebugLevel, + bool(int32_t*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetDebugLevelAsync, + void(const base::Callback<void(int32_t)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(SetDebugLevel, + bool(int32_t, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(SetDebugLevelAsync, + void(int32_t, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetServiceOrder, + bool(std::string*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetServiceOrderAsync, + void(const base::Callback<void(const std::string&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(SetServiceOrder, + bool(const std::string&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(SetServiceOrderAsync, + void(const std::string&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetDebugTags, + bool(std::string*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetDebugTagsAsync, + void(const base::Callback<void(const std::string&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(SetDebugTags, + bool(const std::string&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(SetDebugTagsAsync, + void(const std::string&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(ListDebugTags, + bool(std::string*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(ListDebugTagsAsync, + void(const base::Callback<void(const std::string&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetNetworksForGeolocation, + bool(brillo::VariantDictionary*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetNetworksForGeolocationAsync, + void(const base::Callback<void(const brillo::VariantDictionary&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD10(VerifyDestination, + bool(const std::string& /*in_certificate*/, + const std::string& /*in_public_key*/, + const std::string& /*in_nonce*/, + const std::string& /*in_signed_data*/, + const std::string& /*in_destination_udn*/, + const std::string& /*in_hotspot_ssid*/, + const std::string& /*in_hotspot_bssid*/, + bool*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD10(VerifyDestinationAsync, + void(const std::string& /*in_certificate*/, + const std::string& /*in_public_key*/, + const std::string& /*in_nonce*/, + const std::string& /*in_signed_data*/, + const std::string& /*in_destination_udn*/, + const std::string& /*in_hotspot_ssid*/, + const std::string& /*in_hotspot_bssid*/, + const base::Callback<void(bool)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + bool VerifyAndEncryptCredentials(const std::string& /*in_certificate*/, + const std::string& /*in_public_key*/, + const std::string& /*in_nonce*/, + const std::string& /*in_signed_data*/, + const std::string& /*in_destination_udn*/, + const std::string& /*in_hotspot_ssid*/, + const std::string& /*in_hotspot_bssid*/, + const dbus::ObjectPath& /*in_network*/, + std::string*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/) override { + LOG(WARNING) << "VerifyAndEncryptCredentials(): gmock can't handle methods with 11 arguments. You can override this method in a subclass if you need to."; + return false; + } + void VerifyAndEncryptCredentialsAsync(const std::string& /*in_certificate*/, + const std::string& /*in_public_key*/, + const std::string& /*in_nonce*/, + const std::string& /*in_signed_data*/, + const std::string& /*in_destination_udn*/, + const std::string& /*in_hotspot_ssid*/, + const std::string& /*in_hotspot_bssid*/, + const dbus::ObjectPath& /*in_network*/, + const base::Callback<void(const std::string&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/) override { + LOG(WARNING) << "VerifyAndEncryptCredentialsAsync(): gmock can't handle methods with 11 arguments. You can override this method in a subclass if you need to."; + } + bool VerifyAndEncryptData(const std::string& /*in_certificate*/, + const std::string& /*in_public_key*/, + const std::string& /*in_nonce*/, + const std::string& /*in_signed_data*/, + const std::string& /*in_destination_udn*/, + const std::string& /*in_hotspot_ssid*/, + const std::string& /*in_hotspot_bssid*/, + const std::string& /*in_data*/, + std::string*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/) override { + LOG(WARNING) << "VerifyAndEncryptData(): gmock can't handle methods with 11 arguments. You can override this method in a subclass if you need to."; + return false; + } + void VerifyAndEncryptDataAsync(const std::string& /*in_certificate*/, + const std::string& /*in_public_key*/, + const std::string& /*in_nonce*/, + const std::string& /*in_signed_data*/, + const std::string& /*in_destination_udn*/, + const std::string& /*in_hotspot_ssid*/, + const std::string& /*in_hotspot_bssid*/, + const std::string& /*in_data*/, + const base::Callback<void(const std::string&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/) override { + LOG(WARNING) << "VerifyAndEncryptDataAsync(): gmock can't handle methods with 11 arguments. You can override this method in a subclass if you need to."; + } + MOCK_METHOD2(ConnectToBestServices, + bool(brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(ConnectToBestServicesAsync, + void(const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(CreateConnectivityReport, + bool(brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(CreateConnectivityReportAsync, + void(const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(ClaimInterface, + bool(const std::string& /*in_claimer_name*/, + const std::string& /*in_interface_name*/, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD5(ClaimInterfaceAsync, + void(const std::string& /*in_claimer_name*/, + const std::string& /*in_interface_name*/, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(ReleaseInterface, + bool(const std::string& /*in_claimer_name*/, + const std::string& /*in_interface_name*/, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD5(ReleaseInterfaceAsync, + void(const std::string& /*in_claimer_name*/, + const std::string& /*in_interface_name*/, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(SetSchedScan, + bool(bool, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(SetSchedScanAsync, + void(bool, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(SetupApModeInterface, + bool(std::string* /*out_interface_name*/, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(SetupApModeInterfaceAsync, + void(const base::Callback<void(const std::string& /*interface_name*/)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(SetupStationModeInterface, + bool(std::string* /*out_interface_name*/, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(SetupStationModeInterfaceAsync, + void(const base::Callback<void(const std::string& /*interface_name*/)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(RegisterPropertyChangedSignalHandler, + void(const base::Callback<void(const std::string&, + const brillo::Any&)>& /*signal_callback*/, + dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/)); + MOCK_METHOD2(RegisterStateChangedSignalHandler, + void(const base::Callback<void(const std::string&)>& /*signal_callback*/, + dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/)); + MOCK_CONST_METHOD0(GetObjectPath, const dbus::ObjectPath&()); + + private: + DISALLOW_COPY_AND_ASSIGN(ManagerProxyMock); +}; +} // namespace flimflam +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { +namespace flimflam { + +// Mock object for ServiceProxyInterface. +class ServiceProxyMock : public ServiceProxyInterface { + public: + ServiceProxyMock() = default; + + MOCK_METHOD3(GetProperties, + bool(brillo::VariantDictionary*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetPropertiesAsync, + void(const base::Callback<void(const brillo::VariantDictionary&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(SetProperty, + bool(const std::string&, + const brillo::Any&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD5(SetPropertyAsync, + void(const std::string&, + const brillo::Any&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(SetProperties, + bool(const brillo::VariantDictionary&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(SetPropertiesAsync, + void(const brillo::VariantDictionary&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(ClearProperty, + bool(const std::string&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(ClearPropertyAsync, + void(const std::string&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD4(ClearProperties, + bool(const std::vector<std::string>&, + std::vector<bool>*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(ClearPropertiesAsync, + void(const std::vector<std::string>&, + const base::Callback<void(const std::vector<bool>&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(Connect, + bool(brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(ConnectAsync, + void(const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(Disconnect, + bool(brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(DisconnectAsync, + void(const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(Remove, + bool(brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(RemoveAsync, + void(const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(ActivateCellularModem, + bool(const std::string&, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(ActivateCellularModemAsync, + void(const std::string&, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(CompleteCellularActivation, + bool(brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(CompleteCellularActivationAsync, + void(const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetLoadableProfileEntries, + bool(std::map<dbus::ObjectPath, std::string>*, + brillo::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetLoadableProfileEntriesAsync, + void(const base::Callback<void(const std::map<dbus::ObjectPath, std::string>&)>& /*success_callback*/, + const base::Callback<void(brillo::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(RegisterPropertyChangedSignalHandler, + void(const base::Callback<void(const std::string&, + const brillo::Any&)>& /*signal_callback*/, + dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/)); + MOCK_CONST_METHOD0(GetObjectPath, const dbus::ObjectPath&()); + + private: + DISALLOW_COPY_AND_ASSIGN(ServiceProxyMock); +}; +} // namespace flimflam +} // namespace chromium +} // namespace org + +#endif // ____CHROMEOS_DBUS_BINDING___UPDATE_ENGINE_INCLUDE_SHILL_DBUS_PROXY_MOCKS_H
diff --git a/update_engine/include/update_includes.sh b/update_engine/include/update_includes.sh new file mode 100755 index 0000000..6008d59 --- /dev/null +++ b/update_engine/include/update_includes.sh
@@ -0,0 +1,79 @@ +#!/bin/bash + +# +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +DBUS_GENERATOR=$(which dbus-binding-generator) +MY_DIR=$(dirname "$0") + +if [[ -z "${ANDROID_HOST_OUT}" ]]; then + echo "You must run envsetup.sh and lunch first." >&2 + exit 1 +fi + +if [[ -z "${DBUS_GENERATOR}" ]]; then + echo "DBus bindings generator not found." >&2 + exit 1 +fi + +set -e + +# generate <kind> <dir> <xml> [xml ...] +# Generate a DBus proxy and/or proxy mock in the passed |dir| for the provided +# |xml| service files. +# The parameter |kind| determines whether it should generate the mock only +# (mock), the proxy only (proxy) or both (both). +generate() { + local kind="$1" + local dir="$2" + local xmls=("${@:3}") + + mkdir -p "${MY_DIR}/${dir}" + local outdir=$(realpath "${MY_DIR}/${dir}") + local proxyh="${outdir}/dbus-proxies.h" + local mockh="${outdir}/dbus-proxy-mocks.h" + + ${DBUS_GENERATOR} "${xmls[@]}" --mock="${mockh}" --proxy="${proxyh}" + + # Fix the include path to the dbus-proxies.h to include ${dir}. + sed "s,include \"dbus-proxies.h\",include \"${dir}/dbus-proxies.h\"," \ + -i "${mockh}" + + # Fix the header guards to be independent from the checkout location. + local guard=$(realpath "${MY_DIR}/../.." | tr '[:lower:]/ ' '[:upper:]__') + for header in "${mockh}" "${proxyh}"; do + sed "s,___CHROMEOS_DBUS_BINDING__${guard},___CHROMEOS_DBUS_BINDING__," \ + -i "${header}" + done + + # Remove the files not requested. + if [[ "${kind}" == "mock" ]]; then + rm -f "${proxyh}" + elif [[ "${kind}" == "proxy" ]]; then + rm -f "${mockh}" + fi +} + +UE_DIR=$(realpath "${MY_DIR}/..") +SHILL_DIR=$(realpath "${UE_DIR}/../connectivity/shill") + +generate mock "libcros" \ + "${UE_DIR}/dbus_bindings/org.chromium.LibCrosService.dbus-xml" + +generate mock "shill" \ + "${SHILL_DIR}"/dbus_bindings/org.chromium.flimflam.{Manager,Service}.dbus-xml + +echo "Done."
diff --git a/update_engine/init/update-engine.conf b/update_engine/init/update-engine.conf new file mode 100644 index 0000000..4c05cf4 --- /dev/null +++ b/update_engine/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/update_engine/libcros_proxy.cc b/update_engine/libcros_proxy.cc new file mode 100644 index 0000000..3aa87cb --- /dev/null +++ b/update_engine/libcros_proxy.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/libcros_proxy.h" + +#include "update_engine/dbus_connection.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 = DBusConnection::Get()->GetDBus(); + service_interface_proxy_.reset( + new LibCrosServiceInterfaceProxy(bus, kLibCrosServiceName)); + ue_proxy_resolved_interface_.reset( + 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/update_engine/libcros_proxy.h b/update_engine/libcros_proxy.h new file mode 100644 index 0000000..03bf312 --- /dev/null +++ b/update_engine/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: + LibCrosProxy(); + 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/update_engine/libcurl_http_fetcher.cc b/update_engine/libcurl_http_fetcher.cc new file mode 100644 index 0000000..7cada57 --- /dev/null +++ b/update_engine/libcurl_http_fetcher.cc
@@ -0,0 +1,718 @@ +// +// 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/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/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_->IsOOBEEnabled() && !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::StartsWith( + proxy, "socks5://", base::CompareCase::INSENSITIVE_ASCII) || + base::StartsWith( + proxy, "socks://", base::CompareCase::INSENSITIVE_ASCII)) { + *out_type = CURLPROXY_SOCKS5_HOSTNAME; + return true; + } + if (base::StartsWith( + proxy, "socks4://", base::CompareCase::INSENSITIVE_ASCII)) { + *out_type = CURLPROXY_SOCKS4A; + return true; + } + if (base::StartsWith( + proxy, "http://", base::CompareCase::INSENSITIVE_ASCII) || + base::StartsWith( + proxy, "https://", base::CompareCase::INSENSITIVE_ASCII)) { + *out_type = CURLPROXY_HTTP; + return true; + } + if (base::StartsWith(proxy, kNoProxy, base::CompareCase::INSENSITIVE_ASCII)) { + // 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_); + ignore_failure_ = false; + + 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); + } + + // Setup extra HTTP headers. + if (curl_http_headers_) { + curl_slist_free_all(curl_http_headers_); + curl_http_headers_ = nullptr; + } + for (const auto& header : extra_headers_) { + // curl_slist_append() copies the string. + curl_http_headers_ = + curl_slist_append(curl_http_headers_, header.second.c_str()); + } + if (post_data_set_) { + // Set the Content-Type HTTP header, if one was specifically set. + 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(curl_http_headers_, content_type_attr.c_str()); + } else { + LOG(WARNING) << "no content type set, using libcurl default"; + } + } + CHECK_EQ( + curl_easy_setopt(curl_handle_, CURLOPT_HTTPHEADER, curl_http_headers_), + CURLE_OK); + + 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::StartsWith( + url_, "http://", base::CompareCase::INSENSITIVE_ASCII)) { + SetCurlOptionsForHttp(); + } else if (base::StartsWith( + url_, "https://", base::CompareCase::INSENSITIVE_ASCII)) { + SetCurlOptionsForHttps(); +#if !defined(__CHROMEOS__) && !defined(__BRILLO__) + } else if (base::StartsWith( + url_, "file://", base::CompareCase::INSENSITIVE_ASCII)) { + SetCurlOptionsForFile(); +#endif + } else { + LOG(ERROR) << "Received invalid URI: " << url_; + // Lock down to no protocol supported for the transfer. + CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, 0), CURLE_OK); + } + } 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_SSL_VERIFYHOST, 2), + 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); + +#ifdef USE_NESTLABS + CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_VERBOSE, 1L), + CURLE_OK); + CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_CAINFO, + constants::kCACertificatesFilePath), + CURLE_OK); + CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSLCERTTYPE, + "PEM"), + CURLE_OK); + CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSLCERT, + constants::kSSLCertPath), + CURLE_OK); + CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSLKEYTYPE, + "PEM"), + CURLE_OK); + CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_SSLKEY, + constants::kSSLKeyPath), + CURLE_OK); +#endif + + 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); + } +} + +// Lock down only the protocol in case of a local file. +void LibcurlHttpFetcher::SetCurlOptionsForFile() { + LOG(INFO) << "Setting up curl options for FILE"; + CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_PROTOCOLS, CURLPROTO_FILE), + CURLE_OK); + CHECK_EQ( + curl_easy_setopt(curl_handle_, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_FILE), + 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; + + // If we are paused, we delay these two operations until Unpause is called. + if (transfer_paused_) { + restart_transfer_on_unpause_ = true; + return; + } + 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::SetHeader(const string& header_name, + const string& header_value) { + string header_line = header_name + ": " + header_value; + // Avoid the space if no data on the right side of the semicolon. + if (header_value.empty()) + header_line = header_name + ":"; + TEST_AND_RETURN(header_line.find('\n') == string::npos); + TEST_AND_RETURN(header_name.find(':') == string::npos); + extra_headers_[base::ToLowerASCII(header_name)] = header_line; +} + +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 the transfer completes while paused, we should ignore the failure once + // the fetcher is unpaused. + if (running_handles == 0 && transfer_paused_ && !ignore_failure_) { + LOG(INFO) << "Connection closed while paused, ignoring failure."; + ignore_failure_ = true; + } + + if (running_handles != 0 || transfer_paused_) { + // There's either more work to do or we are paused, so we just keep the + // file descriptors to watch up to date and exit, until we are done with the + // work and we are not paused. + SetupMessageLoopSources(); + return; + } + + // At this point, the transfer was completed in some way (error, connection + // closed or download finished). + + 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_; + } else 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 + return; + } + } else if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) { + if (!ignore_failure_) + 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 + return; + } + // 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); + } + return; + } + // If we reach this point is because TransferComplete() was not called in any + // of the previous branches. The delegate is allowed to destroy the object + // once TransferComplete is called so this would be illegal. + ignore_failure_ = false; +} + +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() { + if (transfer_paused_) { + LOG(ERROR) << "Fetcher already paused."; + return; + } + transfer_paused_ = true; + if (!transfer_in_progress_) { + // If pause before we started a connection, we don't need to notify curl + // about that, we will simply not start the connection later. + return; + } + CHECK(curl_handle_); + CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_ALL), CURLE_OK); +} + +void LibcurlHttpFetcher::Unpause() { + if (!transfer_paused_) { + LOG(ERROR) << "Resume attempted when fetcher not paused."; + return; + } + transfer_paused_ = false; + if (restart_transfer_on_unpause_) { + restart_transfer_on_unpause_ = false; + ResumeTransfer(url_); + CurlPerformOnce(); + return; + } + if (!transfer_in_progress_) { + // If resumed before starting the connection, there's no need to notify + // anybody. We will simply start the connection once it is time. + return; + } + CHECK(curl_handle_); + CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_CONT), CURLE_OK); + // Since the transfer is in progress, we need to dispatch a CurlPerformOnce() + // now to let the connection continue, otherwise it would be called by the + // TimeoutCallback but with a delay. + CurlPerformOnce(); +} + +// 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) { + VLOG(1) << "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() { + if (transfer_paused_) { + restart_transfer_on_unpause_ = true; + return; + } + 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; + transfer_paused_ = false; + restart_transfer_on_unpause_ = false; +} + +void LibcurlHttpFetcher::GetHttpResponseCode() { + long http_response_code = 0; // NOLINT(runtime/int) - curl needs long. + if (base::StartsWith(url_, "file://", base::CompareCase::INSENSITIVE_ASCII)) { + // Fake out a valid response code for file:// URLs. + http_response_code_ = 299; + } else 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/update_engine/libcurl_http_fetcher.h b/update_engine/libcurl_http_fetcher.h new file mode 100644 index 0000000..1541ea4 --- /dev/null +++ b/update_engine/libcurl_http_fetcher.h
@@ -0,0 +1,268 @@ +// +// 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_LIBCURL_HTTP_FETCHER_H_ +#define UPDATE_ENGINE_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/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; + + // Pass the headers to libcurl. + void SetHeader(const std::string& header_name, + const std::string& header_value) 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, if any, or complete + // the transfer and finish the action if no work left to do. + // This method will not block. + 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(); + + // Sets the curl options for file URI. + void SetCurlOptionsForFile(); + + // 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}; + + // The extra headers that will be sent on each request. + std::map<std::string, std::string> extra_headers_; + + // 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}; + bool transfer_paused_{false}; + + // Whether it should ignore transfer failures for the purpose of retrying the + // connection. + bool ignore_failure_{false}; + + // Whether we should restart the transfer once Unpause() is called. This can + // be caused because either the connection dropped while pause or the proxy + // was resolved and we never started the transfer in the first place. + bool restart_transfer_on_unpause_{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}; + + DISALLOW_COPY_AND_ASSIGN(LibcurlHttpFetcher); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_LIBCURL_HTTP_FETCHER_H_
diff --git a/update_engine/libupdate_engine-client-test.pc.in b/update_engine/libupdate_engine-client-test.pc.in new file mode 100644 index 0000000..92a4af3 --- /dev/null +++ b/update_engine/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/update_engine/libupdate_engine-client.pc.in b/update_engine/libupdate_engine-client.pc.in new file mode 100644 index 0000000..4c87e1d --- /dev/null +++ b/update_engine/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/update_engine/local_coverage_rate b/update_engine/local_coverage_rate new file mode 100755 index 0000000..b189806 --- /dev/null +++ b/update_engine/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/update_engine/main.cc b/update_engine/main.cc new file mode 100644 index 0000000..4275bc1 --- /dev/null +++ b/update_engine/main.cc
@@ -0,0 +1,131 @@ +// +// 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 "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/update_engine/metrics.cc b/update_engine/metrics.cc new file mode 100644 index 0000000..742ba7e --- /dev/null +++ b/update_engine/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/update_engine/metrics.h b/update_engine/metrics.h new file mode 100644 index 0000000..7c369ee --- /dev/null +++ b/update_engine/metrics.h
@@ -0,0 +1,321 @@ +// +// 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/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. + kUpdateCanceled, // Update canceled by the user. + + 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/update_engine/metrics_utils.cc b/update_engine/metrics_utils.cc new file mode 100644 index 0000000..263bacd --- /dev/null +++ b/update_engine/metrics_utils.cc
@@ -0,0 +1,309 @@ +// +// 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; + + case ErrorCode::kUserCanceled: + return metrics::AttemptResult::kUpdateCanceled; + + // 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: + // TODO(deymo): The next two items belong in their own category; they + // should not be counted as internal errors. b/27112092 + case ErrorCode::kOmahaUpdateDeferredPerPolicy: + case ErrorCode::kNonCriticalUpdateInOOBE: + case ErrorCode::kOmahaErrorInHTTPResponse: + case ErrorCode::kDownloadMetadataSignatureMissingError: + case ErrorCode::kOmahaUpdateDeferredForBackoff: + 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::kNonCriticalUpdateInOOBE: + 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: + case ErrorCode::kUserCanceled: + 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(ConnectionType type, + ConnectionTethering tethering) { + switch (type) { + case ConnectionType::kUnknown: + return metrics::ConnectionType::kUnknown; + + case ConnectionType::kEthernet: + if (tethering == ConnectionTethering::kConfirmed) + return metrics::ConnectionType::kTetheredEthernet; + else + return metrics::ConnectionType::kEthernet; + + case ConnectionType::kWifi: + if (tethering == ConnectionTethering::kConfirmed) + return metrics::ConnectionType::kTetheredWifi; + else + return metrics::ConnectionType::kWifi; + + case ConnectionType::kWimax: + return metrics::ConnectionType::kWimax; + + case ConnectionType::kBluetooth: + return metrics::ConnectionType::kBluetooth; + + case ConnectionType::kCellular: + return metrics::ConnectionType::kCellular; + } + + 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/update_engine/metrics_utils.h b/update_engine/metrics_utils.h new file mode 100644 index 0000000..d9826c1 --- /dev/null +++ b/update_engine/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_utils.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(ConnectionType type, + ConnectionTethering tethering); + +// This function returns the duration on the wallclock since the last +// time it was called for the same |state_variable_key| value. +// +// If the function returns |true|, the duration (always non-negative) +// is returned in |out_duration|. If the function returns |false| +// something went wrong or there was no previous measurement. +bool WallclockDurationHelper(SystemState* system_state, + const std::string& state_variable_key, + base::TimeDelta* out_duration); + +// This function returns the duration on the monotonic clock since the +// last time it was called for the same |storage| pointer. +// +// You should pass a pointer to a 64-bit integer in |storage| which +// should be initialized to 0. +// +// If the function returns |true|, the duration (always non-negative) +// is returned in |out_duration|. If the function returns |false| +// something went wrong or there was no previous measurement. +bool MonotonicDurationHelper(SystemState* system_state, + int64_t* storage, + base::TimeDelta* out_duration); + +} // namespace metrics_utils +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_METRICS_UTILS_H_
diff --git a/update_engine/metrics_utils_unittest.cc b/update_engine/metrics_utils_unittest.cc new file mode 100644 index 0000000..edf6bc3 --- /dev/null +++ b/update_engine/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(ConnectionType::kUnknown, + ConnectionTethering::kUnknown)); + EXPECT_EQ(metrics::ConnectionType::kEthernet, + GetConnectionType(ConnectionType::kEthernet, + ConnectionTethering::kUnknown)); + EXPECT_EQ(metrics::ConnectionType::kWifi, + GetConnectionType(ConnectionType::kWifi, + ConnectionTethering::kUnknown)); + EXPECT_EQ(metrics::ConnectionType::kWimax, + GetConnectionType(ConnectionType::kWimax, + ConnectionTethering::kUnknown)); + EXPECT_EQ(metrics::ConnectionType::kBluetooth, + GetConnectionType(ConnectionType::kBluetooth, + ConnectionTethering::kUnknown)); + EXPECT_EQ(metrics::ConnectionType::kCellular, + GetConnectionType(ConnectionType::kCellular, + ConnectionTethering::kUnknown)); + EXPECT_EQ(metrics::ConnectionType::kTetheredEthernet, + GetConnectionType(ConnectionType::kEthernet, + ConnectionTethering::kConfirmed)); + EXPECT_EQ(metrics::ConnectionType::kTetheredWifi, + GetConnectionType(ConnectionType::kWifi, + ConnectionTethering::kConfirmed)); + + // Ensure that we don't report tethered ethernet unless it's confirmed. + EXPECT_EQ(metrics::ConnectionType::kEthernet, + GetConnectionType(ConnectionType::kEthernet, + ConnectionTethering::kNotDetected)); + EXPECT_EQ(metrics::ConnectionType::kEthernet, + GetConnectionType(ConnectionType::kEthernet, + ConnectionTethering::kSuspected)); + EXPECT_EQ(metrics::ConnectionType::kEthernet, + GetConnectionType(ConnectionType::kEthernet, + ConnectionTethering::kUnknown)); + + // Ditto for tethered wifi. + EXPECT_EQ(metrics::ConnectionType::kWifi, + GetConnectionType(ConnectionType::kWifi, + ConnectionTethering::kNotDetected)); + EXPECT_EQ(metrics::ConnectionType::kWifi, + GetConnectionType(ConnectionType::kWifi, + ConnectionTethering::kSuspected)); + EXPECT_EQ(metrics::ConnectionType::kWifi, + GetConnectionType(ConnectionType::kWifi, + ConnectionTethering::kUnknown)); +} + +TEST(MetricsUtilsTest, WallclockDurationHelper) { + FakeSystemState fake_system_state; + FakeClock fake_clock; + base::TimeDelta duration; + const std::string state_variable_key = "test-prefs"; + FakePrefs fake_prefs; + + fake_system_state.set_clock(&fake_clock); + fake_system_state.set_prefs(&fake_prefs); + + // Initialize wallclock to 1 sec. + fake_clock.SetWallclockTime(base::Time::FromInternalValue(1000000)); + + // First time called so no previous measurement available. + EXPECT_FALSE(metrics_utils::WallclockDurationHelper(&fake_system_state, + state_variable_key, + &duration)); + + // Next time, we should get zero since the clock didn't advance. + EXPECT_TRUE(metrics_utils::WallclockDurationHelper(&fake_system_state, + state_variable_key, + &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // We can also call it as many times as we want with it being + // considered a failure. + EXPECT_TRUE(metrics_utils::WallclockDurationHelper(&fake_system_state, + state_variable_key, + &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + EXPECT_TRUE(metrics_utils::WallclockDurationHelper(&fake_system_state, + state_variable_key, + &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // Advance the clock one second, then we should get 1 sec on the + // next call and 0 sec on the subsequent call. + fake_clock.SetWallclockTime(base::Time::FromInternalValue(2000000)); + EXPECT_TRUE(metrics_utils::WallclockDurationHelper(&fake_system_state, + state_variable_key, + &duration)); + EXPECT_EQ(duration.InSeconds(), 1); + EXPECT_TRUE(metrics_utils::WallclockDurationHelper(&fake_system_state, + state_variable_key, + &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // Advance clock two seconds and we should get 2 sec and then 0 sec. + fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000)); + EXPECT_TRUE(metrics_utils::WallclockDurationHelper(&fake_system_state, + state_variable_key, + &duration)); + EXPECT_EQ(duration.InSeconds(), 2); + EXPECT_TRUE(metrics_utils::WallclockDurationHelper(&fake_system_state, + state_variable_key, + &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // There's a possibility that the wallclock can go backwards (NTP + // adjustments, for example) so check that we properly handle this + // case. + fake_clock.SetWallclockTime(base::Time::FromInternalValue(3000000)); + EXPECT_FALSE(metrics_utils::WallclockDurationHelper(&fake_system_state, + state_variable_key, + &duration)); + fake_clock.SetWallclockTime(base::Time::FromInternalValue(4000000)); + EXPECT_TRUE(metrics_utils::WallclockDurationHelper(&fake_system_state, + state_variable_key, + &duration)); + EXPECT_EQ(duration.InSeconds(), 1); +} + +TEST(MetricsUtilsTest, MonotonicDurationHelper) { + int64_t storage = 0; + FakeSystemState fake_system_state; + FakeClock fake_clock; + base::TimeDelta duration; + + fake_system_state.set_clock(&fake_clock); + + // Initialize monotonic clock to 1 sec. + fake_clock.SetMonotonicTime(base::Time::FromInternalValue(1000000)); + + // First time called so no previous measurement available. + EXPECT_FALSE(metrics_utils::MonotonicDurationHelper(&fake_system_state, + &storage, + &duration)); + + // Next time, we should get zero since the clock didn't advance. + EXPECT_TRUE(metrics_utils::MonotonicDurationHelper(&fake_system_state, + &storage, + &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // We can also call it as many times as we want with it being + // considered a failure. + EXPECT_TRUE(metrics_utils::MonotonicDurationHelper(&fake_system_state, + &storage, + &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + EXPECT_TRUE(metrics_utils::MonotonicDurationHelper(&fake_system_state, + &storage, + &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // Advance the clock one second, then we should get 1 sec on the + // next call and 0 sec on the subsequent call. + fake_clock.SetMonotonicTime(base::Time::FromInternalValue(2000000)); + EXPECT_TRUE(metrics_utils::MonotonicDurationHelper(&fake_system_state, + &storage, + &duration)); + EXPECT_EQ(duration.InSeconds(), 1); + EXPECT_TRUE(metrics_utils::MonotonicDurationHelper(&fake_system_state, + &storage, + &duration)); + EXPECT_EQ(duration.InSeconds(), 0); + + // Advance clock two seconds and we should get 2 sec and then 0 sec. + fake_clock.SetMonotonicTime(base::Time::FromInternalValue(4000000)); + EXPECT_TRUE(metrics_utils::MonotonicDurationHelper(&fake_system_state, + &storage, + &duration)); + EXPECT_EQ(duration.InSeconds(), 2); + EXPECT_TRUE(metrics_utils::MonotonicDurationHelper(&fake_system_state, + &storage, + &duration)); + EXPECT_EQ(duration.InSeconds(), 0); +} + +} // namespace metrics_utils +} // namespace chromeos_update_engine
diff --git a/update_engine/mock_certificate_checker.h b/update_engine/mock_certificate_checker.h new file mode 100644 index 0000000..c86f502 --- /dev/null +++ b/update_engine/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_MOCK_CERTIFICATE_CHECKER_H_ +#define UPDATE_ENGINE_MOCK_CERTIFICATE_CHECKER_H_ + +#include <gmock/gmock.h> +#include <openssl/ssl.h> + +#include "update_engine/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_MOCK_CERTIFICATE_CHECKER_H_
diff --git a/update_engine/mock_connection_manager.h b/update_engine/mock_connection_manager.h new file mode 100644 index 0000000..e37460b --- /dev/null +++ b/update_engine/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(ConnectionType* out_type, + ConnectionTethering* out_tethering)); + + MOCK_CONST_METHOD2(IsUpdateAllowedOver, + bool(ConnectionType type, ConnectionTethering tethering)); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_MOCK_CONNECTION_MANAGER_H_
diff --git a/update_engine/mock_file_writer.h b/update_engine/mock_file_writer.h new file mode 100644 index 0000000..72d6a86 --- /dev/null +++ b/update_engine/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/update_engine/mock_omaha_request_params.h b/update_engine/mock_omaha_request_params.h new file mode 100644 index 0000000..5d5d47b --- /dev/null +++ b/update_engine/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/update_engine/mock_p2p_manager.h b/update_engine/mock_p2p_manager.h new file mode 100644 index 0000000..5f4418e --- /dev/null +++ b/update_engine/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/update_engine/mock_payload_state.h b/update_engine/mock_payload_state.h new file mode 100644 index 0000000..2f654c7 --- /dev/null +++ b/update_engine/mock_payload_state.h
@@ -0,0 +1,82 @@ +// +// 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()); + MOCK_CONST_METHOD0(GetAttemptErrorCode, ErrorCode()); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_MOCK_PAYLOAD_STATE_H_
diff --git a/update_engine/mock_power_manager.h b/update_engine/mock_power_manager.h new file mode 100644 index 0000000..8363171 --- /dev/null +++ b/update_engine/mock_power_manager.h
@@ -0,0 +1,35 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_MOCK_POWER_MANAGER_H_ +#define UPDATE_ENGINE_MOCK_POWER_MANAGER_H_ + +#include <gmock/gmock.h> + +#include "update_engine/power_manager_interface.h" + +namespace chromeos_update_engine { + +class MockPowerManager : public PowerManagerInterface { + public: + MockPowerManager() = default; + + MOCK_METHOD0(RequestReboot, bool(void)); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_MOCK_POWER_MANAGER_H_
diff --git a/update_engine/mock_proxy_resolver.h b/update_engine/mock_proxy_resolver.h new file mode 100644 index 0000000..0595f5a --- /dev/null +++ b/update_engine/mock_proxy_resolver.h
@@ -0,0 +1,38 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_MOCK_PROXY_RESOLVER_H_ +#define UPDATE_ENGINE_MOCK_PROXY_RESOLVER_H_ + +#include <string> + +#include <gmock/gmock.h> + +#include "update_engine/proxy_resolver.h" + +namespace chromeos_update_engine { + +class MockProxyResolver : public ProxyResolver { + public: + MOCK_METHOD3(GetProxiesForUrl, + bool(const std::string& url, + ProxiesResolvedFn callback, + void* data)); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_MOCK_PROXY_RESOLVER_H_
diff --git a/update_engine/mock_update_attempter.h b/update_engine/mock_update_attempter.h new file mode 100644 index 0000000..89f163e --- /dev/null +++ b/update_engine/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/update_engine/network_selector.h b/update_engine/network_selector.h new file mode 100644 index 0000000..22aed8e --- /dev/null +++ b/update_engine/network_selector.h
@@ -0,0 +1,33 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_NETWORK_SELECTOR_H_ +#define UPDATE_ENGINE_NETWORK_SELECTOR_H_ + +#include <memory> + +#include "update_engine/network_selector_interface.h" + +namespace chromeos_update_engine { +namespace network { + +// Creates the NetworkSelectorInterface instance for the given platform. +std::unique_ptr<NetworkSelectorInterface> CreateNetworkSelector(); + +} // namespace network +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_NETWORK_SELECTOR_H_
diff --git a/update_engine/network_selector_android.cc b/update_engine/network_selector_android.cc new file mode 100644 index 0000000..6879b69 --- /dev/null +++ b/update_engine/network_selector_android.cc
@@ -0,0 +1,45 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/network_selector_android.h" + +#include <android/multinetwork.h> +#include <base/logging.h> +#include <brillo/make_unique_ptr.h> + +namespace chromeos_update_engine { + +namespace network { + +// Factory defined in network_selector.h. +std::unique_ptr<NetworkSelectorInterface> CreateNetworkSelector() { + return brillo::make_unique_ptr(new NetworkSelectorAndroid()); +} + +} // namespace network + +// Defined in network_selector_interface.h. +const NetworkId kDefaultNetworkId = NETWORK_UNSPECIFIED; + +bool NetworkSelectorAndroid::SetProcessNetwork(NetworkId network_id) { + if (android_setprocnetwork(network_id) < 0) { + PLOG(ERROR) << "Binding the network to " << network_id; + return false; + } + return true; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/network_selector_android.h b/update_engine/network_selector_android.h new file mode 100644 index 0000000..135536c --- /dev/null +++ b/update_engine/network_selector_android.h
@@ -0,0 +1,40 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_NETWORK_SELECTOR_ANDROID_H_ +#define UPDATE_ENGINE_NETWORK_SELECTOR_ANDROID_H_ + +#include <base/macros.h> + +#include "update_engine/network_selector_interface.h" + +namespace chromeos_update_engine { + +class NetworkSelectorAndroid final : public NetworkSelectorInterface { + public: + NetworkSelectorAndroid() = default; + ~NetworkSelectorAndroid() override = default; + + // NetworkSelectorInterface overrides. + bool SetProcessNetwork(NetworkId network_id) override; + + private: + DISALLOW_COPY_AND_ASSIGN(NetworkSelectorAndroid); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_NETWORK_SELECTOR_ANDROID_H_
diff --git a/update_engine/network_selector_interface.h b/update_engine/network_selector_interface.h new file mode 100644 index 0000000..6c17b2c --- /dev/null +++ b/update_engine/network_selector_interface.h
@@ -0,0 +1,49 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_NETWORK_SELECTOR_INTERFACE_H_ +#define UPDATE_ENGINE_NETWORK_SELECTOR_INTERFACE_H_ + +#include <cstdint> + +namespace chromeos_update_engine { + +typedef uint64_t NetworkId; + +// A constant value used to indicate the default network id. Defined in the +// network_selector_*.cc file. +extern const NetworkId kDefaultNetworkId; + +// A class that handles the network used for the connections performed from this +// process in a platform-specific way. + +class NetworkSelectorInterface { + public: + + virtual ~NetworkSelectorInterface() = default; + + // Set the current process network. All sockets created in the future will be + // bound to this particular network. Call this with the special value + // kNetworkId to use the default network. + virtual bool SetProcessNetwork(NetworkId network_id) = 0; + + protected: + NetworkSelectorInterface() = default; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_NETWORK_SELECTOR_INTERFACE_H_
diff --git a/update_engine/network_selector_stub.cc b/update_engine/network_selector_stub.cc new file mode 100644 index 0000000..218d454 --- /dev/null +++ b/update_engine/network_selector_stub.cc
@@ -0,0 +1,44 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/network_selector_stub.h" + +#include <base/logging.h> +#include <brillo/make_unique_ptr.h> + +namespace chromeos_update_engine { + +namespace network { + +// Factory defined in network_selector.h. +std::unique_ptr<NetworkSelectorInterface> CreateNetworkSelector() { + return brillo::make_unique_ptr(new NetworkSelectorStub()); +} + +} // namespace network + +// Defined in network_selector_interface.h. +const NetworkId kDefaultNetworkId = 0; + +bool NetworkSelectorStub::SetProcessNetwork(NetworkId network_id) { + if (network_id != kDefaultNetworkId) { + LOG(ERROR) << "SetProcessNetwork not implemented."; + return false; + } + return true; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/network_selector_stub.h b/update_engine/network_selector_stub.h new file mode 100644 index 0000000..b3f7b48 --- /dev/null +++ b/update_engine/network_selector_stub.h
@@ -0,0 +1,40 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_NETWORK_SELECTOR_STUB_H_ +#define UPDATE_ENGINE_NETWORK_SELECTOR_STUB_H_ + +#include <base/macros.h> + +#include "update_engine/network_selector_interface.h" + +namespace chromeos_update_engine { + +class NetworkSelectorStub final : public NetworkSelectorInterface { + public: + NetworkSelectorStub() = default; + ~NetworkSelectorStub() override = default; + + // NetworkSelectorInterface overrides. + bool SetProcessNetwork(NetworkId network_id) override; + + private: + DISALLOW_COPY_AND_ASSIGN(NetworkSelectorStub); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_NETWORK_SELECTOR_STUB_H_
diff --git a/update_engine/omaha_request_action.cc b/update_engine/omaha_request_action.cc new file mode 100644 index 0000000..3d2dac1 --- /dev/null +++ b/update_engine/omaha_request_action.cc
@@ -0,0 +1,1535 @@ +// +// 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_interface.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"; + +// X-GoogleUpdate headers. +static const char* kXGoogleUpdateInteractivity = "X-GoogleUpdate-Interactivity"; +static const char* kXGoogleUpdateAppId = "X-GoogleUpdate-AppId"; +static const char* kXGoogleUpdateUpdater = "X-GoogleUpdate-Updater"; + +// updatecheck attributes (without the underscore prefix). +static const char* kEolAttr = "eol"; + +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; + map<string, string> updatecheck_attrs; + 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"]; + // Omaha sends arbitrary key-value pairs as extra attributes starting with + // an underscore. + for (const auto& attr : attrs) { + if (!attr.first.empty() && attr.first[0] == '_') + data->updatecheck_attrs[attr.first.substr(1)] = attr.second; + } + } else if (data->current_path == "/response/daystart") { + // Get the install-date. + data->daystart_elapsed_days = attrs["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, + base::CompareCase::SENSITIVE)) { + 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()->IsOOBEEnabled() || + !system_state->hardware()->IsOOBEComplete(&time_of_oobe)) { + LOG(INFO) << "Not generating Omaha InstallData as we have " + << "no prefs file and OOBE is not complete or not enabled."; + return -1; + } + + 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_)); + + // Set X-GoogleUpdate headers. + http_fetcher_->SetHeader(kXGoogleUpdateInteractivity, + params_->interactive() ? "fg" : "bg"); + http_fetcher_->SetHeader(kXGoogleUpdateAppId, params_->GetAppId()); + http_fetcher_->SetHeader( + kXGoogleUpdateUpdater, + base::StringPrintf( + "%s-%s", constants::kOmahaUpdaterID, kOmahaUpdaterVersion)); + + 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); + + // Parse the updatecheck attributes. + PersistEolStatus(parser_data->updatecheck_attrs); + + 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."; + 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 (system_state_->hardware()->IsOOBEEnabled() && + !system_state_->hardware()->IsOOBEComplete(nullptr) && + output_object.deadline.empty() && + params_->app_version() != "ForcedUpdate") { + output_object.update_exists = false; + LOG(INFO) << "Ignoring non-critical Omaha updates until OOBE is done."; + completer.set_code(ErrorCode::kNonCriticalUpdateInOOBE); + return; + } + + 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 manifest_signature_size = 0; + int64_t next_data_offset = 0; + int64_t next_data_length = 0; + if (system_state_ && + system_state_->prefs()->GetInt64(kPrefsManifestMetadataSize, + &manifest_metadata_size) && + manifest_metadata_size != -1 && + system_state_->prefs()->GetInt64(kPrefsManifestSignatureSize, + &manifest_signature_size) && + manifest_signature_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 + manifest_signature_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 = system_state_->clock()->GetWallclockTime(); + 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 = + system_state_->clock()->GetWallclockTime() - 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; +} + +bool OmahaRequestAction::PersistEolStatus(const map<string, string>& attrs) { + auto eol_attr = attrs.find(kEolAttr); + if (eol_attr != attrs.end()) { + return system_state_->prefs()->SetString(kPrefsOmahaEolStatus, + eol_attr->second); + } else if (system_state_->prefs()->Exists(kPrefsOmahaEolStatus)) { + return system_state_->prefs()->Delete(kPrefsOmahaEolStatus); + } + 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 { + ConnectionType type; + ConnectionTethering 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 " + << connection_utils::StringForConnectionType(type) + << ", Updates allowed: " << (is_allowed ? "Yes" : "No"); + return is_allowed; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/omaha_request_action.h b/update_engine/omaha_request_action.h new file mode 100644 index 0000000..2915a6a --- /dev/null +++ b/update_engine/omaha_request_action.h
@@ -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. +// + +#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 <map> +#include <memory> +#include <string> +#include <vector> + +#include <gtest/gtest_prod.h> // for FRIEND_TEST + +#include <brillo/secure_blob.h> +#include <curl/curl.h> + +#include "update_engine/common/action.h" +#include "update_engine/common/http_fetcher.h" +#include "update_engine/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); + + // Parse and persist the end-of-life status flag sent back in the updatecheck + // tag attributes. The flag will be validated and stored in the Prefs. + bool PersistEolStatus(const std::map<std::string, std::string>& attrs); + + // 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/update_engine/omaha_request_action_unittest.cc b/update_engine/omaha_request_action_unittest.cc new file mode 100644 index 0000000..1c1d25c --- /dev/null +++ b/update_engine/omaha_request_action_unittest.cc
@@ -0,0 +1,2263 @@ +// +// 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/files/scoped_temp_dir.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/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 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(); } + using InputObjectType = + ActionTraits<OutputObjectCollectorAction>::InputObjectType; + using OutputObjectType = + ActionTraits<OutputObjectCollectorAction>::OutputObjectType; + 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( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor))); + loop.Run(); + 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( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor))); + loop.Run(); + EXPECT_FALSE(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, ExtraHeadersSentTest) { + const string http_response = "<?xml invalid response"; + request_params_.set_interactive(true); + + brillo::FakeMessageLoop loop(nullptr); + loop.SetAsCurrent(); + + MockHttpFetcher* fetcher = + new MockHttpFetcher(http_response.data(), http_response.size(), nullptr); + OmahaRequestAction action( + &fake_system_state_, nullptr, brillo::make_unique_ptr(fetcher), false); + ActionProcessor processor; + processor.EnqueueAction(&action); + + loop.PostTask(base::Bind( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor))); + loop.Run(); + EXPECT_FALSE(loop.PendingTasks()); + + // Check that the headers were set in the fetcher during the action. Note that + // we set this request as "interactive". + EXPECT_EQ("fg", fetcher->GetHeader("X-GoogleUpdate-Interactivity")); + EXPECT_EQ(kTestAppId, fetcher->GetHeader("X-GoogleUpdate-AppId")); + EXPECT_NE("", fetcher->GetHeader("X-GoogleUpdate-Updater")); +} + +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>(ConnectionType::kEthernet), + SetArgumentPointee<1>(ConnectionTethering::kUnknown), + Return(true))); + EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::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); +} + +// Verify that update checks called during OOBE will only try to download +// an update if the response includes a non-empty deadline field. +TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBE) { + OmahaResponse response; + + fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); + ASSERT_FALSE( + TestUpdateCheck(nullptr, // request_params + fake_update_response_.GetUpdateResponse(), + -1, + false, // ping_only + ErrorCode::kNonCriticalUpdateInOOBE, + metrics::CheckResult::kUnset, + metrics::CheckReaction::kUnset, + metrics::DownloadErrorCode::kUnset, + &response, + nullptr)); + EXPECT_FALSE(response.update_exists); + + // The IsOOBEComplete() value is ignored when the OOBE flow is not enabled. + fake_system_state_.fake_hardware()->SetIsOOBEEnabled(false); + 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); + fake_system_state_.fake_hardware()->SetIsOOBEEnabled(true); + + // The payload is applied when a deadline was set in the 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); +} + +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( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor))); + loop.Run(); + EXPECT_FALSE(loop.PendingTasks()); + EXPECT_FALSE(processor.IsRunning()); +} + +TEST_F(OmahaRequestActionTest, InvalidXmlTest) { + OmahaResponse response; + ASSERT_FALSE( + TestUpdateCheck(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, + base::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(0U, post_data.size()); +} + +TEST_F(OmahaRequestActionTest, BackInTimePingTest) { + NiceMock<MockPrefs> prefs; + fake_system_state_.set_prefs(&prefs); + EXPECT_CALL(prefs, GetInt64(kPrefsMetricsCheckLastReportingTime, _)) + .Times(AnyNumber()); + EXPECT_CALL(prefs, SetInt64(_, _)).Times(AnyNumber()); + int64_t future = + (Time::Now() + TimeDelta::FromHours(3 * 24 + 4)).ToInternalValue(); + EXPECT_CALL(prefs, GetInt64(kPrefsInstallDateDays, _)) + .WillOnce(DoAll(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, ParseUpdateCheckAttributesTest) { + // Test that the "eol" flags is only parsed from the "_eol" attribute and not + // the "eol" attribute. + ASSERT_TRUE( + TestUpdateCheck(nullptr, // request_params + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response " + "protocol=\"3.0\"><app appid=\"foo\" status=\"ok\">" + "<ping status=\"ok\"/><updatecheck status=\"noupdate\" " + "_eol=\"security-only\" eol=\"eol\" _foo=\"bar\"/>" + "</app></response>", + -1, + false, // ping_only + ErrorCode::kSuccess, + metrics::CheckResult::kNoUpdateAvailable, + metrics::CheckReaction::kUnset, + metrics::DownloadErrorCode::kUnset, + nullptr, + nullptr)); + string eol_pref; + EXPECT_TRUE( + fake_system_state_.prefs()->GetString(kPrefsOmahaEolStatus, &eol_pref)); + // Note that the eol="eol" attribute should be ignored and the _eol should be + // used instead. + EXPECT_EQ("security-only", eol_pref); +} + +TEST_F(OmahaRequestActionTest, NoUniqueIDTest) { + brillo::Blob post_data; + ASSERT_FALSE(TestUpdateCheck(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); + + Time arbitrary_date; + Time::FromString("6/4/1989", &arbitrary_date); + fake_system_state_.fake_clock()->SetWallclockTime(arbitrary_date); + 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)); + EXPECT_EQ(arbitrary_date.ToInternalValue(), timestamp); + 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); + + Time t1, t2; + Time::FromString("1/1/2012", &t1); + Time::FromString("1/3/2012", &t2); + ASSERT_TRUE( + fake_prefs_.SetInt64(kPrefsUpdateFirstSeenAt, t1.ToInternalValue())); + fake_system_state_.fake_clock()->SetWallclockTime(t2); + 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. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + brillo::Blob post_data; + OmahaRequestParams params(&fake_system_state_); + params.set_root(tempdir.path().value()); + params.set_app_id("{22222222-2222-2222-2222-222222222222}"); + params.set_app_version("1.2.3.4"); + params.set_current_channel("canary-channel"); + EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr)); + params.UpdateDownloadChannel(); + 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\" ")); +} + +TEST_F(OmahaRequestActionTest, TestChangingToLessStableChannel) { + // Create a uniquely named test directory. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + brillo::Blob post_data; + OmahaRequestParams params(&fake_system_state_); + params.set_root(tempdir.path().value()); + params.set_app_id("{11111111-1111-1111-1111-111111111111}"); + params.set_app_version("5.6.7.8"); + params.set_current_channel("stable-channel"); + EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr)); + params.UpdateDownloadChannel(); + 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; + + // Simulate a successful update check that happens during OOBE. The + // deadline in the response is needed to force the update attempt to + // occur; responses without a deadline seen during OOBE will normally + // return ErrorCode::kNonCriticalUpdateInOOBE. + fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); + fake_update_response_.deadline = "20101020"; + + // Check that we parse elapsed_days in the Omaha Response correctly. + // and that the kPrefsInstallDateDays value is written to. + EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays)); + EXPECT_TRUE(InstallDateParseHelper("42", &response)); + EXPECT_TRUE(response.update_exists); + EXPECT_EQ(42, response.install_date_days); + EXPECT_TRUE(fake_prefs_.Exists(kPrefsInstallDateDays)); + int64_t prefs_days; + EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 42); + + // If there already is a value set, we shouldn't do anything. + EXPECT_TRUE(InstallDateParseHelper("7", &response)); + EXPECT_TRUE(response.update_exists); + EXPECT_EQ(7, response.install_date_days); + EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 42); + + // Note that elapsed_days is not necessarily divisible by 7 so check + // that we round down correctly when populating kPrefsInstallDateDays. + EXPECT_TRUE(fake_prefs_.Delete(kPrefsInstallDateDays)); + EXPECT_TRUE(InstallDateParseHelper("23", &response)); + EXPECT_TRUE(response.update_exists); + EXPECT_EQ(23, response.install_date_days); + EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 21); + + // Check that we correctly handle elapsed_days not being included in + // the Omaha Response. + EXPECT_TRUE(InstallDateParseHelper("", &response)); + EXPECT_TRUE(response.update_exists); + EXPECT_EQ(-1, response.install_date_days); +} + +// If there is no prefs and OOBE is not complete, we should not +// report anything to Omaha. +TEST_F(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE) { + fake_system_state_.fake_hardware()->UnsetIsOOBEComplete(); + EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), -1); + EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays)); +} + +// If OOBE is complete and happened on a valid date (e.g. after Jan +// 1 2007 0:00 PST), that date should be used and written to +// prefs. However, first try with an invalid date and check we do +// nothing. +TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithInvalidDate) { + Time oobe_date = Time::FromTimeT(42); // Dec 31, 1969 16:00:42 PST. + fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date); + EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), -1); + EXPECT_FALSE(fake_prefs_.Exists(kPrefsInstallDateDays)); +} + +// Then check with a valid date. The date Jan 20, 2007 0:00 PST +// should yield an InstallDate of 14. +TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedWithValidDate) { + Time oobe_date = Time::FromTimeT(1169280000); // Jan 20, 2007 0:00 PST. + fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date); + EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 14); + EXPECT_TRUE(fake_prefs_.Exists(kPrefsInstallDateDays)); + + int64_t prefs_days; + EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 14); +} + +// Now that we have a valid date in prefs, check that we keep using +// that even if OOBE date reports something else. The date Jan 30, +// 2007 0:00 PST should yield an InstallDate of 28... but since +// there's a prefs file, we should still get 14. +TEST_F(OmahaRequestActionTest, GetInstallDateWhenOOBECompletedDateChanges) { + // Set a valid date in the prefs first. + EXPECT_TRUE(fake_prefs_.SetInt64(kPrefsInstallDateDays, 14)); + + Time oobe_date = Time::FromTimeT(1170144000); // Jan 30, 2007 0:00 PST. + fake_system_state_.fake_hardware()->SetIsOOBEComplete(oobe_date); + EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 14); + + int64_t prefs_days; + EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 14); + + // If we delete the prefs file, we should get 28 days. + EXPECT_TRUE(fake_prefs_.Delete(kPrefsInstallDateDays)); + EXPECT_EQ(OmahaRequestAction::GetInstallDate(&fake_system_state_), 28); + EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsInstallDateDays, &prefs_days)); + EXPECT_EQ(prefs_days, 28); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/omaha_request_params.cc b/update_engine/omaha_request_params.cc new file mode 100644 index 0000000..3402451 --- /dev/null +++ b/update_engine/omaha_request_params.cc
@@ -0,0 +1,220 @@ +// +// 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::StartsWith(hwid_, string("SAMS ALEX"), + base::CompareCase::SENSITIVE) || + base::StartsWith(hwid_, string("BUTTERFLY"), + base::CompareCase::SENSITIVE) || + base::StartsWith(hwid_, string("LUMPY"), + base::CompareCase::SENSITIVE) || + base::StartsWith(hwid_, string("PARROT"), + base::CompareCase::SENSITIVE) || + base::StartsWith(hwid_, string("SPRING"), + base::CompareCase::SENSITIVE) || + base::StartsWith(hwid_, string("SNOW"), base::CompareCase::SENSITIVE); +} + +bool OmahaRequestParams::SetTargetChannel(const string& new_target_channel, + bool is_powerwash_allowed, + string* error_message) { + 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/update_engine/omaha_request_params.h b/update_engine/omaha_request_params.h new file mode 100644 index 0000000..379563a --- /dev/null +++ b/update_engine/omaha_request_params.h
@@ -0,0 +1,339 @@ +// +// 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 void set_app_id(const std::string& app_id) { + image_props_.product_id = app_id; + image_props_.canary_product_id = app_id; + } + inline std::string app_lang() const { return app_lang_; } + inline std::string hwid() const { return hwid_; } + inline std::string fw_version() const { return fw_version_; } + inline std::string ec_version() const { return ec_version_; } + + inline 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, ChannelIndexTest); + FRIEND_TEST(OmahaRequestParamsTest, ToMoreStableChannelFlagTest); + 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/update_engine/omaha_request_params_unittest.cc b/update_engine/omaha_request_params_unittest.cc new file mode 100644 index 0000000..7d4dc2d --- /dev/null +++ b/update_engine/omaha_request_params_unittest.cc
@@ -0,0 +1,243 @@ +// +// 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 <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "update_engine/common/constants.h" +#include "update_engine/common/fake_prefs.h" +#include "update_engine/common/platform_constants.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/common/utils.h" +#include "update_engine/fake_system_state.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: + void SetUp() override { + // Create a uniquely named test directory. + ASSERT_TRUE(tempdir_.CreateUniqueTempDir()); + // Create a fresh copy of the params for each test, so there's no + // unintended reuse of state across tests. + params_ = OmahaRequestParams(&fake_system_state_); + params_.set_root(tempdir_.path().value()); + SetLockDown(false); + fake_system_state_.set_prefs(&fake_prefs_); + } + + void SetLockDown(bool locked_down) { + fake_system_state_.fake_hardware()->SetIsOfficialBuild(locked_down); + fake_system_state_.fake_hardware()->SetIsNormalBootMode(locked_down); + } + + OmahaRequestParams params_; + FakeSystemState fake_system_state_; + FakePrefs fake_prefs_; + + base::ScopedTempDir tempdir_; +}; + +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, MissingChannelTest) { + EXPECT_TRUE(params_.Init("", "", false)); + // By default, if no channel is set, we should track the stable-channel. + EXPECT_EQ("stable-channel", params_.target_channel()); +} + +TEST_F(OmahaRequestParamsTest, ForceVersionTest) { + EXPECT_TRUE(params_.Init("ForcedVersion", "", false)); + EXPECT_EQ(string("ForcedVersion_") + GetMachineType(), params_.os_sp()); + EXPECT_EQ("ForcedVersion", params_.app_version()); +} + +TEST_F(OmahaRequestParamsTest, ForcedURLTest) { + EXPECT_TRUE(params_.Init("", "http://forced.google.com", false)); + EXPECT_EQ("http://forced.google.com", params_.update_url()); +} + +TEST_F(OmahaRequestParamsTest, MissingURLTest) { + EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_EQ(constants::kOmahaDefaultProductionURL, params_.update_url()); +} + +TEST_F(OmahaRequestParamsTest, DeltaOKTest) { + EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_TRUE(params_.delta_okay()); +} + +TEST_F(OmahaRequestParamsTest, NoDeltasTest) { + ASSERT_TRUE(WriteFileString(tempdir_.path().Append(".nodelta").value(), "")); + EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_FALSE(params_.delta_okay()); +} + +TEST_F(OmahaRequestParamsTest, SetTargetChannelTest) { + { + OmahaRequestParams params(&fake_system_state_); + params.set_root(tempdir_.path().value()); + EXPECT_TRUE(params.Init("", "", false)); + EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr)); + EXPECT_FALSE(params.is_powerwash_allowed()); + } + params_.set_root(tempdir_.path().value()); + EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_EQ("canary-channel", params_.target_channel()); + EXPECT_FALSE(params_.is_powerwash_allowed()); +} + +TEST_F(OmahaRequestParamsTest, SetIsPowerwashAllowedTest) { + { + OmahaRequestParams params(&fake_system_state_); + params.set_root(tempdir_.path().value()); + EXPECT_TRUE(params.Init("", "", false)); + EXPECT_TRUE(params.SetTargetChannel("canary-channel", true, nullptr)); + EXPECT_TRUE(params.is_powerwash_allowed()); + } + params_.set_root(tempdir_.path().value()); + EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_EQ("canary-channel", params_.target_channel()); + EXPECT_TRUE(params_.is_powerwash_allowed()); +} + +TEST_F(OmahaRequestParamsTest, SetTargetChannelInvalidTest) { + { + OmahaRequestParams params(&fake_system_state_); + params.set_root(tempdir_.path().value()); + 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()); + } + params_.set_root(tempdir_.path().value()); + EXPECT_TRUE(params_.Init("", "", false)); + EXPECT_EQ("stable-channel", params_.target_channel()); + EXPECT_FALSE(params_.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, SetTargetChannelWorks) { + params_.set_target_channel("dev-channel"); + EXPECT_EQ("dev-channel", params_.target_channel()); + + // When an invalid value is set, it should be ignored. + 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. + 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. + 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. + EXPECT_TRUE(params_.SetTargetChannel("stable-channel", true, nullptr)); + EXPECT_EQ("stable-channel", params_.target_channel()); + + // Set a different channel in mutable_image_props_. + params_.set_target_channel("stable-channel"); + + // 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) { + params_.image_props_.current_channel = "canary-channel"; + params_.download_channel_ = "stable-channel"; + EXPECT_TRUE(params_.to_more_stable_channel()); +} + +TEST_F(OmahaRequestParamsTest, CollectECFWVersionsTest) { + params_.hwid_ = string("STUMPY ALEX 12345"); + EXPECT_FALSE(params_.CollectECFWVersions()); + + params_.hwid_ = string("SNOW 12345"); + EXPECT_TRUE(params_.CollectECFWVersions()); + + params_.hwid_ = string("SAMS ALEX 12345"); + EXPECT_TRUE(params_.CollectECFWVersions()); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/omaha_response.h b/update_engine/omaha_response.h new file mode 100644 index 0000000..60ec4ac --- /dev/null +++ b/update_engine/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; +}; +static_assert(sizeof(off_t) == 8, "off_t not 64 bit"); + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_OMAHA_RESPONSE_H_
diff --git a/update_engine/omaha_response_handler_action.cc b/update_engine/omaha_response_handler_action.cc new file mode 100644 index 0000000..33380d7 --- /dev/null +++ b/update_engine/omaha_response_handler_action.cc
@@ -0,0 +1,210 @@ +// +// 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_.payload_type = response.is_delta_payload + ? InstallPayloadType::kDelta + : InstallPayloadType::kFull; + + 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::StartsWith(install_plan_.download_url, "https://", + base::CompareCase::INSENSITIVE_ASCII)) { + 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::StartsWith(response.payload_urls[i], "https://", + base::CompareCase::INSENSITIVE_ASCII)) { + 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/update_engine/omaha_response_handler_action.h b/update_engine/omaha_response_handler_action.h new file mode 100644 index 0000000..51dfa7a --- /dev/null +++ b/update_engine/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/update_engine/omaha_response_handler_action_unittest.cc b/update_engine/omaha_response_handler_action_unittest.cc new file mode 100644 index 0000000..60b139b --- /dev/null +++ b/update_engine/omaha_response_handler_action_unittest.cc
@@ -0,0 +1,424 @@ +// +// 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 <base/files/scoped_temp_dir.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; +using testing::_; + +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(1U, 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( + static_cast<mode_t>(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(0U, 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(1U, 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. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + OmahaRequestParams params(&fake_system_state_); + fake_system_state_.fake_hardware()->SetIsOfficialBuild(false); + params.set_root(tempdir.path().value()); + params.set_current_channel("canary-channel"); + // The ImageProperties in Android uses prefs to store MutableImageProperties. +#ifdef __ANDROID__ + EXPECT_CALL(*fake_system_state_.mock_prefs(), SetString(_, "stable-channel")) + .WillOnce(Return(true)); + EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, true)) + .WillOnce(Return(true)); +#endif // __ANDROID__ + EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr)); + params.UpdateDownloadChannel(); + EXPECT_TRUE(params.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); +} + +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. + base::ScopedTempDir tempdir; + ASSERT_TRUE(tempdir.CreateUniqueTempDir()); + + OmahaRequestParams params(&fake_system_state_); + fake_system_state_.fake_hardware()->SetIsOfficialBuild(false); + params.set_root(tempdir.path().value()); + params.set_current_channel("stable-channel"); + // The ImageProperties in Android uses prefs to store MutableImageProperties. +#ifdef __ANDROID__ + EXPECT_CALL(*fake_system_state_.mock_prefs(), SetString(_, "canary-channel")) + .WillOnce(Return(true)); + EXPECT_CALL(*fake_system_state_.mock_prefs(), SetBoolean(_, false)) + .WillOnce(Return(true)); +#endif // __ANDROID__ + EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr)); + params.UpdateDownloadChannel(); + EXPECT_FALSE(params.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); +} + +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/update_engine/omaha_utils.cc b/update_engine/omaha_utils.cc new file mode 100644 index 0000000..6bd7525 --- /dev/null +++ b/update_engine/omaha_utils.cc
@@ -0,0 +1,57 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/omaha_utils.h" + +#include <base/logging.h> + +namespace chromeos_update_engine { + +namespace { + +// The possible string values for the end-of-life status. +const char kEolStatusSupported[] = "supported"; +const char kEolStatusSecurityOnly[] = "security-only"; +const char kEolStatusEol[] = "eol"; + +} // namespace + +const char* EolStatusToString(EolStatus eol_status) { + switch (eol_status) { + case EolStatus::kSupported: + return kEolStatusSupported; + case EolStatus::kSecurityOnly: + return kEolStatusSecurityOnly; + case EolStatus::kEol: + return kEolStatusEol; + } + // Only reached if an invalid number is casted to |EolStatus|. + LOG(WARNING) << "Invalid EolStatus value: " << static_cast<int>(eol_status); + return kEolStatusSupported; +} + +EolStatus StringToEolStatus(const std::string& eol_status) { + if (eol_status == kEolStatusSupported || eol_status.empty()) + return EolStatus::kSupported; + if (eol_status == kEolStatusSecurityOnly) + return EolStatus::kSecurityOnly; + if (eol_status == kEolStatusEol) + return EolStatus::kEol; + LOG(WARNING) << "Invalid end-of-life attribute: " << eol_status; + return EolStatus::kSupported; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/omaha_utils.h b/update_engine/omaha_utils.h new file mode 100644 index 0000000..8614540 --- /dev/null +++ b/update_engine/omaha_utils.h
@@ -0,0 +1,40 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_OMAHA_UTILS_H_ +#define UPDATE_ENGINE_OMAHA_UTILS_H_ + +#include <string> + +namespace chromeos_update_engine { + +// The end-of-life status of the device. +enum class EolStatus { + kSupported = 0, + kSecurityOnly, + kEol, +}; + +// Returns the string representation of the |eol_status|. +const char* EolStatusToString(EolStatus eol_status); + +// Converts the end-of-life status string to an EolStatus numeric value. In case +// of an invalid string, the default "supported" value will be used instead. +EolStatus StringToEolStatus(const std::string& eol_status); + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_OMAHA_UTILS_H_
diff --git a/update_engine/omaha_utils_unittest.cc b/update_engine/omaha_utils_unittest.cc new file mode 100644 index 0000000..8ceb76b --- /dev/null +++ b/update_engine/omaha_utils_unittest.cc
@@ -0,0 +1,42 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/omaha_utils.h" + +#include <gtest/gtest.h> +#include <vector> + +namespace chromeos_update_engine { + +class OmahaUtilsTest : public ::testing::Test {}; + +TEST(OmahaUtilsTest, EolStatusTest) { + EXPECT_EQ(EolStatus::kEol, StringToEolStatus("eol")); + + // Supported values are converted back and forth properly. + const std::vector<EolStatus> tests = { + EolStatus::kSupported, EolStatus::kSecurityOnly, EolStatus::kEol}; + for (EolStatus eol_status : tests) { + EXPECT_EQ(eol_status, StringToEolStatus(EolStatusToString(eol_status))) + << "The StringToEolStatus() was " << EolStatusToString(eol_status); + } + + // Invalid values are assumed as "supported". + EXPECT_EQ(EolStatus::kSupported, StringToEolStatus("")); + EXPECT_EQ(EolStatus::kSupported, StringToEolStatus("hello, world!")); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/p2p_manager.cc b/update_engine/p2p_manager.cc new file mode 100644 index 0000000..127e5ff --- /dev/null +++ b/update_engine/p2p_manager.cc
@@ -0,0 +1,742 @@ +// +// 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, + base::CompareCase::SENSITIVE) || + base::EndsWith(name.value(), ext_non_visible, + base::CompareCase::SENSITIVE))) { + 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, + base::CompareCase::SENSITIVE) || + base::EndsWith(name.value(), ext_non_visible, + base::CompareCase::SENSITIVE)) { + 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/update_engine/p2p_manager.h b/update_engine/p2p_manager.h new file mode 100644 index 0000000..4ffab9a --- /dev/null +++ b/update_engine/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/update_engine/p2p_manager_unittest.cc b/update_engine/p2p_manager_unittest.cc new file mode 100644 index 0000000..463c0e2 --- /dev/null +++ b/update_engine/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/update_engine/parcelable_update_engine_status.cc b/update_engine/parcelable_update_engine_status.cc new file mode 100644 index 0000000..d8eb6db --- /dev/null +++ b/update_engine/parcelable_update_engine_status.cc
@@ -0,0 +1,77 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/parcelable_update_engine_status.h" + +#include <binder/Parcel.h> + +namespace android { +namespace brillo { + +status_t ParcelableUpdateEngineStatus::writeToParcel(Parcel* parcel) const { + status_t status; + + status = parcel->writeInt64(last_checked_time_); + if (status != OK) { + return status; + } + + status = parcel->writeDouble(progress_); + if (status != OK) { + return status; + } + + status = parcel->writeString16(current_operation_); + if (status != OK) { + return status; + } + + status = parcel->writeString16(new_version_); + if (status != OK) { + return status; + } + + return parcel->writeInt64(new_size_); +} + +status_t ParcelableUpdateEngineStatus::readFromParcel(const Parcel* parcel) { + status_t status; + + status = parcel->readInt64(&last_checked_time_); + if (status != OK) { + return status; + } + + status = parcel->readDouble(&progress_); + if (status != OK) { + return status; + } + + status = parcel->readString16(¤t_operation_); + if (status != OK) { + return status; + } + + status = parcel->readString16(&new_version_); + if (status != OK) { + return status; + } + + return parcel->readInt64(&new_size_); +} + +} // namespace brillo +} // namespace android
diff --git a/update_engine/parcelable_update_engine_status.h b/update_engine/parcelable_update_engine_status.h new file mode 100644 index 0000000..2cfedd9 --- /dev/null +++ b/update_engine/parcelable_update_engine_status.h
@@ -0,0 +1,46 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_UPDATE_ENGINE_STATUS_H_ +#define UPDATE_ENGINE_UPDATE_ENGINE_STATUS_H_ + +#include <binder/Parcelable.h> +#include <utils/String16.h> + +namespace android { +namespace brillo { + +// Parcelable object containing the current status of update engine, to be sent +// over binder to clients from the server. +class ParcelableUpdateEngineStatus : public Parcelable { + public: + ParcelableUpdateEngineStatus() = default; + virtual ~ParcelableUpdateEngineStatus() = default; + + status_t writeToParcel(Parcel* parcel) const override; + status_t readFromParcel(const Parcel* parcel) override; + + int64_t last_checked_time_; + double progress_; + android::String16 current_operation_; + android::String16 new_version_; + int64_t new_size_; +}; + +} // namespace brillo +} // namespace android + +#endif // UPDATE_ENGINE_UPDATE_ENGINE_STATUS_H_
diff --git a/update_engine/payload_consumer/bzip_extent_writer.cc b/update_engine/payload_consumer/bzip_extent_writer.cc new file mode 100644 index 0000000..0fcc8ba --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/bzip_extent_writer.h b/update_engine/payload_consumer/bzip_extent_writer.h new file mode 100644 index 0000000..0ad542e --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/bzip_extent_writer_unittest.cc b/update_engine/payload_consumer/bzip_extent_writer_unittest.cc new file mode 100644 index 0000000..8ac3e59 --- /dev/null +++ b/update_engine/payload_consumer/bzip_extent_writer_unittest.cc
@@ -0,0 +1,132 @@ +// +// 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 <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 uint32_t kBlockSize = 4096; +} + +class BzipExtentWriterTest : public ::testing::Test { + protected: + void SetUp() override { + fd_.reset(new EintrSafeFileDescriptor); + ASSERT_TRUE(fd_->Open(temp_file_.path().c_str(), O_RDWR, 0600)); + } + void TearDown() override { + fd_->Close(); + } + void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size); + void TestZeroPad(bool aligned_size); + + FileDescriptorPtr fd_; + test_utils::ScopedTempFile temp_file_{"BzipExtentWriterTest-file.XXXXXX"}; +}; + +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(temp_file_.path(), &buf)); + EXPECT_EQ(strlen(test_uncompressed), buf.size()); + EXPECT_EQ(string(buf.begin(), buf.end()), string(test_uncompressed)); +} + +TEST_F(BzipExtentWriterTest, ChunkedTest) { + // Generated with: + // yes "ABC" | head -c 819200 | bzip2 -9 | + // hexdump -v -e '" " 11/1 "0x%02x, " "\n"' + static const uint8_t kCompressedData[] = { + 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xbe, + 0x1c, 0xda, 0xee, 0x03, 0x1f, 0xff, 0xc4, 0x00, 0x00, 0x10, 0x38, + 0x00, 0x20, 0x00, 0x50, 0x66, 0x9a, 0x05, 0x28, 0x38, 0x00, 0x11, + 0x60, 0x00, 0x22, 0xd0, 0x00, 0x45, 0xc0, 0x00, 0x8b, 0xc5, 0xdc, + 0x91, 0x4e, 0x14, 0x24, 0x2f, 0x87, 0x36, 0xbb, 0x80}; + brillo::Blob compressed_data(std::begin(kCompressedData), + std::end(kCompressedData)); + + const brillo::Blob::size_type kDecompressedLength = 800 * 1024; // 800 KiB + const size_t kChunkSize = 3; + + brillo::Blob decompressed_data(kDecompressedLength); + for (size_t i = 0; i < decompressed_data.size(); ++i) + decompressed_data[i] = static_cast<uint8_t>("ABC\n"[i % 4]); + + vector<Extent> extents; + Extent extent; + extent.set_start_block(0); + extent.set_num_blocks((kDecompressedLength + kBlockSize - 1) / kBlockSize); + extents.push_back(extent); + + 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(temp_file_.path(), &output)); + EXPECT_EQ(kDecompressedLength, output.size()); + test_utils::ExpectVectorsEq(decompressed_data, output); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/delta_performer.cc b/update_engine/payload_consumer/delta_performer.cc new file mode 100644 index 0000000..507ad8c --- /dev/null +++ b/update_engine/payload_consumer/delta_performer.cc
@@ -0,0 +1,1875 @@ +// +// 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 <applypatch/imgpatch.h> +#include <base/files/file_util.h> +#include <base/format_macros.h> +#include <base/strings/string_number_conversions.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) { + // Try to mark the block device read-only based on the mode. Ignore any + // failure since this won't work when passing regular files. + utils::SetBlockDeviceReadOnly(path, (mode & O_ACCMODE) == O_RDONLY); + + 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; +} + +// Discard the tail of the block device referenced by |fd|, from the offset +// |data_size| until the end of the block device. Returns whether the data was +// discarded. +bool DiscardPartitionTail(const FileDescriptorPtr& fd, uint64_t data_size) { + uint64_t part_size = fd->BlockDevSize(); + if (!part_size || part_size <= data_size) + return false; + + struct blkioctl_request { + int number; + const char* name; + }; + const vector<blkioctl_request> blkioctl_requests = { + {BLKSECDISCARD, "BLKSECDISCARD"}, + {BLKDISCARD, "BLKDISCARD"}, +#ifdef BLKZEROOUT + {BLKZEROOUT, "BLKZEROOUT"}, +#endif + }; + for (const auto& req : blkioctl_requests) { + int error = 0; + if (fd->BlkIoctl(req.number, data_size, part_size - data_size, &error) && + error == 0) { + return true; + } + LOG(WARNING) << "Error discarding the last " + << (part_size - data_size) / 1024 << " KiB using ioctl(" + << req.name << ")"; + } + return false; +} + +} // namespace + + +// 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; + static_assert(kProgressDownloadWeight + kProgressOperationsWeight == 100, + "Progress weights don't 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; + + size_t partition_first_op_num = + current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0; + LOG(ERROR) << "Failed to perform " << op_type_name << " operation " + << next_operation_num_ << ", which is the operation " + << next_operation_num_ - partition_first_op_num + << " in partition \"" + << partitions_[current_partition_].partition_name() << "\""; + if (*error == ErrorCode::kSuccess) + *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_->payload_type == InstallPayloadType::kDelta && + 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; + } + + LOG(INFO) << "Applying " << partition.operations().size() + << " operations to partition \"" << partition.partition_name() + << "\""; + + // Discard the end of the partition, but ignore failures. + DiscardPartitionTail( + target_fd_, install_plan_->partitions[current_partition_].target_size); + + 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_->payload_type == InstallPayloadType::kDelta + ? kSupportedMinorPayloadVersion + : kFullPayloadMinorVersion; + } +} + +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. + static_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. + static_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. + static_assert(sizeof(metadata_signature_size_) == + kDeltaMetadataSignatureSizeSize, + "metadata_signature_size size mismatch"); + uint64_t metadata_signature_size_offset; + if (!GetMetadataSignatureSizeOffset(&metadata_signature_size_offset)) { + *error = ErrorCode::kError; + return 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."; + LOG_IF(WARNING, !prefs_->SetInt64(kPrefsManifestSignatureSize, + metadata_signature_size_)) + << "Unable to save the manifest signature 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, error); + break; + case InstallOperation::SOURCE_BSDIFF: + op_result = PerformSourceBsdiffOperation(op, error); + break; + case InstallOperation::IMGDIFF: + op_result = PerformImgdiffOperation(op, error); + 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 we already extracted the signature we should skip this step. + if (major_payload_version_ == kBrilloMajorPayloadVersion && + manifest_.has_signatures_offset() && manifest_.has_signatures_size() && + signatures_message_data_.empty()) { + if (manifest_.signatures_offset() != buffer_offset_) { + LOG(ERROR) << "Payload signatures offset points to blob offset " + << 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); + // Since we extracted the SignatureMessage we need to advance the + // checkpoint, otherwise we would reload the signature and try to extract + // it again. + CheckpointUpdateProgress(); + } + + 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)); + } + + // 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 (install_part.run_postinstall) { + install_part.postinstall_path = + (partition.has_postinstall_path() ? partition.postinstall_path() + : kPostinstallDefaultScript); + install_part.filesystem_type = partition.filesystem_type(); + install_part.postinstall_optional = partition.postinstall_optional(); + } + + 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) { + // If we don't have a data blob we can apply it right away. + if (!operation.has_data_offset() && !operation.has_data_length()) + 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()); + +#ifdef BLKZEROOUT + bool attempt_ioctl = true; + int request = + (operation.type() == InstallOperation::ZERO ? BLKZEROOUT : BLKDISCARD); +#else // !defined(BLKZEROOUT) + bool attempt_ioctl = false; + int request = 0; +#endif // !defined(BLKZEROOUT) + + brillo::Blob zeros; + for (const Extent& extent : operation.dst_extents()) { + const uint64_t start = extent.start_block() * block_size_; + const uint64_t length = extent.num_blocks() * block_size_; + if (attempt_ioctl) { + int result = 0; + if (target_fd_->BlkIoctl(request, start, length, &result) && result == 0) + continue; + attempt_ioctl = false; + 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 (const 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 (const Extent& ext : extents) { + sum += ext.num_blocks(); + } + return sum; +} + +// Compare |calculated_hash| with source hash in |operation|, return false and +// dump hash and set |error| if don't match. +bool ValidateSourceHash(const brillo::Blob& calculated_hash, + const InstallOperation& operation, + ErrorCode* error) { + brillo::Blob expected_source_hash(operation.src_sha256_hash().begin(), + operation.src_sha256_hash().end()); + if (calculated_hash != expected_source_hash) { + LOG(ERROR) << "The hash of the source data on disk for this operation " + << "doesn't match the expected value. This could mean that the " + << "delta update payload was targeted for another version, or " + << "that the source partition was modified after it was " + << "installed, for example, by mounting a filesystem."; + LOG(ERROR) << "Expected: sha256|hex = " + << base::HexEncode(expected_source_hash.data(), + expected_source_hash.size()); + LOG(ERROR) << "Calculated: sha256|hex = " + << base::HexEncode(calculated_hash.data(), + calculated_hash.size()); + + vector<string> source_extents; + for (const Extent& ext : operation.src_extents()) { + source_extents.push_back( + base::StringPrintf("%" PRIu64 ":%" PRIu64, + static_cast<uint64_t>(ext.start_block()), + static_cast<uint64_t>(ext.num_blocks()))); + } + LOG(ERROR) << "Operation source (offset:size) in blocks: " + << base::JoinString(source_extents, ","); + + *error = ErrorCode::kDownloadStateInitializationError; + return false; + } + return true; +} + +} // namespace + +bool DeltaPerformer::PerformSourceCopyOperation( + const InstallOperation& operation, ErrorCode* error) { + 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, error)); + } + + 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 (const Extent& extent : extents) { + int64_t start = extent.start_block() * block_size; + uint64_t this_length = + min(full_length - length, + static_cast<uint64_t>(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, ErrorCode* error) { + // Since we delete data off the beginning of the buffer as we use it, + // the data we need should be exactly at the beginning of the buffer. + TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset()); + TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length()); + 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, static_cast<uint64_t>(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, error)); + } + + 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::PerformImgdiffOperation(const InstallOperation& operation, + ErrorCode* error) { + // Since we delete data off the beginning of the buffer as we use it, + // the data we need should be exactly at the beginning of the buffer. + TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset()); + TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length()); + + uint64_t src_blocks = GetBlockCount(operation.src_extents()); + brillo::Blob src_data(src_blocks * block_size_); + + ssize_t bytes_read = 0; + for (const Extent& extent : operation.src_extents()) { + ssize_t bytes_read_this_iteration = 0; + ssize_t bytes_to_read = extent.num_blocks() * block_size_; + TEST_AND_RETURN_FALSE(utils::PReadAll(source_fd_, + &src_data[bytes_read], + bytes_to_read, + extent.start_block() * block_size_, + &bytes_read_this_iteration)); + TEST_AND_RETURN_FALSE(bytes_read_this_iteration == bytes_to_read); + bytes_read += bytes_read_this_iteration; + } + + if (operation.has_src_sha256_hash()) { + brillo::Blob src_hash; + TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfData(src_data, &src_hash)); + TEST_AND_RETURN_FALSE(ValidateSourceHash(src_hash, operation, error)); + } + + vector<Extent> target_extents(operation.dst_extents().begin(), + operation.dst_extents().end()); + DirectExtentWriter writer; + TEST_AND_RETURN_FALSE(writer.Init(target_fd_, target_extents, block_size_)); + TEST_AND_RETURN_FALSE( + ApplyImagePatch(src_data.data(), + src_data.size(), + buffer_.data(), + operation.data_length(), + [](const unsigned char* data, ssize_t len, void* token) { + return reinterpret_cast<ExtentWriter*>(token) + ->Write(data, len) + ? len + : 0; + }, + &writer) == 0); + TEST_AND_RETURN_FALSE(writer.End()); + + DiscardBuffer(true, buffer_.size()); + return true; +} + +bool DeltaPerformer::ExtractSignatureMessageFromOperation( + const InstallOperation& operation) { + if (operation.type() != InstallOperation::REPLACE || + !manifest_.has_signatures_offset() || + manifest_.signatures_offset() != operation.data_offset()) { + return false; + } + TEST_AND_RETURN_FALSE(manifest_.has_signatures_size() && + manifest_.signatures_size() == operation.data_length()); + TEST_AND_RETURN_FALSE(ExtractSignatureMessage()); + return true; +} + +bool DeltaPerformer::ExtractSignatureMessage() { + TEST_AND_RETURN_FALSE(signatures_message_data_.empty()); + TEST_AND_RETURN_FALSE(buffer_offset_ == manifest_.signatures_offset()); + 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. + + bool has_old_fields = + (manifest_.has_old_kernel_info() || manifest_.has_old_rootfs_info()); + for (const PartitionUpdate& partition : manifest_.partitions()) { + has_old_fields = has_old_fields || partition.has_old_partition_info(); + } + + // The presence of an old partition hash is the sole indicator for a delta + // update. + InstallPayloadType actual_payload_type = + has_old_fields ? InstallPayloadType::kDelta : InstallPayloadType::kFull; + + if (install_plan_->payload_type == InstallPayloadType::kUnknown) { + LOG(INFO) << "Detected a '" + << InstallPayloadTypeToString(actual_payload_type) + << "' payload."; + install_plan_->payload_type = actual_payload_type; + } else if (install_plan_->payload_type != actual_payload_type) { + LOG(ERROR) << "InstallPlan expected a '" + << InstallPayloadTypeToString(install_plan_->payload_type) + << "' payload but the downloaded manifest contains a '" + << InstallPayloadTypeToString(actual_payload_type) + << "' payload."; + return ErrorCode::kPayloadMismatchedType; + } + + // Check that the minor version is compatible. + if (actual_payload_type == InstallPayloadType::kFull) { + 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; +} + +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, + const 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; + // Note that storing this value is optional, but if it is there it should not + // be more than the limit. + if (prefs->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures) && + resumed_update_failures > kMaxResumedUpdateFailures) + return false; + + // Sanity check the rest. + 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; + + int64_t manifest_signature_size = 0; + if (!(prefs->GetInt64(kPrefsManifestSignatureSize, + &manifest_signature_size) && + manifest_signature_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(kPrefsManifestSignatureSize, -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; + + int64_t manifest_signature_size = 0; + TEST_AND_RETURN_FALSE( + prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size) && + manifest_signature_size >= 0); + metadata_signature_size_ = manifest_signature_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/update_engine/payload_consumer/delta_performer.h b/update_engine/payload_consumer/delta_performer.h new file mode 100644 index 0000000..74143e0 --- /dev/null +++ b/update_engine/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, + const 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); + + // 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. + // |error| will be set if source hash mismatch, otherwise |error| might not be + // set even if it fails. + bool PerformReplaceOperation(const InstallOperation& operation); + bool PerformZeroOrDiscardOperation(const InstallOperation& operation); + bool PerformMoveOperation(const InstallOperation& operation); + bool PerformBsdiffOperation(const InstallOperation& operation); + bool PerformSourceCopyOperation(const InstallOperation& operation, + ErrorCode* error); + bool PerformSourceBsdiffOperation(const InstallOperation& operation, + ErrorCode* error); + bool PerformImgdiffOperation(const InstallOperation& operation, + ErrorCode* error); + + // Extracts the payload signature message from the blob on the |operation| if + // the offset matches the one specified by the manifest. Returns whether the + // signature was extracted. + bool ExtractSignatureMessageFromOperation(const InstallOperation& operation); + + // Extracts the payload signature message from the current |buffer_| if the + // offset matches the one specified by the manifest. Returns whether the + // signature was extracted. + 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_{std::numeric_limits<uint64_t>::max()}; + + // 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/update_engine/payload_consumer/delta_performer_integration_test.cc b/update_engine/payload_consumer/delta_performer_integration_test.cc new file mode 100644 index 0000000..afbb8dc --- /dev/null +++ b/update_engine/payload_consumer/delta_performer_integration_test.cc
@@ -0,0 +1,1045 @@ +// +// 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 <openssl/pem.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::GetBuildArtifactsPath; +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 uint32_t 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(0U, 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(0U, 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 bool WriteByteAtOffset(const string& path, off_t offset) { + int fd = open(path.c_str(), O_CREAT | O_WRONLY, 0644); + TEST_AND_RETURN_FALSE_ERRNO(fd >= 0); + ScopedFdCloser fd_closer(&fd); + EXPECT_TRUE(utils::PWriteAll(fd, "\0", 1, offset)); + return true; +} + +static size_t GetSignatureSize(const string& private_key_path) { + const brillo::Blob data(1, 'x'); + brillo::Blob hash; + EXPECT_TRUE(HashCalculator::RawHashOfData(data, &hash)); + brillo::Blob signature; + EXPECT_TRUE(PayloadSigner::SignHash(hash, + private_key_path, + &signature)); + return signature.size(); +} + +static bool InsertSignaturePlaceholder(int signature_size, + 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) { + string private_key_path = GetBuildArtifactsPath(kUnittestPrivateKeyPath); + int signature_size = GetSignatureSize(private_key_path); + brillo::Blob hash; + ASSERT_TRUE(PayloadSigner::HashPayloadForSigning( + payload_path, {signature_size}, &hash, nullptr)); + brillo::Blob signature; + ASSERT_TRUE(PayloadSigner::SignHash(hash, private_key_path, &signature)); + ASSERT_TRUE(PayloadSigner::AddSignatureToPayload( + payload_path, {signature}, {}, payload_path, out_metadata_size)); + EXPECT_TRUE(PayloadSigner::VerifySignedPayload( + payload_path, GetBuildArtifactsPath(kUnittestPublicKeyPath))); +} + +static void SignGeneratedShellPayload(SignatureTest signature_test, + const string& payload_path) { + string private_key_path = GetBuildArtifactsPath(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."; + // The code below executes the equivalent of: + // openssl genrsa -out <private_key_path> 2048 + RSA* rsa = RSA_new(); + BIGNUM* e = BN_new(); + EXPECT_EQ(1, BN_set_word(e, RSA_F4)); + EXPECT_EQ(1, RSA_generate_key_ex(rsa, 2048, e, nullptr)); + BN_free(e); + FILE* fprikey = fopen(private_key_path.c_str(), "w"); + EXPECT_NE(nullptr, fprikey); + EXPECT_EQ(1, + PEM_write_RSAPrivateKey( + fprikey, rsa, nullptr, nullptr, 0, nullptr, nullptr)); + fclose(fprikey); + RSA_free(rsa); + } + 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); + string delta_generator_path = GetBuildArtifactsPath("delta_generator"); + ASSERT_EQ(0, + System(base::StringPrintf( + "%s -in_file=%s -signature_size=%s -out_hash_file=%s", + delta_generator_path.c_str(), + payload_path.c_str(), + signature_size_string.c_str(), + hash_file.c_str()))); + + // Sign the hash + brillo::Blob hash, signature; + ASSERT_TRUE(utils::ReadFile(hash_file, &hash)); + ASSERT_TRUE(PayloadSigner::SignHash(hash, private_key_path, &signature)); + + string sig_file; + ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file, nullptr)); + ScopedPathUnlinker sig_unlinker(sig_file); + ASSERT_TRUE(test_utils::WriteFileVector(sig_file, signature)); + + 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_TRUE(PayloadSigner::SignHash( + hash, GetBuildArtifactsPath(kUnittestPrivateKey2Path), &signature)); + ASSERT_TRUE(test_utils::WriteFileVector(sig_file2, signature)); + // Append second sig file to first path + sig_file += ":" + sig_file2; + } + + ASSERT_EQ(0, + System(base::StringPrintf( + "%s -in_file=%s -signature_file=%s -out_file=%s", + delta_generator_path.c_str(), + payload_path.c_str(), + sig_file.c_str(), + payload_path.c_str()))); + int verify_result = System(base::StringPrintf( + "%s -in_file=%s -public_key=%s -public_key_version=%d", + delta_generator_path.c_str(), + payload_path.c_str(), + (signature_test == kSignatureGeneratedShellRotateCl2 + ? GetBuildArtifactsPath(kUnittestPublicKey2Path) + : GetBuildArtifactsPath(kUnittestPublicKeyPath)) + .c_str(), + signature_test == kSignatureGeneratedShellRotateCl2 ? 2 : 1)); + if (signature_test == kSignatureGeneratedShellBadKey) { + ASSERT_NE(0, verify_result); + } 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)); + + EXPECT_TRUE( + base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"), + base::FilePath(state->a_img))); + + state->image_size = utils::FileSize(state->a_img); + + // Create ImageInfo A & B + ImageInfo old_image_info; + ImageInfo new_image_info; + + if (!full_rootfs) { + old_image_info.set_channel("src-channel"); + old_image_info.set_board("src-board"); + old_image_info.set_version("src-version"); + old_image_info.set_key("src-key"); + old_image_info.set_build_channel("src-build-channel"); + old_image_info.set_build_version("src-build-version"); + } + + 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(static_cast<int>(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_TRUE(WriteByteAtOffset( + base::StringPrintf("%s/move-semi-sparse", a_mnt.c_str()), 4096)); + + // 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)); + } + + EXPECT_TRUE( + base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"), + base::FilePath(state->b_img))); + + // Make some changes to the B image. + string b_mnt; + ScopedLoopMounter b_mounter(state->b_img, &b_mnt, 0); + base::FilePath mnt_path(b_mnt); + + EXPECT_TRUE(base::CopyFile(mnt_path.Append("regular-small"), + mnt_path.Append("regular-small2"))); + EXPECT_TRUE(base::DeleteFile(mnt_path.Append("regular-small"), false)); + EXPECT_TRUE(base::Move(mnt_path.Append("regular-small2"), + mnt_path.Append("regular-small"))); + EXPECT_TRUE( + test_utils::WriteFileString(mnt_path.Append("foo").value(), "foo")); + EXPECT_EQ(0, base::WriteFile(mnt_path.Append("emptyfile"), "", 0)); + + EXPECT_TRUE( + WriteSparseFile(mnt_path.Append("fullsparse").value(), 1024 * 1024)); + EXPECT_TRUE( + WriteSparseFile(mnt_path.Append("move-to-sparse").value(), 16 * 1024)); + + brillo::Blob zeros(16 * 1024, 0); + EXPECT_EQ(static_cast<int>(zeros.size()), + base::WriteFile(mnt_path.Append("move-from-sparse"), + reinterpret_cast<const char*>(zeros.data()), + zeros.size())); + + EXPECT_TRUE( + WriteByteAtOffset(mnt_path.Append("move-semi-sparse").value(), 4096)); + EXPECT_TRUE(WriteByteAtOffset(mnt_path.Append("partsparse").value(), 4096)); + + EXPECT_TRUE( + base::CopyFile(mnt_path.Append("regular-16k"), mnt_path.Append("tmp"))); + EXPECT_TRUE(base::Move(mnt_path.Append("tmp"), + mnt_path.Append("link-hard-regular-16k"))); + + EXPECT_TRUE(base::DeleteFile(mnt_path.Append("link-short_symlink"), false)); + EXPECT_TRUE(test_utils::WriteFileString( + mnt_path.Append("link-short_symlink").value(), "foobar")); + + 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 + ? GetBuildArtifactsPath(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.version.major = kChromeOSMajorPayloadVersion; + payload_config.version.minor = 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(static_cast<off_t>(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(static_cast<off_t>(state->image_size + 1024 * 1024), + utils::FileSize(state->b_img)); + + if (signature_test == kSignatureGeneratedPlaceholder || + signature_test == kSignatureGeneratedPlaceholderMismatch) { + int signature_size = + GetSignatureSize(GetBuildArtifactsPath(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::LoadPayloadMetadata(state->delta_path, + nullptr, + &manifest, + nullptr, + &state->metadata_size, + nullptr)); + LOG(INFO) << "Metadata size: " << state->metadata_size; + EXPECT_TRUE(utils::ReadFile(state->delta_path, &state->delta)); + + 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(1U, signature.version()); + + uint64_t expected_sig_data_length = 0; + vector<string> key_paths{GetBuildArtifactsPath(kUnittestPrivateKeyPath)}; + if (signature_test == kSignatureGeneratedShellRotateCl1 || + signature_test == kSignatureGeneratedShellRotateCl2) { + key_paths.push_back(GetBuildArtifactsPath(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(kPrefsManifestSignatureSize, 0)) + .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->payload_type = (full_kernel && full_rootfs) + ? InstallPayloadType::kFull + : InstallPayloadType::kDelta; + 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, + GetBuildArtifactsPath(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); + string public_key_path = GetBuildArtifactsPath(kUnittestPublicKeyPath); + EXPECT_TRUE(utils::FileExists(public_key_path.c_str())); + (*performer)->set_public_key_path(public_key_path); + DeltaPerformerIntegrationTest::SetSupportedVersion(*performer, minor_version); + + EXPECT_EQ(static_cast<off_t>(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(2U, 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(static_cast<off_t>(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/update_engine/payload_consumer/delta_performer_unittest.cc b/update_engine/payload_consumer/delta_performer_unittest.cc new file mode 100644 index 0000000..09bbb7c --- /dev/null +++ b/update_engine/payload_consumer/delta_performer_unittest.cc
@@ -0,0 +1,914 @@ +// +// 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 <inttypes.h> + +#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_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::GetBuildArtifactsPath; +using test_utils::System; +using test_utils::kRandomString; +using testing::_; + +extern const char* kUnittestPrivateKeyPath; +extern const char* kUnittestPublicKeyPath; + +namespace { + +const char kBogusMetadataSignature1[] = + "awSFIUdUZz2VWFiR+ku0Pj00V7bPQPQFYQSXjEXr3vaw3TE4xHV5CraY3/YrZpBv" + "J5z4dSBskoeuaO1TNC/S6E05t+yt36tE4Fh79tMnJ/z9fogBDXWgXLEUyG78IEQr" + "YH6/eBsQGT2RJtBgXIXbZ9W+5G9KmGDoPOoiaeNsDuqHiBc/58OFsrxskH8E6vMS" + "BmMGGk82mvgzic7ApcoURbCGey1b3Mwne/hPZ/bb9CIyky8Og9IfFMdL2uAweOIR" + "fjoTeLYZpt+WN65Vu7jJ0cQN8e1y+2yka5112wpRf/LLtPgiAjEZnsoYpLUd7CoV" + "pLRtClp97kN2+tXGNBQqkA=="; + +#ifdef __ANDROID__ +const char kZlibFingerprintPath[] = + "/data/nativetest/update_engine_unittests/zlib_fingerprint"; +#else +const char kZlibFingerprintPath[] = "/etc/zlib_fingerprint"; +#endif // __ANDROID__ + +// 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, +}; + +// Gzipped 'abc', generated with: +// echo -n abc | minigzip | hexdump -v -e '" " 12/1 "0x%02x, " "\n"' +const uint8_t kSourceGzippedData[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4b, 0x4c, + 0x4a, 0x06, 0x00, 0xc2, 0x41, 0x24, 0x35, 0x03, 0x00, 0x00, 0x00, +}; + +// Gzipped 'def', generated with: +// echo -n def | minigzip | hexdump -v -e '" " 12/1 "0x%02x, " "\n"' +const uint8_t kTargetGzippedData[] = { + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x4b, 0x49, + 0x4d, 0x03, 0x00, 0x61, 0xe1, 0xc4, 0x0c, 0x03, 0x00, 0x00, 0x00, +}; + +// Imgdiff data, generated with: +// echo -n abc | minigzip > abc && truncate -s 4096 abc +// echo -n def | minigzip > def && truncate -s 4096 def +// imgdiff abc def patch && hexdump -v -e '" " 12/1 "0x%02x, " "\n"' patch +const uint8_t kImgdiffData[] = { + 0x49, 0x4d, 0x47, 0x44, 0x49, 0x46, 0x46, 0x32, 0x03, 0x00, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x1f, 0x8b, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xf1, 0xff, + 0xff, 0xff, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x0f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x42, 0x53, 0x44, 0x49, 0x46, 0x46, 0x34, 0x30, 0x2a, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x5a, + 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xc3, 0xc8, 0xfb, 0x1f, + 0x00, 0x00, 0x01, 0x40, 0x00, 0x5c, 0x00, 0x20, 0x00, 0x30, 0xcd, 0x34, + 0x12, 0x34, 0x54, 0x60, 0x5c, 0xce, 0x2e, 0xe4, 0x8a, 0x70, 0xa1, 0x21, + 0x87, 0x91, 0xf6, 0x3e, 0x42, 0x5a, 0x68, 0x39, 0x17, 0x72, 0x45, 0x38, + 0x50, 0x90, 0x00, 0x00, 0x00, 0x00, 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, + 0x59, 0x26, 0x53, 0x59, 0x42, 0x3c, 0xb0, 0xf9, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x07, 0x00, 0x20, 0x00, 0x21, 0x98, 0x19, 0x84, 0x61, 0x77, 0x24, + 0x53, 0x85, 0x09, 0x04, 0x23, 0xcb, 0x0f, 0x90, 0x42, 0x53, 0x44, 0x49, + 0x46, 0x46, 0x34, 0x30, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf1, 0x0f, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, + 0x53, 0x59, 0x6f, 0x02, 0x77, 0xf3, 0x00, 0x00, 0x07, 0x40, 0x41, 0xe0, + 0x10, 0xc0, 0x00, 0x00, 0x02, 0x20, 0x00, 0x20, 0x00, 0x21, 0x29, 0xa3, + 0x10, 0x86, 0x03, 0x84, 0x04, 0xae, 0x5f, 0x17, 0x72, 0x45, 0x38, 0x50, + 0x90, 0x6f, 0x02, 0x77, 0xf3, 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, + 0x26, 0x53, 0x59, 0x71, 0x62, 0xbd, 0xa7, 0x00, 0x00, 0x20, 0x40, 0x32, + 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x80, 0x00, 0x48, 0x20, 0x00, + 0x30, 0xc0, 0x02, 0xa5, 0x19, 0xa5, 0x92, 0x6f, 0xc2, 0x5d, 0xac, 0x0e, + 0x17, 0x72, 0x45, 0x38, 0x50, 0x90, 0x71, 0x62, 0xbd, 0xa7, 0x42, 0x5a, + 0x68, 0x39, 0x17, 0x72, 0x45, 0x38, 0x50, 0x90, 0x00, 0x00, 0x00, 0x00, +}; + +} // 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, + InstallPayloadType payload_type, + ErrorCode expected) { + install_plan_.payload_type = payload_type; + + // 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.version.major = major_version; + config.version.minor = minor_version; + + PayloadFile payload; + EXPECT_TRUE(payload.Init(config)); + + PartitionConfig old_part(kLegacyPartitionNameRoot); + if (minor_version != kFullPayloadMinorVersion) { + // When generating a delta payload we need to include the old partition + // information to mark it as a delta payload. + old_part.path = "/dev/null"; + old_part.size = 0; + } + 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); + string private_key = + sign_payload ? GetBuildArtifactsPath(kUnittestPrivateKeyPath) : ""; + EXPECT_TRUE(payload.WritePayload( + payload_path, blob_path, private_key, &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, + GetBuildArtifactsPath(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. + string public_key_path = GetBuildArtifactsPath(kUnittestPublicKeyPath); + EXPECT_TRUE(utils::FileExists(public_key_path.c_str())); + performer_.set_public_key_path(public_key_path); + + // Init actual_error with an invalid value so that we make sure + // ParsePayloadMetadata properly populates it in all cases. + actual_error = ErrorCode::kUmaReportedMax; + 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_.payload_type = InstallPayloadType::kFull; + brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString), + std::end(kRandomString)); + expected_data.resize(4096); // block size + vector<AnnotatedOperation> aops; + AnnotatedOperation aop; + *(aop.op.add_dst_extents()) = ExtentForRange(0, 1); + aop.op.set_data_offset(0); + aop.op.set_data_length(expected_data.size()); + aop.op.set_type(InstallOperation::REPLACE); + aops.push_back(aop); + + brillo::Blob payload_data = GeneratePayload(expected_data, aops, false, + kChromeOSMajorPayloadVersion, kFullPayloadMinorVersion); + + EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null", true)); +} + +TEST_F(DeltaPerformerTest, ShouldCancelTest) { + install_plan_.payload_type = InstallPayloadType::kFull; + brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString), + std::end(kRandomString)); + expected_data.resize(4096); // block size + vector<AnnotatedOperation> aops; + AnnotatedOperation aop; + *(aop.op.add_dst_extents()) = ExtentForRange(0, 1); + aop.op.set_data_offset(0); + aop.op.set_data_length(expected_data.size()); + aop.op.set_type(InstallOperation::REPLACE); + aops.push_back(aop); + + brillo::Blob payload_data = GeneratePayload(expected_data, aops, false, + 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, ImgdiffOperationTest) { + brillo::Blob imgdiff_data(std::begin(kImgdiffData), std::end(kImgdiffData)); + + AnnotatedOperation aop; + *(aop.op.add_src_extents()) = ExtentForRange(0, 1); + *(aop.op.add_dst_extents()) = ExtentForRange(0, 1); + aop.op.set_data_offset(0); + aop.op.set_data_length(imgdiff_data.size()); + aop.op.set_type(InstallOperation::IMGDIFF); + + brillo::Blob payload_data = GeneratePayload(imgdiff_data, {aop}, false); + + string source_path; + EXPECT_TRUE(utils::MakeTempFile("Source-XXXXXX", &source_path, nullptr)); + ScopedPathUnlinker path_unlinker(source_path); + brillo::Blob source_data(std::begin(kSourceGzippedData), + std::end(kSourceGzippedData)); + source_data.resize(4096); // block size + EXPECT_TRUE(utils::WriteFile( + source_path.c_str(), source_data.data(), source_data.size())); + + brillo::Blob target_data(std::begin(kTargetGzippedData), + std::end(kTargetGzippedData)); + target_data.resize(4096); // block size + EXPECT_EQ(target_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}; + static_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, + InstallPayloadType::kFull, + 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, + InstallPayloadType::kDelta, + ErrorCode::kSuccess); +} + +TEST_F(DeltaPerformerTest, ValidateManifestFullUnsetMinorVersion) { + // The Manifest we are validating. + DeltaArchiveManifest manifest; + + RunManifestValidation(manifest, + DeltaPerformer::kSupportedMajorPayloadVersion, + InstallPayloadType::kFull, + ErrorCode::kSuccess); +} + +TEST_F(DeltaPerformerTest, ValidateManifestDeltaUnsetMinorVersion) { + // The Manifest we are validating. + DeltaArchiveManifest manifest; + // Add an empty old_rootfs_info() to trick the DeltaPerformer into think that + // this is a delta payload manifest with a missing minor version. + manifest.mutable_old_rootfs_info(); + + RunManifestValidation(manifest, + DeltaPerformer::kSupportedMajorPayloadVersion, + InstallPayloadType::kDelta, + 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, + InstallPayloadType::kFull, + ErrorCode::kPayloadMismatchedType); +} + +TEST_F(DeltaPerformerTest, ValidateManifestFullOldRootfsTest) { + // The Manifest we are validating. + DeltaArchiveManifest manifest; + manifest.mutable_old_rootfs_info(); + manifest.mutable_new_kernel_info(); + manifest.mutable_new_rootfs_info(); + manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion); + + RunManifestValidation(manifest, + kChromeOSMajorPayloadVersion, + InstallPayloadType::kFull, + 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, + InstallPayloadType::kFull, + 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); + // Mark the manifest as a delta payload by setting old_rootfs_info. + manifest.mutable_old_rootfs_info(); + + RunManifestValidation(manifest, + DeltaPerformer::kSupportedMajorPayloadVersion, + InstallPayloadType::kDelta, + 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(24U, 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( + {GetBuildArtifactsPath(kUnittestPrivateKeyPath)}, &signature_length)); + performer_.metadata_signature_size_ = signature_length; + performer_.set_public_key_path(GetBuildArtifactsPath(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. + + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + string non_existing_file = temp_dir.path().Append("non-existing").value(); + string existing_file = temp_dir.path().Append("existing").value(); + 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)); +} + +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(GetBuildArtifactsPath().Append("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); +} + +// Test that we recognize our own zlib compressor implementation as supported. +// All other equivalent implementations should be added to +// kCompatibleZlibFingerprint. +TEST_F(DeltaPerformerTest, ZlibFingerprintMatch) { + string fingerprint; + EXPECT_TRUE(base::ReadFileToString(base::FilePath(kZlibFingerprintPath), + &fingerprint)); + EXPECT_TRUE(utils::IsZlibCompatible(fingerprint)); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/download_action.cc b/update_engine/payload_consumer/download_action.cc new file mode 100644 index 0000000..084848e --- /dev/null +++ b/update_engine/payload_consumer/download_action.cc
@@ -0,0 +1,329 @@ +// +// 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/error_code_utils.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, + BootControlInterface* boot_control, + HardwareInterface* hardware, + SystemState* system_state, + HttpFetcher* http_fetcher) + : prefs_(prefs), + boot_control_(boot_control), + hardware_(hardware), + 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 (!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_, boot_control_, 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::SuspendAction() { + http_fetcher_->Pause(); +} + +void DownloadAction::ResumeAction() { + http_fetcher_->Unpause(); +} + +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 " << utils::ErrorCodeToString(code_) << " (" << 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_ && system_state_ && 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/update_engine/payload_consumer/download_action.h b/update_engine/payload_consumer/download_action.h new file mode 100644 index 0000000..285930a --- /dev/null +++ b/update_engine/payload_consumer/download_action.h
@@ -0,0 +1,183 @@ +// +// 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 "update_engine/common/action.h" +#include "update_engine/common/boot_control_interface.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: + // Debugging/logging + static std::string StaticType() { return "DownloadAction"; } + + // Takes ownership of the passed in HttpFetcher. Useful for testing. + // A good calling pattern is: + // DownloadAction(prefs, boot_contol, hardware, system_state, + // new WhateverHttpFetcher); + DownloadAction(PrefsInterface* prefs, + BootControlInterface* boot_control, + HardwareInterface* hardware, + SystemState* system_state, + HttpFetcher* http_fetcher); + ~DownloadAction() override; + + // InstallPlanAction overrides. + void PerformAction() override; + void SuspendAction() override; + void ResumeAction() override; + void TerminateProcessing() override; + std::string Type() const override { return StaticType(); } + + // Testing + void SetTestFileWriter(FileWriter* writer) { + writer_ = writer; + } + + int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); } + + // HttpFetcherDelegate methods (see http_fetcher.h) + 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_; + + // SystemState required pointers. + PrefsInterface* prefs_; + BootControlInterface* boot_control_; + HardwareInterface* hardware_; + + // 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. +static_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/update_engine/payload_consumer/download_action_unittest.cc b/update_engine/payload_consumer/download_action_unittest.cc new file mode 100644 index 0000000..5e9ef5c --- /dev/null +++ b/update_engine/payload_consumer/download_action_unittest.cc
@@ -0,0 +1,628 @@ +// +// 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.path().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; + install_plan.payload_type = InstallPayloadType::kDelta; + install_plan.payload_size = size; + install_plan.payload_hash = hash; + 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.boot_control(), + fake_system_state.hardware(), + &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.path(); + 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.path().c_str(), O_WRONLY | O_CREAT, 0)); + + // takes ownership of passed in HttpFetcher + ObjectFeederAction<InstallPlan> feeder_action; + InstallPlan install_plan; + feeder_action.set_obj(install_plan); + FakeSystemState fake_system_state_; + MockPrefs prefs; + DownloadAction download_action( + &prefs, + fake_system_state_.boot_control(), + fake_system_state_.hardware(), + &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.path())); + EXPECT_GE(resulting_file_size, 0); + if (resulting_file_size != 0) + EXPECT_EQ(kMockHttpFetcherChunkSize, + static_cast<size_t>(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; + install_plan.payload_size = 1; + install_plan.payload_hash = HashCalculator::HashOfString("x"); + ObjectFeederAction<InstallPlan> feeder_action; + feeder_action.set_obj(install_plan); + MockPrefs prefs; + FakeSystemState fake_system_state_; + DownloadAction download_action(&prefs, + fake_system_state_.boot_control(), + fake_system_state_.hardware(), + &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( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor))); + 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.path().c_str(), O_WRONLY | O_CREAT, 0)); + InstallPlan install_plan; + install_plan.payload_size = data_.length(); + install_plan.payload_hash = "1234hash"; + 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_.boot_control(), + fake_system_state_.hardware(), + &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.path(); + 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(static_cast<int>(data_.length()), + p2p_manager_->FileGetSize(file_id)); + EXPECT_EQ(static_cast<int>(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(static_cast<ssize_t>(data_.length()), + p2p_manager_->FileGetSize(file_id)); + EXPECT_EQ(static_cast<ssize_t>(data_.length()), + p2p_manager_->FileGetExpectedSize(file_id)); + 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(1000, p2p_manager_->FileGetSize(file_id)); + EXPECT_EQ(1, p2p_manager_->CountSharedFiles()); + + StartDownload(false); // use_p2p_to_share + + // DownloadAction should have deleted the p2p file. Check that it's gone. + EXPECT_EQ(-1, p2p_manager_->FileGetSize(file_id)); + EXPECT_EQ(0, p2p_manager_->CountSharedFiles()); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/extent_writer.cc b/update_engine/payload_consumer/extent_writer.cc new file mode 100644 index 0000000..5501e22 --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/extent_writer.h b/update_engine/payload_consumer/extent_writer.h new file mode 100644 index 0000000..6484ebf --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/extent_writer_unittest.cc b/update_engine/payload_consumer/extent_writer_unittest.cc new file mode 100644 index 0000000..24d238e --- /dev/null +++ b/update_engine/payload_consumer/extent_writer_unittest.cc
@@ -0,0 +1,266 @@ +// +// 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 <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 { + +static_assert(sizeof(off_t) == 8, "off_t not 64 bit"); + +namespace { +const size_t kBlockSize = 4096; +} + +class ExtentWriterTest : public ::testing::Test { + protected: + void SetUp() override { + fd_.reset(new EintrSafeFileDescriptor); + ASSERT_TRUE(fd_->Open(temp_file_.path().c_str(), O_RDWR, 0600)); + } + void TearDown() override { + fd_->Close(); + } + + // 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_; + test_utils::ScopedTempFile temp_file_{"ExtentWriterTest-file.XXXXXX"}; +}; + +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(static_cast<off_t>(kBlockSize + bytes.size()), + utils::FileSize(temp_file_.path())); + + brillo::Blob result_file; + EXPECT_TRUE(utils::ReadFile(temp_file_.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(static_cast<off_t>(data.size()), + utils::FileSize(temp_file_.path())); + + brillo::Blob result_file; + EXPECT_TRUE(utils::ReadFile(temp_file_.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(static_cast<off_t>(data.size()), + utils::FileSize(temp_file_.path())); + + brillo::Blob result_file; + EXPECT_TRUE(utils::ReadFile(temp_file_.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(static_cast<off_t>(2 * kBlockSize), + utils::FileSize(temp_file_.path())); + + brillo::Blob resultant_data; + EXPECT_TRUE(utils::ReadFile(temp_file_.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/update_engine/payload_consumer/fake_extent_writer.h b/update_engine/payload_consumer/fake_extent_writer.h new file mode 100644 index 0000000..762c6d5 --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/file_descriptor.cc b/update_engine/payload_consumer/file_descriptor.cc new file mode 100644 index 0000000..8a23dea --- /dev/null +++ b/update_engine/payload_consumer/file_descriptor.cc
@@ -0,0 +1,138 @@ +// +// 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> + +#include "update_engine/common/utils.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); +} + +uint64_t EintrSafeFileDescriptor::BlockDevSize() { + if (fd_ < 0) + return 0; + struct stat stbuf; + if (fstat(fd_, &stbuf) < 0) { + PLOG(ERROR) << "Error stat-ing fd " << fd_; + return 0; + } + if (!S_ISBLK(stbuf.st_mode)) + return 0; + off_t block_size = utils::BlockDevSize(fd_); + return block_size < 0 ? 0 : block_size; +} + +bool EintrSafeFileDescriptor::BlkIoctl(int request, + uint64_t start, + uint64_t length, + int* result) { + // If the ioctl BLKZEROOUT is not defined, just fail to perform any of these + // operations. +#ifndef BLKZEROOUT + return false; +#else // defined(BLKZEROOUT) + 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; +#endif // defined(BLKZEROOUT) +} + +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/update_engine/payload_consumer/file_descriptor.h b/update_engine/payload_consumer/file_descriptor.h new file mode 100644 index 0000000..7bb2974 --- /dev/null +++ b/update_engine/payload_consumer/file_descriptor.h
@@ -0,0 +1,146 @@ +// +// 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; + + // Return the size of the block device in bytes, or 0 if the device is not a + // block device or an error occurred. + virtual uint64_t BlockDevSize() = 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; + uint64_t BlockDevSize() 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/update_engine/payload_consumer/file_writer.cc b/update_engine/payload_consumer/file_writer.cc new file mode 100644 index 0000000..d280ddb --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/file_writer.h b/update_engine/payload_consumer/file_writer.h new file mode 100644 index 0000000..96ebde6 --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/file_writer_unittest.cc b/update_engine/payload_consumer/file_writer_unittest.cc new file mode 100644 index 0000000..debb4c3 --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/filesystem_verifier_action.cc b/update_engine/payload_consumer/filesystem_verifier_action.cc new file mode 100644 index 0000000..5156f96 --- /dev/null +++ b/update_engine/payload_consumer/filesystem_verifier_action.cc
@@ -0,0 +1,262 @@ +// +// 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/data_encoding.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; + +string StringForHashBytes(const brillo::Blob& hash) { + return brillo::data_encoding::Base64Encode(hash.data(), hash.size()); +} +} // namespace + +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(); + + 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()) { + Cleanup(ErrorCode::kSuccess); + return; + } + InstallPlan::Partition& partition = + install_plan_.partitions[partition_index_]; + + string part_path; + switch (verifier_step_) { + case VerifierStep::kVerifySourceHash: + part_path = partition.source_path; + remaining_size_ = partition.source_size; + break; + case VerifierStep::kVerifyTargetHash: + part_path = partition.target_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_step_) { + case VerifierStep::kVerifyTargetHash: + if (partition.target_hash != hasher_->raw_hash()) { + LOG(ERROR) << "New '" << partition.name + << "' partition verification failed."; + if (install_plan_.payload_type == InstallPayloadType::kFull) + return Cleanup(ErrorCode::kNewRootfsVerificationError); + // If we have not verified source partition yet, now that the target + // partition does not match, and it's not a full payload, we need to + // switch to kVerifySourceHash step to check if it's because the source + // partition does not match either. + verifier_step_ = VerifierStep::kVerifySourceHash; + } else { + partition_index_++; + } + break; + case VerifierStep::kVerifySourceHash: + if (partition.source_hash != hasher_->raw_hash()) { + LOG(ERROR) << "Old '" << partition.name + << "' partition verification failed."; + LOG(ERROR) << "This is a server-side error due to mismatched delta" + << " update image!"; + LOG(ERROR) << "The delta I've been given contains a " << partition.name + << " delta update that must be applied over a " + << partition.name << " with a specific checksum, but the " + << partition.name + << " 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 " + << partition.name << " partition I have has hash: " + << StringForHashBytes(hasher_->raw_hash()) + << " but the update expected me to have " + << StringForHashBytes(partition.source_hash) << " ."; + LOG(INFO) << "To get the checksum of the " << partition.name + << " partition run this command: dd if=" + << partition.source_path + << " bs=1M count=" << partition.source_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"; + return Cleanup(ErrorCode::kDownloadStateInitializationError); + } + // The action will skip kVerifySourceHash step if target partition hash + // matches, if we are in this step, it means target hash does not match, + // and now that the source partition hash matches, we should set the error + // code to reflect the error in target partition. + // We only need to verify the source partition which the target hash does + // not match, the rest of the partitions don't matter. + return Cleanup(ErrorCode::kNewRootfsVerificationError); + } + // Start hashing the next partition, if any. + hasher_.reset(); + buffer_.clear(); + src_stream_->CloseBlocking(nullptr); + StartPartitionHashing(); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/filesystem_verifier_action.h b/update_engine/payload_consumer/filesystem_verifier_action.h new file mode 100644 index 0000000..616f7b7 --- /dev/null +++ b/update_engine/payload_consumer/filesystem_verifier_action.h
@@ -0,0 +1,121 @@ +// +// 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 "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 the target slot involved in the +// update. The hashes are then verified against the ones in the InstallPlan. +// If the target hash does not match, the action will fail. In case of failure, +// the error code will depend on whether the source slot hashes are provided and +// match. + +namespace chromeos_update_engine { + +// The step FilesystemVerifier is on. 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. +// If the hash matches, then we skip the kVerifySourceHash step, otherwise we +// need to check if the source is the root cause of the mismatch. +enum class VerifierStep { + kVerifyTargetHash, + kVerifySourceHash, +}; + +class FilesystemVerifierAction : public InstallPlanAction { + public: + FilesystemVerifierAction() = default; + + 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: + // Starts the hashing of the current partition. If there aren't any partitions + // remaining to be hashed, it finishes the action. + void StartPartitionHashing(); + + // Schedules the asynchronous read of the filesystem. + void ScheduleRead(); + + // 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. + VerifierStep verifier_step_ = VerifierStep::kVerifyTargetHash; + + // 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/update_engine/payload_consumer/filesystem_verifier_action_unittest.cc b/update_engine/payload_consumer/filesystem_verifier_action_unittest.cc new file mode 100644 index 0000000..2e1d95d --- /dev/null +++ b/update_engine/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -0,0 +1,302 @@ +// +// 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/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); + + brillo::FakeMessageLoop loop_{nullptr}; +}; + +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(); + } +} + +bool FilesystemVerifierActionTest::DoTest(bool terminate_early, + bool hash_fail) { + 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, false, &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"; + part.target_size = kLoopFileSize - (hash_fail ? 1 : 0); + part.target_path = a_dev; + if (!HashCalculator::RawHashOfData(a_loop_data, &part.target_hash)) { + ADD_FAILURE(); + success = false; + } + part.source_size = kLoopFileSize; + part.source_path = 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; + 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; + 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; + 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; + 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(0U, getuid()); + EXPECT_TRUE(DoTest(false, false)); +} + +TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) { + ASSERT_EQ(0U, getuid()); + EXPECT_TRUE(DoTest(false, true)); +} + +TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) { + ASSERT_EQ(0U, getuid()); + EXPECT_TRUE(DoTest(true, false)); + // TerminateEarlyTest may leak some null callbacks from the Stream class. + while (loop_.RunOnce(false)) {} +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/install_plan.cc b/update_engine/payload_consumer/install_plan.cc new file mode 100644 index 0000000..b04da74 --- /dev/null +++ b/update_engine/payload_consumer/install_plan.cc
@@ -0,0 +1,122 @@ +// +// 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 { + +string InstallPayloadTypeToString(InstallPayloadType type) { + switch (type) { + case InstallPayloadType::kUnknown: + return "unknown"; + case InstallPayloadType::kFull: + return "full"; + case InstallPayloadType::kDelta: + return "delta"; + } + return "invalid type"; +} + +bool InstallPlan::operator==(const InstallPlan& that) const { + return ((is_resume == that.is_resume) && + (payload_type == that.payload_type) && + (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 ", postinst:%s)", + partition.name.c_str(), + partition.source_size, + partition.target_size, + utils::ToString(partition.run_postinstall).c_str()); + } + + LOG(INFO) << "InstallPlan: " + << (is_resume ? "resume" : "new_update") + << ", payload type: " << InstallPayloadTypeToString(payload_type) + << ", 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 && + postinstall_path == that.postinstall_path && + filesystem_type == that.filesystem_type && + postinstall_optional == that.postinstall_optional); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/install_plan.h b/update_engine/payload_consumer/install_plan.h new file mode 100644 index 0000000..3f0005c --- /dev/null +++ b/update_engine/payload_consumer/install_plan.h
@@ -0,0 +1,154 @@ +// +// 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 { + +enum class InstallPayloadType { + kUnknown, + kFull, + kDelta, +}; + +std::string InstallPayloadTypeToString(InstallPayloadType type); + +struct InstallPlan { + 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}; + InstallPayloadType payload_type{InstallPayloadType::kUnknown}; + 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. DownloadAction fills in the expected source and target partition sizes + // and hashes based on the manifest. + // + // 2. FilesystemVerifierAction computes and verifies the partition sizes and + // hashes against the expected values. + 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 and the + // postinstall parameters. + bool run_postinstall{false}; + std::string postinstall_path; + std::string filesystem_type; + bool postinstall_optional{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/update_engine/payload_consumer/mock_download_action.h b/update_engine/payload_consumer/mock_download_action.h new file mode 100644 index 0000000..3abb809 --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/mtd_file_descriptor.cc b/update_engine/payload_consumer/mtd_file_descriptor.cc new file mode 100644 index 0000000..3f0a33f --- /dev/null +++ b/update_engine/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::StartsWith(ubi_name.MaybeAsASCII(), "ubi", + base::CompareCase::SENSITIVE)); + + return static_cast<bool>(GetUbiVolumeInfo(path)); +} + +bool UbiFileDescriptor::Open(const char* path, int flags, mode_t mode) { + std::unique_ptr<UbiVolumeInfo> info = GetUbiVolumeInfo(path); + if (!info) { + return false; + } + + // This File Descriptor does not support read and write. + TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR); + TEST_AND_RETURN_FALSE( + EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode)); + + usable_eb_blocks_ = info->reserved_ebs; + eraseblock_size_ = info->eraseblock_size; + volume_size_ = usable_eb_blocks_ * eraseblock_size_; + + if ((flags & O_ACCMODE) == O_WRONLY) { + // It's best to use volume update ioctl so that UBI layer will mark the + // volume as being updated, and only clear that mark if the update is + // successful. We will need to pad to the whole volume size at close. + uint64_t vsize = volume_size_; + if (ioctl(fd_, UBI_IOCVOLUP, &vsize) != 0) { + PLOG(ERROR) << "Cannot issue volume update ioctl"; + EintrSafeFileDescriptor::Close(); + return false; + } + mode_ = kWriteOnly; + nr_written_ = 0; + } else { + mode_ = kReadOnly; + } + + return true; +} + +bool UbiFileDescriptor::Open(const char* path, int flags) { + mode_t cur = umask(022); + umask(cur); + return Open(path, flags, 0777 & ~cur); +} + +ssize_t UbiFileDescriptor::Read(void* buf, size_t count) { + CHECK(mode_ == kReadOnly); + return EintrSafeFileDescriptor::Read(buf, count); +} + +ssize_t UbiFileDescriptor::Write(const void* buf, size_t count) { + CHECK(mode_ == kWriteOnly); + ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, count); + if (nr_chunk >= 0) { + nr_written_ += nr_chunk; + } + return nr_chunk; +} + +off64_t UbiFileDescriptor::Seek(off64_t offset, int whence) { + if (mode_ == kWriteOnly) { + // Ignore seek in write mode. + return nr_written_; + } + return EintrSafeFileDescriptor::Seek(offset, whence); +} + +bool UbiFileDescriptor::Close() { + bool pad_ok = true; + if (IsOpen() && mode_ == kWriteOnly) { + char buf[1024]; + memset(buf, 0xFF, sizeof(buf)); + while (nr_written_ < volume_size_) { + // We have written less than the whole volume. In order for us to clear + // the update marker, we need to fill the rest. It is recommended to fill + // UBI writes with 0xFF. + uint64_t to_write = volume_size_ - nr_written_; + if (to_write > sizeof(buf)) { + to_write = sizeof(buf); + } + ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, to_write); + if (nr_chunk < 0) { + LOG(ERROR) << "Cannot 0xFF-pad before closing."; + // There is an error, but we can't really do any meaningful thing here. + pad_ok = false; + break; + } + nr_written_ += nr_chunk; + } + } + return EintrSafeFileDescriptor::Close() && pad_ok; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/mtd_file_descriptor.h b/update_engine/payload_consumer/mtd_file_descriptor.h new file mode 100644 index 0000000..6c945b2 --- /dev/null +++ b/update_engine/payload_consumer/mtd_file_descriptor.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_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; + uint64_t BlockDevSize() override { return 0; } + bool BlkIoctl(int request, + uint64_t start, + uint64_t length, + int* result) override { + return false; + } + bool Close() override; + + private: + std::unique_ptr<MtdReadContext, decltype(&mtd_read_close)> read_ctx_; + std::unique_ptr<MtdWriteContext, decltype(&mtd_write_close)> write_ctx_; + uint64_t nr_written_; +}; + +struct UbiVolumeInfo { + // Number of eraseblocks. + uint64_t reserved_ebs; + // Size of each eraseblock. + uint64_t eraseblock_size; +}; + +// A file descriptor to update a UBI volume, similar to MtdFileDescriptor. +// Once the file descriptor is opened for write, the volume is marked as being +// updated. The volume will not be usable until an update is completed. See +// UBI_IOCVOLUP ioctl operation. +class UbiFileDescriptor : public EintrSafeFileDescriptor { + public: + // Perform some queries about |path| to see if it is a UBI volume. + static bool IsUbi(const char* path); + + bool Open(const char* path, int flags, mode_t mode) override; + bool Open(const char* path, int flags) override; + ssize_t Read(void* buf, size_t count) override; + ssize_t Write(const void* buf, size_t count) override; + off64_t Seek(off64_t offset, int whence) override; + uint64_t BlockDevSize() override { return 0; } + bool BlkIoctl(int request, + uint64_t start, + uint64_t length, + int* result) override { + return false; + } + bool Close() override; + + private: + enum Mode { + kReadOnly, + kWriteOnly + }; + + uint64_t usable_eb_blocks_; + uint64_t eraseblock_size_; + uint64_t volume_size_; + uint64_t nr_written_; + + Mode mode_; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_MTD_FILE_DESCRIPTOR_H_
diff --git a/update_engine/payload_consumer/payload_constants.cc b/update_engine/payload_consumer/payload_constants.cc new file mode 100644 index 0000000..6078a74 --- /dev/null +++ b/update_engine/payload_consumer/payload_constants.cc
@@ -0,0 +1,71 @@ +// +// 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 uint32_t kImgdiffMinorPayloadVersion = 4; + +const char kLegacyPartitionNameKernel[] = "boot"; +const char kLegacyPartitionNameRoot[] = "system"; + +const char kDeltaMagic[4] = {'C', 'r', 'A', 'U'}; +const char kBspatchPath[] = "bspatch"; + +// The zlib in Android and Chrome OS are currently compatible with each other, +// so they are sharing the same array, but if in the future they are no longer +// compatible with each other, we coule make the same change on the other one to +// make them compatible again or use ifdef here. +const char kCompatibleZlibFingerprint[][65] = { + "ea973605ccbbdb24f59f449c5f65861a1a9bc7a4353377aaaa06cb3e0f1cfbd7", + "3747fa404cceb00a5ec3606fc779510aaa784d5864ab1d5c28b9e267c40aad5c", +}; + +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"; + case InstallOperation::IMGDIFF: + return "IMGDIFF"; + } + return "<unknown_op>"; +} + +}; // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/payload_constants.h b/update_engine/payload_consumer/payload_constants.h new file mode 100644 index 0000000..2dbc5fa --- /dev/null +++ b/update_engine/payload_consumer/payload_constants.h
@@ -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. +// + +#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 minor version that allows IMGDIFF operation. +extern const uint32_t kImgdiffMinorPayloadVersion; + + +// 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]; + +// The list of compatible SHA256 hashes of zlib source code. +// This is used to check if the source image have a compatible zlib (produce +// same compressed result given the same input). +// When a new fingerprint is found, please examine the changes in zlib source +// carefully and determine if it's still compatible with previous version, if +// yes then add the new fingerprint to this array, otherwise remove all previous +// fingerprints in the array first, and only include the new fingerprint. +extern const char kCompatibleZlibFingerprint[2][65]; + +// 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/update_engine/payload_consumer/payload_verifier.cc b/update_engine/payload_consumer/payload_verifier.cc new file mode 100644 index 0000000..ab5238c --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/payload_verifier.h b/update_engine/payload_consumer/payload_verifier.h new file mode 100644 index 0000000..22ced40 --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/postinstall_runner_action.cc b/update_engine/payload_consumer/postinstall_runner_action.cc new file mode 100644 index 0000000..a1b6f25 --- /dev/null +++ b/update_engine/payload_consumer/postinstall_runner_action.cc
@@ -0,0 +1,377 @@ +// +// 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 <fcntl.h> +#include <signal.h> +#include <stdlib.h> +#include <sys/mount.h> +#include <sys/types.h> +#include <unistd.h> + +#include <cmath> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/logging.h> +#include <base/strings/string_split.h> +#include <base/strings/string_util.h> + +#include "update_engine/common/action_processor.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/platform_constants.h" +#include "update_engine/common/subprocess.h" +#include "update_engine/common/utils.h" + +namespace { + +// The file descriptor number from the postinstall program's perspective where +// it can report status updates. This can be any number greater than 2 (stderr), +// but must be kept in sync with the "bin/postinst_progress" defined in the +// sample_images.sh file. +const int kPostinstallStatusFd = 3; + +} // namespace + +namespace chromeos_update_engine { + +using brillo::MessageLoop; +using std::string; +using std::vector; + +void PostinstallRunnerAction::PerformAction() { + CHECK(HasInputObject()); + install_plan_ = GetInputObject(); + + if (install_plan_.powerwash_required) { + if (hardware_->SchedulePowerwash()) { + powerwash_scheduled_ = true; + } else { + return CompletePostinstall(ErrorCode::kPostinstallPowerwashError); + } + } + + // Initialize all the partition weights. + partition_weight_.resize(install_plan_.partitions.size()); + total_weight_ = 0; + for (size_t i = 0; i < install_plan_.partitions.size(); ++i) { + // TODO(deymo): This code sets the weight to all the postinstall commands, + // but we could remember how long they took in the past and use those + // values. + partition_weight_[i] = install_plan_.partitions[i].run_postinstall; + total_weight_ += partition_weight_[i]; + } + accumulated_weight_ = 0; + ReportProgress(0); + + PerformPartitionPostinstall(); +} + +void PostinstallRunnerAction::PerformPartitionPostinstall() { + if (install_plan_.download_url.empty()) { + LOG(INFO) << "Skipping post-install during rollback"; + return CompletePostinstall(ErrorCode::kSuccess); + } + + // 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. +#ifdef __ANDROID__ + fs_mount_dir_ = "/postinstall"; +#else // __ANDROID__ + base::FilePath temp_dir; + TEST_AND_RETURN(base::CreateNewTempDirectory("au_postint_mount", &temp_dir)); + fs_mount_dir_ = temp_dir.value(); +#endif // __ANDROID__ + + base::FilePath postinstall_path(partition.postinstall_path); + if (postinstall_path.IsAbsolute()) { + LOG(ERROR) << "Invalid absolute path passed to postinstall, use a relative" + "path instead: " + << partition.postinstall_path; + return CompletePostinstall(ErrorCode::kPostinstallRunnerError); + } + + string abs_path = + base::FilePath(fs_mount_dir_).Append(postinstall_path).value(); + if (!base::StartsWith( + abs_path, fs_mount_dir_, base::CompareCase::SENSITIVE)) { + LOG(ERROR) << "Invalid relative postinstall path: " + << partition.postinstall_path; + return CompletePostinstall(ErrorCode::kPostinstallRunnerError); + } + +#ifdef __ANDROID__ + // In Chromium OS, the postinstall step is allowed to write to the block + // device on the target image, so we don't mark it as read-only and should + // be read-write since we just wrote to it during the update. + + // Mark the block device as read-only before mounting for post-install. + if (!utils::SetBlockDeviceReadOnly(mountable_device, true)) { + return CompletePartitionPostinstall( + 1, "Error marking the device " + mountable_device + " read only."); + } +#endif // __ANDROID__ + + if (!utils::MountFilesystem(mountable_device, + fs_mount_dir_, + MS_RDONLY, + partition.filesystem_type, + constants::kPostinstallMountOptions)) { + return CompletePartitionPostinstall( + 1, "Error mounting the device " + mountable_device); + } + + LOG(INFO) << "Performing postinst (" << partition.postinstall_path << " at " + << abs_path << ") 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 " << partition.postinstall_path + << " is: " << utils::GetFileFormat(abs_path); + + // Runs the postinstall script asynchronously to free up the main loop while + // it's running. + vector<string> command = {abs_path}; +#ifdef __ANDROID__ + // In Brillo and Android, we pass the slot number and status fd. + command.push_back(std::to_string(install_plan_.target_slot)); + command.push_back(std::to_string(kPostinstallStatusFd)); +#else + // Chrome OS postinstall expects the target rootfs as the first parameter. + command.push_back(partition.target_path); +#endif // __ANDROID__ + + current_command_ = Subprocess::Get().ExecFlags( + command, + Subprocess::kRedirectStderrToStdout, + {kPostinstallStatusFd}, + base::Bind(&PostinstallRunnerAction::CompletePartitionPostinstall, + base::Unretained(this))); + // Subprocess::Exec should never return a negative process id. + CHECK_GE(current_command_, 0); + + if (!current_command_) { + CompletePartitionPostinstall(1, "Postinstall didn't launch"); + return; + } + + // Monitor the status file descriptor. + progress_fd_ = + Subprocess::Get().GetPipeFd(current_command_, kPostinstallStatusFd); + int fd_flags = fcntl(progress_fd_, F_GETFL, 0) | O_NONBLOCK; + if (HANDLE_EINTR(fcntl(progress_fd_, F_SETFL, fd_flags)) < 0) { + PLOG(ERROR) << "Unable to set non-blocking I/O mode on fd " << progress_fd_; + } + + progress_task_ = MessageLoop::current()->WatchFileDescriptor( + FROM_HERE, + progress_fd_, + MessageLoop::WatchMode::kWatchRead, + true, + base::Bind(&PostinstallRunnerAction::OnProgressFdReady, + base::Unretained(this))); +} + +void PostinstallRunnerAction::OnProgressFdReady() { + char buf[1024]; + size_t bytes_read; + do { + bytes_read = 0; + bool eof; + bool ok = + utils::ReadAll(progress_fd_, buf, arraysize(buf), &bytes_read, &eof); + progress_buffer_.append(buf, bytes_read); + // Process every line. + vector<string> lines = base::SplitString( + progress_buffer_, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); + if (!lines.empty()) { + progress_buffer_ = lines.back(); + lines.pop_back(); + for (const auto& line : lines) { + ProcessProgressLine(line); + } + } + if (!ok || eof) { + // There was either an error or an EOF condition, so we are done watching + // the file descriptor. + MessageLoop::current()->CancelTask(progress_task_); + progress_task_ = MessageLoop::kTaskIdNull; + return; + } + } while (bytes_read); +} + +bool PostinstallRunnerAction::ProcessProgressLine(const string& line) { + double frac = 0; + if (sscanf(line.c_str(), "global_progress %lf", &frac) == 1 && + !std::isnan(frac)) { + ReportProgress(frac); + return true; + } + + return false; +} + +void PostinstallRunnerAction::ReportProgress(double frac) { + if (!delegate_) + return; + if (current_partition_ >= partition_weight_.size()) { + delegate_->ProgressUpdate(1.); + return; + } + if (!std::isfinite(frac) || frac < 0) + frac = 0; + if (frac > 1) + frac = 1; + double postinst_action_progress = + (accumulated_weight_ + partition_weight_[current_partition_] * frac) / + total_weight_; + delegate_->ProgressUpdate(postinst_action_progress); +} + +void PostinstallRunnerAction::Cleanup() { + utils::UnmountFilesystem(fs_mount_dir_); +#ifndef __ANDROID__ + if (!base::DeleteFile(base::FilePath(fs_mount_dir_), false)) { + PLOG(WARNING) << "Not removing temporary mountpoint " << fs_mount_dir_; + } +#endif // !__ANDROID__ + fs_mount_dir_.clear(); + + progress_fd_ = -1; + if (progress_task_ != MessageLoop::kTaskIdNull) { + MessageLoop::current()->CancelTask(progress_task_); + progress_task_ = MessageLoop::kTaskIdNull; + } + progress_buffer_.clear(); +} + +void PostinstallRunnerAction::CompletePartitionPostinstall( + int return_code, const string& output) { + current_command_ = 0; + Cleanup(); + + 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; + } + + // If postinstall script for this partition is optional we can ignore the + // result. + if (install_plan_.partitions[current_partition_].postinstall_optional) { + LOG(INFO) << "Ignoring postinstall failure since it is optional"; + } else { + return CompletePostinstall(error_code); + } + } + accumulated_weight_ += partition_weight_[current_partition_]; + current_partition_++; + ReportProgress(0); + + 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); + completer.set_code(error_code); + + if (error_code != ErrorCode::kSuccess) { + LOG(ERROR) << "Postinstall action failed."; + + // Undo any changes done to trigger Powerwash. + if (powerwash_scheduled_) + hardware_->CancelPowerwash(); + + return; + } + + LOG(INFO) << "All post-install commands succeeded"; + if (HasOutputPipe()) { + SetOutputObject(install_plan_); + } +} + +void PostinstallRunnerAction::SuspendAction() { + if (!current_command_) + return; + if (kill(current_command_, SIGSTOP) != 0) { + PLOG(ERROR) << "Couldn't pause child process " << current_command_; + } +} + +void PostinstallRunnerAction::ResumeAction() { + if (!current_command_) + return; + if (kill(current_command_, SIGCONT) != 0) { + PLOG(ERROR) << "Couldn't resume child process " << current_command_; + } +} + +void PostinstallRunnerAction::TerminateProcessing() { + if (!current_command_) + return; + // Calling KillExec() will discard the callback we registered and therefore + // the unretained reference to this object. + Subprocess::Get().KillExec(current_command_); + current_command_ = 0; + Cleanup(); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/postinstall_runner_action.h b/update_engine/payload_consumer/postinstall_runner_action.h new file mode 100644 index 0000000..2bde3ca --- /dev/null +++ b/update_engine/payload_consumer/postinstall_runner_action.h
@@ -0,0 +1,150 @@ +// +// 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 <vector> + +#include <brillo/message_loops/message_loop.h> +#include <gtest/gtest_prod.h> + +#include "update_engine/common/action.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/hardware_interface.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: + PostinstallRunnerAction(BootControlInterface* boot_control, + HardwareInterface* hardware) + : boot_control_(boot_control), hardware_(hardware) {} + + // InstallPlanAction overrides. + void PerformAction() override; + void SuspendAction() override; + void ResumeAction() override; + void TerminateProcessing() override; + + class DelegateInterface { + public: + virtual ~DelegateInterface() = default; + + // Called whenever there is an overall progress update from the postinstall + // programs. + virtual void ProgressUpdate(double progress) = 0; + }; + + void set_delegate(DelegateInterface* delegate) { delegate_ = delegate; } + + // Debugging/logging + static std::string StaticType() { return "PostinstallRunnerAction"; } + std::string Type() const override { return StaticType(); } + + private: + friend class PostinstallRunnerActionTest; + FRIEND_TEST(PostinstallRunnerActionTest, ProcessProgressLineTest); + + void PerformPartitionPostinstall(); + + // Called whenever the |progress_fd_| has data available to read. + void OnProgressFdReady(); + + // Updates the action progress according to the |line| passed from the + // postinstall program. Valid lines are: + // global_progress <frac> + // <frac> should be between 0.0 and 1.0; sets the progress to the + // <frac> value. + bool ProcessProgressLine(const std::string& line); + + // Report the progress to the delegate given that the postinstall operation + // for |current_partition_| has a current progress of |frac|, a value between + // 0 and 1 for that step. + void ReportProgress(double frac); + + // Cleanup the setup made when running postinstall for a given partition. + // Unmount and remove the mountpoint directory if needed and cleanup the + // status file descriptor and message loop task watching for it. + void Cleanup(); + + // 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_; + + // The path where the filesystem will be mounted during post-install. + std::string fs_mount_dir_; + + // The partition being processed on the list of partitions specified in the + // InstallPlan. + size_t current_partition_{0}; + + // A non-negative value representing the estimated weight of each partition + // passed in the install plan. The weight is used to predict the overall + // progress from the individual progress of each partition and should + // correspond to the time it takes to run it. + std::vector<double> partition_weight_; + + // The sum of all the weights in |partition_weight_|. + double total_weight_{0}; + + // The sum of all the weights in |partition_weight_| up to but not including + // the |current_partition_|. + double accumulated_weight_{0}; + + // The delegate used to notify of progress updates, if any. + DelegateInterface* delegate_{nullptr}; + + // The BootControlInerface used to mark the new slot as ready. + BootControlInterface* boot_control_; + + // HardwareInterface used to signal powerwash. + HardwareInterface* hardware_; + + // Whether the Powerwash was scheduled before invoking post-install script. + // Used for cleaning up if post-install fails. + bool powerwash_scheduled_{false}; + + // Postinstall command currently running, or 0 if no program running. + pid_t current_command_{0}; + + // The parent progress file descriptor used to watch for progress reports from + // the postinstall program and the task watching for them. + int progress_fd_{-1}; + brillo::MessageLoop::TaskId progress_task_{brillo::MessageLoop::kTaskIdNull}; + + // A buffer of a partial read line from the progress file descriptor. + std::string progress_buffer_; + + DISALLOW_COPY_AND_ASSIGN(PostinstallRunnerAction); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_
diff --git a/update_engine/payload_consumer/postinstall_runner_action_unittest.cc b/update_engine/payload_consumer/postinstall_runner_action_unittest.cc new file mode 100644 index 0000000..e82a866 --- /dev/null +++ b/update_engine/payload_consumer/postinstall_runner_action_unittest.cc
@@ -0,0 +1,362 @@ +// +// 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/bind.h> +#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 <gmock/gmock.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/test_utils.h" +#include "update_engine/common/utils.h" + +using brillo::MessageLoop; +using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder; +using std::string; +using std::vector; + +namespace chromeos_update_engine { + +class PostinstActionProcessorDelegate : public ActionProcessorDelegate { + public: + PostinstActionProcessorDelegate() = default; + void ProcessingDone(const ActionProcessor* processor, + ErrorCode code) override { + MessageLoop::current()->BreakLoop(); + processing_done_called_ = true; + } + void ProcessingStopped(const ActionProcessor* processor) override { + MessageLoop::current()->BreakLoop(); + processing_stopped_called_ = true; + } + + void ActionCompleted(ActionProcessor* processor, + AbstractAction* action, + ErrorCode code) override { + if (action->Type() == PostinstallRunnerAction::StaticType()) { + code_ = code; + code_set_ = true; + } + } + + ErrorCode code_{ErrorCode::kError}; + bool code_set_{false}; + bool processing_done_called_{false}; + bool processing_stopped_called_{false}; +}; + +class MockPostinstallRunnerActionDelegate + : public PostinstallRunnerAction::DelegateInterface { + public: + MOCK_METHOD1(ProgressUpdate, void(double progress)); +}; + +class PostinstallRunnerActionTest : public ::testing::Test { + protected: + void SetUp() override { + loop_.SetAsCurrent(); + async_signal_handler_.Init(); + subprocess_.Init(&async_signal_handler_); + // These tests use the postinstall files generated by "generate_images.sh" + // stored in the "disk_ext2_unittest.img" image. + postinstall_image_ = + test_utils::GetBuildArtifactsPath("gen/disk_ext2_unittest.img"); + } + + // Setup an action processor and run the PostinstallRunnerAction with a single + // partition |device_path|, running the |postinstall_program| command from + // there. + void RunPosinstallAction(const string& device_path, + const string& postinstall_program, + bool powerwash_required); + + public: + void ResumeRunningAction() { + ASSERT_NE(nullptr, postinstall_action_); + postinstall_action_->ResumeAction(); + } + + void SuspendRunningAction() { + if (!postinstall_action_ || !postinstall_action_->current_command_ || + test_utils::Readlink(base::StringPrintf( + "/proc/%d/fd/0", postinstall_action_->current_command_)) != + "/dev/zero") { + // We need to wait for the postinstall command to start and flag that it + // is ready by redirecting its input to /dev/zero. + loop_.PostDelayedTask( + FROM_HERE, + base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction, + base::Unretained(this)), + base::TimeDelta::FromMilliseconds(100)); + } else { + postinstall_action_->SuspendAction(); + // Schedule to be resumed in a little bit. + loop_.PostDelayedTask( + FROM_HERE, + base::Bind(&PostinstallRunnerActionTest::ResumeRunningAction, + base::Unretained(this)), + base::TimeDelta::FromMilliseconds(100)); + } + } + + void CancelWhenStarted() { + if (!postinstall_action_ || !postinstall_action_->current_command_) { + // Wait for the postinstall command to run. + loop_.PostDelayedTask( + FROM_HERE, + base::Bind(&PostinstallRunnerActionTest::CancelWhenStarted, + base::Unretained(this)), + base::TimeDelta::FromMilliseconds(10)); + } else { + CHECK(processor_); + processor_->StopProcessing(); + } + } + + protected: + base::MessageLoopForIO base_loop_; + brillo::BaseMessageLoop loop_{&base_loop_}; + brillo::AsynchronousSignalHandler async_signal_handler_; + Subprocess subprocess_; + + // The path to the postinstall sample image. + string postinstall_image_; + + FakeBootControl fake_boot_control_; + FakeHardware fake_hardware_; + PostinstActionProcessorDelegate processor_delegate_; + + // The PostinstallRunnerAction delegate receiving the progress updates. + PostinstallRunnerAction::DelegateInterface* setup_action_delegate_{nullptr}; + + // A pointer to the posinstall_runner action and the processor. + PostinstallRunnerAction* postinstall_action_{nullptr}; + ActionProcessor* processor_{nullptr}; +}; + +void PostinstallRunnerActionTest::RunPosinstallAction( + const string& device_path, + const string& postinstall_program, + bool powerwash_required) { + ActionProcessor processor; + processor_ = &processor; + ObjectFeederAction<InstallPlan> feeder_action; + InstallPlan::Partition part; + part.name = "part"; + part.target_path = device_path; + part.run_postinstall = true; + part.postinstall_path = postinstall_program; + InstallPlan install_plan; + install_plan.partitions = {part}; + install_plan.download_url = "http://127.0.0.1:8080/update"; + install_plan.powerwash_required = powerwash_required; + feeder_action.set_obj(install_plan); + PostinstallRunnerAction runner_action(&fake_boot_control_, &fake_hardware_); + postinstall_action_ = &runner_action; + runner_action.set_delegate(setup_action_delegate_); + BondActions(&feeder_action, &runner_action); + ObjectCollectorAction<InstallPlan> collector_action; + BondActions(&runner_action, &collector_action); + processor.EnqueueAction(&feeder_action); + processor.EnqueueAction(&runner_action); + processor.EnqueueAction(&collector_action); + processor.set_delegate(&processor_delegate_); + + loop_.PostTask( + FROM_HERE, + base::Bind( + [](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(&processor))); + loop_.Run(); + ASSERT_FALSE(processor.IsRunning()); + postinstall_action_ = nullptr; + processor_ = nullptr; + EXPECT_TRUE(processor_delegate_.processing_stopped_called_ || + processor_delegate_.processing_done_called_); + if (processor_delegate_.processing_done_called_) { + // Sanity check that the code was set when the processor finishes. + EXPECT_TRUE(processor_delegate_.code_set_); + } +} + +TEST_F(PostinstallRunnerActionTest, ProcessProgressLineTest) { + PostinstallRunnerAction action(&fake_boot_control_, &fake_hardware_); + testing::StrictMock<MockPostinstallRunnerActionDelegate> mock_delegate_; + action.set_delegate(&mock_delegate_); + + action.current_partition_ = 1; + action.partition_weight_ = {1, 2, 5}; + action.accumulated_weight_ = 1; + action.total_weight_ = 8; + + // 50% of the second action is 2/8 = 0.25 of the total. + EXPECT_CALL(mock_delegate_, ProgressUpdate(0.25)); + action.ProcessProgressLine("global_progress 0.5"); + testing::Mock::VerifyAndClearExpectations(&mock_delegate_); + + // 1.5 should be read as 100%, to catch rounding error cases like 1.000001. + // 100% of the second is 3/8 of the total. + EXPECT_CALL(mock_delegate_, ProgressUpdate(0.375)); + action.ProcessProgressLine("global_progress 1.5"); + testing::Mock::VerifyAndClearExpectations(&mock_delegate_); + + // None of these should trigger a progress update. + action.ProcessProgressLine("foo_bar"); + action.ProcessProgressLine("global_progress"); + action.ProcessProgressLine("global_progress "); + action.ProcessProgressLine("global_progress NaN"); + action.ProcessProgressLine("global_progress Exception in ... :)"); +} + +// Test that postinstall succeeds in the simple case of running the default +// /postinst command which only exits 0. +TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + RunPosinstallAction(loop.dev(), kPostinstallDefaultScript, false); + EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); + EXPECT_TRUE(processor_delegate_.processing_done_called_); + + // Since powerwash_required was false, this should not trigger a powerwash. + EXPECT_FALSE(fake_hardware_.IsPowerwashScheduled()); +} + +TEST_F(PostinstallRunnerActionTest, RunAsRootRunSymlinkFileTest) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + RunPosinstallAction(loop.dev(), "bin/postinst_link", false); + EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); +} + +TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + // Run a simple postinstall program but requiring a powerwash. + RunPosinstallAction(loop.dev(), "bin/postinst_example", true); + EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); + + // Check that powerwash was scheduled. + EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled()); +} + +// Runs postinstall from a partition file that doesn't mount, so it should +// fail. +TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) { + RunPosinstallAction("/dev/null", kPostinstallDefaultScript, false); + EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_); + + // In case of failure, Postinstall should not signal a powerwash even if it + // was requested. + EXPECT_FALSE(fake_hardware_.IsPowerwashScheduled()); +} + +// Check that the failures from the postinstall script cause the action to +// fail. +TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + RunPosinstallAction(loop.dev(), "bin/postinst_fail1", false); + EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_); +} + +// The exit code 3 and 4 are a specials cases that would be reported back to +// UMA with a different error code. Test those cases are properly detected. +TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + RunPosinstallAction(loop.dev(), "bin/postinst_fail3", false); + EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB, + processor_delegate_.code_); +} + +// Check that you can't specify an absolute path. +TEST_F(PostinstallRunnerActionTest, RunAsRootAbsolutePathNotAllowedTest) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + RunPosinstallAction(loop.dev(), "/etc/../bin/sh", false); + EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_); +} + +#ifdef __ANDROID__ +// Check that the postinstall file is relabeled to the postinstall label. +// SElinux labels are only set on Android. +TEST_F(PostinstallRunnerActionTest, RunAsRootCheckFileContextsTest) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + RunPosinstallAction(loop.dev(), "bin/self_check_context", false); + EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); +} +#endif // __ANDROID__ + +// Check that you can suspend/resume postinstall actions. +TEST_F(PostinstallRunnerActionTest, RunAsRootSuspendResumeActionTest) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + + // We need to wait for the child to run and setup its signal handler. + loop_.PostTask(FROM_HERE, + base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction, + base::Unretained(this))); + RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false); + // postinst_suspend returns 0 only if it was suspended at some point. + EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); + EXPECT_TRUE(processor_delegate_.processing_done_called_); +} + +// Test that we can cancel a postinstall action while it is running. +TEST_F(PostinstallRunnerActionTest, RunAsRootCancelPostinstallActionTest) { + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + + // Wait for the action to start and then cancel it. + CancelWhenStarted(); + RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false); + // When canceling the action, the action never finished and therefore we had + // a ProcessingStopped call instead. + EXPECT_FALSE(processor_delegate_.code_set_); + EXPECT_TRUE(processor_delegate_.processing_stopped_called_); +} + +// Test that we parse and process the progress reports from the progress +// file descriptor. +TEST_F(PostinstallRunnerActionTest, RunAsRootProgressUpdatesTest) { + testing::StrictMock<MockPostinstallRunnerActionDelegate> mock_delegate_; + testing::InSequence s; + EXPECT_CALL(mock_delegate_, ProgressUpdate(0)); + + // The postinst_progress program will call with 0.25, 0.5 and 1. + EXPECT_CALL(mock_delegate_, ProgressUpdate(0.25)); + EXPECT_CALL(mock_delegate_, ProgressUpdate(0.5)); + EXPECT_CALL(mock_delegate_, ProgressUpdate(1.)); + + EXPECT_CALL(mock_delegate_, ProgressUpdate(1.)); + + ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr); + setup_action_delegate_ = &mock_delegate_; + RunPosinstallAction(loop.dev(), "bin/postinst_progress", false); + EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_consumer/xz_extent_writer.cc b/update_engine/payload_consumer/xz_extent_writer.cc new file mode 100644 index 0000000..370a00e --- /dev/null +++ b/update_engine/payload_consumer/xz_extent_writer.cc
@@ -0,0 +1,123 @@ +// +// 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) { + if (request.in_size != request.in_pos) { + LOG(ERROR) << "request.in_size != request.in_pos ( " + << request.in_size << " vs " << request.in_pos << " )"; + return false; + } + } + 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/update_engine/payload_consumer/xz_extent_writer.h b/update_engine/payload_consumer/xz_extent_writer.h new file mode 100644 index 0000000..a6b3257 --- /dev/null +++ b/update_engine/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/update_engine/payload_consumer/xz_extent_writer_unittest.cc b/update_engine/payload_consumer/xz_extent_writer_unittest.cc new file mode 100644 index 0000000..fb8bb40 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/ab_generator.cc b/update_engine/payload_generator/ab_generator.cc new file mode 100644 index 0000000..efb8ccf --- /dev/null +++ b/update_engine/payload_generator/ab_generator.cc
@@ -0,0 +1,321 @@ +// +// 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 chromeos_update_engine::diff_utils::IsAReplaceOperation; +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, + config.version, + blob_file)); + LOG(INFO) << "done reading " << new_part.name; + + TEST_AND_RETURN_FALSE( + FragmentOperations(config.version, 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, config.version, merge_chunk_blocks, new_part.path, blob_file)); + + if (config.version.minor >= 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(const PayloadVersion& version, + 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 (IsAReplaceOperation(aop.op.type())) { + TEST_AND_RETURN_FALSE(SplitAReplaceOp( + version, aop, target_part_path, &fragmented_aops, blob_file)); + } else { + fragmented_aops.push_back(aop); + } + } + *aops = std::move(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++) { + const 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::SplitAReplaceOp(const PayloadVersion& version, + const AnnotatedOperation& original_aop, + const string& target_part_path, + vector<AnnotatedOperation>* result_aops, + BlobFileWriter* blob_file) { + InstallOperation original_op = original_aop.op; + TEST_AND_RETURN_FALSE(IsAReplaceOperation(original_op.type())); + const bool is_replace = original_op.type() == InstallOperation::REPLACE; + + uint32_t data_offset = original_op.data_offset(); + for (int i = 0; i < original_op.dst_extents_size(); i++) { + const 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, version, target_part_path, blob_file)); + + result_aops->push_back(new_aop); + } + return true; +} + +bool ABGenerator::MergeOperations(vector<AnnotatedOperation>* aops, + const PayloadVersion& version, + 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(); + bool last_is_a_replace = IsAReplaceOperation(last_aop.op.type()); + + 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 is_a_replace = IsAReplaceOperation(curr_aop.op.type()); + + bool is_delta_op = curr_aop.op.type() == InstallOperation::SOURCE_COPY; + if (((is_delta_op && (last_aop.op.type() == curr_aop.op.type())) || + (is_a_replace && last_is_a_replace)) && + 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()); + + if (is_delta_op) { + 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 (is_a_replace) + 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/REPLACE_XZ operations that have been + // merged. + for (AnnotatedOperation& curr_aop : new_aops) { + if (curr_aop.op.data_length() == 0 && + IsAReplaceOperation(curr_aop.op.type())) { + TEST_AND_RETURN_FALSE( + AddDataAndSetType(&curr_aop, version, target_part_path, blob_file)); + } + } + + *aops = new_aops; + return true; +} + +bool ABGenerator::AddDataAndSetType(AnnotatedOperation* aop, + const PayloadVersion& version, + const string& target_part_path, + BlobFileWriter* blob_file) { + TEST_AND_RETURN_FALSE(IsAReplaceOperation(aop->op.type())); + + 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 blob; + InstallOperation_Type op_type; + TEST_AND_RETURN_FALSE( + diff_utils::GenerateBestFullOperation(data, version, &blob, &op_type)); + + // If the operation doesn't point to a data blob or points to a data blob of + // a different type then we add it. + if (aop->op.type() != op_type || aop->op.data_length() != blob.size()) { + aop->op.set_type(op_type); + aop->SetOperationBlob(blob, 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/update_engine/payload_generator/ab_generator.h b/update_engine/payload_generator/ab_generator.h new file mode 100644 index 0000000..77afb87 --- /dev/null +++ b/update_engine/payload_generator/ab_generator.h
@@ -0,0 +1,136 @@ +// +// 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(const PayloadVersion& version, + 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 or REPLACE_XZ 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 of a different + // type depending on whether compression is advantageous. + static bool SplitAReplaceOp(const PayloadVersion& version, + const AnnotatedOperation& original_aop, + const std::string& target_part, + std::vector<AnnotatedOperation>* result_aops, + BlobFileWriter* blob_file); + + // Takes a sorted (by first destination extent) vector of operations |aops| + // and merges SOURCE_COPY, REPLACE, REPLACE_BZ and REPLACE_XZ, operations in + // that vector. + // It will merge two operations if: + // - They are both REPLACE_*, or they are both SOURCE_COPY, + // - Their destination blocks 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, + const PayloadVersion& version, + 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/REPLACE_XZ operation |aop| + // by reading its output extents from |target_part_path| and appending a + // corresponding data blob to |blob_file|. 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 PayloadVersion& version, + 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/update_engine/payload_generator/ab_generator_unittest.cc b/update_engine/payload_generator/ab_generator_unittest.cc new file mode 100644 index 0000000..3fd2323 --- /dev/null +++ b/update_engine/payload_generator/ab_generator_unittest.cc
@@ -0,0 +1,599 @@ +// +// 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 <random> +#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(const 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; + PayloadVersion version(kChromeOSMajorPayloadVersion, + kSourceMinorPayloadVersion); + ASSERT_TRUE(ABGenerator::SplitAReplaceOp( + version, aop, part_path, &result_ops, &blob_file)); + + // Check the result. + InstallOperation_Type expected_type = + compressible ? InstallOperation::REPLACE_BZ : InstallOperation::REPLACE; + + ASSERT_EQ(2U, 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, static_cast<ssize_t>(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, static_cast<ssize_t>(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(0U, 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. + PayloadVersion version(kChromeOSMajorPayloadVersion, + kSourceMinorPayloadVersion); + EXPECT_TRUE( + ABGenerator::MergeOperations(&aops, version, 5, part_path, &blob_file)); + + // Check the result. + InstallOperation_Type expected_op_type = + compressible ? InstallOperation::REPLACE_BZ : InstallOperation::REPLACE; + EXPECT_EQ(1U, 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(), + static_cast<size_t>(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(static_cast<ssize_t>(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(3U, result_ops.size()); + + 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(2U, first_op.src_extents(0).start_block()); + EXPECT_EQ(2U, 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(10U, first_op.dst_extents(0).start_block()); + EXPECT_EQ(2U, 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(4U, second_op.src_extents(0).start_block()); + EXPECT_EQ(1U, second_op.src_extents(0).num_blocks()); + EXPECT_EQ(6U, second_op.src_extents(1).start_block()); + EXPECT_EQ(1U, second_op.src_extents(1).num_blocks()); + EXPECT_EQ(8U, second_op.src_extents(2).start_block()); + EXPECT_EQ(1U, 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(14U, second_op.dst_extents(0).start_block()); + EXPECT_EQ(3U, 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(9U, third_op.src_extents(0).start_block()); + EXPECT_EQ(3U, 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(18U, third_op.dst_extents(0).start_block()); + EXPECT_EQ(3U, 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(3U, aops.size()); + 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); + PayloadVersion version(kChromeOSMajorPayloadVersion, + kSourceMinorPayloadVersion); + EXPECT_TRUE(ABGenerator::MergeOperations(&aops, version, 5, "", &blob_file)); + + EXPECT_EQ(1U, aops.size()); + 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::ZERO); + *(first_op.add_dst_extents()) = ExtentForRange(0, 1); + 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); + PayloadVersion version(kChromeOSMajorPayloadVersion, + kSourceMinorPayloadVersion); + EXPECT_TRUE(ABGenerator::MergeOperations(&aops, version, 4, "", &blob_file)); + + // No operations were merged, the number of ops is the same. + EXPECT_EQ(4U, aops.size()); +} + +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/update_engine/payload_generator/annotated_operation.cc b/update_engine/payload_generator/annotated_operation.cc new file mode 100644 index 0000000..e28fe85 --- /dev/null +++ b/update_engine/payload_generator/annotated_operation.cc
@@ -0,0 +1,75 @@ +// +// 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(const brillo::Blob& blob, + BlobFileWriter* blob_file) { + if (blob.empty()) { + op.clear_data_offset(); + op.clear_data_length(); + return true; + } + 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/update_engine/payload_generator/annotated_operation.h b/update_engine/payload_generator/annotated_operation.h new file mode 100644 index 0000000..653bab0 --- /dev/null +++ b/update_engine/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 |blob_file|. It sets the data_offset and + // data_length in AnnotatedOperation to match the offset and size of |blob| + // in |blob_file|. + bool SetOperationBlob(const 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/update_engine/payload_generator/blob_file_writer.cc b/update_engine/payload_generator/blob_file_writer.cc new file mode 100644 index 0000000..8225df4 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/blob_file_writer.h b/update_engine/payload_generator/blob_file_writer.h new file mode 100644 index 0000000..cbc13ae --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/blob_file_writer_unittest.cc b/update_engine/payload_generator/blob_file_writer_unittest.cc new file mode 100644 index 0000000..5f94ef3 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/block_mapping.cc b/update_engine/payload_generator/block_mapping.cc new file mode 100644 index 0000000..ff10f0b --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/block_mapping.h b/update_engine/payload_generator/block_mapping.h new file mode 100644 index 0000000..3fe94ab --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/block_mapping_unittest.cc b/update_engine/payload_generator/block_mapping_unittest.cc new file mode 100644 index 0000000..4d09710 --- /dev/null +++ b/update_engine/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(4U, 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/update_engine/payload_generator/bzip.cc b/update_engine/payload_generator/bzip.cc new file mode 100644 index 0000000..c2388ca --- /dev/null +++ b/update_engine/payload_generator/bzip.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/payload_generator/bzip.h" + +#include <bzlib.h> +#include <stdlib.h> + +#include <algorithm> +#include <limits> + +#include "update_engine/common/utils.h" + +namespace chromeos_update_engine { + +bool BzipCompress(const brillo::Blob& in, brillo::Blob* out) { + TEST_AND_RETURN_FALSE(out); + out->clear(); + if (in.size() == 0) + return true; + + // We expect a compression ratio of about 35% with bzip2, so we start with + // that much output space, which will then be doubled if needed. + size_t buf_size = 40 + in.size() * 35 / 100; + out->resize(buf_size); + + // Try increasing buffer size until it works + for (;;) { + if (buf_size > std::numeric_limits<uint32_t>::max()) + return false; + uint32_t data_size = buf_size; + int rc = BZ2_bzBuffToBuffCompress( + reinterpret_cast<char*>(out->data()), + &data_size, + reinterpret_cast<char*>(const_cast<uint8_t*>(in.data())), + in.size(), + 9, // Best compression + 0, // Silent verbosity + 0); // Default work factor + 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 chromeos_update_engine
diff --git a/update_engine/payload_generator/bzip.h b/update_engine/payload_generator/bzip.h new file mode 100644 index 0000000..198768f --- /dev/null +++ b/update_engine/payload_generator/bzip.h
@@ -0,0 +1,29 @@ +// +// 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 <brillo/secure_blob.h> + +namespace chromeos_update_engine { + +// Compresses the input buffer |in| into |out| with bzip2. +bool BzipCompress(const brillo::Blob& in, brillo::Blob* out); + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_BZIP_H_
diff --git a/update_engine/payload_generator/cycle_breaker.cc b/update_engine/payload_generator/cycle_breaker.cc new file mode 100644 index 0000000..52a6f60 --- /dev/null +++ b/update_engine/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 = std::numeric_limits<uint64_t>::max(); + size_t edges_considered = 0; + for (vector<Vertex::Index>::const_iterator it = stack_.begin(); + it != (stack_.end() - 1); ++it) { + Edge edge = make_pair(*it, *(it + 1)); + if (cut_edges_.find(edge) != cut_edges_.end()) { + stack_.pop_back(); + return; + } + uint64_t edge_weight = graph_utils::EdgeWeight(subgraph_, edge); + if (edge_weight < min_edge_weight) { + min_edge_weight = edge_weight; + min_edge = edge; + } + edges_considered++; + if (edges_considered == kMaxEdgesToConsider) + break; + } + cut_edges_.insert(min_edge); + stack_.pop_back(); +} + +void CycleBreaker::Unblock(Vertex::Index u) { + blocked_[u] = false; + + for (Vertex::EdgeMap::iterator it = blocked_graph_[u].out_edges.begin(); + it != blocked_graph_[u].out_edges.end(); ) { + Vertex::Index w = it->first; + blocked_graph_[u].out_edges.erase(it++); + if (blocked_[w]) + Unblock(w); + } +} + +bool CycleBreaker::StackContainsCutEdge() const { + for (vector<Vertex::Index>::const_iterator it = ++stack_.begin(), + e = stack_.end(); it != e; ++it) { + Edge edge = make_pair(*(it - 1), *it); + if (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/update_engine/payload_generator/cycle_breaker.h b/update_engine/payload_generator/cycle_breaker.h new file mode 100644 index 0000000..231d63a --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/cycle_breaker_unittest.cc b/update_engine/payload_generator/cycle_breaker_unittest.cc new file mode 100644 index 0000000..e92bc30 --- /dev/null +++ b/update_engine/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(3U, broken_edges.size()); +} + +namespace { +pair<Vertex::Index, EdgeProperties> EdgeWithWeight(Vertex::Index dest, +uint64_t weight) { + EdgeProperties props; + props.extents.resize(1); + props.extents[0].set_num_blocks(weight); + return make_pair(dest, props); +} +} // namespace + + +// This creates a bunch of cycles like this: +// +// root <------. +// (t)-> / | \ | +// V V V | +// N N N | +// \ | / | +// VVV | +// N | +// / | \ | +// V V V | +// N N N | +// ... | +// (s)-> \ | / | +// VVV | +// N | +// \_________/ +// +// such that the original cutting algo would cut edges (s). We changed +// the algorithm to cut cycles (t) instead, since they are closer to the +// root, and that can massively speed up cycle cutting. +TEST(CycleBreakerTest, AggressiveCutTest) { + size_t counter = 0; + + const int kNodesPerGroup = 4; + const int kGroups = 33; + + Graph graph(kGroups * kNodesPerGroup + 1); // + 1 for the root node + SetOpForNodes(&graph); + + const Vertex::Index n_root = counter++; + + Vertex::Index last_hub = n_root; + for (int i = 0; i < kGroups; i++) { + uint64_t weight = 5; + if (i == 0) + weight = 2; + else if (i == (kGroups - 1)) + weight = 1; + + const Vertex::Index next_hub = counter++; + + for (int j = 0; j < (kNodesPerGroup - 1); j++) { + const Vertex::Index node = counter++; + graph[last_hub].out_edges.insert(EdgeWithWeight(node, weight)); + graph[node].out_edges.insert(EdgeWithWeight(next_hub, weight)); + } + last_hub = next_hub; + } + + graph[last_hub].out_edges.insert(EdgeWithWeight(n_root, 5)); + + EXPECT_EQ(counter, graph.size()); + + CycleBreaker breaker; + + set<Edge> broken_edges; + LOG(INFO) << "If this hangs for more than 1 second, the test has failed."; + breaker.BreakCycles(graph, &broken_edges); + + set<Edge> expected_cuts; + + for (Vertex::EdgeMap::const_iterator it = graph[n_root].out_edges.begin(), + e = graph[n_root].out_edges.end(); it != e; ++it) { + expected_cuts.insert(make_pair(n_root, it->first)); + } + + EXPECT_TRUE(broken_edges == expected_cuts); +} + +TEST(CycleBreakerTest, WeightTest) { + size_t counter = 0; + const Vertex::Index n_a = counter++; + const Vertex::Index n_b = counter++; + const Vertex::Index n_c = counter++; + const Vertex::Index n_d = counter++; + const Vertex::Index n_e = counter++; + const Vertex::Index n_f = counter++; + const Vertex::Index n_g = counter++; + const Vertex::Index n_h = counter++; + const Vertex::Index n_i = counter++; + const Vertex::Index n_j = counter++; + const Graph::size_type kNodeCount = counter++; + + Graph graph(kNodeCount); + SetOpForNodes(&graph); + + graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 4)); + graph[n_a].out_edges.insert(EdgeWithWeight(n_f, 3)); + graph[n_a].out_edges.insert(EdgeWithWeight(n_h, 2)); + graph[n_b].out_edges.insert(EdgeWithWeight(n_a, 3)); + graph[n_b].out_edges.insert(EdgeWithWeight(n_c, 4)); + graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 5)); + graph[n_c].out_edges.insert(EdgeWithWeight(n_d, 3)); + graph[n_d].out_edges.insert(EdgeWithWeight(n_a, 6)); + graph[n_d].out_edges.insert(EdgeWithWeight(n_e, 3)); + graph[n_e].out_edges.insert(EdgeWithWeight(n_d, 4)); + graph[n_e].out_edges.insert(EdgeWithWeight(n_g, 5)); + graph[n_f].out_edges.insert(EdgeWithWeight(n_g, 2)); + graph[n_g].out_edges.insert(EdgeWithWeight(n_f, 3)); + graph[n_g].out_edges.insert(EdgeWithWeight(n_d, 5)); + graph[n_h].out_edges.insert(EdgeWithWeight(n_i, 8)); + graph[n_i].out_edges.insert(EdgeWithWeight(n_e, 4)); + graph[n_i].out_edges.insert(EdgeWithWeight(n_h, 9)); + graph[n_i].out_edges.insert(EdgeWithWeight(n_j, 6)); + + CycleBreaker breaker; + + set<Edge> broken_edges; + breaker.BreakCycles(graph, &broken_edges); + + // These are required to be broken: + EXPECT_TRUE(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) { + size_t counter = 0; + const Vertex::Index n_a = counter++; + const Vertex::Index n_b = counter++; + const Vertex::Index n_c = counter++; + const Vertex::Index n_d = counter++; + const Graph::size_type kNodeCount = counter++; + + Graph graph(kNodeCount); + SetOpForNodes(&graph); + + graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 1)); + graph[n_a].out_edges.insert(EdgeWithWeight(n_c, 1)); + graph[n_b].out_edges.insert(EdgeWithWeight(n_c, 2)); + graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 2)); + graph[n_b].out_edges.insert(EdgeWithWeight(n_d, 2)); + graph[n_d].out_edges.insert(EdgeWithWeight(n_a, 2)); + + CycleBreaker breaker; + + set<Edge> broken_edges; + breaker.BreakCycles(graph, &broken_edges); + + // These are required to be broken: + EXPECT_TRUE(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) { + size_t counter = 0; + const Vertex::Index n_a = counter++; + const Vertex::Index n_b = counter++; + const Vertex::Index n_c = counter++; + const Graph::size_type kNodeCount = counter++; + + Graph graph(kNodeCount); + SetOpForNodes(&graph); + graph[n_a].aop.op.set_type(InstallOperation::REPLACE_BZ); + graph[n_c].aop.op.set_type(InstallOperation::REPLACE); + + graph[n_a].out_edges.insert(EdgeWithWeight(n_b, 1)); + graph[n_c].out_edges.insert(EdgeWithWeight(n_b, 1)); + + CycleBreaker breaker; + + set<Edge> broken_edges; + breaker.BreakCycles(graph, &broken_edges); + + EXPECT_EQ(2U, breaker.skipped_ops()); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/delta_diff_generator.cc b/update_engine/payload_generator/delta_diff_generator.cc new file mode 100644 index 0000000..a140d21 --- /dev/null +++ b/update_engine/payload_generator/delta_diff_generator.cc
@@ -0,0 +1,139 @@ +// +// 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) { + if (!config.version.Validate()) { + LOG(ERROR) << "Unsupported major.minor version: " << config.version.major + << "." << config.version.minor; + return false; + } + + // 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() && + !diff_utils::IsSquashfs4Filesystem(new_part.path)) { + // Delta update. + if (config.version.minor == kInPlaceMinorPayloadVersion) { + LOG(INFO) << "Using generator InplaceGenerator()."; + strategy.reset(new InplaceGenerator()); + } else { + LOG(INFO) << "Using generator ABGenerator()."; + strategy.reset(new ABGenerator()); + } + } 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/update_engine/payload_generator/delta_diff_generator.h b/update_engine/payload_generator/delta_diff_generator.h new file mode 100644 index 0000000..d8bdae2 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/delta_diff_utils.cc b/update_engine/payload_generator/delta_diff_utils.cc new file mode 100644 index 0000000..50fbdf2 --- /dev/null +++ b/update_engine/payload_generator/delta_diff_utils.cc
@@ -0,0 +1,836 @@ +// +// 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 <endian.h> +#include <ext2fs/ext2fs.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" +#include "update_engine/payload_generator/xz.h" + +using std::map; +using std::string; +using std::vector; + +namespace chromeos_update_engine { +namespace { + +const char* const kBsdiffPath = "bsdiff"; +const char* const kImgdiffPath = "imgdiff"; + +// 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 + +// The maximum destination size allowed for imgdiff. In general, imgdiff should +// work for arbitrary big files, but the payload application is quite memory +// intensive, so we limit these operations to 50 MiB. +const uint64_t kMaxImgdiffDestinationSize = 50 * 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; +} + +// Returns true if the given blob |data| contains gzip header magic. +bool ContainsGZip(const brillo::Blob& data) { + const uint8_t kGZipMagic[] = {0x1f, 0x8b, 0x08, 0x00}; + return std::search(data.begin(), + data.end(), + std::begin(kGZipMagic), + std::end(kGZipMagic)) != data.end(); +} + +} // 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, + const PayloadVersion& version, + BlobFileWriter* blob_file) { + ExtentRanges old_visited_blocks; + ExtentRanges new_visited_blocks; + + TEST_AND_RETURN_FALSE(DeltaMovedAndZeroBlocks( + aops, + old_part.path, + new_part.path, + old_part.size / kBlockSize, + new_part.size / kBlockSize, + soft_chunk_blocks, + version, + 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, + version, + blob_file)); + } + // 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, + version, + blob_file)); + + 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, + const PayloadVersion& version, + 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)); + + // If the update is inplace, we map all the blocks that didn't move, + // regardless of the contents since they are already copied and no operation + // is required. + if (version.InplaceUpdate()) { + uint64_t num_blocks = std::min(old_num_blocks, new_num_blocks); + for (uint64_t block = 0; block < num_blocks; block++) { + if (old_block_ids[block] == new_block_ids[block] && + !old_visited_blocks->ContainsBlock(block) && + !new_visited_blocks->ContainsBlock(block)) { + old_visited_blocks->AddBlock(block); + new_visited_blocks->AddBlock(block); + } + } + } + + // A mapping from the block_id to the list of block numbers with that block id + // in the old partition. This is used to lookup where in the old partition + // is a block from the new partition. + 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); + + // Mark all zeroed blocks in the old image as "used" since it doesn't make + // any sense to spend I/O to read zeros from the source partition and more + // importantly, these could sometimes be blocks discarded in the SSD which + // would read non-zero values. + if (old_block_ids[block] == 0) + old_visited_blocks->AddBlock(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 used in the in-place update doesn't support that. + if (version.InplaceUpdate()) + old_blocks_map_it->second.pop_back(); + } + + // Produce operations for the zero blocks split per output extent. + // TODO(deymo): Produce ZERO operations instead of calling DeltaReadFile(). + size_t num_ops = aops->size(); + new_visited_blocks->AddExtents(new_zeros); + for (const Extent& extent : new_zeros) { + TEST_AND_RETURN_FALSE(DeltaReadFile(aops, + "", + new_part, + vector<Extent>(), // old_extents + vector<Extent>{extent}, // new_extents + "<zeros>", + chunk_blocks, + version, + blob_file)); + } + 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 (const 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(version.OperationAllowed(InstallOperation::SOURCE_COPY) + ? InstallOperation::SOURCE_COPY + : InstallOperation::MOVE); + + uint64_t chunk_num_blocks = + std::min(static_cast<uint64_t>(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, + const PayloadVersion& version, + BlobFileWriter* blob_file) { + brillo::Blob data; + InstallOperation operation; + + uint64_t total_blocks = BlocksInExtents(new_extents); + if (chunk_blocks == -1) + chunk_blocks = total_blocks; + + 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, + version, + &data, + &operation)); + + // 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 + TEST_AND_RETURN_FALSE(aop.SetOperationBlob(data, blob_file)); + aops->emplace_back(aop); + } + return true; +} + +bool GenerateBestFullOperation(const brillo::Blob& new_data, + const PayloadVersion& version, + brillo::Blob* out_blob, + InstallOperation_Type* out_type) { + if (new_data.empty()) + return false; + + if (version.OperationAllowed(InstallOperation::ZERO) && + std::all_of( + new_data.begin(), new_data.end(), [](uint8_t x) { return x == 0; })) { + // The read buffer is all zeros, so produce a ZERO operation. No need to + // check other types of operations in this case. + *out_blob = brillo::Blob(); + *out_type = InstallOperation::ZERO; + return true; + } + + bool out_blob_set = false; + + // Try compressing |new_data| with xz first. + if (version.OperationAllowed(InstallOperation::REPLACE_XZ)) { + brillo::Blob new_data_xz; + if (XzCompress(new_data, &new_data_xz) && !new_data_xz.empty()) { + *out_type = InstallOperation::REPLACE_XZ; + *out_blob = std::move(new_data_xz); + out_blob_set = true; + } + } + + // Try compressing it with bzip2. + if (version.OperationAllowed(InstallOperation::REPLACE_BZ)) { + brillo::Blob new_data_bz; + // TODO(deymo): Implement some heuristic to determine if it is worth trying + // to compress the blob with bzip2 if we already have a good REPLACE_XZ. + if (BzipCompress(new_data, &new_data_bz) && !new_data_bz.empty() && + (!out_blob_set || out_blob->size() > new_data_bz.size())) { + // A REPLACE_BZ is better or nothing else was set. + *out_type = InstallOperation::REPLACE_BZ; + *out_blob = std::move(new_data_bz); + out_blob_set = true; + } + } + + // If nothing else worked or it was badly compressed we try a REPLACE. + if (!out_blob_set || out_blob->size() >= new_data.size()) { + *out_type = InstallOperation::REPLACE; + // This needs to make a copy of the data in the case bzip or xz didn't + // compress well, which is not the common case so the performance hit is + // low. + *out_blob = new_data; + } + return true; +} + +bool ReadExtentsToDiff(const string& old_part, + const string& new_part, + const vector<Extent>& old_extents, + const vector<Extent>& new_extents, + const PayloadVersion& version, + brillo::Blob* out_data, + InstallOperation* out_op) { + InstallOperation operation; + + // 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); + + // Disable bsdiff and imgdiff when the data is too big. + bool bsdiff_allowed = + version.OperationAllowed(InstallOperation::SOURCE_BSDIFF) || + version.OperationAllowed(InstallOperation::BSDIFF); + if (bsdiff_allowed && + blocks_to_read * kBlockSize > kMaxBsdiffDestinationSize) { + LOG(INFO) << "bsdiff blacklisted, data too big: " + << blocks_to_read * kBlockSize << " bytes"; + bsdiff_allowed = false; + } + + bool imgdiff_allowed = version.OperationAllowed(InstallOperation::IMGDIFF); + if (imgdiff_allowed && + blocks_to_read * kBlockSize > kMaxImgdiffDestinationSize) { + LOG(INFO) << "imgdiff blacklisted, data too big: " + << blocks_to_read * kBlockSize << " bytes"; + imgdiff_allowed = false; + } + + // 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()); + + // Data blob that will be written to delta file. + brillo::Blob data_blob; + + // Try generating a full operation for the given new data, regardless of the + // old_data. + InstallOperation_Type op_type; + TEST_AND_RETURN_FALSE( + GenerateBestFullOperation(new_data, version, &data_blob, &op_type)); + operation.set_type(op_type); + + brillo::Blob old_data; + 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. + operation.set_type(version.OperationAllowed(InstallOperation::SOURCE_COPY) + ? InstallOperation::SOURCE_COPY + : InstallOperation::MOVE); + data_blob = brillo::Blob(); + } else if (bsdiff_allowed || imgdiff_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())); + + if (bsdiff_allowed) { + brillo::Blob bsdiff_delta; + TEST_AND_RETURN_FALSE(DiffFiles( + kBsdiffPath, 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()) { + operation.set_type( + version.OperationAllowed(InstallOperation::SOURCE_BSDIFF) + ? InstallOperation::SOURCE_BSDIFF + : InstallOperation::BSDIFF); + data_blob = std::move(bsdiff_delta); + } + } + if (imgdiff_allowed && ContainsGZip(old_data) && ContainsGZip(new_data)) { + brillo::Blob imgdiff_delta; + // Imgdiff might fail in some cases, only use the result if it succeed, + // otherwise print the extents to analyze. + if (DiffFiles(kImgdiffPath, + old_chunk.value(), + new_chunk.value(), + &imgdiff_delta) && + imgdiff_delta.size() > 0) { + if (imgdiff_delta.size() < data_blob.size()) { + operation.set_type(InstallOperation::IMGDIFF); + data_blob = std::move(imgdiff_delta); + } + } else { + LOG(ERROR) << "Imgdiff failed with source extents: " + << ExtentsToString(src_extents) + << ", destination extents: " + << ExtentsToString(dst_extents); + } + } + } + } + + 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 (IsAReplaceOperation(operation.type())) { + operation.clear_src_extents(); + operation.clear_src_length(); + } + + *out_data = std::move(data_blob); + *out_op = operation; + + return true; +} + +// Runs the bsdiff or imgdiff tool in |diff_path| on two files and returns the +// resulting delta in |out|. Returns true on success. +bool DiffFiles(const string& diff_path, + 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(diff_path); + cmd.push_back(old_file); + cmd.push_back(new_file); + cmd.push_back(patch_file_path); + + int rc = 1; + string stdout; + TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &rc, &stdout)); + if (rc != 0) { + LOG(ERROR) << diff_path << " returned " << rc << std::endl << stdout; + return false; + } + TEST_AND_RETURN_FALSE(utils::ReadFile(patch_file_path, out)); + unlink(patch_file_path.c_str()); + return true; +} + +bool IsAReplaceOperation(InstallOperation_Type op_type) { + return (op_type == InstallOperation::REPLACE || + op_type == InstallOperation::REPLACE_BZ || + op_type == InstallOperation::REPLACE_XZ); +} + +// 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; +} + +bool IsExtFilesystem(const string& device) { + brillo::Blob header; + // 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 (!utils::ReadFileChunk( + device, 0, SUPERBLOCK_OFFSET + SUPERBLOCK_SIZE, &header) || + header.size() < SUPERBLOCK_OFFSET + SUPERBLOCK_SIZE) + return false; + + const uint8_t* superblock = header.data() + 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); + + if (magic != EXT2_SUPER_MAGIC) + return false; + + // Sanity check the parameters. + TEST_AND_RETURN_FALSE(log_block_size >= EXT2_MIN_BLOCK_LOG_SIZE && + log_block_size <= EXT2_MAX_BLOCK_LOG_SIZE); + TEST_AND_RETURN_FALSE(block_count > 0); + return true; +} + +bool IsSquashfs4Filesystem(const string& device) { + brillo::Blob header; + // See fs/squashfs/squashfs_fs.h for format details. We only support + // Squashfs 4.x little endian. + + // The first 96 is enough to read the squashfs superblock. + const ssize_t kSquashfsSuperBlockSize = 96; + if (!utils::ReadFileChunk(device, 0, kSquashfsSuperBlockSize, &header) || + header.size() < kSquashfsSuperBlockSize) + return false; + + // Check magic, squashfs_fs.h: SQUASHFS_MAGIC + if (memcmp(header.data(), "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*>( + header.data() + 5 * sizeof(uint32_t) + 4 * sizeof(uint16_t)); + + if (s_major != 4) { + LOG(ERROR) << "Found unsupported squashfs major version " << s_major; + return false; + } + return true; +} + +} // namespace diff_utils + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/delta_diff_utils.h b/update_engine/payload_generator/delta_diff_utils.h new file mode 100644 index 0000000..4cc85fc --- /dev/null +++ b/update_engine/payload_generator/delta_diff_utils.h
@@ -0,0 +1,155 @@ +// +// 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 |blob_file|. |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, + const PayloadVersion& version, + BlobFileWriter* blob_file); + +// 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 |blob_file|. +// 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, + const PayloadVersion& version, + 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 |blob_file|. 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, + const PayloadVersion& version, + BlobFileWriter* blob_file); + +// 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 or SOURCE_COPY operation. If there is a change, the smallest of the +// operations allowed in the given |version| (REPLACE, REPLACE_BZ, BSDIFF, +// SOURCE_BSDIFF or IMGDIFF) wins. +// |new_extents| must not be empty. 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, + const PayloadVersion& version, + brillo::Blob* out_data, + InstallOperation* out_op); + +// Runs the bsdiff or imgdiff tool in |diff_path| on two files and returns the +// resulting delta in |out|. Returns true on success. +bool DiffFiles(const std::string& diff_path, + const std::string& old_file, + const std::string& new_file, + brillo::Blob* out); + +// Generates the best allowed full operation to produce |new_data|. The allowed +// operations are based on |payload_version|. The operation blob will be stored +// in |out_blob| and the resulting operation type in |out_type|. Returns whether +// a valid full operation was generated. +bool GenerateBestFullOperation(const brillo::Blob& new_data, + const PayloadVersion& version, + brillo::Blob* out_blob, + InstallOperation_Type* out_type); + +// Returns whether op_type is one of the REPLACE full operations. +bool IsAReplaceOperation(InstallOperation_Type op_type); + +// Returns true if |op| is a no-op operation that doesn't do any useful work +// (e.g., a move operation that copies blocks onto themselves). +bool IsNoopOperation(const InstallOperation& op); + +// Filters all the operations that are no-op, maintaining the relative order +// of the rest of the operations. +void FilterNoopOperations(std::vector<AnnotatedOperation>* ops); + +bool InitializePartitionInfo(const PartitionConfig& partition, + PartitionInfo* info); + +// Compare two AnnotatedOperations by the start block of the first Extent in +// their destination extents. +bool CompareAopsByDestination(AnnotatedOperation first_aop, + AnnotatedOperation second_aop); + +// 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 squashfs4 filesystem. In case of failure, +// such as if the file |device| doesn't exists or can't be read, it returns +// false. +bool IsSquashfs4Filesystem(const std::string& device); + +} // namespace diff_utils + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_DELTA_DIFF_UTILS_H_
diff --git a/update_engine/payload_generator/delta_diff_utils_unittest.cc b/update_engine/payload_generator/delta_diff_utils_unittest.cc new file mode 100644 index 0000000..7044b95 --- /dev/null +++ b/update_engine/payload_generator/delta_diff_utils_unittest.cc
@@ -0,0 +1,773 @@ +// +// 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 <random> +#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 { + +// 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 +}; + +// 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(static_cast<uint64_t>(extent.num_blocks()) * block_size, + static_cast<uint64_t>(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 avoids repeating all the arguments that never change. + bool RunDeltaMovedAndZeroBlocks(ssize_t chunk_blocks, + uint32_t minor_version) { + BlobFileWriter blob_file(blob_fd_, &blob_size_); + PayloadVersion version(kChromeOSMajorPayloadVersion, minor_version); + version.imgdiff_allowed = true; // Assume no fingerprint mismatch. + return diff_utils::DeltaMovedAndZeroBlocks(&aops_, + old_part_.path, + new_part_.path, + old_part_.size / block_size_, + new_part_.size / block_size_, + chunk_blocks, + version, + &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, + PayloadVersion(kChromeOSMajorPayloadVersion, kInPlaceMinorPayloadVersion), + &data, + &op)); + EXPECT_TRUE(data.empty()); + + EXPECT_TRUE(op.has_type()); + EXPECT_EQ(InstallOperation::MOVE, op.type()); + EXPECT_FALSE(op.has_data_offset()); + EXPECT_FALSE(op.has_data_length()); + EXPECT_EQ(1, op.src_extents_size()); + EXPECT_EQ(kBlockSize, op.src_length()); + EXPECT_EQ(1, op.dst_extents_size()); + EXPECT_EQ(kBlockSize, op.dst_length()); + EXPECT_EQ(BlocksInExtents(op.src_extents()), + BlocksInExtents(op.dst_extents())); + EXPECT_EQ(1U, 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, + PayloadVersion(kChromeOSMajorPayloadVersion, kInPlaceMinorPayloadVersion), + &data, + &op)); + + EXPECT_TRUE(data.empty()); + + EXPECT_TRUE(op.has_type()); + EXPECT_EQ(InstallOperation::MOVE, op.type()); + EXPECT_FALSE(op.has_data_offset()); + EXPECT_FALSE(op.has_data_length()); + + // The expected old and new extents that actually moved. See comment above. + old_extents = { + ExtentForRange(20, 1), + ExtentForRange(23, 1), + ExtentForRange(28, 1) }; + new_extents = { + ExtentForRange(18, 1), + ExtentForRange(20, 1), + ExtentForRange(26, 1) }; + num_blocks = BlocksInExtents(old_extents); + + EXPECT_EQ(num_blocks * kBlockSize, op.src_length()); + EXPECT_EQ(num_blocks * kBlockSize, op.dst_length()); + + EXPECT_EQ(old_extents.size(), static_cast<size_t>(op.src_extents_size())); + for (int i = 0; i < op.src_extents_size(); i++) { + EXPECT_EQ(old_extents[i].start_block(), op.src_extents(i).start_block()) + << "i == " << i; + EXPECT_EQ(old_extents[i].num_blocks(), op.src_extents(i).num_blocks()) + << "i == " << i; + } + + EXPECT_EQ(new_extents.size(), static_cast<size_t>(op.dst_extents_size())); + for (int i = 0; i < op.dst_extents_size(); i++) { + EXPECT_EQ(new_extents[i].start_block(), op.dst_extents(i).start_block()) + << "i == " << i; + EXPECT_EQ(new_extents[i].num_blocks(), op.dst_extents(i).num_blocks()) + << "i == " << i; + } +} + +TEST_F(DeltaDiffUtilsTest, BsdiffSmallTest) { + // Test a BSDIFF operation from block 1 to block 2. + brillo::Blob data_blob(kBlockSize); + test_utils::FillWithData(&data_blob); + + // The old file is on a different block than the new one. + vector<Extent> old_extents = { ExtentForRange(1, 1) }; + vector<Extent> new_extents = { ExtentForRange(2, 1) }; + + EXPECT_TRUE(WriteExtents(old_part_.path, old_extents, kBlockSize, data_blob)); + // Modify one byte in the new file. + data_blob[0]++; + EXPECT_TRUE(WriteExtents(new_part_.path, new_extents, kBlockSize, data_blob)); + + brillo::Blob data; + InstallOperation op; + EXPECT_TRUE(diff_utils::ReadExtentsToDiff( + old_part_.path, + new_part_.path, + old_extents, + new_extents, + PayloadVersion(kChromeOSMajorPayloadVersion, kInPlaceMinorPayloadVersion), + &data, + &op)); + + EXPECT_FALSE(data.empty()); + + EXPECT_TRUE(op.has_type()); + EXPECT_EQ(InstallOperation::BSDIFF, op.type()); + EXPECT_FALSE(op.has_data_offset()); + EXPECT_FALSE(op.has_data_length()); + EXPECT_EQ(1, op.src_extents_size()); + EXPECT_EQ(kBlockSize, op.src_length()); + EXPECT_EQ(1, op.dst_extents_size()); + EXPECT_EQ(kBlockSize, op.dst_length()); + EXPECT_EQ(BlocksInExtents(op.src_extents()), + BlocksInExtents(op.dst_extents())); + EXPECT_EQ(1U, BlocksInExtents(op.dst_extents())); +} + +TEST_F(DeltaDiffUtilsTest, ReplaceSmallTest) { + // The old file is on a different block than the new one. + vector<Extent> old_extents = { ExtentForRange(1, 1) }; + 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, + PayloadVersion(kChromeOSMajorPayloadVersion, + kInPlaceMinorPayloadVersion), + &data, + &op)); + 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(1U, 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, + PayloadVersion(kChromeOSMajorPayloadVersion, kSourceMinorPayloadVersion), + &data, + &op)); + 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, + PayloadVersion(kChromeOSMajorPayloadVersion, kSourceMinorPayloadVersion), + &data, + &op)); + + 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 + kInPlaceMinorPayloadVersion)); + + EXPECT_EQ(0U, old_visited_blocks_.blocks()); + EXPECT_EQ(0U, 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 + kInPlaceMinorPayloadVersion)); + + EXPECT_EQ(kDefaultBlockCount, old_visited_blocks_.blocks()); + EXPECT_EQ(kDefaultBlockCount, new_visited_blocks_.blocks()); + EXPECT_EQ(0, blob_size_); + EXPECT_TRUE(aops_.empty()); +} + +// Test that when the partitions have identical blocks in the same positions +// MOVE operation is performed and all the blocks are handled. +TEST_F(DeltaDiffUtilsTest, IdenticalBlocksAreCopiedFromSource) { + // 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 + kSourceMinorPayloadVersion)); + + 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 + kSourceMinorPayloadVersion)); + + // 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(1U, 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 + kInPlaceMinorPayloadVersion)); + + // 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 + kSourceMinorPayloadVersion)); + + 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(1U, 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_); +} + +TEST_F(DeltaDiffUtilsTest, IsExtFilesystemTest) { + EXPECT_TRUE(diff_utils::IsExtFilesystem( + test_utils::GetBuildArtifactsPath("gen/disk_ext2_1k.img"))); + EXPECT_TRUE(diff_utils::IsExtFilesystem( + test_utils::GetBuildArtifactsPath("gen/disk_ext2_4k.img"))); +} + +TEST_F(DeltaDiffUtilsTest, IsSquashfs4FilesystemTest) { + uint8_t buffer[sizeof(kSquashfsFile)]; + memcpy(buffer, kSquashfsFile, sizeof(kSquashfsFile)); + string img; + EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr)); + ScopedPathUnlinker img_unlinker(img); + + // Not enough bytes passed. + EXPECT_TRUE(utils::WriteFile(img.c_str(), buffer, 10)); + EXPECT_FALSE(diff_utils::IsSquashfs4Filesystem(img)); + + // The whole file system is passed, which is enough for parsing. + EXPECT_TRUE(utils::WriteFile(img.c_str(), buffer, sizeof(kSquashfsFile))); + EXPECT_TRUE(diff_utils::IsSquashfs4Filesystem(img)); + + // Modify the major version to 5. + uint16_t* s_major = reinterpret_cast<uint16_t*>(buffer + 0x1c); + *s_major = 5; + EXPECT_TRUE(utils::WriteFile(img.c_str(), buffer, sizeof(kSquashfsFile))); + EXPECT_FALSE(diff_utils::IsSquashfs4Filesystem(img)); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/ext2_filesystem.cc b/update_engine/payload_generator/ext2_filesystem.cc new file mode 100644 index 0000000..b452b41 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/ext2_filesystem.h b/update_engine/payload_generator/ext2_filesystem.h new file mode 100644 index 0000000..248e208 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/ext2_filesystem_unittest.cc b/update_engine/payload_generator/ext2_filesystem_unittest.cc new file mode 100644 index 0000000..a3c7731 --- /dev/null +++ b/update_engine/payload_generator/ext2_filesystem_unittest.cc
@@ -0,0 +1,204 @@ +// +// 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::GetBuildArtifactsPath; +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(0U, extent.start_block()); + EXPECT_LE(extent.start_block() + extent.num_blocks(), total_blocks); + } +} + +} // namespace + +class Ext2FilesystemTest : public ::testing::Test {}; + +TEST_F(Ext2FilesystemTest, InvalidFilesystem) { + test_utils::ScopedTempFile fs_filename_{"Ext2FilesystemTest-XXXXXX"}; + ASSERT_EQ(0, truncate(fs_filename_.path().c_str(), kDefaultFilesystemSize)); + unique_ptr<Ext2Filesystem> fs = + Ext2Filesystem::CreateFromFile(fs_filename_.path()); + ASSERT_EQ(nullptr, fs.get()); + + fs = Ext2Filesystem::CreateFromFile("/path/to/invalid/file"); + ASSERT_EQ(nullptr, fs.get()); +} + +TEST_F(Ext2FilesystemTest, EmptyFilesystem) { + unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile( + GetBuildArtifactsPath("gen/disk_ext2_4k_empty.img")); + + 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(2U, 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 = 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 = { + "/", + "/cdev", + "/dir1", + "/dir1/file", + "/dir1/dir2", + "/dir1/dir2/file", + "/dir1/dir2/dir1", + "/empty-file", + "/fifo", + "/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(1U, 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(1U, BlocksInExtents(map_files["/regular-small"].extents)); + EXPECT_EQ(1U, 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(1U, BlocksInExtents(map_files["/sparse-16k-last_block"].extents)); + EXPECT_EQ(1U, + BlocksInExtents(map_files["/sparse-16k-first_block"].extents)); + EXPECT_EQ(2U, BlocksInExtents(map_files["/sparse-16k-holes"].extents)); + } +} + +TEST_F(Ext2FilesystemTest, LoadSettingsFailsTest) { + unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile( + GetBuildArtifactsPath("gen/disk_ext2_1k.img")); + ASSERT_NE(nullptr, fs.get()); + + 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) { + unique_ptr<Ext2Filesystem> fs = Ext2Filesystem::CreateFromFile( + GetBuildArtifactsPath("gen/disk_ext2_unittest.img")); + ASSERT_NE(nullptr, fs.get()); + + 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/update_engine/payload_generator/extent_ranges.cc b/update_engine/payload_generator/extent_ranges.cc new file mode 100644 index 0000000..848fdc7 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/extent_ranges.h b/update_engine/payload_generator/extent_ranges.h new file mode 100644 index 0000000..198c834 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/extent_ranges_unittest.cc b/update_engine/payload_generator/extent_ranges_unittest.cc new file mode 100644 index 0000000..3705bac --- /dev/null +++ b/update_engine/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(40U, 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/update_engine/payload_generator/extent_utils.cc b/update_engine/payload_generator/extent_utils.cc new file mode 100644 index 0000000..89ccca2 --- /dev/null +++ b/update_engine/payload_generator/extent_utils.cc
@@ -0,0 +1,166 @@ +// +// 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 <inttypes.h> + +#include <string> +#include <utility> +#include <vector> + +#include <base/logging.h> +#include <base/macros.h> +#include <base/strings/stringprintf.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)); + } +} + +string ExtentsToString(const vector<Extent>& extents) { + string ext_str; + for (const Extent& e : extents) + ext_str += base::StringPrintf("[%" PRIu64 ", %" PRIu64 "] ", + static_cast<uint64_t>(e.start_block()), + static_cast<uint64_t>(e.num_blocks())); + return ext_str; +} + +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/update_engine/payload_generator/extent_utils.h b/update_engine/payload_generator/extent_utils.h new file mode 100644 index 0000000..3e45264 --- /dev/null +++ b/update_engine/payload_generator/extent_utils.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_EXTENT_UTILS_H_ +#define UPDATE_ENGINE_PAYLOAD_GENERATOR_EXTENT_UTILS_H_ + +#include <string> +#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); + +// Returns a string representing all extents in |extents|. +std::string ExtentsToString(const std::vector<Extent>& extents); + +// 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/update_engine/payload_generator/extent_utils_unittest.cc b/update_engine/payload_generator/extent_utils_unittest.cc new file mode 100644 index 0000000..d470e7b --- /dev/null +++ b/update_engine/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(0U, extents.size()); + AppendBlockToExtents(&extents, kSparseHole); + EXPECT_EQ(1U, extents.size()); + AppendBlockToExtents(&extents, 0); + EXPECT_EQ(2U, extents.size()); + AppendBlockToExtents(&extents, kSparseHole); + AppendBlockToExtents(&extents, kSparseHole); + + ASSERT_EQ(3U, extents.size()); + EXPECT_EQ(kSparseHole, extents[0].start_block()); + EXPECT_EQ(1U, extents[0].num_blocks()); + EXPECT_EQ(0U, extents[1].start_block()); + EXPECT_EQ(1U, extents[1].num_blocks()); + EXPECT_EQ(kSparseHole, extents[2].start_block()); + EXPECT_EQ(2U, extents[2].num_blocks()); +} + +TEST(ExtentUtilsTest, BlocksInExtentsTest) { + { + vector<Extent> extents; + EXPECT_EQ(0U, BlocksInExtents(extents)); + extents.push_back(ExtentForRange(0, 1)); + EXPECT_EQ(1U, BlocksInExtents(extents)); + extents.push_back(ExtentForRange(23, 55)); + EXPECT_EQ(56U, BlocksInExtents(extents)); + extents.push_back(ExtentForRange(1, 2)); + EXPECT_EQ(58U, BlocksInExtents(extents)); + } + { + google::protobuf::RepeatedPtrField<Extent> extents; + EXPECT_EQ(0U, BlocksInExtents(extents)); + *extents.Add() = ExtentForRange(0, 1); + EXPECT_EQ(1U, BlocksInExtents(extents)); + *extents.Add() = ExtentForRange(23, 55); + EXPECT_EQ(56U, BlocksInExtents(extents)); + *extents.Add() = ExtentForRange(1, 2); + EXPECT_EQ(58U, 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(0U, extents.size()); + + extents = { ExtentForRange(0, 3) }; + NormalizeExtents(&extents); + EXPECT_EQ(1U, 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(3U, 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/update_engine/payload_generator/fake_filesystem.cc b/update_engine/payload_generator/fake_filesystem.cc new file mode 100644 index 0000000..234e2f6 --- /dev/null +++ b/update_engine/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(0U, 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/update_engine/payload_generator/fake_filesystem.h b/update_engine/payload_generator/fake_filesystem.h new file mode 100644 index 0000000..1b13920 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/filesystem_interface.h b/update_engine/payload_generator/filesystem_interface.h new file mode 100644 index 0000000..866c46b --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/full_update_generator.cc b/update_engine/payload_generator/full_update_generator.cc new file mode 100644 index 0000000..8fdb6ec --- /dev/null +++ b/update_engine/payload_generator/full_update_generator.cc
@@ -0,0 +1,205 @@ +// +// 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/delta_diff_utils.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(const PayloadVersion& version, + int fd, + off_t offset, + size_t size, + BlobFileWriter* blob_file, + AnnotatedOperation* aop) + : version_(version), + 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. + const PayloadVersion& version_; + 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_)); + + InstallOperation_Type op_type; + TEST_AND_RETURN_FALSE(diff_utils::GenerateBestFullOperation( + buffer_in_, version_, &op_blob, &op_type)); + + aop_->op.set_type(op_type); + TEST_AND_RETURN_FALSE(aop_->SetOperationBlob(op_blob, blob_file_)); + 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( + config.version, + 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/update_engine/payload_generator/full_update_generator.h b/update_engine/payload_generator/full_update_generator.h new file mode 100644 index 0000000..d722028 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/full_update_generator_unittest.cc b/update_engine/payload_generator/full_update_generator_unittest.cc new file mode 100644 index 0000000..9e62de2 --- /dev/null +++ b/update_engine/payload_generator/full_update_generator_unittest.cc
@@ -0,0 +1,144 @@ +// +// 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_.version.minor = 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, static_cast<int64_t>(aops.size())); + for (off_t i = 0; i < new_part_chunks; ++i) { + EXPECT_EQ(1, aops[i].op.dst_extents_size()); + EXPECT_EQ( + static_cast<uint64_t>(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(2U, 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(1U, aops.size()); + EXPECT_EQ(new_part.size() / config_.block_size, + BlocksInExtents(aops[0].op.dst_extents())); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/generate_delta_main.cc b/update_engine/payload_generator/generate_delta_main.cc new file mode 100644 index 0000000..99af679 --- /dev/null +++ b/update_engine/payload_generator/generate_delta_main.cc
@@ -0,0 +1,587 @@ +// +// 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/payload_generator/xz.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, ":", base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL); + 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, ":", base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL); + 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 ExtractProperties(const string& payload_path, const string& props_file) { + brillo::KeyValueStore properties; + TEST_AND_RETURN_FALSE( + PayloadSigner::ExtractPayloadProperties(payload_path, &properties)); + if (props_file == "-") { + printf("%s", properties.SaveToString().c_str()); + } else { + properties.Save(base::FilePath(props_file)); + LOG(INFO) << "Generated properties file at " << props_file; + } + return true; +} + +int Main(int argc, char** argv) { + DEFINE_string(old_image, "", "Path to the old rootfs"); + DEFINE_string(new_image, "", "Path to the new rootfs"); + 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(properties_file, "", + "If passed, dumps the payload properties of the payload passed " + "in --in_file and exits."); + DEFINE_string(zlib_fingerprint, "", + "The fingerprint of zlib in the source image in hash string " + "format, used to check imgdiff compatibility."); + + 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); + + // Initialize the Xz compressor. + XzCompressInit(); + + 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_properties_file.empty()) { + return ExtractProperties(FLAGS_in_file, FLAGS_properties_file) ? 0 : 1; + } + 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; + + partition_names = + base::SplitString(FLAGS_partition_names, ":", base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL); + CHECK(!partition_names.empty()); + if (FLAGS_major_version == kChromeOSMajorPayloadVersion || + FLAGS_new_partitions.empty()) { + LOG_IF(FATAL, partition_names.size() != 2) + << "To support more than 2 partitions, please use the " + << "--new_partitions flag and major version 2."; + LOG_IF(FATAL, partition_names[0] != 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."; + new_partitions = + base::SplitString(FLAGS_new_partitions, ":", base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL); + 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()) { + old_partitions = + base::SplitString(FLAGS_old_partitions, ":", base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL); + 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.version.major = 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.version.minor = 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.version.minor = minor_version; + break; + } + } + } else { + payload_config.version.minor = kFullPayloadMinorVersion; + } + LOG(INFO) << "Auto-detected minor_version=" << payload_config.version.minor; + } else { + payload_config.version.minor = FLAGS_minor_version; + LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version; + } + + if (!FLAGS_zlib_fingerprint.empty()) { + if (utils::IsZlibCompatible(FLAGS_zlib_fingerprint)) { + payload_config.version.imgdiff_allowed = true; + } else { + LOG(INFO) << "IMGDIFF operation disabled due to fingerprint mismatch."; + } + } + + 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/update_engine/payload_generator/graph_types.cc b/update_engine/payload_generator/graph_types.cc new file mode 100644 index 0000000..7da76f7 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/graph_types.h b/update_engine/payload_generator/graph_types.h new file mode 100644 index 0000000..fee8575 --- /dev/null +++ b/update_engine/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; +static_assert(kTempBlockStart != 0, "kTempBlockStart invalid"); + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_GRAPH_TYPES_H_
diff --git a/update_engine/payload_generator/graph_utils.cc b/update_engine/payload_generator/graph_utils.cc new file mode 100644 index 0000000..2d5fb63 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/graph_utils.h b/update_engine/payload_generator/graph_utils.h new file mode 100644 index 0000000..b32e666 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/graph_utils_unittest.cc b/update_engine/payload_generator/graph_utils_unittest.cc new file mode 100644 index 0000000..dddf815 --- /dev/null +++ b/update_engine/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(0U, extents.size()); + AppendBlockToExtents(&extents, 0); + EXPECT_EQ(1U, extents.size()); + AppendBlockToExtents(&extents, 1); + AppendBlockToExtents(&extents, 2); + EXPECT_EQ(1U, extents.size()); + AppendBlockToExtents(&extents, 4); + + EXPECT_EQ(2U, extents.size()); + EXPECT_EQ(0U, extents[0].start_block()); + EXPECT_EQ(3U, extents[0].num_blocks()); + EXPECT_EQ(4U, extents[1].start_block()); + EXPECT_EQ(1U, extents[1].num_blocks()); + + EXPECT_EQ(4U, graph_utils::EdgeWeight(graph, make_pair(0, 1))); +} + + +TEST(GraphUtilsTest, DepsTest) { + Graph graph(3); + + graph_utils::AddReadBeforeDep(&graph[0], 1, 3); + EXPECT_EQ(1U, graph[0].out_edges.size()); + { + Extent& extent = graph[0].out_edges[1].extents[0]; + EXPECT_EQ(3U, extent.start_block()); + EXPECT_EQ(1U, extent.num_blocks()); + } + graph_utils::AddReadBeforeDep(&graph[0], 1, 4); + EXPECT_EQ(1U, graph[0].out_edges.size()); + { + Extent& extent = graph[0].out_edges[1].extents[0]; + EXPECT_EQ(3U, extent.start_block()); + EXPECT_EQ(2U, extent.num_blocks()); + } + graph_utils::AddReadBeforeDepExtents(&graph[2], 1, + vector<Extent>(1, ExtentForRange(5, 2))); + EXPECT_EQ(1U, graph[2].out_edges.size()); + { + Extent& extent = graph[2].out_edges[1].extents[0]; + EXPECT_EQ(5U, extent.start_block()); + EXPECT_EQ(2U, extent.num_blocks()); + } + // Change most recent edge from read-before to write-before + graph[2].out_edges[1].write_extents.swap(graph[2].out_edges[1].extents); + graph_utils::DropWriteBeforeDeps(&graph[2].out_edges); + EXPECT_EQ(0U, graph[2].out_edges.size()); + + EXPECT_EQ(1U, graph[0].out_edges.size()); + graph_utils::DropIncomingEdgesTo(&graph, 1); + EXPECT_EQ(0U, graph[0].out_edges.size()); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/inplace_generator.cc b/update_engine/payload_generator/inplace_generator.cc new file mode 100644 index 0000000..bc140e8 --- /dev/null +++ b/update_engine/payload_generator/inplace_generator.cc
@@ -0,0 +1,821 @@ +// +// 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 { + +// The only PayloadVersion supported by this implementation. +const PayloadVersion kInPlacePayloadVersion{kChromeOSMajorPayloadVersion, + kInPlaceMinorPayloadVersion}; + +// This class allocates non-existent temp blocks, starting from +// kTempBlockStart. Other code is responsible for converting these +// temp blocks into real blocks, as the client can't read or write to +// these blocks. +class DummyExtentAllocator { + public: + vector<Extent> Allocate(const uint64_t block_count) { + vector<Extent> ret(1); + ret[0].set_start_block(next_block_); + ret[0].set_num_blocks(block_count); + next_block_ += block_count; + return ret; + } + + private: + uint64_t next_block_{kTempBlockStart}; +}; + +// Takes a vector of blocks and returns an equivalent vector of Extent +// objects. +vector<Extent> CompressExtents(const vector<uint64_t>& blocks) { + vector<Extent> new_extents; + for (uint64_t block : blocks) { + AppendBlockToExtents(&new_extents, block); + } + return new_extents; +} + +// Helper class to compare two operations by start block of the first Extent in +// their destination extents given the index of the operations in the graph. +class IndexedInstallOperationsDstComparator { + public: + explicit IndexedInstallOperationsDstComparator(Graph* graph) + : graph_(graph) {} + + // Compares the operations in the vertex a and b of graph_. + bool operator()(size_t a, size_t b) const { + return diff_utils::CompareAopsByDestination((*graph_)[a].aop, + (*graph_)[b].aop); + } + + private: + const Graph* const graph_; +}; + +} // namespace + +void InplaceGenerator::CheckGraph(const Graph& graph) { + for (const Vertex& v : graph) { + CHECK(v.aop.op.has_type()); + } +} + +void InplaceGenerator::SubstituteBlocks( + Vertex* vertex, + const vector<Extent>& remove_extents, + const vector<Extent>& replace_extents) { + // First, expand out the blocks that op reads from + vector<uint64_t> read_blocks = + ExpandExtents(vertex->aop.op.src_extents()); + { + // Expand remove_extents and replace_extents + vector<uint64_t> remove_extents_expanded = ExpandExtents(remove_extents); + vector<uint64_t> replace_extents_expanded = ExpandExtents(replace_extents); + CHECK_EQ(remove_extents_expanded.size(), replace_extents_expanded.size()); + map<uint64_t, uint64_t> conversion; + for (vector<uint64_t>::size_type i = 0; + i < replace_extents_expanded.size(); i++) { + conversion[remove_extents_expanded[i]] = replace_extents_expanded[i]; + } + ApplyMap(&read_blocks, conversion); + for (auto& edge_prop_pair : vertex->out_edges) { + vector<uint64_t> write_before_deps_expanded = ExpandExtents( + edge_prop_pair.second.write_extents); + ApplyMap(&write_before_deps_expanded, conversion); + edge_prop_pair.second.write_extents = + CompressExtents(write_before_deps_expanded); + } + } + // Convert read_blocks back to extents + vertex->aop.op.clear_src_extents(); + vector<Extent> new_extents = CompressExtents(read_blocks); + StoreExtents(new_extents, vertex->aop.op.mutable_src_extents()); +} + +bool InplaceGenerator::CutEdges(Graph* graph, + const set<Edge>& edges, + vector<CutEdgeVertexes>* out_cuts) { + DummyExtentAllocator scratch_allocator; + vector<CutEdgeVertexes> cuts; + cuts.reserve(edges.size()); + + uint64_t scratch_blocks_used = 0; + for (const Edge& edge : edges) { + cuts.resize(cuts.size() + 1); + vector<Extent> old_extents = + (*graph)[edge.first].out_edges[edge.second].extents; + // Choose some scratch space + scratch_blocks_used += graph_utils::EdgeWeight(*graph, edge); + cuts.back().tmp_extents = + scratch_allocator.Allocate(graph_utils::EdgeWeight(*graph, edge)); + // create vertex to copy original->scratch + cuts.back().new_vertex = graph->size(); + graph->emplace_back(); + cuts.back().old_src = edge.first; + cuts.back().old_dst = edge.second; + + EdgeProperties& cut_edge_properties = + (*graph)[edge.first].out_edges.find(edge.second)->second; + + // This should never happen, as we should only be cutting edges between + // real file nodes, and write-before relationships are created from + // a real file node to a temp copy node: + CHECK(cut_edge_properties.write_extents.empty()) + << "Can't cut edge that has write-before relationship."; + + // make node depend on the copy operation + (*graph)[edge.first].out_edges.insert(make_pair(graph->size() - 1, + cut_edge_properties)); + + // Set src/dst extents and other proto variables for copy operation + graph->back().aop.op.set_type(InstallOperation::MOVE); + StoreExtents(cut_edge_properties.extents, + graph->back().aop.op.mutable_src_extents()); + StoreExtents(cuts.back().tmp_extents, + graph->back().aop.op.mutable_dst_extents()); + graph->back().aop.op.set_src_length( + graph_utils::EdgeWeight(*graph, edge) * kBlockSize); + graph->back().aop.op.set_dst_length(graph->back().aop.op.src_length()); + + // make the dest node read from the scratch space + SubstituteBlocks( + &((*graph)[edge.second]), + (*graph)[edge.first].out_edges[edge.second].extents, + cuts.back().tmp_extents); + + // delete the old edge + CHECK_EQ(static_cast<Graph::size_type>(1), + (*graph)[edge.first].out_edges.erase(edge.second)); + + // Add an edge from dst to copy operation + EdgeProperties write_before_edge_properties; + write_before_edge_properties.write_extents = cuts.back().tmp_extents; + (*graph)[edge.second].out_edges.insert( + make_pair(graph->size() - 1, write_before_edge_properties)); + } + out_cuts->swap(cuts); + return true; +} + +// Creates all the edges for the graph. Writers of a block point to +// readers of the same block. This is because for an edge A->B, B +// must complete before A executes. +void InplaceGenerator::CreateEdges( + Graph* graph, + const vector<Block>& blocks) { + for (vector<Block>::size_type i = 0; + i < blocks.size(); i++) { + // Blocks with both a reader and writer get an edge + if (blocks[i].reader == Vertex::kInvalidIndex || + blocks[i].writer == Vertex::kInvalidIndex) + continue; + // Don't have a node depend on itself + if (blocks[i].reader == blocks[i].writer) + continue; + // See if there's already an edge we can add onto + Vertex::EdgeMap::iterator edge_it = + (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader); + if (edge_it == (*graph)[blocks[i].writer].out_edges.end()) { + // No existing edge. Create one + (*graph)[blocks[i].writer].out_edges.insert( + make_pair(blocks[i].reader, EdgeProperties())); + edge_it = (*graph)[blocks[i].writer].out_edges.find(blocks[i].reader); + CHECK(edge_it != (*graph)[blocks[i].writer].out_edges.end()); + } + AppendBlockToExtents(&edge_it->second.extents, i); + } +} + +namespace { + +class SortCutsByTopoOrderLess { + public: + explicit SortCutsByTopoOrderLess( + const vector<vector<Vertex::Index>::size_type>& table) + : table_(table) {} + bool operator()(const CutEdgeVertexes& a, const CutEdgeVertexes& b) { + return table_[a.old_dst] < table_[b.old_dst]; + } + private: + const vector<vector<Vertex::Index>::size_type>& table_; +}; + +} // namespace + +void InplaceGenerator::GenerateReverseTopoOrderMap( + const vector<Vertex::Index>& op_indexes, + vector<vector<Vertex::Index>::size_type>* reverse_op_indexes) { + vector<vector<Vertex::Index>::size_type> table(op_indexes.size()); + for (vector<Vertex::Index>::size_type i = 0, e = op_indexes.size(); + i != e; ++i) { + Vertex::Index node = op_indexes[i]; + if (table.size() < (node + 1)) { + table.resize(node + 1); + } + table[node] = i; + } + reverse_op_indexes->swap(table); +} + +void InplaceGenerator::SortCutsByTopoOrder( + const vector<Vertex::Index>& op_indexes, + vector<CutEdgeVertexes>* cuts) { + // first, make a reverse lookup table. + vector<vector<Vertex::Index>::size_type> table; + GenerateReverseTopoOrderMap(op_indexes, &table); + SortCutsByTopoOrderLess less(table); + sort(cuts->begin(), cuts->end(), less); +} + +void InplaceGenerator::MoveAndSortFullOpsToBack( + Graph* graph, + vector<Vertex::Index>* op_indexes) { + vector<Vertex::Index> ret; + vector<Vertex::Index> full_ops; + ret.reserve(op_indexes->size()); + for (auto op_index : *op_indexes) { + InstallOperation_Type type = (*graph)[op_index].aop.op.type(); + if (type == InstallOperation::REPLACE || + type == InstallOperation::REPLACE_BZ) { + full_ops.push_back(op_index); + } else { + ret.push_back(op_index); + } + } + LOG(INFO) << "Stats: " << full_ops.size() << " full ops out of " + << (full_ops.size() + ret.size()) << " total ops."; + // Sort full ops according to their dst_extents. + sort(full_ops.begin(), full_ops.end(), + IndexedInstallOperationsDstComparator(graph)); + ret.insert(ret.end(), full_ops.begin(), full_ops.end()); + op_indexes->swap(ret); +} + +namespace { + +template<typename T> +bool TempBlocksExistInExtents(const T& extents) { + for (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. + kInPlacePayloadVersion, + blob_file)); + TEST_AND_RETURN_FALSE(new_aop.size() == 1); + TEST_AND_RETURN_FALSE(AddInstallOpToGraph( + graph, cut.old_dst, nullptr, new_aop.front().op, new_aop.front().name)); + + (*graph)[cut.old_dst].out_edges = out_edges; + + // Right now we don't have doubly-linked edges, so we have to scan + // the whole graph. + graph_utils::DropIncomingEdgesTo(graph, cut.old_dst); + } + + // Delete temp node + (*graph)[cut.old_src].out_edges.erase(cut.new_vertex); + CHECK((*graph)[cut.old_dst].out_edges.find(cut.new_vertex) == + (*graph)[cut.old_dst].out_edges.end()); + (*graph)[cut.new_vertex].valid = false; + LOG(INFO) << "marked node invalid: " << cut.new_vertex; + return true; +} + +bool InplaceGenerator::ConvertGraphToDag(Graph* graph, + const string& new_part, + BlobFileWriter* blob_file, + vector<Vertex::Index>* final_order, + Vertex::Index scratch_vertex) { + CycleBreaker cycle_breaker; + LOG(INFO) << "Finding cycles..."; + set<Edge> cut_edges; + cycle_breaker.BreakCycles(*graph, &cut_edges); + LOG(INFO) << "done finding cycles"; + CheckGraph(*graph); + + // Calculate number of scratch blocks needed + + LOG(INFO) << "Cutting cycles..."; + vector<CutEdgeVertexes> cuts; + TEST_AND_RETURN_FALSE(CutEdges(graph, cut_edges, &cuts)); + LOG(INFO) << "done cutting cycles"; + LOG(INFO) << "There are " << cuts.size() << " cuts."; + CheckGraph(*graph); + + LOG(INFO) << "Creating initial topological order..."; + TopologicalSort(*graph, final_order); + LOG(INFO) << "done with initial topo order"; + CheckGraph(*graph); + + LOG(INFO) << "Moving full ops to the back"; + MoveAndSortFullOpsToBack(graph, final_order); + LOG(INFO) << "done moving full ops to back"; + + vector<vector<Vertex::Index>::size_type> inverse_final_order; + GenerateReverseTopoOrderMap(*final_order, &inverse_final_order); + + SortCutsByTopoOrder(*final_order, &cuts); + + if (!cuts.empty()) + TEST_AND_RETURN_FALSE(AssignTempBlocks(graph, + new_part, + blob_file, + final_order, + &inverse_final_order, + cuts)); + LOG(INFO) << "Making sure all temp blocks have been allocated"; + + // Remove the scratch node, if any + if (scratch_vertex != Vertex::kInvalidIndex) { + final_order->erase(final_order->begin() + + inverse_final_order[scratch_vertex]); + (*graph)[scratch_vertex].valid = false; + GenerateReverseTopoOrderMap(*final_order, &inverse_final_order); + } + + graph_utils::DumpGraph(*graph); + CHECK(NoTempBlocksRemain(*graph)); + LOG(INFO) << "done making sure all temp blocks are allocated"; + return true; +} + +void InplaceGenerator::CreateScratchNode(uint64_t start_block, + uint64_t num_blocks, + Vertex* vertex) { + vertex->aop.name = "<scratch>"; + vertex->aop.op.set_type(InstallOperation::REPLACE_BZ); + vertex->aop.op.set_data_offset(0); + vertex->aop.op.set_data_length(0); + Extent* extent = vertex->aop.op.add_dst_extents(); + extent->set_start_block(start_block); + extent->set_num_blocks(num_blocks); +} + +bool InplaceGenerator::AddInstallOpToBlocksVector( + const InstallOperation& operation, + const Graph& graph, + Vertex::Index vertex, + vector<Block>* blocks) { + // See if this is already present. + TEST_AND_RETURN_FALSE(operation.dst_extents_size() > 0); + + enum BlockField { READER = 0, WRITER, BLOCK_FIELD_COUNT }; + for (int field = READER; field < BLOCK_FIELD_COUNT; field++) { + const char* past_participle = (field == READER) ? "read" : "written"; + const google::protobuf::RepeatedPtrField<Extent>& extents = + (field == READER) ? operation.src_extents() : operation.dst_extents(); + Vertex::Index Block::*access_type = (field == READER) ? + &Block::reader : &Block::writer; + + for (const Extent& extent : extents) { + for (uint64_t block = extent.start_block(); + block < (extent.start_block() + extent.num_blocks()); block++) { + if ((*blocks)[block].*access_type != Vertex::kInvalidIndex) { + LOG(FATAL) << "Block " << block << " is already " + << past_participle << " by " + << (*blocks)[block].*access_type << "(" + << graph[(*blocks)[block].*access_type].aop.name + << ") and also " << vertex << "(" + << graph[vertex].aop.name << ")"; + } + (*blocks)[block].*access_type = vertex; + } + } + } + return true; +} + +bool InplaceGenerator::AddInstallOpToGraph(Graph* graph, + Vertex::Index existing_vertex, + vector<Block>* blocks, + const InstallOperation& operation, + const string& op_name) { + Vertex::Index vertex = existing_vertex; + if (vertex == Vertex::kInvalidIndex) { + graph->emplace_back(); + vertex = graph->size() - 1; + } + (*graph)[vertex].aop.op = operation; + CHECK((*graph)[vertex].aop.op.has_type()); + (*graph)[vertex].aop.name = op_name; + + if (blocks) + TEST_AND_RETURN_FALSE(InplaceGenerator::AddInstallOpToBlocksVector( + (*graph)[vertex].aop.op, + *graph, + vertex, + blocks)); + return true; +} + +void InplaceGenerator::ApplyMap(vector<uint64_t>* collection, + const map<uint64_t, uint64_t>& the_map) { + for (uint64_t& elem : *collection) { + const auto& map_it = the_map.find(elem); + if (map_it != the_map.end()) + elem = map_it->second; + } +} + +bool InplaceGenerator::ResolveReadAfterWriteDependencies( + const PartitionConfig& old_part, + const PartitionConfig& new_part, + uint64_t partition_size, + size_t block_size, + BlobFileWriter* blob_file, + vector<AnnotatedOperation>* aops) { + // Convert the operations to the graph. + Graph graph; + CheckGraph(graph); + vector<Block> blocks(std::max(old_part.size, new_part.size) / block_size); + for (const auto& aop : *aops) { + AddInstallOpToGraph( + &graph, Vertex::kInvalidIndex, &blocks, aop.op, aop.name); + } + CheckGraph(graph); + + // Final scratch block (if there's space) + Vertex::Index scratch_vertex = Vertex::kInvalidIndex; + if (blocks.size() < (partition_size / block_size)) { + scratch_vertex = graph.size(); + graph.emplace_back(); + size_t scratch_blocks = (partition_size / block_size) - blocks.size(); + LOG(INFO) << "Added " << scratch_blocks << " scratch space blocks."; + CreateScratchNode(blocks.size(), scratch_blocks, &graph.back()); + } + CheckGraph(graph); + + LOG(INFO) << "Creating edges..."; + CreateEdges(&graph, blocks); + LOG(INFO) << "Done creating edges"; + CheckGraph(graph); + + vector<Vertex::Index> final_order; + TEST_AND_RETURN_FALSE(ConvertGraphToDag( + &graph, + new_part.path, + blob_file, + &final_order, + scratch_vertex)); + + // Copy operations over to the |aops| vector in the final_order generated by + // the topological sort. + aops->clear(); + aops->reserve(final_order.size()); + for (const Vertex::Index vertex_index : final_order) { + const Vertex& vertex = graph[vertex_index]; + aops->push_back(vertex.aop); + } + return true; +} + +bool InplaceGenerator::GenerateOperations( + const PayloadGenerationConfig& config, + const PartitionConfig& old_part, + const PartitionConfig& new_part, + BlobFileWriter* blob_file, + vector<AnnotatedOperation>* aops) { + TEST_AND_RETURN_FALSE(old_part.name == new_part.name); + TEST_AND_RETURN_FALSE(config.version.major == kInPlacePayloadVersion.major); + TEST_AND_RETURN_FALSE(config.version.minor == kInPlacePayloadVersion.minor); + + ssize_t hard_chunk_blocks = (config.hard_chunk_size == -1 ? -1 : + config.hard_chunk_size / config.block_size); + size_t soft_chunk_blocks = config.soft_chunk_size / config.block_size; + uint64_t partition_size = new_part.size; + if (new_part.name == 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, + config.version, + blob_file)); + LOG(INFO) << "Done reading " << new_part.name; + + TEST_AND_RETURN_FALSE(ResolveReadAfterWriteDependencies( + old_part, new_part, partition_size, config.block_size, blob_file, aops)); + LOG(INFO) << "Done reordering " << new_part.name; + return true; +} + +}; // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/inplace_generator.h b/update_engine/payload_generator/inplace_generator.h new file mode 100644 index 0000000..f108639 --- /dev/null +++ b/update_engine/payload_generator/inplace_generator.h
@@ -0,0 +1,243 @@ +// +// Copyright (C) 2015 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_ +#define UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_ + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "update_engine/payload_generator/blob_file_writer.h" +#include "update_engine/payload_generator/delta_diff_generator.h" +#include "update_engine/payload_generator/graph_types.h" +#include "update_engine/payload_generator/operations_generator.h" + +// InplaceGenerator contains all functionality related to the inplace algorithm +// for generating update payloads. These are the functions used when delta minor +// version is 1. + +namespace chromeos_update_engine { + +// This struct stores all relevant info for an edge that is cut between +// nodes old_src -> old_dst by creating new vertex new_vertex. The new +// relationship is: +// old_src -(read before)-> new_vertex <-(write before)- old_dst +// new_vertex is a MOVE operation that moves some existing blocks into +// temp space. The temp extents are, by necessity, stored in new_vertex +// (as dst extents) and old_dst (as src extents), but they are also broken +// out into tmp_extents, as the nodes themselves may contain many more +// extents. +struct CutEdgeVertexes { + Vertex::Index new_vertex; + Vertex::Index old_src; + Vertex::Index old_dst; + std::vector<Extent> tmp_extents; +}; + +class InplaceGenerator : public OperationsGenerator { + public: + // Represents a disk block on the install partition. + struct Block { + // During install, each block on the install partition will be written + // and some may be read (in all likelihood, many will be read). + // The reading and writing will be performed by InstallOperations, + // each of which has a corresponding vertex in a graph. + // A Block object tells which vertex will read or write this block + // at install time. + // Generally, there will be a vector of Block objects whose length + // is the number of blocks on the install partition. + Block() : reader(Vertex::kInvalidIndex), writer(Vertex::kInvalidIndex) {} + Vertex::Index reader; + Vertex::Index writer; + }; + + InplaceGenerator() = default; + + // Checks all the operations in the graph have a type assigned. + static void CheckGraph(const Graph& graph); + + // Modifies blocks read by 'op' so that any blocks referred to by + // 'remove_extents' are replaced with blocks from 'replace_extents'. + // 'remove_extents' and 'replace_extents' must be the same number of blocks. + // Blocks will be substituted in the order listed in the vectors. + // E.g. if 'op' reads blocks 1, 2, 3, 4, 5, 6, 7, 8, remove_extents + // contains blocks 6, 2, 3, 5, and replace blocks contains + // 12, 13, 14, 15, then op will be changed to read from: + // 1, 13, 14, 4, 15, 12, 7, 8 + static void SubstituteBlocks(Vertex* vertex, + const std::vector<Extent>& remove_extents, + const std::vector<Extent>& replace_extents); + + // Cuts 'edges' from 'graph' according to the AU algorithm. This means + // for each edge A->B, remove the dependency that B occur before A. + // Do this by creating a new operation X that copies from the blocks + // specified by the edge's properties to temp space T. Modify B to read + // from T rather than the blocks in the edge. Modify A to depend on X, + // but not on B. Free space is found by looking in 'blocks'. + // Returns true on success. + static bool CutEdges(Graph* graph, + const std::set<Edge>& edges, + std::vector<CutEdgeVertexes>* out_cuts); + + // Creates all the edges for the graph. Writers of a block point to + // readers of the same block. This is because for an edge A->B, B + // must complete before A executes. + static void CreateEdges(Graph* graph, + const std::vector<Block>& blocks); + + // Takes |op_indexes|, which is effectively a mapping from order in + // which the op is performed -> graph vertex index, and produces the + // reverse: a mapping from graph vertex index -> op_indexes index. + static void GenerateReverseTopoOrderMap( + const std::vector<Vertex::Index>& op_indexes, + std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes); + + // Sorts the vector |cuts| by its |cuts[].old_dest| member. Order is + // determined by the order of elements in op_indexes. + static void SortCutsByTopoOrder( + const std::vector<Vertex::Index>& op_indexes, + std::vector<CutEdgeVertexes>* cuts); + + // Given a topologically sorted graph |op_indexes| and |graph|, alters + // |op_indexes| to move all the full operations to the end of the vector. + // Full operations should not be depended on, so this is safe. + static void MoveAndSortFullOpsToBack(Graph* graph, + std::vector<Vertex::Index>* op_indexes); + + // Returns true iff there are no extents in the graph that refer to temp + // blocks. Temp blocks are in the range [kTempBlockStart, kSparseHole). + static bool NoTempBlocksRemain(const Graph& graph); + + // Takes a |graph|, which has edges that must be cut, as listed in + // |cuts|. Cuts the edges. Maintains a list in which the operations + // will be performed (in |op_indexes|) and the reverse (in + // |reverse_op_indexes|). Cutting edges requires scratch space, and + // if insufficient scratch is found, the file is reread and will be + // send down (either as REPLACE or REPLACE_BZ). Returns true on + // success. + static bool AssignTempBlocks( + Graph* graph, + const std::string& new_part, + BlobFileWriter* blob_file, + std::vector<Vertex::Index>* op_indexes, + std::vector<std::vector<Vertex::Index>::size_type>* reverse_op_indexes, + const std::vector<CutEdgeVertexes>& cuts); + + // Handles allocation of temp blocks to a cut edge by converting the + // dest node to a full op. This removes the need for temp blocks, but + // comes at the cost of a worse compression ratio. + // For example, say we have A->B->A. It would first be cut to form: + // A->B->N<-A, where N copies blocks to temp space. If there are no + // temp blocks, this function can be called to convert it to the form: + // A->B. Now, A is a full operation. + static bool ConvertCutToFullOp(Graph* graph, + const CutEdgeVertexes& cut, + const std::string& new_part, + BlobFileWriter* blob_file); + + // Takes a graph, which is not a DAG, which represents the files just + // read from disk, and converts it into a DAG by breaking all cycles + // and finding temp space to resolve broken edges. + // The final order of the nodes is given in |final_order| + // Some files may need to be reread from disk, thus |fd| and + // |data_file_size| are be passed. + // If |scratch_vertex| is not kInvalidIndex, removes it from + // |final_order| before returning. + // Returns true on success. + static bool ConvertGraphToDag(Graph* graph, + const std::string& new_part, + BlobFileWriter* blob_file, + std::vector<Vertex::Index>* final_order, + Vertex::Index scratch_vertex); + + // Creates a dummy REPLACE_BZ node in the given |vertex|. This can be used + // to provide scratch space. The node writes |num_blocks| blocks starting at + // |start_block|The node should be marked invalid before writing all nodes to + // the output file. + static void CreateScratchNode(uint64_t start_block, + uint64_t num_blocks, + Vertex* vertex); + + // The |blocks| vector contains a reader and writer for each block on the + // filesystem that's being in-place updated. We populate the reader/writer + // fields of |blocks| by calling this function. + // For each block in |operation| that is read or written, find that block + // in |blocks| and set the reader/writer field to the vertex passed. + // |graph| is not strictly necessary, but useful for printing out + // error messages. + static bool AddInstallOpToBlocksVector(const InstallOperation& operation, + const Graph& graph, + Vertex::Index vertex, + std::vector<Block>* blocks); + + // Add a vertex (if |existing_vertex| is kInvalidVertex) or update an + // |existing_vertex| with the passed |operation|. + // This method will also register the vertex as the reader or writer of the + // blocks involved in the operation updating the |blocks| vector. The + // |op_name| associated with the Vertex is used for logging purposes. + static bool AddInstallOpToGraph(Graph* graph, + Vertex::Index existing_vertex, + std::vector<Block>* blocks, + const InstallOperation& operation, + const std::string& op_name); + + // Apply the transformation stored in |the_map| to the |collection| vector + // replacing the map keys found in |collection| with its associated value in + // |the_map|. + static void ApplyMap(std::vector<uint64_t>* collection, + const std::map<uint64_t, uint64_t>& the_map); + + // Resolve all read-after-write dependencies in the operation list |aops|. The + // operations in |aops| are such that they generate the desired |new_part| if + // applied reading always from the original image. This function reorders the + // operations and generates new operations when needed to make these + // operations produce the same |new_part| result when applied in-place. + // The new operations will create blobs in |data_file_fd| and update + // the file size pointed by |data_file_size| if needed. + // On success, stores the new operations in |aops| in the right order and + // returns true. + static bool ResolveReadAfterWriteDependencies( + const PartitionConfig& old_part, + const PartitionConfig& new_part, + uint64_t partition_size, + size_t block_size, + BlobFileWriter* blob_file, + std::vector<AnnotatedOperation>* aops); + + // Generate the update payload operations for the given partition using + // only operations that read from the target and/or write to the target, + // hence, applying the payload "in-place" in the target partition. This method + // assumes that the contents of the source image are pre-copied to the target + // partition, up to the size of the source image. Use this method to generate + // a delta update with the minor version kInPlaceMinorPayloadVersion. + // The operations are stored in |aops|. All the offsets in the operations + // reference the data written to |blob_file|. + bool GenerateOperations( + const PayloadGenerationConfig& config, + const PartitionConfig& old_part, + const PartitionConfig& new_part, + BlobFileWriter* blob_file, + std::vector<AnnotatedOperation>* aops) override; + + private: + DISALLOW_COPY_AND_ASSIGN(InplaceGenerator); +}; + +}; // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_INPLACE_GENERATOR_H_
diff --git a/update_engine/payload_generator/inplace_generator_unittest.cc b/update_engine/payload_generator/inplace_generator_unittest.cc new file mode 100644 index 0000000..20ac50b --- /dev/null +++ b/update_engine/payload_generator/inplace_generator_unittest.cc
@@ -0,0 +1,750 @@ +// +// 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/delta_diff_utils.h" +#include "update_engine/payload_generator/extent_ranges.h" +#include "update_engine/payload_generator/graph_types.h" +#include "update_engine/payload_generator/graph_utils.h" + +using std::map; +using std::set; +using std::string; +using std::stringstream; +using std::vector; + +namespace chromeos_update_engine { + +using Block = InplaceGenerator::Block; + +namespace { + +void GenVertex(Vertex* out, + const vector<Extent>& src_extents, + const vector<Extent>& dst_extents, + const string& path, + InstallOperation_Type type) { + out->aop.op.set_type(type); + out->aop.name = path; + StoreExtents(src_extents, out->aop.op.mutable_src_extents()); + StoreExtents(dst_extents, out->aop.op.mutable_dst_extents()); +} + +vector<Extent> VectOfExt(uint64_t start_block, uint64_t num_blocks) { + return vector<Extent>(1, ExtentForRange(start_block, num_blocks)); +} + +EdgeProperties EdgeWithReadDep(const vector<Extent>& extents) { + EdgeProperties ret; + ret.extents = extents; + return ret; +} + +EdgeProperties EdgeWithWriteDep(const vector<Extent>& extents) { + EdgeProperties ret; + ret.write_extents = extents; + return ret; +} + +template<typename T> +void DumpVect(const vector<T>& vect) { + stringstream ss(stringstream::out); + for (typename vector<T>::const_iterator it = vect.begin(), e = vect.end(); + it != e; ++it) { + ss << *it << ", "; + } + LOG(INFO) << "{" << ss.str() << "}"; +} + +void AppendExtent(vector<Extent>* vect, uint64_t start, uint64_t length) { + vect->resize(vect->size() + 1); + vect->back().set_start_block(start); + vect->back().set_num_blocks(length); +} + +void OpAppendExtent(InstallOperation* op, uint64_t start, uint64_t length) { + Extent* extent = op->add_src_extents(); + extent->set_start_block(start); + extent->set_num_blocks(length); +} + +} // namespace + +class InplaceGeneratorTest : public ::testing::Test { + protected: + // Initialize |blob_path_|, |blob_file_size_| and |blob_file_fd_| variables + // with a new blob file. The file is closed and removed automatically when + // the test finishes. + void CreateBlobFile() { + // blob_fd_closer_ takes a pointer to blob_fd_. Make sure we destroy a + // previous instance before overriding blob_fd_. + blob_fd_closer_.reset(); + EXPECT_TRUE(utils::MakeTempFile( + "InplaceGenerator_blob_file.XXXXXX", &blob_path_, &blob_fd_)); + blob_path_unlinker_.reset(new ScopedPathUnlinker(blob_path_)); + blob_fd_closer_.reset(new ScopedFdCloser(&blob_fd_)); + blob_file_size_ = 0; + EXPECT_GE(blob_fd_, 0); + blob_file_.reset(new BlobFileWriter(blob_fd_, &blob_file_size_)); + } + + // Dump the list of operations |aops| in case of test failure. + void DumpAopsOnFailure(const vector<AnnotatedOperation>& aops) { + if (HasNonfatalFailure()) { + LOG(INFO) << "Result operation list:"; + for (const auto& aop : aops) { + LOG(INFO) << aop; + } + } + } + + // Blob file name, file descriptor and file size used to store operation + // blobs. + string blob_path_; + int blob_fd_{-1}; + off_t blob_file_size_{0}; + std::unique_ptr<BlobFileWriter> blob_file_; + std::unique_ptr<ScopedPathUnlinker> blob_path_unlinker_; + std::unique_ptr<ScopedFdCloser> blob_fd_closer_; +}; + +TEST_F(InplaceGeneratorTest, BlockDefaultValues) { + // Tests that a Block is initialized with the default values as a + // Vertex::kInvalidIndex. This is required by the delta generators. + Block block; + EXPECT_EQ(Vertex::kInvalidIndex, block.reader); + EXPECT_EQ(Vertex::kInvalidIndex, block.writer); +} + +TEST_F(InplaceGeneratorTest, SubstituteBlocksTest) { + vector<Extent> remove_blocks; + AppendExtent(&remove_blocks, 3, 3); + AppendExtent(&remove_blocks, 7, 1); + vector<Extent> replace_blocks; + AppendExtent(&replace_blocks, 10, 2); + AppendExtent(&replace_blocks, 13, 2); + Vertex vertex; + InstallOperation& op = vertex.aop.op; + OpAppendExtent(&op, 4, 3); + OpAppendExtent(&op, kSparseHole, 4); // Sparse hole in file + OpAppendExtent(&op, 3, 1); + OpAppendExtent(&op, 7, 3); + + InplaceGenerator::SubstituteBlocks(&vertex, remove_blocks, replace_blocks); + + EXPECT_EQ(7, op.src_extents_size()); + EXPECT_EQ(11U, op.src_extents(0).start_block()); + EXPECT_EQ(1U, op.src_extents(0).num_blocks()); + EXPECT_EQ(13U, op.src_extents(1).start_block()); + EXPECT_EQ(1U, op.src_extents(1).num_blocks()); + EXPECT_EQ(6U, op.src_extents(2).start_block()); + EXPECT_EQ(1U, op.src_extents(2).num_blocks()); + EXPECT_EQ(kSparseHole, op.src_extents(3).start_block()); + EXPECT_EQ(4U, op.src_extents(3).num_blocks()); + EXPECT_EQ(10U, op.src_extents(4).start_block()); + EXPECT_EQ(1U, op.src_extents(4).num_blocks()); + EXPECT_EQ(14U, op.src_extents(5).start_block()); + EXPECT_EQ(1U, op.src_extents(5).num_blocks()); + EXPECT_EQ(8U, op.src_extents(6).start_block()); + EXPECT_EQ(2U, op.src_extents(6).num_blocks()); +} + +TEST_F(InplaceGeneratorTest, CutEdgesTest) { + Graph graph; + vector<Block> blocks(9); + + // Create nodes in graph + { + graph.resize(graph.size() + 1); + graph.back().aop.op.set_type(InstallOperation::MOVE); + // Reads from blocks 3, 5, 7 + vector<Extent> extents; + AppendBlockToExtents(&extents, 3); + AppendBlockToExtents(&extents, 5); + AppendBlockToExtents(&extents, 7); + StoreExtents(extents, graph.back().aop.op.mutable_src_extents()); + blocks[3].reader = graph.size() - 1; + blocks[5].reader = graph.size() - 1; + blocks[7].reader = graph.size() - 1; + + // Writes to blocks 1, 2, 4 + extents.clear(); + AppendBlockToExtents(&extents, 1); + AppendBlockToExtents(&extents, 2); + AppendBlockToExtents(&extents, 4); + StoreExtents(extents, graph.back().aop.op.mutable_dst_extents()); + blocks[1].writer = graph.size() - 1; + blocks[2].writer = graph.size() - 1; + blocks[4].writer = graph.size() - 1; + } + { + graph.resize(graph.size() + 1); + graph.back().aop.op.set_type(InstallOperation::MOVE); + // Reads from blocks 1, 2, 4 + vector<Extent> extents; + AppendBlockToExtents(&extents, 1); + AppendBlockToExtents(&extents, 2); + AppendBlockToExtents(&extents, 4); + StoreExtents(extents, graph.back().aop.op.mutable_src_extents()); + blocks[1].reader = graph.size() - 1; + blocks[2].reader = graph.size() - 1; + blocks[4].reader = graph.size() - 1; + + // Writes to blocks 3, 5, 6 + extents.clear(); + AppendBlockToExtents(&extents, 3); + AppendBlockToExtents(&extents, 5); + AppendBlockToExtents(&extents, 6); + StoreExtents(extents, graph.back().aop.op.mutable_dst_extents()); + blocks[3].writer = graph.size() - 1; + blocks[5].writer = graph.size() - 1; + blocks[6].writer = graph.size() - 1; + } + + // Create edges + InplaceGenerator::CreateEdges(&graph, blocks); + + // Find cycles + CycleBreaker cycle_breaker; + set<Edge> cut_edges; + cycle_breaker.BreakCycles(graph, &cut_edges); + + EXPECT_EQ(1U, cut_edges.size()); + EXPECT_TRUE(cut_edges.end() != cut_edges.find( + std::pair<Vertex::Index, Vertex::Index>(1, 0))); + + vector<CutEdgeVertexes> cuts; + EXPECT_TRUE(InplaceGenerator::CutEdges(&graph, cut_edges, &cuts)); + + EXPECT_EQ(3U, graph.size()); + + // Check new node in graph: + EXPECT_EQ(InstallOperation::MOVE, graph.back().aop.op.type()); + EXPECT_EQ(2, graph.back().aop.op.src_extents_size()); + EXPECT_EQ(1, graph.back().aop.op.dst_extents_size()); + EXPECT_EQ(kTempBlockStart, graph.back().aop.op.dst_extents(0).start_block()); + EXPECT_EQ(2U, graph.back().aop.op.dst_extents(0).num_blocks()); + EXPECT_TRUE(graph.back().out_edges.empty()); + + // Check that old node reads from new blocks + EXPECT_EQ(2, graph[0].aop.op.src_extents_size()); + EXPECT_EQ(kTempBlockStart, graph[0].aop.op.src_extents(0).start_block()); + EXPECT_EQ(2U, graph[0].aop.op.src_extents(0).num_blocks()); + EXPECT_EQ(7U, graph[0].aop.op.src_extents(1).start_block()); + EXPECT_EQ(1U, graph[0].aop.op.src_extents(1).num_blocks()); + + // And that the old dst extents haven't changed + EXPECT_EQ(2, graph[0].aop.op.dst_extents_size()); + EXPECT_EQ(1U, graph[0].aop.op.dst_extents(0).start_block()); + EXPECT_EQ(2U, graph[0].aop.op.dst_extents(0).num_blocks()); + EXPECT_EQ(4U, graph[0].aop.op.dst_extents(1).start_block()); + EXPECT_EQ(1U, graph[0].aop.op.dst_extents(1).num_blocks()); + + // Ensure it only depends on the next node and the new temp node + EXPECT_EQ(2U, graph[0].out_edges.size()); + EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(1)); + EXPECT_TRUE(graph[0].out_edges.end() != graph[0].out_edges.find(graph.size() - + 1)); + + // Check second node has unchanged extents + EXPECT_EQ(2, graph[1].aop.op.src_extents_size()); + EXPECT_EQ(1U, graph[1].aop.op.src_extents(0).start_block()); + EXPECT_EQ(2U, graph[1].aop.op.src_extents(0).num_blocks()); + EXPECT_EQ(4U, graph[1].aop.op.src_extents(1).start_block()); + EXPECT_EQ(1U, graph[1].aop.op.src_extents(1).num_blocks()); + + EXPECT_EQ(2, graph[1].aop.op.dst_extents_size()); + EXPECT_EQ(3U, graph[1].aop.op.dst_extents(0).start_block()); + EXPECT_EQ(1U, graph[1].aop.op.dst_extents(0).num_blocks()); + EXPECT_EQ(5U, graph[1].aop.op.dst_extents(1).start_block()); + EXPECT_EQ(2U, graph[1].aop.op.dst_extents(1).num_blocks()); + + // Ensure it only depends on the next node + EXPECT_EQ(1U, graph[1].out_edges.size()); + EXPECT_TRUE(graph[1].out_edges.end() != graph[1].out_edges.find(2)); +} + +TEST_F(InplaceGeneratorTest, AssignTempBlocksReuseTest) { + Graph graph(9); + + const vector<Extent> empt; + uint64_t tmp = kTempBlockStart; + const string kFilename = "/foo"; + + vector<CutEdgeVertexes> cuts; + cuts.resize(3); + + // Simple broken loop: + GenVertex( + &graph[0], VectOfExt(0, 1), VectOfExt(1, 1), "", InstallOperation::MOVE); + GenVertex(&graph[1], + VectOfExt(tmp, 1), + VectOfExt(0, 1), + "", + InstallOperation::MOVE); + GenVertex(&graph[2], + VectOfExt(1, 1), + VectOfExt(tmp, 1), + "", + InstallOperation::MOVE); + // Corresponding edges: + graph[0].out_edges[2] = EdgeWithReadDep(VectOfExt(1, 1)); + graph[1].out_edges[2] = EdgeWithWriteDep(VectOfExt(tmp, 1)); + graph[1].out_edges[0] = EdgeWithReadDep(VectOfExt(0, 1)); + // Store the cut: + cuts[0].old_dst = 1; + cuts[0].old_src = 0; + cuts[0].new_vertex = 2; + cuts[0].tmp_extents = VectOfExt(tmp, 1); + tmp++; + + // Slightly more complex pair of loops: + GenVertex( + &graph[3], VectOfExt(4, 2), VectOfExt(2, 2), "", InstallOperation::MOVE); + GenVertex( + &graph[4], VectOfExt(6, 1), VectOfExt(7, 1), "", InstallOperation::MOVE); + GenVertex(&graph[5], + VectOfExt(tmp, 3), + VectOfExt(4, 3), + kFilename, + InstallOperation::MOVE); + GenVertex(&graph[6], + VectOfExt(2, 2), + VectOfExt(tmp, 2), + "", + InstallOperation::MOVE); + GenVertex(&graph[7], + VectOfExt(7, 1), + VectOfExt(tmp + 2, 1), + "", + InstallOperation::MOVE); + // Corresponding edges: + graph[3].out_edges[6] = EdgeWithReadDep(VectOfExt(2, 2)); + graph[4].out_edges[7] = EdgeWithReadDep(VectOfExt(7, 1)); + graph[5].out_edges[6] = EdgeWithWriteDep(VectOfExt(tmp, 2)); + graph[5].out_edges[7] = EdgeWithWriteDep(VectOfExt(tmp + 2, 1)); + graph[5].out_edges[3] = EdgeWithReadDep(VectOfExt(4, 2)); + graph[5].out_edges[4] = EdgeWithReadDep(VectOfExt(6, 1)); + // Store the cuts: + cuts[1].old_dst = 5; + cuts[1].old_src = 3; + cuts[1].new_vertex = 6; + cuts[1].tmp_extents = VectOfExt(tmp, 2); + cuts[2].old_dst = 5; + cuts[2].old_src = 4; + cuts[2].new_vertex = 7; + cuts[2].tmp_extents = VectOfExt(tmp + 2, 1); + + // Supplier of temp block: + GenVertex(&graph[8], empt, VectOfExt(8, 1), "", InstallOperation::REPLACE); + + // Specify the final order: + vector<Vertex::Index> op_indexes; + op_indexes.push_back(2); + op_indexes.push_back(0); + op_indexes.push_back(1); + op_indexes.push_back(6); + op_indexes.push_back(3); + op_indexes.push_back(7); + op_indexes.push_back(4); + op_indexes.push_back(5); + op_indexes.push_back(8); + + vector<vector<Vertex::Index>::size_type> reverse_op_indexes; + InplaceGenerator::GenerateReverseTopoOrderMap(op_indexes, + &reverse_op_indexes); + + CreateBlobFile(); + EXPECT_TRUE(InplaceGenerator::AssignTempBlocks(&graph, + "/dev/zero", + blob_file_.get(), + &op_indexes, + &reverse_op_indexes, + cuts)); + EXPECT_FALSE(graph[6].valid); + EXPECT_FALSE(graph[7].valid); + EXPECT_EQ(1, graph[1].aop.op.src_extents_size()); + EXPECT_EQ(2U, graph[1].aop.op.src_extents(0).start_block()); + EXPECT_EQ(1U, graph[1].aop.op.src_extents(0).num_blocks()); + EXPECT_EQ(InstallOperation::REPLACE_BZ, graph[5].aop.op.type()); +} + +TEST_F(InplaceGeneratorTest, MoveAndSortFullOpsToBackTest) { + Graph graph(4); + graph[0].aop.name = "A"; + graph[0].aop.op.set_type(InstallOperation::REPLACE); + graph[1].aop.name = "B"; + graph[1].aop.op.set_type(InstallOperation::BSDIFF); + graph[2].aop.name = "C"; + graph[2].aop.op.set_type(InstallOperation::REPLACE_BZ); + graph[3].aop.name = "D"; + graph[3].aop.op.set_type(InstallOperation::MOVE); + + vector<Vertex::Index> vect(graph.size()); + + for (vector<Vertex::Index>::size_type i = 0; i < vect.size(); ++i) { + vect[i] = i; + } + InplaceGenerator::MoveAndSortFullOpsToBack(&graph, &vect); + EXPECT_EQ(vect.size(), graph.size()); + EXPECT_EQ(graph[vect[0]].aop.name, "B"); + EXPECT_EQ(graph[vect[1]].aop.name, "D"); + EXPECT_EQ(graph[vect[2]].aop.name, "A"); + EXPECT_EQ(graph[vect[3]].aop.name, "C"); +} + +TEST_F(InplaceGeneratorTest, AssignTempBlocksTest) { + Graph graph(9); + const vector<Extent> empt; // empty + const string kFilename = "/foo"; + + // Some scratch space: + GenVertex(&graph[0], empt, VectOfExt(200, 1), "", InstallOperation::REPLACE); + GenVertex(&graph[1], empt, VectOfExt(210, 10), "", InstallOperation::REPLACE); + GenVertex(&graph[2], empt, VectOfExt(220, 1), "", InstallOperation::REPLACE); + + // A cycle that requires 10 blocks to break: + GenVertex(&graph[3], + VectOfExt(10, 11), + VectOfExt(0, 9), + "", + InstallOperation::BSDIFF); + graph[3].out_edges[4] = EdgeWithReadDep(VectOfExt(0, 9)); + GenVertex(&graph[4], + VectOfExt(0, 9), + VectOfExt(10, 11), + "", + InstallOperation::BSDIFF); + graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11)); + + // A cycle that requires 9 blocks to break: + GenVertex(&graph[5], + VectOfExt(40, 11), + VectOfExt(30, 10), + "", + InstallOperation::BSDIFF); + graph[5].out_edges[6] = EdgeWithReadDep(VectOfExt(30, 10)); + GenVertex(&graph[6], + VectOfExt(30, 10), + VectOfExt(40, 11), + "", + InstallOperation::BSDIFF); + graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11)); + + // A cycle that requires 40 blocks to break (which is too many): + GenVertex(&graph[7], + VectOfExt(120, 50), + VectOfExt(60, 40), + "", + InstallOperation::BSDIFF); + graph[7].out_edges[8] = EdgeWithReadDep(VectOfExt(60, 40)); + GenVertex(&graph[8], + VectOfExt(60, 40), + VectOfExt(120, 50), + kFilename, + InstallOperation::BSDIFF); + graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50)); + + graph_utils::DumpGraph(graph); + + vector<Vertex::Index> final_order; + + CreateBlobFile(); + EXPECT_TRUE(InplaceGenerator::ConvertGraphToDag(&graph, + "/dev/zero", + blob_file_.get(), + &final_order, + Vertex::kInvalidIndex)); + + Graph expected_graph(12); + GenVertex(&expected_graph[0], + empt, + VectOfExt(200, 1), + "", + InstallOperation::REPLACE); + GenVertex(&expected_graph[1], + empt, + VectOfExt(210, 10), + "", + InstallOperation::REPLACE); + GenVertex(&expected_graph[2], + empt, + VectOfExt(220, 1), + "", + InstallOperation::REPLACE); + GenVertex(&expected_graph[3], + VectOfExt(10, 11), + VectOfExt(0, 9), + "", + InstallOperation::BSDIFF); + expected_graph[3].out_edges[9] = EdgeWithReadDep(VectOfExt(0, 9)); + GenVertex(&expected_graph[4], + VectOfExt(60, 9), + VectOfExt(10, 11), + "", + InstallOperation::BSDIFF); + expected_graph[4].out_edges[3] = EdgeWithReadDep(VectOfExt(10, 11)); + expected_graph[4].out_edges[9] = EdgeWithWriteDep(VectOfExt(60, 9)); + GenVertex(&expected_graph[5], + VectOfExt(40, 11), + VectOfExt(30, 10), + "", + InstallOperation::BSDIFF); + expected_graph[5].out_edges[10] = EdgeWithReadDep(VectOfExt(30, 10)); + + GenVertex(&expected_graph[6], + VectOfExt(60, 10), + VectOfExt(40, 11), + "", + InstallOperation::BSDIFF); + expected_graph[6].out_edges[5] = EdgeWithReadDep(VectOfExt(40, 11)); + expected_graph[6].out_edges[10] = EdgeWithWriteDep(VectOfExt(60, 10)); + + GenVertex(&expected_graph[7], + VectOfExt(120, 50), + VectOfExt(60, 40), + "", + InstallOperation::BSDIFF); + expected_graph[7].out_edges[6] = EdgeWithReadDep(VectOfExt(60, 10)); + + GenVertex(&expected_graph[8], + empt, + VectOfExt(0, 50), + "/foo", + InstallOperation::REPLACE_BZ); + expected_graph[8].out_edges[7] = EdgeWithReadDep(VectOfExt(120, 50)); + + GenVertex(&expected_graph[9], + VectOfExt(0, 9), + VectOfExt(60, 9), + "", + InstallOperation::MOVE); + + GenVertex(&expected_graph[10], + VectOfExt(30, 10), + VectOfExt(60, 10), + "", + InstallOperation::MOVE); + expected_graph[10].out_edges[4] = EdgeWithReadDep(VectOfExt(60, 9)); + + EXPECT_EQ(12U, graph.size()); + EXPECT_FALSE(graph.back().valid); + for (Graph::size_type i = 0; i < graph.size() - 1; i++) { + EXPECT_TRUE(graph[i].out_edges == expected_graph[i].out_edges); + if (i == 8) { + // special case + } else { + // EXPECT_TRUE(graph[i] == expected_graph[i]) << "i = " << i; + } + } +} + +TEST_F(InplaceGeneratorTest, CreateScratchNodeTest) { + Vertex vertex; + InplaceGenerator::CreateScratchNode(12, 34, &vertex); + EXPECT_EQ(InstallOperation::REPLACE_BZ, vertex.aop.op.type()); + EXPECT_EQ(0U, vertex.aop.op.data_offset()); + EXPECT_EQ(0U, vertex.aop.op.data_length()); + EXPECT_EQ(1, vertex.aop.op.dst_extents_size()); + EXPECT_EQ(12U, vertex.aop.op.dst_extents(0).start_block()); + EXPECT_EQ(34U, vertex.aop.op.dst_extents(0).num_blocks()); +} + +TEST_F(InplaceGeneratorTest, ApplyMapTest) { + vector<uint64_t> collection = {1, 2, 3, 4, 6}; + vector<uint64_t> expected_values = {1, 2, 5, 4, 8}; + map<uint64_t, uint64_t> value_map; + value_map[3] = 5; + value_map[6] = 8; + value_map[5] = 10; + + InplaceGenerator::ApplyMap(&collection, value_map); + EXPECT_EQ(expected_values, collection); +} + +// We can't produce MOVE operations with a source or destination in the block 0. +// This test checks that the cycle breaker procedure doesn't produce such +// operations. +TEST_F(InplaceGeneratorTest, ResolveReadAfterWriteDependenciesAvoidMoveToZero) { + size_t block_size = 4096; + size_t num_blocks = 4; + vector<AnnotatedOperation> aops; + + // Create a REPLACE_BZ for block 0, and a circular dependency among all other + // blocks. This situation would prefer to issue a MOVE to scratch space and + // the only available block is 0. + aops.emplace_back(); + aops.back().name = base::StringPrintf("<bz-block-0>"); + aops.back().op.set_type(InstallOperation::REPLACE_BZ); + StoreExtents({ExtentForRange(0, 1)}, aops.back().op.mutable_dst_extents()); + + for (size_t i = 1; i < num_blocks; i++) { + AnnotatedOperation aop; + aop.name = base::StringPrintf("<op-%" PRIuS ">", i); + aop.op.set_type(InstallOperation::BSDIFF); + StoreExtents({ExtentForRange(1 + i % (num_blocks - 1), 1)}, + aop.op.mutable_src_extents()); + StoreExtents({ExtentForRange(i, 1)}, aop.op.mutable_dst_extents()); + aops.push_back(aop); + } + + PartitionConfig part("part"); + part.path = "/dev/zero"; + part.size = num_blocks * block_size; + + CreateBlobFile(); + + // We ran two tests here. The first one without enough blocks for the scratch + // space, forcing it to create a new full operation and the second case with + // one extra block in the partition that can be used for the move operation. + for (const auto part_blocks : vector<uint64_t>{num_blocks, num_blocks + 1}) { + SCOPED_TRACE( + base::StringPrintf("Using partition_blocks=%" PRIu64, part_blocks)); + vector<AnnotatedOperation> result_aops = aops; + EXPECT_TRUE(InplaceGenerator::ResolveReadAfterWriteDependencies( + part, + part, + part_blocks * block_size, + block_size, + blob_file_.get(), + &result_aops)); + + size_t full_ops = 0; + for (const auto& aop : result_aops) { + if (diff_utils::IsAReplaceOperation(aop.op.type())) + full_ops++; + + if (aop.op.type() != InstallOperation::MOVE) + continue; + for (const Extent& extent : aop.op.src_extents()) { + EXPECT_NE(0U, extent.start_block()) + << "On src extents for aop: " << aop; + } + for (const Extent& extent : aop.op.dst_extents()) { + EXPECT_NE(0U, extent.start_block()) + << "On dst extents for aop: " << aop; + } + } + + // If there's extra space in the partition, it should not use a new full + // operation for it. + EXPECT_EQ(part_blocks == num_blocks ? 2U : 1U, full_ops); + + DumpAopsOnFailure(result_aops); + } +} + +// Test that we can shrink a filesystem and break cycles. +TEST_F(InplaceGeneratorTest, ResolveReadAfterWriteDependenciesShrinkData) { + size_t block_size = 4096; + size_t old_blocks = 10; + size_t new_blocks = 8; + vector<AnnotatedOperation> aops; + + // Create a loop using the blocks 1-6 and one other operation writing to the + // block 7 from outside the new partition. The loop in the blocks 1-6 uses + // two-block operations, so it needs two blocks of scratch space. It can't use + // the block 0 as scratch space (see previous test) and it can't use the + // blocks 7 or 8 due the last move operation. + + aops.emplace_back(); + aops.back().name = base::StringPrintf("<bz-block-0>"); + aops.back().op.set_type(InstallOperation::REPLACE_BZ); + StoreExtents({ExtentForRange(0, 1)}, aops.back().op.mutable_dst_extents()); + + const size_t num_ops = 3; + for (size_t i = 0; i < num_ops; i++) { + AnnotatedOperation aop; + aop.name = base::StringPrintf("<op-%" PRIuS ">", i); + aop.op.set_type(InstallOperation::BSDIFF); + StoreExtents({ExtentForRange(1 + 2 * i, 2)}, aop.op.mutable_src_extents()); + StoreExtents({ExtentForRange(1 + 2 * ((i + 1) % num_ops), 2)}, + aop.op.mutable_dst_extents()); + aops.push_back(aop); + } + + { + AnnotatedOperation aop; + aop.name = "<op-shrink>"; + aop.op.set_type(InstallOperation::BSDIFF); + StoreExtents({ExtentForRange(8, 1)}, aop.op.mutable_src_extents()); + StoreExtents({ExtentForRange(7, 1)}, aop.op.mutable_dst_extents()); + aops.push_back(aop); + } + + PartitionConfig old_part("part"); + old_part.path = "/dev/zero"; + old_part.size = old_blocks * block_size; + + PartitionConfig new_part("part"); + new_part.path = "/dev/zero"; + new_part.size = new_blocks * block_size; + + CreateBlobFile(); + + EXPECT_TRUE(InplaceGenerator::ResolveReadAfterWriteDependencies( + old_part, + new_part, + (old_blocks + 2) * block_size, // enough scratch space. + block_size, + blob_file_.get(), + &aops)); + + size_t full_ops = 0; + for (const auto& aop : aops) { + if (diff_utils::IsAReplaceOperation(aop.op.type())) + full_ops++; + } + // There should be only one REPLACE* operation, the one we added for block 0. + EXPECT_EQ(1U, full_ops); + + // There should be only one MOVE operation, the one used to break the loop + // which should write to scratch space past the block 7 (the last block of the + // new partition) which is being written later. + size_t move_ops = 0; + for (const auto& aop : aops) { + if (aop.op.type() == InstallOperation::MOVE) { + move_ops++; + for (const Extent& extent : aop.op.dst_extents()) { + EXPECT_LE(7U, extent.start_block()) << "On dst extents for aop: " + << aop; + } + } + } + EXPECT_EQ(1U, move_ops); + + DumpAopsOnFailure(aops); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/operations_generator.h b/update_engine/payload_generator/operations_generator.h new file mode 100644 index 0000000..9127d7b --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/payload_file.cc b/update_engine/payload_generator/payload_file.cc new file mode 100644 index 0000000..2f95b21 --- /dev/null +++ b/update_engine/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) { + TEST_AND_RETURN_FALSE(config.version.Validate()); + major_version_ = config.version.major; + manifest_.set_minor_version(config.version.minor); + + if (!config.source.ImageInfoIsEmpty()) + *(manifest_.mutable_old_image_info()) = config.source.image_info; + + if (!config.target.ImageInfoIsEmpty()) + *(manifest_.mutable_new_image_info()) = config.target.image_info; + + manifest_.set_block_size(config.block_size); + 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); + partition->set_postinstall_optional(part.postinstall.optional); + } + 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/update_engine/payload_generator/payload_file.h b/update_engine/payload_generator/payload_file.h new file mode 100644 index 0000000..7cc792a --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/payload_file_unittest.cc b/update_engine/payload_generator/payload_file_unittest.cc new file mode 100644 index 0000000..e8e7e14 --- /dev/null +++ b/update_engine/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(2U, part0_aops.size()); + EXPECT_EQ(0U, part0_aops[0].op.data_offset()); + EXPECT_EQ(3U, part0_aops[0].op.data_length()); + EXPECT_EQ(3U, part0_aops[1].op.data_offset()); + EXPECT_EQ(1U, part0_aops[1].op.data_length()); + + EXPECT_EQ(1U, part1_aops.size()); + EXPECT_EQ(4U, part1_aops[0].op.data_offset()); + EXPECT_EQ(6U, part1_aops[0].op.data_length()); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/payload_generation_config.cc b/update_engine/payload_generator/payload_generation_config.cc new file mode 100644 index 0000000..38a72a9 --- /dev/null +++ b/update_engine/payload_generator/payload_generation_config.cc
@@ -0,0 +1,216 @@ +// +// 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/delta_diff_utils.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 && path.empty() && filesystem_type.empty() && !optional; +} + +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())); + return true; +} + +bool PartitionConfig::OpenFilesystem() { + if (path.empty()) + return true; + fs_interface.reset(); + if (diff_utils::IsExtFilesystem(path)) { + fs_interface = Ext2Filesystem::CreateFromFile(path); + // TODO(deymo): The delta generator algorithm doesn't support a block size + // different than 4 KiB. Remove this check once that's fixed. b/26972455 + if (fs_interface) + TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize); + } + + 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); + store.GetBoolean("POSTINSTALL_OPTIONAL_" + part.name, + &part.postinstall.optional); + } + 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(); +} + +PayloadVersion::PayloadVersion(uint64_t major_version, uint32_t minor_version) { + major = major_version; + minor = minor_version; +} + +bool PayloadVersion::Validate() const { + TEST_AND_RETURN_FALSE(major == kChromeOSMajorPayloadVersion || + major == kBrilloMajorPayloadVersion); + TEST_AND_RETURN_FALSE(minor == kFullPayloadMinorVersion || + minor == kInPlaceMinorPayloadVersion || + minor == kSourceMinorPayloadVersion || + minor == kOpSrcHashMinorPayloadVersion || + minor == kImgdiffMinorPayloadVersion); + return true; +} + +bool PayloadVersion::OperationAllowed(InstallOperation_Type operation) const { + switch (operation) { + // Full operations: + case InstallOperation::REPLACE: + case InstallOperation::REPLACE_BZ: + // These operations were included in the original payload format. + return true; + + case InstallOperation::REPLACE_XZ: + // These operations are included in the major version used in Brillo, but + // can also be used with minor version 3 or newer. + return major == kBrilloMajorPayloadVersion || + minor >= kOpSrcHashMinorPayloadVersion; + + case InstallOperation::ZERO: + case InstallOperation::DISCARD: + // The implementation of these operations had a bug in earlier versions + // that prevents them from being used in any payload. We will enable + // them for delta payloads for now. + return minor >= kImgdiffMinorPayloadVersion; + + // Delta operations: + case InstallOperation::MOVE: + case InstallOperation::BSDIFF: + // MOVE and BSDIFF were replaced by SOURCE_COPY and SOURCE_BSDIFF and + // should not be used in newer delta versions, since the idempotent checks + // were removed. + return minor == kInPlaceMinorPayloadVersion; + + case InstallOperation::SOURCE_COPY: + case InstallOperation::SOURCE_BSDIFF: + return minor >= kSourceMinorPayloadVersion; + + case InstallOperation::IMGDIFF: + return minor >= kImgdiffMinorPayloadVersion && imgdiff_allowed; + } + return false; +} + +bool PayloadVersion::IsDelta() const { + return minor != kFullPayloadMinorVersion; +} + +bool PayloadVersion::InplaceUpdate() const { + return minor == kInPlaceMinorPayloadVersion; +} + +bool PayloadGenerationConfig::Validate() const { + TEST_AND_RETURN_FALSE(version.Validate()); + TEST_AND_RETURN_FALSE(version.IsDelta() == is_delta); + 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()); + } + + // 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()); + } + + // 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 (version.minor == kInPlaceMinorPayloadVersion && + part.name == kLegacyPartitionNameRoot) + TEST_AND_RETURN_FALSE(rootfs_partition_size >= part.size); + if (version.major == 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/update_engine/payload_generator/payload_generation_config.h b/update_engine/payload_generator/payload_generation_config.h new file mode 100644 index 0000000..373c7cd --- /dev/null +++ b/update_engine/payload_generator/payload_generation_config.h
@@ -0,0 +1,192 @@ +// +// 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; + + // Whether this postinstall script should be ignored if it fails. + bool optional = false; +}; + +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; +}; + +struct PayloadVersion { + PayloadVersion() : PayloadVersion(0, 0) {} + PayloadVersion(uint64_t major_version, uint32_t minor_version); + + // Returns whether the PayloadVersion is valid. + bool Validate() const; + + // Return whether the passed |operation| is allowed by this payload. + bool OperationAllowed(InstallOperation_Type operation) const; + + // Whether this payload version is a delta payload. + bool IsDelta() const; + + // Tells whether the update is done in-place, that is, whether the operations + // read and write from the same partition. + bool InplaceUpdate() const; + + // The major version of the payload. + uint64_t major; + + // The minor version of the payload. + uint32_t minor; + + // Wheter the IMGDIFF operation is allowed based on the available compressor + // in the delta_generator and the one supported by the target. + bool imgdiff_allowed = false; +}; + +// 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/minor version of the payload. + PayloadVersion 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/update_engine/payload_generator/payload_generation_config_unittest.cc b/update_engine/payload_generator/payload_generation_config_unittest.cc new file mode 100644 index 0000000..3545056 --- /dev/null +++ b/update_engine/payload_generator/payload_generation_config_unittest.cc
@@ -0,0 +1,54 @@ +// +// 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\n" + "POSTINSTALL_OPTIONAL_root=true")); + 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); + EXPECT_TRUE(image_config.partitions[0].postinstall.optional); +} + +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/update_engine/payload_generator/payload_signer.cc b/update_engine/payload_generator/payload_signer.cc new file mode 100644 index 0000000..824195d --- /dev/null +++ b/update_engine/payload_generator/payload_signer.cc
@@ -0,0 +1,554 @@ +// +// 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_number_conversions.h> +#include <base/strings/string_split.h> +#include <base/strings/string_util.h> +#include <brillo/data_encoding.h> +#include <brillo/streams/file_stream.h> +#include <brillo/streams/stream.h> +#include <openssl/err.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; + + DeltaArchiveManifest manifest; + uint64_t metadata_size, major_version; + uint32_t metadata_signature_size; + TEST_AND_RETURN_FALSE( + PayloadSigner::LoadPayloadMetadata(payload_path, + nullptr, + &manifest, + &major_version, + &metadata_size, + &metadata_signature_size)); + + brillo::Blob payload; + TEST_AND_RETURN_FALSE(utils::ReadFile(payload_path, &payload)); + + 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(signatures_offset >= + metadata_size + metadata_signature_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::LoadPayloadMetadata(const string& payload_path, + brillo::Blob* out_payload_metadata, + DeltaArchiveManifest* out_manifest, + uint64_t* out_major_version, + uint64_t* out_metadata_size, + uint32_t* out_metadata_signature_size) { + brillo::StreamPtr payload_file = + brillo::FileStream::Open(base::FilePath(payload_path), + brillo::Stream::AccessMode::READ, + brillo::FileStream::Disposition::OPEN_EXISTING, + nullptr); + TEST_AND_RETURN_FALSE(payload_file); + brillo::Blob payload_metadata; + + payload_metadata.resize(DeltaPerformer::kMaxPayloadHeaderSize); + TEST_AND_RETURN_FALSE(payload_file->ReadAllBlocking( + payload_metadata.data(), payload_metadata.size(), nullptr)); + + const uint8_t* read_pointer = payload_metadata.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; + + uint64_t header_size = read_pointer - payload_metadata.data(); + uint64_t metadata_size = header_size + manifest_size; + if (out_metadata_size) + *out_metadata_size = metadata_size; + + size_t bytes_read = payload_metadata.size(); + payload_metadata.resize(metadata_size); + TEST_AND_RETURN_FALSE( + payload_file->ReadAllBlocking(payload_metadata.data() + bytes_read, + payload_metadata.size() - bytes_read, + nullptr)); + if (out_manifest) { + TEST_AND_RETURN_FALSE(out_manifest->ParseFromArray( + payload_metadata.data() + header_size, manifest_size)); + } + if (out_payload_metadata) + *out_payload_metadata = std::move(payload_metadata); + return true; +} + +bool PayloadSigner::VerifySignedPayload(const string& payload_path, + const string& public_key_path) { + DeltaArchiveManifest manifest; + uint64_t metadata_size; + uint32_t metadata_signature_size; + TEST_AND_RETURN_FALSE(LoadPayloadMetadata(payload_path, + nullptr, + &manifest, + nullptr, + &metadata_size, + &metadata_signature_size)); + brillo::Blob payload; + TEST_AND_RETURN_FALSE(utils::ReadFile(payload_path, &payload)); + 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; + // We expect unpadded SHA256 hash coming in + TEST_AND_RETURN_FALSE(hash.size() == 32); + brillo::Blob padded_hash(hash); + PayloadVerifier::PadRSA2048SHA256Hash(&padded_hash); + + // The code below executes the equivalent of: + // + // openssl rsautl -raw -sign -inkey |private_key_path| + // -in |padded_hash| -out |out_signature| + + FILE* fprikey = fopen(private_key_path.c_str(), "rb"); + TEST_AND_RETURN_FALSE(fprikey != nullptr); + RSA* rsa = PEM_read_RSAPrivateKey(fprikey, nullptr, nullptr, nullptr); + fclose(fprikey); + TEST_AND_RETURN_FALSE(rsa != nullptr); + brillo::Blob signature(RSA_size(rsa)); + ssize_t signature_size = RSA_private_encrypt(padded_hash.size(), + padded_hash.data(), + signature.data(), + rsa, + RSA_NO_PADDING); + RSA_free(rsa); + if (signature_size < 0) { + LOG(ERROR) << "Signing hash failed: " + << ERR_error_string(ERR_get_error(), nullptr); + return false; + } + TEST_AND_RETURN_FALSE(static_cast<size_t>(signature_size) == + signature.size()); + out_signature->swap(signature); + return true; +} + +bool PayloadSigner::SignHashWithKeys(const brillo::Blob& hash_data, + const vector<string>& private_key_paths, + brillo::Blob* out_signature_blob) { + 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; +} + +bool PayloadSigner::ExtractPayloadProperties( + const string& payload_path, brillo::KeyValueStore* properties) { + DeltaArchiveManifest manifest; + brillo::Blob payload_metadata; + uint64_t major_version, metadata_size; + uint32_t metadata_signature_size; + uint64_t file_size = utils::FileSize(payload_path); + + TEST_AND_RETURN_FALSE( + PayloadSigner::LoadPayloadMetadata(payload_path, + &payload_metadata, + &manifest, + &major_version, + &metadata_size, + &metadata_signature_size)); + + properties->SetString(kPayloadPropertyFileSize, std::to_string(file_size)); + properties->SetString(kPayloadPropertyMetadataSize, + std::to_string(metadata_size)); + + brillo::Blob file_hash, metadata_hash; + TEST_AND_RETURN_FALSE( + HashCalculator::RawHashOfFile(payload_path, file_size, &file_hash) == + static_cast<off_t>(file_size)); + TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfBytes( + payload_metadata.data(), payload_metadata.size(), &metadata_hash)); + + properties->SetString(kPayloadPropertyFileHash, + brillo::data_encoding::Base64Encode(file_hash)); + properties->SetString(kPayloadPropertyMetadataHash, + brillo::data_encoding::Base64Encode(metadata_hash)); + return true; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/payload_signer.h b/update_engine/payload_generator/payload_signer.h new file mode 100644 index 0000000..00e32fa --- /dev/null +++ b/update_engine/payload_generator/payload_signer.h
@@ -0,0 +1,147 @@ +// +// 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/key_value_store.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 metadata from the given |payload_path| into the + // |out_payload_metadata| vector if not null. 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. Returns + // whether a valid payload metadata was found and parsed. + static bool LoadPayloadMetadata(const std::string& payload_path, + brillo::Blob* out_payload_metadata, + 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); + + static bool ExtractPayloadProperties(const std::string& payload_path, + brillo::KeyValueStore* properties); + + 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/update_engine/payload_generator/payload_signer_unittest.cc b/update_engine/payload_generator/payload_signer_unittest.cc new file mode 100644 index 0000000..62b6e7a --- /dev/null +++ b/update_engine/payload_generator/payload_signer_unittest.cc
@@ -0,0 +1,264 @@ +// +// 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/test_utils.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 chromeos_update_engine::test_utils::GetBuildArtifactsPath; +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, 0U); + brillo::Blob hash_blob; + EXPECT_TRUE(HashCalculator::RawHashOfBytes(data_blob.data(), + data_blob.size(), + &hash_blob)); + EXPECT_TRUE(PayloadSigner::SignHashWithKeys( + hash_blob, + private_keys, + out_signature_blob)); + EXPECT_EQ(length, out_signature_blob->size()); +} +} // 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_metadata_blob; + DeltaArchiveManifest manifest; + uint64_t load_metadata_size, load_major_version; + EXPECT_TRUE(PayloadSigner::LoadPayloadMetadata(payload_path, + &payload_metadata_blob, + &manifest, + &load_major_version, + &load_metadata_size, + nullptr)); + EXPECT_EQ(metadata_size, payload_metadata_blob.size()); + EXPECT_EQ(config.version.major, 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.version.major = kChromeOSMajorPayloadVersion; + DoWriteAndLoadPayloadTest(config); +} + +TEST_F(PayloadSignerTest, LoadPayloadV2Test) { + PayloadGenerationConfig config; + config.version.major = kBrilloMajorPayloadVersion; + DoWriteAndLoadPayloadTest(config); +} + +TEST_F(PayloadSignerTest, SignSimpleTextTest) { + brillo::Blob signature_blob; + SignSampleData(&signature_blob, + {GetBuildArtifactsPath(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(1U, 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, + {GetBuildArtifactsPath(kUnittestPrivateKeyPath), + GetBuildArtifactsPath(kUnittestPrivateKey2Path)}); + + // Either public key should pass the verification. + EXPECT_TRUE(PayloadVerifier::VerifySignature( + signature_blob, + GetBuildArtifactsPath(kUnittestPublicKeyPath), + padded_hash_data_)); + EXPECT_TRUE(PayloadVerifier::VerifySignature( + signature_blob, + GetBuildArtifactsPath(kUnittestPublicKey2Path), + padded_hash_data_)); +} + +TEST_F(PayloadSignerTest, VerifySignatureTest) { + brillo::Blob signature_blob; + SignSampleData(&signature_blob, + {GetBuildArtifactsPath(kUnittestPrivateKeyPath)}); + + EXPECT_TRUE(PayloadVerifier::VerifySignature( + signature_blob, + GetBuildArtifactsPath(kUnittestPublicKeyPath), + padded_hash_data_)); + // Passing the invalid key should fail the verification. + EXPECT_FALSE(PayloadVerifier::VerifySignature( + signature_blob, + GetBuildArtifactsPath(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.version.major = 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", + GetBuildArtifactsPath(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.version.major = kBrilloMajorPayloadVersion; + PayloadFile payload; + EXPECT_TRUE(payload.Init(config)); + uint64_t metadata_size; + EXPECT_TRUE( + payload.WritePayload(payload_path, + "/dev/null", + GetBuildArtifactsPath(kUnittestPrivateKeyPath), + &metadata_size)); + EXPECT_TRUE(PayloadSigner::VerifySignedPayload( + payload_path, GetBuildArtifactsPath(kUnittestPublicKeyPath))); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/raw_filesystem.cc b/update_engine/payload_generator/raw_filesystem.cc new file mode 100644 index 0000000..2fb1400 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/raw_filesystem.h b/update_engine/payload_generator/raw_filesystem.h new file mode 100644 index 0000000..0aecd81 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/tarjan.cc b/update_engine/payload_generator/tarjan.cc new file mode 100644 index 0000000..98e29f9 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/tarjan.h b/update_engine/payload_generator/tarjan.h new file mode 100644 index 0000000..50cf563 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/tarjan_unittest.cc b/update_engine/payload_generator/tarjan_unittest.cc new file mode 100644 index 0000000..c29cbdc --- /dev/null +++ b/update_engine/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(5U, 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(1U, 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(2U, 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/update_engine/payload_generator/topological_sort.cc b/update_engine/payload_generator/topological_sort.cc new file mode 100644 index 0000000..f164336 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/topological_sort.h b/update_engine/payload_generator/topological_sort.h new file mode 100644 index 0000000..461cbe1 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/topological_sort_unittest.cc b/update_engine/payload_generator/topological_sort_unittest.cc new file mode 100644 index 0000000..1d866a7 --- /dev/null +++ b/update_engine/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/update_engine/payload_generator/xz.h b/update_engine/payload_generator/xz.h new file mode 100644 index 0000000..6e0a26c --- /dev/null +++ b/update_engine/payload_generator/xz.h
@@ -0,0 +1,34 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_XZ_H_ +#define UPDATE_ENGINE_PAYLOAD_GENERATOR_XZ_H_ + +#include <brillo/secure_blob.h> + +namespace chromeos_update_engine { + +// Initialize the xz compression unit. Call once before any call to +// XzCompress(). +void XzCompressInit(); + +// Compresses the input buffer |in| into |out| with xz. The compressed stream +// will be the equivalent of running xz -9 --check=none +bool XzCompress(const brillo::Blob& in, brillo::Blob* out); + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_XZ_H_
diff --git a/update_engine/payload_generator/xz_android.cc b/update_engine/payload_generator/xz_android.cc new file mode 100644 index 0000000..f3b836d --- /dev/null +++ b/update_engine/payload_generator/xz_android.cc
@@ -0,0 +1,116 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_generator/xz.h" + +#include <7zCrc.h> +#include <Xz.h> +#include <XzEnc.h> + +#include <algorithm> + +#include <base/logging.h> + +namespace { + +bool xz_initialized = false; + +// An ISeqInStream implementation that reads all the data from the passed Blob. +struct BlobReaderStream : public ISeqInStream { + explicit BlobReaderStream(const brillo::Blob& data) : data_(data) { + Read = &BlobReaderStream::ReadStatic; + } + + static SRes ReadStatic(void* p, void* buf, size_t* size) { + auto* self = + static_cast<BlobReaderStream*>(reinterpret_cast<ISeqInStream*>(p)); + *size = std::min(*size, self->data_.size() - self->pos_); + memcpy(buf, self->data_.data() + self->pos_, *size); + self->pos_ += *size; + return SZ_OK; + } + + const brillo::Blob& data_; + + // The current reader position. + size_t pos_ = 0; +}; + +// An ISeqOutStream implementation that writes all the data to the passed Blob. +struct BlobWriterStream : public ISeqOutStream { + explicit BlobWriterStream(brillo::Blob* data) : data_(data) { + Write = &BlobWriterStream::WriteStatic; + } + + static size_t WriteStatic(void* p, const void* buf, size_t size) { + auto* self = + static_cast<BlobWriterStream*>(reinterpret_cast<ISeqOutStream*>(p)); + const uint8_t* buffer = reinterpret_cast<const uint8_t*>(buf); + self->data_->reserve(self->data_->size() + size); + self->data_->insert(self->data_->end(), buffer, buffer + size); + return size; + } + + brillo::Blob* data_; +}; + +} // namespace + +namespace chromeos_update_engine { + +void XzCompressInit() { + if (xz_initialized) + return; + xz_initialized = true; + // Although we don't include a CRC32 for the stream, the xz file header has + // a CRC32 of the header itself, which required the CRC table to be + // initialized. + CrcGenerateTable(); +} + +bool XzCompress(const brillo::Blob& in, brillo::Blob* out) { + CHECK(xz_initialized) << "Initialize XzCompress first"; + out->clear(); + if (in.empty()) + return true; + + // Xz compression properties. + CXzProps props; + XzProps_Init(&props); + // No checksum in the xz stream. xz-embedded (used by the decompressor) only + // supports CRC32, but we already check the sha-1 of the whole blob during + // payload application. + props.checkId = XZ_CHECK_NO; + + // LZMA2 compression properties. + CLzma2EncProps lzma2Props; + props.lzma2Props = &lzma2Props; + Lzma2EncProps_Init(&lzma2Props); + // LZMA compression "level 6" requires 9 MB of RAM to decompress in the worst + // case. + lzma2Props.lzmaProps.level = 6; + lzma2Props.lzmaProps.numThreads = 1; + // The input size data is used to reduce the dictionary size if possible. + lzma2Props.lzmaProps.reduceSize = in.size(); + Lzma2EncProps_Normalize(&lzma2Props); + + BlobWriterStream out_writer(out); + BlobReaderStream in_reader(in); + SRes res = Xz_Encode(&out_writer, &in_reader, &props, nullptr /* progress */); + return res == SZ_OK; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/xz_chromeos.cc b/update_engine/payload_generator/xz_chromeos.cc new file mode 100644 index 0000000..a8cda4e --- /dev/null +++ b/update_engine/payload_generator/xz_chromeos.cc
@@ -0,0 +1,28 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_generator/xz.h" + +namespace chromeos_update_engine { + +void XzCompressInit() {} + +bool XzCompress(const brillo::Blob& in, brillo::Blob* out) { + // No Xz compressor implementation in Chrome OS delta_generator builds. + return false; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_generator/zip_unittest.cc b/update_engine/payload_generator/zip_unittest.cc new file mode 100644 index 0000000..54adfcb --- /dev/null +++ b/update_engine/payload_generator/zip_unittest.cc
@@ -0,0 +1,172 @@ +// +// 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 <brillo/make_unique_ptr.h> +#include <gtest/gtest.h> + +#include "update_engine/common/test_utils.h" +#include "update_engine/payload_consumer/bzip_extent_writer.h" +#include "update_engine/payload_consumer/extent_writer.h" +#include "update_engine/payload_consumer/xz_extent_writer.h" +#include "update_engine/payload_generator/bzip.h" +#include "update_engine/payload_generator/xz.h" + +using chromeos_update_engine::test_utils::kRandomString; +using std::string; +using std::vector; + +namespace chromeos_update_engine { + +namespace { + +// ExtentWriter class that writes to memory, used to test the decompression +// step with the corresponding extent writer. +class MemoryExtentWriter : public ExtentWriter { + public: + // Creates the ExtentWriter that will write all the bytes to the passed |data| + // blob. + explicit MemoryExtentWriter(brillo::Blob* data) : data_(data) { + data_->clear(); + } + ~MemoryExtentWriter() override = default; + + bool Init(FileDescriptorPtr fd, + const vector<Extent>& extents, + uint32_t block_size) override { + return true; + } + bool Write(const void* bytes, size_t count) override { + data_->reserve(data_->size() + count); + data_->insert(data_->end(), + static_cast<const uint8_t*>(bytes), + static_cast<const uint8_t*>(bytes) + count); + return true; + } + bool EndImpl() override { return true; } + + private: + brillo::Blob* data_; +}; + +template <typename W> +bool DecompressWithWriter(const brillo::Blob& in, brillo::Blob* out) { + std::unique_ptr<ExtentWriter> writer( + new W(brillo::make_unique_ptr(new MemoryExtentWriter(out)))); + // Init() parameters are ignored by the testing MemoryExtentWriter. + bool ok = writer->Init(nullptr, {}, 1); + ok = writer->Write(in.data(), in.size()) && ok; + // Call End() even if the Write failed. + ok = writer->End() && ok; + return ok; +} + +} // namespace + +template <typename T> +class ZipTest : public ::testing::Test { + public: + bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const = 0; + bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const = 0; +}; + +class BzipTest {}; + +template <> +class ZipTest<BzipTest> : public ::testing::Test { + public: + bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const { + return BzipCompress(in, out); + } + bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const { + return DecompressWithWriter<BzipExtentWriter>(in, out); + } +}; + +class XzTest {}; + +template <> +class ZipTest<XzTest> : public ::testing::Test { + public: + bool ZipCompress(const brillo::Blob& in, brillo::Blob* out) const { + return XzCompress(in, out); + } + bool ZipDecompress(const brillo::Blob& in, brillo::Blob* out) const { + return DecompressWithWriter<XzExtentWriter>(in, out); + } +}; + +#ifdef __ANDROID__ +typedef ::testing::Types<BzipTest, XzTest> ZipTestTypes; +#else +// Chrome OS implementation of Xz compressor just returns false. +typedef ::testing::Types<BzipTest> ZipTestTypes; +#endif // __ANDROID__ + +TYPED_TEST_CASE(ZipTest, ZipTestTypes); + +TYPED_TEST(ZipTest, SimpleTest) { + string in_str( + "this should compress well xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + brillo::Blob in(in_str.begin(), in_str.end()); + brillo::Blob out; + EXPECT_TRUE(this->ZipCompress(in, &out)); + EXPECT_LT(out.size(), in.size()); + EXPECT_GT(out.size(), 0U); + 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) { + brillo::Blob in(std::begin(kRandomString), std::end(kRandomString)); + brillo::Blob out; + EXPECT_TRUE(this->ZipCompress(in, &out)); + EXPECT_GT(out.size(), in.size()); + brillo::Blob decompressed; + EXPECT_TRUE(this->ZipDecompress(out, &decompressed)); + EXPECT_EQ(in.size(), decompressed.size()); + EXPECT_EQ(in, decompressed); +} + +TYPED_TEST(ZipTest, MalformedZipTest) { + brillo::Blob in(std::begin(kRandomString), std::end(kRandomString)); + brillo::Blob out; + EXPECT_FALSE(this->ZipDecompress(in, &out)); +} + +TYPED_TEST(ZipTest, EmptyInputsTest) { + brillo::Blob in; + brillo::Blob out; + EXPECT_TRUE(this->ZipDecompress(in, &out)); + EXPECT_EQ(0U, out.size()); + + EXPECT_TRUE(this->ZipCompress(in, &out)); + EXPECT_EQ(0U, out.size()); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_properties.h b/update_engine/payload_properties.h new file mode 100644 index 0000000..05c60f0 --- /dev/null +++ b/update_engine/payload_properties.h
@@ -0,0 +1,13 @@ +#ifndef PAYLOAD_PROPERTIES_H_ +#define PAYLOAD_PROPERTIES_H_ + +#include "update_engine/omaha_response.h" + +namespace chromeos_update_engine { + +// reuse existing response structure from Omaha. +using PayloadProperties = OmahaResponse; + +}; // namespace chromeos_update_engine + +#endif // PAYLOAD_PROPERTIES_H_
diff --git a/update_engine/payload_properties_handler_action.cc b/update_engine/payload_properties_handler_action.cc new file mode 100644 index 0000000..74150df --- /dev/null +++ b/update_engine/payload_properties_handler_action.cc
@@ -0,0 +1,58 @@ +#include "update_engine/payload_properties_handler_action.h" +#include "update_engine/payload_state.h" + +namespace chromeos_update_engine { + +PayloadPropertiesHandlerAction::PayloadPropertiesHandlerAction(SystemState* system_state) + : system_state_(system_state) +{ +} + +PayloadPropertiesHandlerAction::~PayloadPropertiesHandlerAction() +{ +} + +void PayloadPropertiesHandlerAction::PerformAction() +{ + // Get the InstallPlan and read it + CHECK(HasInputObject()); + const PayloadProperties &payload_prop = GetInputObject(); + + LOG(INFO) << "Update URL " << system_state_->payload_state()->GetCurrentUrl();; + + install_plan_.download_url = system_state_->payload_state()->GetCurrentUrl();; + install_plan_.payload_type = InstallPayloadType::kFull; + + install_plan_.source_slot = system_state_->boot_control()->GetCurrentSlot(); + if (install_plan_.source_slot == BootControlInterface::kInvalidSlot) + { + LOG(ERROR) << "Current slot is invalid! Exit with error."; + processor_->ActionComplete(this, ErrorCode::kError); + return; + } + + if (!system_state_->boot_control()->IsSlotBootable(install_plan_.source_slot)) + { + LOG(ERROR) << "Current slot " << system_state_->boot_control() + << " is not bootable. Exit with error."; + processor_->ActionComplete(this, ErrorCode::kError); + return; + } + + install_plan_.target_slot = (install_plan_.source_slot + 1) % system_state_->boot_control()->GetNumSlots(); + + install_plan_.hash_checks_mandatory = true; + install_plan_.powerwash_required = false; + install_plan_.version = payload_prop.version; + install_plan_.payload_size = payload_prop.size; + install_plan_.payload_hash = payload_prop.hash; + install_plan_.metadata_size = payload_prop.metadata_size; + install_plan_.is_resume = false; + if (HasOutputPipe()) + SetOutputObject(install_plan_); + + LOG(INFO) << "Using this install plan:"; + install_plan_.Dump(); + processor_->ActionComplete(this, ErrorCode::kSuccess); +} +}; // chromeos_update_engine
diff --git a/update_engine/payload_properties_handler_action.h b/update_engine/payload_properties_handler_action.h new file mode 100644 index 0000000..c42f422 --- /dev/null +++ b/update_engine/payload_properties_handler_action.h
@@ -0,0 +1,55 @@ +#ifndef PAYLOAD_PROPERTIES_HANDLER_ACTION_H_ +#define PAYLOAD_PROPERTIES_HANDLER_ACTION_H_ + +#include "update_engine/common/action.h" +#include "update_engine/system_state.h" +#include "update_engine/payload_consumer/install_plan.h" +#include "update_engine/payload_properties.h" + +// This class reads in Payload properties and converts what it sees into +// an install plan which is passed out. + +namespace chromeos_update_engine { + +class PayloadPropertiesHandlerAction; + +template<> +class ActionTraits<PayloadPropertiesHandlerAction> { + public: + // Takes parameters on the input pipe. + typedef PayloadProperties InputObjectType; + typedef InstallPlan OutputObjectType; +}; + +class PayloadPropertiesHandlerAction : public Action<PayloadPropertiesHandlerAction> +{ + public: + + PayloadPropertiesHandlerAction(SystemState* system_state); + ~PayloadPropertiesHandlerAction() override; + + typedef ActionTraits<PayloadPropertiesHandlerAction>::InputObjectType InputObjectType; + typedef ActionTraits<PayloadPropertiesHandlerAction>::OutputObjectType OutputObjectType; + + void PerformAction() override; + + const InstallPlan& install_plan() const { return install_plan_; } + + // Debugging/logging + static std::string StaticType() { return "PayloadPropertiesHandlerAction"; } + std::string Type() const override { return StaticType(); } + + private: + + // Global system context. + SystemState* system_state_; + + // The install plan, if we have an update. + InstallPlan install_plan_; + + DISALLOW_COPY_AND_ASSIGN(PayloadPropertiesHandlerAction); +}; + +} // namespace chromeos_update_engine + +#endif // PAYLOAD_PROPERTIES_HANDLER_ACTION_H_
diff --git a/update_engine/payload_properties_request_action.cc b/update_engine/payload_properties_request_action.cc new file mode 100644 index 0000000..cf0aa6c --- /dev/null +++ b/update_engine/payload_properties_request_action.cc
@@ -0,0 +1,201 @@ +// +// 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/string_split.h> +#include <base/strings/stringprintf.h> +#include <base/strings/string_number_conversions.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_interface.h" +#include "update_engine/metrics.h" +#include "update_engine/metrics_utils.h" +#include "update_engine/p2p_manager.h" +#include "update_engine/payload_state_interface.h" +#include "update_engine/payload_properties_request_action.h" + +#define HAS_KEY(x, y) ((x).find((y)) != (x).end()) + +namespace chromeos_update_engine { + +using std::string; +using std::pair; +using std::vector; + +PayloadPropertiesRequestAction::PayloadPropertiesRequestAction( + SystemState* system_state, + PayloadProperties *properties, + HttpFetcher *http_fetcher) + : system_state_(system_state), + properties_(properties), + http_fetcher_(http_fetcher) +{ +} + +PayloadPropertiesRequestAction::~PayloadPropertiesRequestAction() {} + +void PayloadPropertiesRequestAction::PerformAction() { + http_fetcher_->set_delegate(this); + + // we support only one URL for now + CHECK(properties_->payload_urls.size() == 1); + + LOG(INFO) << "URL: " << properties_->payload_urls[0]; + http_fetcher_->BeginTransfer(properties_->payload_urls[0]); +} + +void PayloadPropertiesRequestAction::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 PayloadPropertiesRequestAction::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); +} + +// 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 PayloadPropertiesRequestAction::TransferComplete(HttpFetcher *fetcher, + bool successful) { + ScopedActionCompleter completer(processor_, this); + + if (!successful) { + LOG(ERROR) << "Payload properties 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::kHTTPResponseBase) + code)); + return; + } + + auto prop_end = std::find(response_buffer_.begin(), response_buffer_.end(), 0); + vector<pair<string, string>> kv_pairs; + std::map<string, string> kv_map; + if (!base::SplitStringIntoKeyValuePairs(string(response_buffer_.begin(), prop_end), + ' ', '\n', &kv_pairs)) + { + LOG(ERROR) << "Can't parse Payload properties"; + return; + } + std::copy(kv_pairs.begin(), kv_pairs.end(), + std::inserter(kv_map, kv_map.begin())); + + if (!HAS_KEY(kv_map, "FILE_SIZE:") || + !base::StringToInt64(kv_map.at("FILE_SIZE:"), &properties_->size)) + { + LOG(ERROR) << "Can't get FILE_SIZE from Payload properties: "; + return; + } + if (!HAS_KEY(kv_map, "METADATA_SIZE:") || + !base::StringToInt64(kv_map.at("METADATA_SIZE:"), &properties_->metadata_size)) + { + LOG(ERROR) << "Can't get METADATA_SIZE from Payload properties: "; + return; + } + if (!HAS_KEY(kv_map, "FILE_HASH:")) + { + LOG(ERROR) << "Can't get FILE_HASH from Payload properties: "; + return; + } + properties_->hash = kv_map["FILE_HASH:"]; + if (!HAS_KEY(kv_map, "Larry-app-version:")) + { + LOG(ERROR) << "Can't get Larry-app-version from Payload properties: "; + return; + } + properties_->version = kv_map["Larry-app-version:"]; + + properties_->update_exists = true; + + system_state_->payload_state()->SetResponse(*properties_); + + if (HasOutputPipe()) + SetOutputObject(*properties_); + completer.set_code(ErrorCode::kSuccess); + + // TODO add more properties to be able to recover update after crash or reboot +} + +void PayloadPropertiesRequestAction::ActionCompleted(ErrorCode code) +{ + 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()) { + result = metrics::CheckResult::kUpdateAvailable; + reaction = metrics::CheckReaction::kUpdating; + } else { + result = metrics::CheckResult::kNoUpdateAvailable; + } + 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); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/payload_properties_request_action.h b/update_engine/payload_properties_request_action.h new file mode 100644 index 0000000..b986515 --- /dev/null +++ b/update_engine/payload_properties_request_action.h
@@ -0,0 +1,97 @@ +// +// 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_PROPERTIES_REQUEST_ACTION_H_ +#define UPDATE_ENGINE_PAYLOAD_PROPERTIES_REQUEST_ACTION_H_ + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <map> +#include <memory> +#include <string> +#include <vector> + +#include <gtest/gtest_prod.h> // for FRIEND_TEST + +#include <brillo/secure_blob.h> +#include <curl/curl.h> + +#include "update_engine/common/action.h" +#include "update_engine/common/http_fetcher.h" +#include "update_engine/payload_properties.h" +#include "update_engine/system_state.h" + +// The Payload Properties action downloads properties header and outputs +// properties on the output ActionPipe. + +namespace chromeos_update_engine { + +class PayloadPropertiesRequestAction; + +template<> +class ActionTraits<PayloadPropertiesRequestAction> { + public: + // Takes parameters on the input pipe. + typedef NoneType InputObjectType; + typedef PayloadProperties OutputObjectType; +}; + + +class PayloadPropertiesRequestAction : public Action<PayloadPropertiesRequestAction>, + public HttpFetcherDelegate { + public: + PayloadPropertiesRequestAction(SystemState* system_state, + PayloadProperties *properties, + HttpFetcher *http_fetcher); + ~PayloadPropertiesRequestAction() override; + typedef ActionTraits<PayloadPropertiesRequestAction>::InputObjectType InputObjectType; + typedef ActionTraits<PayloadPropertiesRequestAction>::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 "PayloadPropertiesRequestAction"; } + 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; + +private: + // Global system context. + SystemState* system_state_; + + PayloadProperties * properties_; + + // pointer to the HttpFetcher that does the http work + std::unique_ptr<HttpFetcher> http_fetcher_; + + // Stores the response from the omaha server + brillo::Blob response_buffer_; + + DISALLOW_COPY_AND_ASSIGN(PayloadPropertiesRequestAction); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_PROPERTIES_REQUEST_ACTION_H_
diff --git a/update_engine/payload_state.cc b/update_engine/payload_state.cc new file mode 100644 index 0000000..1da472f --- /dev/null +++ b/update_engine/payload_state.cc
@@ -0,0 +1,1397 @@ +// +// 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/error_code_utils.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/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_error_code_(ErrorCode::kSuccess), + 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; + ConnectionType network_connection_type; + ConnectionTethering 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; + } + attempt_error_code_ = ErrorCode::kSuccess; + + // 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::ErrorCodeToString(base_error) << ")"; + attempt_error_code_ = 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::kNonCriticalUpdateInOOBE: + case ErrorCode::kOmahaUpdateDeferredForBackoff: + case ErrorCode::kPostinstallPowerwashError: + case ErrorCode::kUpdateCanceledByChannelChange: + case ErrorCode::kOmahaRequestXMLHasEntityDecl: + case ErrorCode::kFilesystemVerifierError: + case ErrorCode::kUserCanceled: + 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::StartsWith(current_url, "https://", + base::CompareCase::INSENSITIVE_ASCII)) { + current_download_source_ = kDownloadSourceHttpsServer; + } else if (base::StartsWith(current_url, "http://", + base::CompareCase::INSENSITIVE_ASCII)) { + 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::kUpdateCanceled: + 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::StartsWith(candidate_url, "http://", + base::CompareCase::INSENSITIVE_ASCII) && + !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/update_engine/payload_state.h b/update_engine/payload_state.h new file mode 100644 index 0000000..46711b6 --- /dev/null +++ b/update_engine/payload_state.h
@@ -0,0 +1,580 @@ +// +// 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_; + } + + inline ErrorCode GetAttemptErrorCode() const override { + return attempt_error_code_; + } + + 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_; + + // The attempt error code when the attempt finished. + ErrorCode attempt_error_code_; + + // 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/update_engine/payload_state_interface.h b/update_engine/payload_state_interface.h new file mode 100644 index 0000000..68798ee --- /dev/null +++ b/update_engine/payload_state_interface.h
@@ -0,0 +1,200 @@ +// +// 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; + virtual ErrorCode GetAttemptErrorCode() const = 0; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_STATE_INTERFACE_H_
diff --git a/update_engine/payload_state_unittest.cc b/update_engine/payload_state_unittest.cc new file mode 100644 index 0000000..b671722 --- /dev/null +++ b/update_engine/payload_state_unittest.cc
@@ -0,0 +1,1652 @@ +// +// 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, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(0U, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(0U, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(0U, 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(3U, 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(1U, 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(1U, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(0U, payload_state.GetUrlSwitchCount()); + EXPECT_EQ(0U, + payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer)); + EXPECT_EQ(0U, + payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer)); + EXPECT_EQ( + 0U, payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpsServer)); + EXPECT_EQ(0U, + 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(1U, 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(1U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(1U, 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(2U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(1U, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(2U, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(3U, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(4U, 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(1U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(4U, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(4U, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(0U, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(0U, 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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(0U, 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(1U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(1U, payload_state.GetUrlSwitchCount()); + + // Now, simulate a corrupted url index on persisted store which gets + // loaded when update_engine restarts. Using a different prefs object + // so as to not bother accounting for the uninteresting calls above. + FakeSystemState fake_system_state2; + NiceMock<MockPrefs>* prefs2 = fake_system_state2.mock_prefs(); + EXPECT_CALL(*prefs2, Exists(_)).WillRepeatedly(Return(true)); + EXPECT_CALL(*prefs2, GetInt64(_, _)).Times(AtLeast(1)); + EXPECT_CALL(*prefs2, GetInt64(kPrefsPayloadAttemptNumber, _)) + .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(0U, payload_state.GetUrlFailureCount()); + EXPECT_EQ(0U, 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; + uint64_t https_total = 0; + uint64_t 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. + uint64_t 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. + uint64_t 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(0U, 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. + uint64_t 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); + uint64_t third_chunk = 32345678; + uint64_t 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); + uint64_t 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(0U, + payload_state.GetCurrentBytesDownloaded(kDownloadSourceHttpServer)); + EXPECT_EQ(0U, + payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpServer)); + EXPECT_EQ(0U, payload_state.GetCurrentBytesDownloaded( + kDownloadSourceHttpsServer)); + EXPECT_EQ(0U, + 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. + uint64_t 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); + + uint64_t 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(0U, payload_state.GetCurrentBytesDownloaded( + kDownloadSourceHttpsServer)); + EXPECT_EQ(0U, + payload_state.GetTotalBytesDownloaded(kDownloadSourceHttpsServer)); + + payload_state.UpdateRestarted(); + // Make sure the current bytes downloaded is reset, but not the total bytes. + EXPECT_EQ(0U, + 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(0U, payload_state.GetNumReboots()); + + fake_system_state.set_system_rebooted(true); + payload_state.UpdateResumed(); + // Num reboots should be incremented because system rebooted detected. + EXPECT_EQ(1U, payload_state.GetNumReboots()); + + fake_system_state.set_system_rebooted(false); + payload_state.UpdateResumed(); + // Num reboots should now be 1 as reboot was not detected. + EXPECT_EQ(1U, payload_state.GetNumReboots()); + + // Restart the update again to verify we set the num of reboots back to 0. + payload_state.UpdateRestarted(); + EXPECT_EQ(0U, 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(0U, 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(0U, 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(1U, payload_state.GetUrlSwitchCount()); + EXPECT_EQ(0U, 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/update_engine/power_manager_android.cc b/update_engine/power_manager_android.cc new file mode 100644 index 0000000..6b7e880 --- /dev/null +++ b/update_engine/power_manager_android.cc
@@ -0,0 +1,34 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/power_manager_android.h" + +#include <base/logging.h> + +namespace chromeos_update_engine { + +namespace power_manager { +std::unique_ptr<PowerManagerInterface> CreatePowerManager() { + return std::unique_ptr<PowerManagerInterface>(new PowerManagerAndroid()); +} +} + +bool PowerManagerAndroid::RequestReboot() { + LOG(WARNING) << "PowerManager not implemented."; + return false; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/power_manager_android.h b/update_engine/power_manager_android.h new file mode 100644 index 0000000..86399ab --- /dev/null +++ b/update_engine/power_manager_android.h
@@ -0,0 +1,40 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_POWER_MANAGER_ANDROID_H_ +#define UPDATE_ENGINE_POWER_MANAGER_ANDROID_H_ + +#include <base/macros.h> + +#include "update_engine/power_manager_interface.h" + +namespace chromeos_update_engine { + +class PowerManagerAndroid : public PowerManagerInterface { + public: + PowerManagerAndroid() = default; + ~PowerManagerAndroid() override = default; + + // PowerManagerInterface overrides. + bool RequestReboot() override; + + private: + DISALLOW_COPY_AND_ASSIGN(PowerManagerAndroid); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_POWER_MANAGER_ANDROID_H_
diff --git a/update_engine/power_manager_chromeos.cc b/update_engine/power_manager_chromeos.cc new file mode 100644 index 0000000..e175f95 --- /dev/null +++ b/update_engine/power_manager_chromeos.cc
@@ -0,0 +1,43 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/power_manager_chromeos.h" + +#include <power_manager/dbus-constants.h> +#include <power_manager/dbus-proxies.h> + +#include "update_engine/dbus_connection.h" + +namespace chromeos_update_engine { + +namespace power_manager { +std::unique_ptr<PowerManagerInterface> CreatePowerManager() { + return std::unique_ptr<PowerManagerInterface>(new PowerManagerChromeOS()); +} +} + +PowerManagerChromeOS::PowerManagerChromeOS() + : power_manager_proxy_(DBusConnection::Get()->GetDBus()) {} + +bool PowerManagerChromeOS::RequestReboot() { + LOG(INFO) << "Calling " << ::power_manager::kPowerManagerInterface << "." + << ::power_manager::kRequestRestartMethod; + brillo::ErrorPtr error; + return power_manager_proxy_.RequestRestart( + ::power_manager::REQUEST_RESTART_FOR_UPDATE, &error); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/power_manager_chromeos.h b/update_engine/power_manager_chromeos.h new file mode 100644 index 0000000..ad49889 --- /dev/null +++ b/update_engine/power_manager_chromeos.h
@@ -0,0 +1,44 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_POWER_MANAGER_H_ +#define UPDATE_ENGINE_POWER_MANAGER_H_ + +#include <base/macros.h> +#include <power_manager/dbus-proxies.h> + +#include "update_engine/power_manager_interface.h" + +namespace chromeos_update_engine { + +class PowerManagerChromeOS : public PowerManagerInterface { + public: + PowerManagerChromeOS(); + ~PowerManagerChromeOS() override = default; + + // PowerManagerInterface overrides. + bool RequestReboot() override; + + private: + // Real DBus proxy using the DBus connection. + org::chromium::PowerManagerProxy power_manager_proxy_; + + DISALLOW_COPY_AND_ASSIGN(PowerManagerChromeOS); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_POWER_MANAGER_H_
diff --git a/update_engine/power_manager_interface.h b/update_engine/power_manager_interface.h new file mode 100644 index 0000000..be059ec --- /dev/null +++ b/update_engine/power_manager_interface.h
@@ -0,0 +1,47 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_POWER_MANAGER_INTERFACE_H_ +#define UPDATE_ENGINE_POWER_MANAGER_INTERFACE_H_ + +#include <memory> + +#include <base/macros.h> + +namespace chromeos_update_engine { + +class PowerManagerInterface { + public: + virtual ~PowerManagerInterface() = default; + + // Request the power manager to restart the device. Returns true on success. + virtual bool RequestReboot() = 0; + + protected: + PowerManagerInterface() = default; + + private: + DISALLOW_COPY_AND_ASSIGN(PowerManagerInterface); +}; + +namespace power_manager { +// Factory function which create a PowerManager. +std::unique_ptr<PowerManagerInterface> CreatePowerManager(); +} + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_POWER_MANAGER_INTERFACE_H_
diff --git a/update_engine/proxy_resolver.cc b/update_engine/proxy_resolver.cc new file mode 100644 index 0000000..abd6f76 --- /dev/null +++ b/update_engine/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/update_engine/proxy_resolver.h b/update_engine/proxy_resolver.h new file mode 100644 index 0000000..2c8824f --- /dev/null +++ b/update_engine/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/update_engine/real_system_state.cc b/update_engine/real_system_state.cc new file mode 100644 index 0000000..4ee7c69 --- /dev/null +++ b/update_engine/real_system_state.cc
@@ -0,0 +1,210 @@ +// +// 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 <string> + +#include <base/bind.h> +#include <base/files/file_util.h> +#include <base/location.h> +#include <base/time/time.h> +#include <brillo/make_unique_ptr.h> +#include <brillo/message_loops/message_loop.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" +#include "update_engine/weave_service_factory.h" + +using brillo::MessageLoop; + +namespace chromeos_update_engine { + +RealSystemState::~RealSystemState() { + // Prevent any DBus communication from UpdateAttempter when shutting down the + // daemon. + if (update_attempter_) + update_attempter_->ClearObservers(); +} + +bool RealSystemState::Initialize() { + metrics_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."; + + connection_manager_ = connection_manager::CreateConnectionManager(this); + if (!connection_manager_) { + LOG(ERROR) << "Error intializing the ConnectionManagerInterface."; + return false; + } + + power_manager_ = power_manager::CreatePowerManager(); + if (!power_manager_) { + LOG(ERROR) << "Error intializing the PowerManagerInterface."; + 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(); + +#if USE_LIBCROS + LibCrosProxy* libcros_proxy = &libcros_proxy_; +#else + LibCrosProxy* libcros_proxy = nullptr; +#endif // USE_LIBCROS + + // Initialize the UpdateAttempter before the UpdateManager. + update_attempter_.reset( + new UpdateAttempter(this, certificate_checker_.get(), libcros_proxy)); + update_attempter_->Init(); + + weave_service_ = ConstructWeaveService(update_attempter_.get()); + if (weave_service_) + update_attempter_->AddObserver(weave_service_.get()); + + // Initialize the Update Manager using the default state factory. + chromeos_update_manager::State* um_state = + chromeos_update_manager::DefaultStateFactory( + &policy_provider_, libcros_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; +} + +bool RealSystemState::StartUpdater() { + // Initiate update checks. + update_attempter_->ScheduleUpdates(); + +#ifndef USE_NESTLABS + // Update boot flags after 45 seconds. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&UpdateAttempter::UpdateBootFlags, + base::Unretained(update_attempter_.get())), + base::TimeDelta::FromSeconds(45)); +#endif // USE_NESTLABS + + // Broadcast the update engine status on startup to ensure consistent system + // state on crashes. + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &UpdateAttempter::BroadcastStatus, + base::Unretained(update_attempter_.get()))); + + // Run the UpdateEngineStarted() method on |update_attempter|. + MessageLoop::current()->PostTask(FROM_HERE, base::Bind( + &UpdateAttempter::UpdateEngineStarted, + base::Unretained(update_attempter_.get()))); + return true; +} + +void RealSystemState::AddObserver(ServiceObserverInterface* observer) { + CHECK(update_attempter_.get()); + update_attempter_->AddObserver(observer); +} + +void RealSystemState::RemoveObserver(ServiceObserverInterface* observer) { + CHECK(update_attempter_.get()); + update_attempter_->RemoveObserver(observer); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/real_system_state.h b/update_engine/real_system_state.h new file mode 100644 index 0000000..b280461 --- /dev/null +++ b/update_engine/real_system_state.h
@@ -0,0 +1,198 @@ +// +// 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 <set> + +#include <metrics/metrics_library.h> +#include <policy/device_policy.h> + +#include "update_engine/certificate_checker.h" +#include "update_engine/common/boot_control_interface.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_interface.h" +#include "update_engine/daemon_state_interface.h" +#include "update_engine/p2p_manager.h" +#include "update_engine/payload_state.h" +#include "update_engine/power_manager_interface.h" +#include "update_engine/omaha_request_params.h" +#if USE_NESTLABS +#include "update_engine/update_attempter_nestlabs.h" +#else +#include "update_engine/update_attempter.h" +#endif +#include "update_engine/update_manager/update_manager.h" +#include "update_engine/weave_service_interface.h" + +namespace chromeos_update_engine { + +// A real implementation of the SystemStateInterface which is +// used by the actual product code. +class RealSystemState : public SystemState, public DaemonStateInterface { + public: + // Constructs all system objects that do not require separate initialization; + // see Initialize() below for the remaining ones. + RealSystemState() = default; + ~RealSystemState() override; + + // Initializes and sets systems objects that require an initialization + // separately from construction. Returns |true| on success. + bool Initialize(); + + // DaemonStateInterface overrides. + // Start the periodic update attempts. Must be called at the beginning of the + // program to start the periodic update check process. + bool StartUpdater() override; + + void AddObserver(ServiceObserverInterface* observer) override; + void RemoveObserver(ServiceObserverInterface* observer) override; + const std::set<ServiceObserverInterface*>& service_observers() override { + CHECK(update_attempter_.get()); + return update_attempter_->service_observers(); + } + + // SystemState overrides. + 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_.get(); + } + + 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 WeaveServiceInterface* weave_service() override { + return weave_service_.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 PowerManagerInterface* power_manager() override { + return power_manager_.get(); + } + + inline bool system_rebooted() override { return system_rebooted_; } + + private: +#if USE_LIBCROS + // LibCros proxy using the DBus connection. + LibCrosProxy libcros_proxy_; +#endif // USE_LIBCROS + + // Interface for the power manager. + std::unique_ptr<PowerManagerInterface> power_manager_; + + // 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. + std::unique_ptr<ConnectionManagerInterface> connection_manager_; + + // 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<WeaveServiceInterface> weave_service_; + + 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/update_engine/run_unittests b/update_engine/run_unittests new file mode 100755 index 0000000..f07078d --- /dev/null +++ b/update_engine/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/update_engine/sample_images/generate_images.sh b/update_engine/sample_images/generate_images.sh new file mode 100755 index 0000000..6a0d1ea --- /dev/null +++ b/update_engine/sample_images/generate_images.sh
@@ -0,0 +1,269 @@ +#!/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 + + # FIFO + sudo mkfifo "${mntdir}"/fifo + + # character special file + sudo mknod "${mntdir}"/cdev c 2 3 + + # 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 +} + +add_files_postinstall() { + local mntdir="$1" + + sudo mkdir -p "${mntdir}"/bin >/dev/null + + # A postinstall bash program. + sudo tee "${mntdir}"/bin/postinst_example >/dev/null <<EOF +#!/etc/../bin/sh +echo "I'm a postinstall program and I know how to write to stdout" +echo "My call was $@" +exit 0 +EOF + + # A symlink to another program. This should also work. + sudo ln -s "postinst_example" "${mntdir}"/bin/postinst_link + + sudo tee "${mntdir}"/bin/postinst_fail3 >/dev/null <<EOF +#!/etc/../bin/sh +exit 3 +EOF + + sudo tee "${mntdir}"/bin/postinst_fail1 >/dev/null <<EOF +#!/etc/../bin/sh +exit 1 +EOF + + # A program that succeeds if it is suspended during the first 5 minutes. + sudo tee "${mntdir}"/bin/postinst_suspend >/dev/null <<EOF +#!/etc/../bin/sh +trap "{ echo Got a SIGCONT; exit 0; }" CONT +# Signal that we are ready to receive the signal by redirecting our stdin to +# /dev/zero, the test can detect that. +exec </dev/zero +# Allow the signal handler to run every 100 ms. +i=3000 +while [ \$i -ge 0 ]; do + sleep 0.1 + i=\$((i-1)) +done +exit 1 +EOF + + # A program that reports back progress. + sudo tee "${mntdir}"/bin/postinst_progress >/dev/null <<EOF +#!/etc/../bin/sh +# These values have exact representation in IEEE 754 so we avoid rounding +# errors. +echo global_progress 0.25 >&3 +echo global_progress 0.5 >&3 +echo global_progress 1.0 >&3 +exit 0 +EOF + + # A postinstall bash program. + sudo tee "${mntdir}"/bin/self_check_context >/dev/null <<EOF +#!/etc/../bin/sh +echo "This is my context:" +ls -lZ "\$0" | grep -F ' u:object_r:postinstall_file:s0 ' || exit 5 +exit 0 +EOF + + sudo tee "${mntdir}"/postinst >/dev/null <<EOF +#!/etc/../bin/sh +echo "postinst" +exit 0 +EOF + + sudo chmod +x "${mntdir}"/postinst "${mntdir}"/bin/* +} + +# 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 + unittest) + add_files_ue_settings "${mntdir}" "${block_size}" + add_files_postinstall "${mntdir}" "${block_size}" + ;; + default) + add_files_default "${mntdir}" "${block_size}" + ;; + empty) + ;; + 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 $((1024 * 1024)) 1024 + generate_image disk_ext2_4k default $((1024 * 4096)) 4096 + generate_image disk_ext2_4k_empty empty $((1024 * 4096)) 4096 + generate_image disk_ext2_unittest unittest $((1024 * 4096)) 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}" \ + --sparse "${IMAGES[@]}" + cd "${OUTPUT_DIR}" + rm "${IMAGES[@]}" +} + +main
diff --git a/update_engine/sample_images/sample_images.tar.bz2 b/update_engine/sample_images/sample_images.tar.bz2 new file mode 100644 index 0000000..72f4eb5 --- /dev/null +++ b/update_engine/sample_images/sample_images.tar.bz2 Binary files differ
diff --git a/update_engine/sample_omaha_v3_response.xml b/update_engine/sample_omaha_v3_response.xml new file mode 100644 index 0000000..abba523 --- /dev/null +++ b/update_engine/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/update_engine/scripts/brillo_update_payload b/update_engine/scripts/brillo_update_payload new file mode 100755 index 0000000..648936b --- /dev/null +++ b/update_engine/scripts/brillo_update_payload
@@ -0,0 +1,627 @@ +#!/bin/bash + +# 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. + +# Script to generate a Brillo update for use by the update engine. +# +# usage: brillo_update_payload COMMAND [ARGS] +# The following commands are supported: +# generate generate an unsigned payload +# hash generate a payload or metadata hash +# sign generate a signed payload +# properties generate a properties file from a payload +# +# Generate command arguments: +# --payload generated unsigned payload output file +# --source_image if defined, generate a delta payload from the specified +# image to the target_image +# --target_image the target image that should be sent to clients +# --metadata_size_file if defined, generate a file containing the size of the payload +# metadata in bytes to the specified file +# +# Hash command arguments: +# --unsigned_payload the input unsigned payload to generate the hash from +# --signature_size signature sizes in bytes in the following format: +# "size1:size2[:...]" +# --payload_hash_file if defined, generate a payload hash and output to the +# specified file +# --metadata_hash_file if defined, generate a metadata hash and output to the +# specified file +# +# Sign command arguments: +# --unsigned_payload the input unsigned payload to insert the signatures +# --payload the output signed payload +# --signature_size signature sizes in bytes in the following format: +# "size1:size2[:...]" +# --payload_signature_file the payload signature files in the following +# format: +# "payload_signature1:payload_signature2[:...]" +# --metadata_signature_file the metadata signature files in the following +# format: +# "metadata_signature1:metadata_signature2[:...]" +# --metadata_size_file if defined, generate a file containing the size of +# the signed payload metadata in bytes to the +# specified file +# Note that the number of signature sizes and payload signatures have to match. +# +# Properties command arguments: +# --payload the input signed or unsigned payload +# --properties_file the output path where to write the properties, or +# '-' for stdout. + + +# Exit codes: +EX_UNSUPPORTED_DELTA=100 + +warn() { + echo "brillo_update_payload: warning: $*" >&2 +} + +die() { + echo "brillo_update_payload: error: $*" >&2 + exit 1 +} + +# Loads shflags. We first look at the default install location; then look for +# crosutils (chroot); finally check our own directory (au-generator zipfile). +load_shflags() { + local my_dir="$(dirname "$(readlink -f "$0")")" + local path + for path in /usr/share/misc {/usr/lib/crosutils,"${my_dir}"}/lib/shflags; do + if [[ -r "${path}/shflags" ]]; then + . "${path}/shflags" || die "Could not load ${path}/shflags." + return + fi + done + die "Could not find shflags." +} + +load_shflags + +HELP_GENERATE="generate: Generate an unsigned update payload." +HELP_HASH="hash: Generate the hashes of the unsigned payload and metadata used \ +for signing." +HELP_SIGN="sign: Insert the signatures into the unsigned payload." +HELP_PROPERTIES="properties: Extract payload properties to a file." + +usage() { + echo "Supported commands:" + echo + echo "${HELP_GENERATE}" + echo "${HELP_HASH}" + echo "${HELP_SIGN}" + echo "${HELP_PROPERTIES}" + echo + echo "Use: \"$0 <command> --help\" for more options." +} + +# Check that a command is specified. +if [[ $# -lt 1 ]]; then + echo "Please specify a command [generate|hash|sign|properties]" + exit 1 +fi + +# Parse command. +COMMAND="${1:-}" +shift + +case "${COMMAND}" in + generate) + FLAGS_HELP="${HELP_GENERATE}" + ;; + + hash) + FLAGS_HELP="${HELP_HASH}" + ;; + + sign) + FLAGS_HELP="${HELP_SIGN}" + ;; + + properties) + FLAGS_HELP="${HELP_PROPERTIES}" + ;; + *) + echo "Unrecognized command: \"${COMMAND}\"" >&2 + usage >&2 + exit 1 + ;; +esac + +# Flags +FLAGS_HELP="Usage: $0 ${COMMAND} [flags] +${FLAGS_HELP}" + +if [[ "${COMMAND}" == "generate" ]]; then + DEFINE_string payload "" \ + "Path to output the generated unsigned payload file." + DEFINE_string target_image "" \ + "Path to the target image that should be sent to clients." + DEFINE_string source_image "" \ + "Optional: Path to a source image. If specified, this makes a delta update." + DEFINE_string metadata_size_file "" \ + "Optional: Path to output metadata size." +fi +if [[ "${COMMAND}" == "hash" || "${COMMAND}" == "sign" ]]; then + DEFINE_string unsigned_payload "" "Path to the input unsigned payload." + DEFINE_string signature_size "" \ + "Signature sizes in bytes in the following format: size1:size2[:...]" +fi +if [[ "${COMMAND}" == "hash" ]]; then + DEFINE_string metadata_hash_file "" \ + "Optional: Path to output metadata hash file." + DEFINE_string payload_hash_file "" \ + "Optional: Path to output payload hash file." +fi +if [[ "${COMMAND}" == "sign" ]]; then + DEFINE_string payload "" \ + "Path to output the generated unsigned payload file." + DEFINE_string metadata_signature_file "" \ + "The metatada signatures in the following format: \ +metadata_signature1:metadata_signature2[:...]" + DEFINE_string payload_signature_file "" \ + "The payload signatures in the following format: \ +payload_signature1:payload_signature2[:...]" + DEFINE_string metadata_size_file "" \ + "Optional: Path to output metadata size." +fi +if [[ "${COMMAND}" == "properties" ]]; then + DEFINE_string payload "" \ + "Path to the input signed or unsigned payload file." + DEFINE_string properties_file "-" \ + "Path to output the extracted property files. If '-' is passed stdout will \ +be used." +fi + +DEFINE_string work_dir "/tmp" "Where to dump temporary files." + +# Parse command line flag arguments +FLAGS "$@" || exit 1 +eval set -- "${FLAGS_ARGV}" +set -e + +# Associative arrays from partition name to file in the source and target +# images. The size of the updated area must be the size of the file. +declare -A SRC_PARTITIONS +declare -A DST_PARTITIONS + +# List of partition names in order. +declare -a PARTITIONS_ORDER + +# A list of temporary files to remove during cleanup. +CLEANUP_FILES=() + +# Global options to force the version of the payload. +FORCE_MAJOR_VERSION="" +FORCE_MINOR_VERSION="" + +# Path to the postinstall config file in target image if exists. +POSTINSTALL_CONFIG_FILE="" + +# The fingerprint of zlib in the source image. +ZLIB_FINGERPRINT="" + +# read_option_int <file.txt> <option_key> [default_value] +# +# Reads the unsigned integer value associated with |option_key| in a key=value +# file |file.txt|. Prints the read value if found and valid, otherwise prints +# the |default_value|. +read_option_uint() { + local file_txt="$1" + local option_key="$2" + local default_value="${3:-}" + local value + if value=$(look "${option_key}=" "${file_txt}" | tail -n 1); then + if value=$(echo "${value}" | cut -f 2- -d "=" | grep -E "^[0-9]+$"); then + echo "${value}" + return + fi + fi + echo "${default_value}" +} + +# truncate_file <file_path> <file_size> +# +# Truncate the given |file_path| to |file_size| using perl. +# The truncate binary might not be available. +truncate_file() { + local file_path="$1" + local file_size="$2" + perl -e "open(FILE, \"+<\", \$ARGV[0]); \ + truncate(FILE, ${file_size}); \ + close(FILE);" "${file_path}" +} + +# Create a temporary file in the work_dir with an optional pattern name. +# Prints the name of the newly created file. +create_tempfile() { + local pattern="${1:-tempfile.XXXXXX}" + mktemp --tmpdir="${FLAGS_work_dir}" "${pattern}" +} + +cleanup() { + local err="" + rm -f "${CLEANUP_FILES[@]}" || err=1 + + # If we are cleaning up after an error, or if we got an error during + # cleanup (even if we eventually succeeded) return a non-zero exit + # code. This triggers additional logging in most environments that call + # this script. + if [[ -n "${err}" ]]; then + die "Cleanup encountered an error." + fi +} + +cleanup_on_error() { + trap - INT TERM ERR EXIT + cleanup + die "Cleanup success after an error." +} + +cleanup_on_exit() { + trap - INT TERM ERR EXIT + cleanup +} + +trap cleanup_on_error INT TERM ERR +trap cleanup_on_exit EXIT + + +# extract_image <image> <partitions_array> [partitions_order] +# +# Detect the format of the |image| file and extract its updatable partitions +# into new temporary files. Add the list of partition names and its files to the +# associative array passed in |partitions_array|. If |partitions_order| is +# passed, set it to list of partition names in order. +extract_image() { + local image="$1" + + # Brillo images are zip files. We detect the 4-byte magic header of the zip + # file. + local magic=$(head --bytes=4 "${image}" | hexdump -e '1/1 "%.2x"') + if [[ "${magic}" == "504b0304" ]]; then + echo "Detected .zip file, extracting Brillo image." + extract_image_brillo "$@" + return + fi + + # Chrome OS images are GPT partitioned disks. We should have the cgpt binary + # bundled here and we will use it to extract the partitions, so the GPT + # headers must be valid. + if cgpt show -q -n "${image}" >/dev/null; then + echo "Detected GPT image, extracting Chrome OS image." + extract_image_cros "$@" + return + fi + + die "Couldn't detect the image format of ${image}" +} + +# extract_image_cros <image.bin> <partitions_array> [partitions_order] +# +# Extract Chromium OS recovery images into new temporary files. +extract_image_cros() { + local image="$1" + local partitions_array="$2" + local partitions_order="${3:-}" + + local kernel root + kernel=$(create_tempfile "kernel.bin.XXXXXX") + CLEANUP_FILES+=("${kernel}") + root=$(create_tempfile "root.bin.XXXXXX") + CLEANUP_FILES+=("${root}") + + cros_generate_update_payload --extract \ + --image "${image}" \ + --kern_path "${kernel}" --root_path "${root}" \ + --work_dir "${FLAGS_work_dir}" --outside_chroot + + # Chrome OS uses major_version 1 payloads for all versions, even if the + # updater supports a newer major version. + FORCE_MAJOR_VERSION="1" + + if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then + # Copy from zlib_fingerprint in source image to stdout. + ZLIB_FINGERPRINT=$(e2cp "${root}":/etc/zlib_fingerprint -) + fi + + # When generating legacy Chrome OS images, we need to use "boot" and "system" + # for the partition names to be compatible with updating Brillo devices with + # Chrome OS images. + eval ${partitions_array}[boot]=\""${kernel}"\" + eval ${partitions_array}[system]=\""${root}"\" + + if [[ -n "${partitions_order}" ]]; then + eval "${partitions_order}=( \"system\" \"boot\" )" + fi + + local part varname + for part in boot system; do + varname="${partitions_array}[${part}]" + printf "md5sum of %s: " "${varname}" + md5sum "${!varname}" + done +} + +# extract_image_brillo <target_files.zip> <partitions_array> [partitions_order] +# +# Extract the A/B updated partitions from a Brillo target_files zip file into +# new temporary files. +extract_image_brillo() { + local image="$1" + local partitions_array="$2" + local partitions_order="${3:-}" + + local partitions=( "boot" "system" ) + local ab_partitions_list + ab_partitions_list=$(create_tempfile "ab_partitions_list.XXXXXX") + CLEANUP_FILES+=("${ab_partitions_list}") + if unzip -p "${image}" "META/ab_partitions.txt" >"${ab_partitions_list}"; then + if grep -v -E '^[a-zA-Z0-9_-]*$' "${ab_partitions_list}" >&2; then + die "Invalid partition names found in the partition list." + fi + partitions=($(cat "${ab_partitions_list}")) + if [[ ${#partitions[@]} -eq 0 ]]; then + die "The list of partitions is empty. Can't generate a payload." + fi + else + warn "No ab_partitions.txt found. Using default." + fi + echo "List of A/B partitions: ${partitions[@]}" + + if [[ -n "${partitions_order}" ]]; then + eval "${partitions_order}=(${partitions[@]})" + fi + + # All Brillo updaters support major version 2. + FORCE_MAJOR_VERSION="2" + + if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then + # Source image + local ue_config=$(create_tempfile "ue_config.XXXXXX") + CLEANUP_FILES+=("${ue_config}") + if ! unzip -p "${image}" "META/update_engine_config.txt" \ + >"${ue_config}"; then + warn "No update_engine_config.txt found. Assuming pre-release image, \ +using payload minor version 2" + fi + # For delta payloads, we use the major and minor version supported by the + # old updater. + FORCE_MINOR_VERSION=$(read_option_uint "${ue_config}" \ + "PAYLOAD_MINOR_VERSION" 2) + FORCE_MAJOR_VERSION=$(read_option_uint "${ue_config}" \ + "PAYLOAD_MAJOR_VERSION" 2) + + # Brillo support for deltas started with minor version 3. + if [[ "${FORCE_MINOR_VERSION}" -le 2 ]]; then + warn "No delta support from minor version ${FORCE_MINOR_VERSION}. \ +Disabling deltas for this source version." + exit ${EX_UNSUPPORTED_DELTA} + fi + + if [[ "${FORCE_MINOR_VERSION}" -ge 4 ]]; then + ZLIB_FINGERPRINT=$(unzip -p "${image}" "META/zlib_fingerprint.txt") + fi + else + # Target image + local postinstall_config=$(create_tempfile "postinstall_config.XXXXXX") + CLEANUP_FILES+=("${postinstall_config}") + if unzip -p "${image}" "META/postinstall_config.txt" \ + >"${postinstall_config}"; then + POSTINSTALL_CONFIG_FILE="${postinstall_config}" + fi + fi + + local part part_file temp_raw filesize + for part in "${partitions[@]}"; do + part_file=$(create_tempfile "${part}.img.XXXXXX") + CLEANUP_FILES+=("${part_file}") + unzip -p "${image}" "IMAGES/${part}.img" >"${part_file}" + + # If the partition is stored as an Android sparse image file, we need to + # convert them to a raw image for the update. + local magic=$(head --bytes=4 "${part_file}" | hexdump -e '1/1 "%.2x"') + if [[ "${magic}" == "3aff26ed" ]]; then + temp_raw=$(create_tempfile "${part}.raw.XXXXXX") + CLEANUP_FILES+=("${temp_raw}") + echo "Converting Android sparse image ${part}.img to RAW." + simg2img "${part_file}" "${temp_raw}" + # At this point, we can drop the contents of the old part_file file, but + # we can't delete the file because it will be deleted in cleanup. + true >"${part_file}" + part_file="${temp_raw}" + fi + + # delta_generator only supports images multiple of 4 KiB. For target images + # we pad the data with zeros if needed, but for source images we truncate + # down the data since the last block of the old image could be padded on + # disk with unknown data. + filesize=$(stat -c%s "${part_file}") + if [[ $(( filesize % 4096 )) -ne 0 ]]; then + if [[ "${partitions_array}" == "SRC_PARTITIONS" ]]; then + echo "Rounding DOWN partition ${part}.img to a multiple of 4 KiB." + : $(( filesize = filesize & -4096 )) + else + echo "Rounding UP partition ${part}.img to a multiple of 4 KiB." + : $(( filesize = (filesize + 4095) & -4096 )) + fi + truncate_file "${part_file}" "${filesize}" + fi + + eval "${partitions_array}[\"${part}\"]=\"${part_file}\"" + echo "Extracted ${partitions_array}[${part}]: ${filesize} bytes" + done +} + +validate_generate() { + [[ -n "${FLAGS_payload}" ]] || + die "You must specify an output filename with --payload FILENAME" + + [[ -n "${FLAGS_target_image}" ]] || + die "You must specify a target image with --target_image FILENAME" +} + +cmd_generate() { + local payload_type="delta" + if [[ -z "${FLAGS_source_image}" ]]; then + payload_type="full" + fi + + echo "Extracting images for ${payload_type} update." + + extract_image "${FLAGS_target_image}" DST_PARTITIONS PARTITIONS_ORDER + if [[ "${payload_type}" == "delta" ]]; then + extract_image "${FLAGS_source_image}" SRC_PARTITIONS + fi + + echo "Generating ${payload_type} update." + # Common payload args: + GENERATOR_ARGS=( -out_file="${FLAGS_payload}" ) + + local part old_partitions="" new_partitions="" partition_names="" + for part in "${PARTITIONS_ORDER[@]}"; do + if [[ -n "${partition_names}" ]]; then + partition_names+=":" + new_partitions+=":" + old_partitions+=":" + fi + partition_names+="${part}" + new_partitions+="${DST_PARTITIONS[${part}]}" + old_partitions+="${SRC_PARTITIONS[${part}]:-}" + done + + # Target image args: + GENERATOR_ARGS+=( + -partition_names="${partition_names}" + -new_partitions="${new_partitions}" + ) + + if [[ "${payload_type}" == "delta" ]]; then + # Source image args: + GENERATOR_ARGS+=( + -old_partitions="${old_partitions}" + ) + if [[ -n "${FORCE_MINOR_VERSION}" ]]; then + GENERATOR_ARGS+=( --minor_version="${FORCE_MINOR_VERSION}" ) + fi + if [[ -n "${ZLIB_FINGERPRINT}" ]]; then + GENERATOR_ARGS+=( --zlib_fingerprint="${ZLIB_FINGERPRINT}" ) + fi + fi + + if [[ -n "${FORCE_MAJOR_VERSION}" ]]; then + GENERATOR_ARGS+=( --major_version="${FORCE_MAJOR_VERSION}" ) + fi + + if [[ -n "${FLAGS_metadata_size_file}" ]]; then + GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" ) + fi + + if [[ -n "${POSTINSTALL_CONFIG_FILE}" ]]; then + GENERATOR_ARGS+=( + --new_postinstall_config_file="${POSTINSTALL_CONFIG_FILE}" + ) + fi + + echo "Running delta_generator with args: ${GENERATOR_ARGS[@]}" + "${GENERATOR}" "${GENERATOR_ARGS[@]}" + + echo "Done generating ${payload_type} update." +} + +validate_hash() { + [[ -n "${FLAGS_signature_size}" ]] || + die "You must specify signature size with --signature_size SIZES" + + [[ -n "${FLAGS_unsigned_payload}" ]] || + die "You must specify the input unsigned payload with \ +--unsigned_payload FILENAME" + + [[ -n "${FLAGS_payload_hash_file}" ]] || + die "You must specify --payload_hash_file FILENAME" + + [[ -n "${FLAGS_metadata_hash_file}" ]] || + die "You must specify --metadata_hash_file FILENAME" +} + +cmd_hash() { + "${GENERATOR}" \ + -in_file="${FLAGS_unsigned_payload}" \ + -signature_size="${FLAGS_signature_size}" \ + -out_hash_file="${FLAGS_payload_hash_file}" \ + -out_metadata_hash_file="${FLAGS_metadata_hash_file}" + + echo "Done generating hash." +} + +validate_sign() { + [[ -n "${FLAGS_signature_size}" ]] || + die "You must specify signature size with --signature_size SIZES" + + [[ -n "${FLAGS_unsigned_payload}" ]] || + die "You must specify the input unsigned payload with \ +--unsigned_payload FILENAME" + + [[ -n "${FLAGS_payload}" ]] || + die "You must specify the output signed payload with --payload FILENAME" + + [[ -n "${FLAGS_payload_signature_file}" ]] || + die "You must specify the payload signature file with \ +--payload_signature_file SIGNATURES" + + [[ -n "${FLAGS_metadata_signature_file}" ]] || + die "You must specify the metadata signature file with \ +--metadata_signature_file SIGNATURES" +} + +cmd_sign() { + GENERATOR_ARGS=( + -in_file="${FLAGS_unsigned_payload}" + -signature_size="${FLAGS_signature_size}" + -signature_file="${FLAGS_payload_signature_file}" + -metadata_signature_file="${FLAGS_metadata_signature_file}" + -out_file="${FLAGS_payload}" + ) + + if [[ -n "${FLAGS_metadata_size_file}" ]]; then + GENERATOR_ARGS+=( --out_metadata_size_file="${FLAGS_metadata_size_file}" ) + fi + + "${GENERATOR}" "${GENERATOR_ARGS[@]}" + echo "Done signing payload." +} + +validate_properties() { + [[ -n "${FLAGS_payload}" ]] || + die "You must specify the payload file with --payload FILENAME" + + [[ -n "${FLAGS_properties_file}" ]] || + die "You must specify a non empty --properties_file FILENAME" +} + +cmd_properties() { + "${GENERATOR}" \ + -in_file="${FLAGS_payload}" \ + -properties_file="${FLAGS_properties_file}" +} + +# Sanity check that the real generator exists: +GENERATOR="$(which delta_generator || true)" +[[ -x "${GENERATOR}" ]] || die "can't find delta_generator" + +case "$COMMAND" in + generate) validate_generate + cmd_generate + ;; + hash) validate_hash + cmd_hash + ;; + sign) validate_sign + cmd_sign + ;; + properties) validate_properties + cmd_properties + ;; +esac
diff --git a/update_engine/scripts/paycheck.py b/update_engine/scripts/paycheck.py new file mode 100755 index 0000000..0195f53 --- /dev/null +++ b/update_engine/scripts/paycheck.py
@@ -0,0 +1,232 @@ +#!/usr/bin/python2 +# +# Copyright (c) 2013 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. + +"""Command-line tool for checking and applying Chrome OS update payloads.""" + +from __future__ import print_function + +import optparse +import os +import sys + +# pylint: disable=F0401 +lib_dir = os.path.join(os.path.dirname(__file__), 'lib') +if os.path.exists(lib_dir) and os.path.isdir(lib_dir): + sys.path.insert(1, lib_dir) +import update_payload + + +_TYPE_FULL = 'full' +_TYPE_DELTA = 'delta' + + +def ParseArguments(argv): + """Parse and validate command-line arguments. + + Args: + argv: command-line arguments to parse (excluding the program name) + + Returns: + A tuple (opts, payload, extra_args), where `opts' are the options + returned by the parser, `payload' is the name of the payload file + (mandatory argument) and `extra_args' are any additional command-line + arguments. + """ + parser = optparse.OptionParser( + usage=('Usage: %prog [OPTION...] PAYLOAD [DST_KERN DST_ROOT ' + '[SRC_KERN SRC_ROOT]]'), + description=('Applies a Chrome OS update PAYLOAD to SRC_KERN and ' + 'SRC_ROOT emitting DST_KERN and DST_ROOT, respectively. ' + 'SRC_KERN and SRC_ROOT are only needed for delta payloads. ' + 'When no partitions are provided, verifies the payload ' + 'integrity.'), + epilog=('Note: a payload may verify correctly but fail to apply, and ' + 'vice versa; this is by design and can be thought of as static ' + 'vs dynamic correctness. A payload that both verifies and ' + 'applies correctly should be safe for use by the Chrome OS ' + 'Update Engine. Use --check to verify a payload prior to ' + 'applying it.')) + + check_opts = optparse.OptionGroup(parser, 'Checking payload integrity') + check_opts.add_option('-c', '--check', action='store_true', default=False, + help=('force payload integrity check (e.g. before ' + 'applying)')) + check_opts.add_option('-D', '--describe', action='store_true', default=False, + help='Print a friendly description of the payload.') + check_opts.add_option('-r', '--report', metavar='FILE', + help="dump payload report (`-' for stdout)") + check_opts.add_option('-t', '--type', metavar='TYPE', dest='assert_type', + help=("assert that payload is either `%s' or `%s'" % + (_TYPE_FULL, _TYPE_DELTA))) + check_opts.add_option('-z', '--block-size', metavar='NUM', default=0, + type='int', + help='assert a non-default (4096) payload block size') + check_opts.add_option('-u', '--allow-unhashed', action='store_true', + default=False, help='allow unhashed operations') + check_opts.add_option('-d', '--disabled_tests', metavar='TESTLIST', + default=(), + help=('comma-separated list of tests to disable; ' + 'available values: ' + + ', '.join(update_payload.CHECKS_TO_DISABLE))) + check_opts.add_option('-k', '--key', metavar='FILE', + help=('Override standard key used for signature ' + 'validation')) + check_opts.add_option('-m', '--meta-sig', metavar='FILE', + help='verify metadata against its signature') + check_opts.add_option('-p', '--root-part-size', metavar='NUM', + default=0, type='int', + help=('override rootfs partition size auto-inference')) + check_opts.add_option('-P', '--kern-part-size', metavar='NUM', + default=0, type='int', + help=('override kernel partition size auto-inference')) + parser.add_option_group(check_opts) + + trace_opts = optparse.OptionGroup(parser, 'Applying payload') + trace_opts.add_option('-x', '--extract-bsdiff', action='store_true', + default=False, + help=('use temp input/output files with BSDIFF ' + 'operations (not in-place)')) + trace_opts.add_option('--bspatch-path', metavar='FILE', + help=('use the specified bspatch binary')) + parser.add_option_group(trace_opts) + + trace_opts = optparse.OptionGroup(parser, 'Block tracing') + trace_opts.add_option('-b', '--root-block', metavar='BLOCK', type='int', + help='trace the origin for a rootfs block') + trace_opts.add_option('-B', '--kern-block', metavar='BLOCK', type='int', + help='trace the origin for a kernel block') + trace_opts.add_option('-s', '--skip', metavar='NUM', default='0', type='int', + help='skip first NUM occurrences of traced block') + parser.add_option_group(trace_opts) + + # Parse command-line arguments. + opts, args = parser.parse_args(argv) + + # Validate a value given to --type, if any. + if opts.assert_type not in (None, _TYPE_FULL, _TYPE_DELTA): + parser.error('invalid argument to --type: %s' % opts.assert_type) + + # Convert and validate --disabled_tests value list, if provided. + if opts.disabled_tests: + opts.disabled_tests = opts.disabled_tests.split(',') + for test in opts.disabled_tests: + if test not in update_payload.CHECKS_TO_DISABLE: + parser.error('invalid argument to --disabled_tests: %s' % test) + + # Ensure consistent use of block tracing options. + do_block_trace = not (opts.root_block is None and opts.kern_block is None) + if opts.skip and not do_block_trace: + parser.error('--skip must be used with either --root-block or --kern-block') + + # There are several options that imply --check. + opts.check = (opts.check or opts.report or opts.assert_type or + opts.block_size or opts.allow_unhashed or + opts.disabled_tests or opts.meta_sig or opts.key or + opts.root_part_size or opts.kern_part_size) + + # Check number of arguments, enforce payload type accordingly. + if len(args) == 3: + if opts.assert_type == _TYPE_DELTA: + parser.error('%s payload requires source partition arguments' % + _TYPE_DELTA) + opts.assert_type = _TYPE_FULL + elif len(args) == 5: + if opts.assert_type == _TYPE_FULL: + parser.error('%s payload does not accept source partition arguments' % + _TYPE_FULL) + opts.assert_type = _TYPE_DELTA + elif len(args) == 1: + # Not applying payload; if block tracing not requested either, do an + # integrity check. + if not do_block_trace: + opts.check = True + if opts.extract_bsdiff: + parser.error('--extract-bsdiff can only be used when applying payloads') + if opts.bspatch_path: + parser.error('--bspatch-path can only be used when applying payloads') + else: + parser.error('unexpected number of arguments') + + # By default, look for a metadata-signature file with a name based on the name + # of the payload we are checking. We only do it if check was triggered. + if opts.check and not opts.meta_sig: + default_meta_sig = args[0] + '.metadata-signature' + if os.path.isfile(default_meta_sig): + opts.meta_sig = default_meta_sig + print('Using default metadata signature', opts.meta_sig, file=sys.stderr) + + return opts, args[0], args[1:] + + +def main(argv): + # Parse and validate arguments. + options, payload_file_name, extra_args = ParseArguments(argv[1:]) + + with open(payload_file_name) as payload_file: + payload = update_payload.Payload(payload_file) + try: + # Initialize payload. + payload.Init() + + if options.describe: + payload.Describe() + + # Perform payload integrity checks. + if options.check: + report_file = None + do_close_report_file = False + metadata_sig_file = None + try: + if options.report: + if options.report == '-': + report_file = sys.stdout + else: + report_file = open(options.report, 'w') + do_close_report_file = True + + metadata_sig_file = options.meta_sig and open(options.meta_sig) + payload.Check( + pubkey_file_name=options.key, + metadata_sig_file=metadata_sig_file, + report_out_file=report_file, + assert_type=options.assert_type, + block_size=int(options.block_size), + rootfs_part_size=options.root_part_size, + kernel_part_size=options.kern_part_size, + allow_unhashed=options.allow_unhashed, + disabled_tests=options.disabled_tests) + finally: + if metadata_sig_file: + metadata_sig_file.close() + if do_close_report_file: + report_file.close() + + # Trace blocks. + if options.root_block is not None: + payload.TraceBlock(options.root_block, options.skip, sys.stdout, False) + if options.kern_block is not None: + payload.TraceBlock(options.kern_block, options.skip, sys.stdout, True) + + # Apply payload. + if extra_args: + dargs = {'bsdiff_in_place': not options.extract_bsdiff} + if options.bspatch_path: + dargs['bspatch_path'] = options.bspatch_path + if options.assert_type == _TYPE_DELTA: + dargs['old_kernel_part'] = extra_args[2] + dargs['old_rootfs_part'] = extra_args[3] + + payload.Apply(extra_args[0], extra_args[1], **dargs) + + except update_payload.PayloadError, e: + sys.stderr.write('Error: %s\n' % e) + return 1 + + return 0 + + +if __name__ == '__main__': + sys.exit(main(sys.argv))
diff --git a/update_engine/scripts/payload_apply.sh b/update_engine/scripts/payload_apply.sh new file mode 100755 index 0000000..9158d65 --- /dev/null +++ b/update_engine/scripts/payload_apply.sh
@@ -0,0 +1,34 @@ +#!/system/bin/sh + +PAYLOAD=$1 +HEADER_SIZE=512 +HEADER_LINES=6 +SIDELOAD_APP=`which update_engine_sideload` + +if [ "$#" -ne 1 ] +then + echo "$0 [PAYLOAD]" + exit 1 +fi + +if [ -z $SIDELOAD_APP ] +then + echo "Can't find update_engine_sideload app" + exit 1 +fi + +if [ ! -f $PAYLOAD ] +then + echo "$PAYLOAD does not exist" + exit 1 +fi + +HEADER=`head -n $HEADER_LINES $PAYLOAD` +FILE_HASH=`echo $HEADER | awk -F"FILE_HASH:" '{print $2}' | awk '{print $1}'` +FILE_SIZE=`echo $HEADER | awk -F"FILE_SIZE:" '{print $2}' | awk '{print $1}'` +METADATA_SIZE=`echo $HEADER | awk -F"METADATA_SIZE:" '{print $2}' | awk '{print $1}'` + +$SIDELOAD_APP --payload=file://$PAYLOAD --offset=$HEADER_SIZE --headers="FILE_HASH=$FILE_HASH +FILE_SIZE=$FILE_SIZE +METADATA_SIZE=$METADATA_SIZE +" || exit 1
diff --git a/update_engine/scripts/test_paycheck.sh b/update_engine/scripts/test_paycheck.sh new file mode 100755 index 0000000..c395db4 --- /dev/null +++ b/update_engine/scripts/test_paycheck.sh
@@ -0,0 +1,175 @@ +#!/bin/bash +# +# Copyright (c) 2013 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. + +# A test script for paycheck.py and the update_payload.py library. +# +# This script requires three payload files, along with a metadata signature for +# each, and a public key for verifying signatures. Payload include: +# +# - A full payload for release X (old_full_payload) +# +# - A full payload for release Y (new_full_payload), where Y > X +# +# - A delta payload from X to Y (delta_payload) +# +# The test performs the following: +# +# - It verifies each payload against its metadata signature, also asserting the +# payload type. Another artifact is a human-readable payload report, which +# is output to stdout to be inspected by the user. +# +# - It performs a random block trace on the delta payload (both kernel and +# rootfs blocks), dumping the traces to stdout for the user to inspect. +# +# - It applies old_full_payload to yield old kernel (old_kern.part) and rootfs +# (old_root.part) partitions. +# +# - It applies delta_payload to old_{kern,root}.part to yield new kernel +# (new_delta_kern.part) and rootfs (new_delta_root.part) partitions. +# +# - It applies new_full_payload to yield reference new kernel +# (new_full_kern.part) and rootfs (new_full_root.part) partitions. +# +# - It compares new_{delta,full}_kern.part and new_{delta,full}_root.part to +# ensure that they are binary identical. +# +# If all steps have completed successfully we know with high certainty that +# paycheck.py (and hence update_payload.py) correctly parses both full and +# delta payloads, and applies them to yield the expected result. We also know +# that tracing works, to the extent it does not crash. Manual inspection of +# payload reports and block traces will improve this our confidence and are +# strongly encouraged. Finally, each paycheck.py execution is timed. + + +# Stop on errors, unset variables. +set -e +set -u + +# Temporary image files. +OLD_KERN_PART=old_kern.part +OLD_ROOT_PART=old_root.part +NEW_DELTA_KERN_PART=new_delta_kern.part +NEW_DELTA_ROOT_PART=new_delta_root.part +NEW_FULL_KERN_PART=new_full_kern.part +NEW_FULL_ROOT_PART=new_full_root.part + + +log() { + echo "$@" >&2 +} + +die() { + log "$@" + exit 1 +} + +usage_and_exit() { + cat >&2 <<EOF +Usage: ${0##*/} old_full_payload delta_payload new_full_payload +EOF + exit +} + +check_payload() { + payload_file=$1 + payload_type=$2 + + time ${paycheck} -t ${payload_type} ${payload_file} +} + +trace_kern_block() { + payload_file=$1 + block=$2 + time ${paycheck} -B ${block} ${payload_file} +} + +trace_root_block() { + payload_file=$1 + block=$2 + time ${paycheck} -b ${block} ${payload_file} +} + +apply_full_payload() { + payload_file=$1 + dst_kern_part="$2/$3" + dst_root_part="$2/$4" + + time ${paycheck} ${payload_file} ${dst_kern_part} ${dst_root_part} +} + +apply_delta_payload() { + payload_file=$1 + dst_kern_part="$2/$3" + dst_root_part="$2/$4" + src_kern_part="$2/$5" + src_root_part="$2/$6" + + time ${paycheck} ${payload_file} ${dst_kern_part} ${dst_root_part} \ + ${src_kern_part} ${src_root_part} +} + +main() { + # Read command-line arguments. + if [ $# == 1 ] && [ "$1" == "-h" ]; then + usage_and_exit + elif [ $# != 3 ]; then + die "Error: unexpected number of arguments" + fi + old_full_payload="$1" + delta_payload="$2" + new_full_payload="$3" + + # Find paycheck.py + paycheck=${0%/*}/paycheck.py + if [ -z "${paycheck}" ] || [ ! -x ${paycheck} ]; then + die "cannot find ${paycheck} or file is not executable" + fi + + # Check the payloads statically. + log "Checking payloads..." + check_payload "${old_full_payload}" full + check_payload "${new_full_payload}" full + check_payload "${delta_payload}" delta + log "Done" + + # Trace a random block between 0-1024 on all payloads. + block=$((RANDOM * 1024 / 32767)) + log "Tracing a random block (${block}) in full/delta payloads..." + trace_kern_block "${new_full_payload}" ${block} + trace_root_block "${new_full_payload}" ${block} + trace_kern_block "${delta_payload}" ${block} + trace_root_block "${delta_payload}" ${block} + log "Done" + + # Apply full/delta payloads and verify results are identical. + tmpdir="$(mktemp -d --tmpdir test_paycheck.XXXXXXXX)" + log "Initiating application of payloads at $tmpdir" + + log "Applying old full payload..." + apply_full_payload "${old_full_payload}" "${tmpdir}" "${OLD_KERN_PART}" \ + "${OLD_ROOT_PART}" + log "Done" + + log "Applying delta payload to old partitions..." + apply_delta_payload "${delta_payload}" "${tmpdir}" "${NEW_DELTA_KERN_PART}" \ + "${NEW_DELTA_ROOT_PART}" "${OLD_KERN_PART}" "${OLD_ROOT_PART}" + log "Done" + + log "Applying new full payload..." + apply_full_payload "${new_full_payload}" "${tmpdir}" "${NEW_FULL_KERN_PART}" \ + "${NEW_FULL_ROOT_PART}" + log "Done" + + log "Comparing results of delta and new full updates..." + diff "${tmpdir}/${NEW_FULL_KERN_PART}" "${tmpdir}/${NEW_DELTA_KERN_PART}" + diff "${tmpdir}/${NEW_FULL_ROOT_PART}" "${tmpdir}/${NEW_DELTA_ROOT_PART}" + log "Done" + + log "Cleaning up" + rm -fr "${tmpdir}" +} + +main "$@"
diff --git a/update_engine/scripts/update_payload/__init__.py b/update_engine/scripts/update_payload/__init__.py new file mode 100644 index 0000000..1906a16 --- /dev/null +++ b/update_engine/scripts/update_payload/__init__.py
@@ -0,0 +1,11 @@ +# Copyright (c) 2013 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. + +"""Library for processing, verifying and applying Chrome OS update payloads.""" + +# Just raise the interface classes to the root namespace. +# pylint: disable=W0401 +from checker import CHECKS_TO_DISABLE +from error import PayloadError +from payload import Payload
diff --git a/update_engine/scripts/update_payload/applier.py b/update_engine/scripts/update_payload/applier.py new file mode 100644 index 0000000..e3708c7 --- /dev/null +++ b/update_engine/scripts/update_payload/applier.py
@@ -0,0 +1,582 @@ +# Copyright (c) 2013 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. + +"""Applying a Chrome OS update payload. + +This module is used internally by the main Payload class for applying an update +payload. The interface for invoking the applier is as follows: + + applier = PayloadApplier(payload) + applier.Run(...) + +""" + +from __future__ import print_function + +import array +import bz2 +import hashlib +import itertools +import os +import shutil +import subprocess +import sys +import tempfile + +import common +from error import PayloadError + + +# +# Helper functions. +# +def _VerifySha256(file_obj, expected_hash, name, length=-1): + """Verifies the SHA256 hash of a file. + + Args: + file_obj: file object to read + expected_hash: the hash digest we expect to be getting + name: name string of this hash, for error reporting + length: precise length of data to verify (optional) + + Raises: + PayloadError if computed hash doesn't match expected one, or if fails to + read the specified length of data. + """ + # pylint: disable=E1101 + hasher = hashlib.sha256() + block_length = 1024 * 1024 + max_length = length if length >= 0 else sys.maxint + + while max_length > 0: + read_length = min(max_length, block_length) + data = file_obj.read(read_length) + if not data: + break + max_length -= len(data) + hasher.update(data) + + if length >= 0 and max_length > 0: + raise PayloadError( + 'insufficient data (%d instead of %d) when verifying %s' % + (length - max_length, length, name)) + + actual_hash = hasher.digest() + if actual_hash != expected_hash: + raise PayloadError('%s hash (%s) not as expected (%s)' % + (name, common.FormatSha256(actual_hash), + common.FormatSha256(expected_hash))) + + +def _ReadExtents(file_obj, extents, block_size, max_length=-1): + """Reads data from file as defined by extent sequence. + + This tries to be efficient by not copying data as it is read in chunks. + + Args: + file_obj: file object + extents: sequence of block extents (offset and length) + block_size: size of each block + max_length: maximum length to read (optional) + + Returns: + A character array containing the concatenated read data. + """ + data = array.array('c') + if max_length < 0: + max_length = sys.maxint + for ex in extents: + if max_length == 0: + break + read_length = min(max_length, ex.num_blocks * block_size) + + # Fill with zeros or read from file, depending on the type of extent. + if ex.start_block == common.PSEUDO_EXTENT_MARKER: + data.extend(itertools.repeat('\0', read_length)) + else: + file_obj.seek(ex.start_block * block_size) + data.fromfile(file_obj, read_length) + + max_length -= read_length + + return data + + +def _WriteExtents(file_obj, data, extents, block_size, base_name): + """Writes data to file as defined by extent sequence. + + This tries to be efficient by not copy data as it is written in chunks. + + Args: + file_obj: file object + data: data to write + extents: sequence of block extents (offset and length) + block_size: size of each block + base_name: name string of extent sequence for error reporting + + Raises: + PayloadError when things don't add up. + """ + data_offset = 0 + data_length = len(data) + for ex, ex_name in common.ExtentIter(extents, base_name): + if not data_length: + raise PayloadError('%s: more write extents than data' % ex_name) + write_length = min(data_length, ex.num_blocks * block_size) + + # Only do actual writing if this is not a pseudo-extent. + if ex.start_block != common.PSEUDO_EXTENT_MARKER: + file_obj.seek(ex.start_block * block_size) + data_view = buffer(data, data_offset, write_length) + file_obj.write(data_view) + + data_offset += write_length + data_length -= write_length + + if data_length: + raise PayloadError('%s: more data than write extents' % base_name) + + +def _ExtentsToBspatchArg(extents, block_size, base_name, data_length=-1): + """Translates an extent sequence into a bspatch-compatible string argument. + + Args: + extents: sequence of block extents (offset and length) + block_size: size of each block + base_name: name string of extent sequence for error reporting + data_length: the actual total length of the data in bytes (optional) + + Returns: + A tuple consisting of (i) a string of the form + "off_1:len_1,...,off_n:len_n", (ii) an offset where zero padding is needed + for filling the last extent, (iii) the length of the padding (zero means no + padding is needed and the extents cover the full length of data). + + Raises: + PayloadError if data_length is too short or too long. + """ + arg = '' + pad_off = pad_len = 0 + if data_length < 0: + data_length = sys.maxint + for ex, ex_name in common.ExtentIter(extents, base_name): + if not data_length: + raise PayloadError('%s: more extents than total data length' % ex_name) + + is_pseudo = ex.start_block == common.PSEUDO_EXTENT_MARKER + start_byte = -1 if is_pseudo else ex.start_block * block_size + num_bytes = ex.num_blocks * block_size + if data_length < num_bytes: + # We're only padding a real extent. + if not is_pseudo: + pad_off = start_byte + data_length + pad_len = num_bytes - data_length + + num_bytes = data_length + + arg += '%s%d:%d' % (arg and ',', start_byte, num_bytes) + data_length -= num_bytes + + if data_length: + raise PayloadError('%s: extents not covering full data length' % base_name) + + return arg, pad_off, pad_len + + +# +# Payload application. +# +class PayloadApplier(object): + """Applying an update payload. + + This is a short-lived object whose purpose is to isolate the logic used for + applying an update payload. + """ + + def __init__(self, payload, bsdiff_in_place=True, bspatch_path=None, + imgpatch_path=None, truncate_to_expected_size=True): + """Initialize the applier. + + Args: + payload: the payload object to check + bsdiff_in_place: whether to perform BSDIFF operation in-place (optional) + bspatch_path: path to the bspatch binary (optional) + imgpatch_path: path to the imgpatch binary (optional) + truncate_to_expected_size: whether to truncate the resulting partitions + to their expected sizes, as specified in the + payload (optional) + """ + assert payload.is_init, 'uninitialized update payload' + self.payload = payload + self.block_size = payload.manifest.block_size + self.minor_version = payload.manifest.minor_version + self.bsdiff_in_place = bsdiff_in_place + self.bspatch_path = bspatch_path or 'bspatch' + self.imgpatch_path = imgpatch_path or 'imgpatch' + self.truncate_to_expected_size = truncate_to_expected_size + + def _ApplyReplaceOperation(self, op, op_name, out_data, part_file, part_size): + """Applies a REPLACE{,_BZ} operation. + + Args: + op: the operation object + op_name: name string for error reporting + out_data: the data to be written + part_file: the partition file object + part_size: the size of the partition + + Raises: + PayloadError if something goes wrong. + """ + block_size = self.block_size + data_length = len(out_data) + + # Decompress data if needed. + if op.type == common.OpType.REPLACE_BZ: + out_data = bz2.decompress(out_data) + data_length = len(out_data) + + # Write data to blocks specified in dst extents. + data_start = 0 + for ex, ex_name in common.ExtentIter(op.dst_extents, + '%s.dst_extents' % op_name): + start_block = ex.start_block + num_blocks = ex.num_blocks + count = num_blocks * block_size + + # Make sure it's not a fake (signature) operation. + if start_block != common.PSEUDO_EXTENT_MARKER: + data_end = data_start + count + + # Make sure we're not running past partition boundary. + if (start_block + num_blocks) * block_size > part_size: + raise PayloadError( + '%s: extent (%s) exceeds partition size (%d)' % + (ex_name, common.FormatExtent(ex, block_size), + part_size)) + + # Make sure that we have enough data to write. + if data_end >= data_length + block_size: + raise PayloadError( + '%s: more dst blocks than data (even with padding)') + + # Pad with zeros if necessary. + if data_end > data_length: + padding = data_end - data_length + out_data += '\0' * padding + + self.payload.payload_file.seek(start_block * block_size) + part_file.seek(start_block * block_size) + part_file.write(out_data[data_start:data_end]) + + data_start += count + + # Make sure we wrote all data. + if data_start < data_length: + raise PayloadError('%s: wrote fewer bytes (%d) than expected (%d)' % + (op_name, data_start, data_length)) + + def _ApplyMoveOperation(self, op, op_name, part_file): + """Applies a MOVE operation. + + Note that this operation must read the whole block data from the input and + only then dump it, due to our in-place update semantics; otherwise, it + might clobber data midway through. + + Args: + op: the operation object + op_name: name string for error reporting + part_file: the partition file object + + Raises: + PayloadError if something goes wrong. + """ + block_size = self.block_size + + # Gather input raw data from src extents. + in_data = _ReadExtents(part_file, op.src_extents, block_size) + + # Dump extracted data to dst extents. + _WriteExtents(part_file, in_data, op.dst_extents, block_size, + '%s.dst_extents' % op_name) + + def _ApplyBsdiffOperation(self, op, op_name, patch_data, new_part_file): + """Applies a BSDIFF operation. + + Args: + op: the operation object + op_name: name string for error reporting + patch_data: the binary patch content + new_part_file: the target partition file object + + Raises: + PayloadError if something goes wrong. + """ + # Implemented using a SOURCE_BSDIFF operation with the source and target + # partition set to the new partition. + self._ApplyDiffOperation(op, op_name, patch_data, new_part_file, + new_part_file) + + def _ApplySourceCopyOperation(self, op, op_name, old_part_file, + new_part_file): + """Applies a SOURCE_COPY operation. + + Args: + op: the operation object + op_name: name string for error reporting + old_part_file: the old partition file object + new_part_file: the new partition file object + + Raises: + PayloadError if something goes wrong. + """ + if not old_part_file: + raise PayloadError( + '%s: no source partition file provided for operation type (%d)' % + (op_name, op.type)) + + block_size = self.block_size + + # Gather input raw data from src extents. + in_data = _ReadExtents(old_part_file, op.src_extents, block_size) + + # Dump extracted data to dst extents. + _WriteExtents(new_part_file, in_data, op.dst_extents, block_size, + '%s.dst_extents' % op_name) + + def _ApplyDiffOperation(self, op, op_name, patch_data, old_part_file, + new_part_file): + """Applies a SOURCE_BSDIFF or IMGDIFF operation. + + Args: + op: the operation object + op_name: name string for error reporting + patch_data: the binary patch content + old_part_file: the source partition file object + new_part_file: the target partition file object + + Raises: + PayloadError if something goes wrong. + """ + if not old_part_file: + raise PayloadError( + '%s: no source partition file provided for operation type (%d)' % + (op_name, op.type)) + + block_size = self.block_size + + # Dump patch data to file. + with tempfile.NamedTemporaryFile(delete=False) as patch_file: + patch_file_name = patch_file.name + patch_file.write(patch_data) + + if (hasattr(new_part_file, 'fileno') and + ((not old_part_file) or hasattr(old_part_file, 'fileno')) and + op.type != common.OpType.IMGDIFF): + # Construct input and output extents argument for bspatch. + in_extents_arg, _, _ = _ExtentsToBspatchArg( + op.src_extents, block_size, '%s.src_extents' % op_name, + data_length=op.src_length) + out_extents_arg, pad_off, pad_len = _ExtentsToBspatchArg( + op.dst_extents, block_size, '%s.dst_extents' % op_name, + data_length=op.dst_length) + + new_file_name = '/dev/fd/%d' % new_part_file.fileno() + # Diff from source partition. + old_file_name = '/dev/fd/%d' % old_part_file.fileno() + + # Invoke bspatch on partition file with extents args. + bspatch_cmd = [self.bspatch_path, old_file_name, new_file_name, + patch_file_name, in_extents_arg, out_extents_arg] + subprocess.check_call(bspatch_cmd) + + # Pad with zeros past the total output length. + if pad_len: + new_part_file.seek(pad_off) + new_part_file.write('\0' * pad_len) + else: + # Gather input raw data and write to a temp file. + input_part_file = old_part_file if old_part_file else new_part_file + in_data = _ReadExtents(input_part_file, op.src_extents, block_size, + max_length=op.src_length) + with tempfile.NamedTemporaryFile(delete=False) as in_file: + in_file_name = in_file.name + in_file.write(in_data) + + # Allocate temporary output file. + with tempfile.NamedTemporaryFile(delete=False) as out_file: + out_file_name = out_file.name + + # Invoke bspatch. + patch_cmd = [self.bspatch_path, in_file_name, out_file_name, + patch_file_name] + if op.type == common.OpType.IMGDIFF: + patch_cmd[0] = self.imgpatch_path + subprocess.check_call(patch_cmd) + + # Read output. + with open(out_file_name, 'rb') as out_file: + out_data = out_file.read() + if len(out_data) != op.dst_length: + raise PayloadError( + '%s: actual patched data length (%d) not as expected (%d)' % + (op_name, len(out_data), op.dst_length)) + + # Write output back to partition, with padding. + unaligned_out_len = len(out_data) % block_size + if unaligned_out_len: + out_data += '\0' * (block_size - unaligned_out_len) + _WriteExtents(new_part_file, out_data, op.dst_extents, block_size, + '%s.dst_extents' % op_name) + + # Delete input/output files. + os.remove(in_file_name) + os.remove(out_file_name) + + # Delete patch file. + os.remove(patch_file_name) + + def _ApplyOperations(self, operations, base_name, old_part_file, + new_part_file, part_size): + """Applies a sequence of update operations to a partition. + + This assumes an in-place update semantics for MOVE and BSDIFF, namely all + reads are performed first, then the data is processed and written back to + the same file. + + Args: + operations: the sequence of operations + base_name: the name of the operation sequence + old_part_file: the old partition file object, open for reading/writing + new_part_file: the new partition file object, open for reading/writing + part_size: the partition size + + Raises: + PayloadError if anything goes wrong while processing the payload. + """ + for op, op_name in common.OperationIter(operations, base_name): + # Read data blob. + data = self.payload.ReadDataBlob(op.data_offset, op.data_length) + + if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ): + self._ApplyReplaceOperation(op, op_name, data, new_part_file, part_size) + elif op.type == common.OpType.MOVE: + self._ApplyMoveOperation(op, op_name, new_part_file) + elif op.type == common.OpType.BSDIFF: + self._ApplyBsdiffOperation(op, op_name, data, new_part_file) + elif op.type == common.OpType.SOURCE_COPY: + self._ApplySourceCopyOperation(op, op_name, old_part_file, + new_part_file) + elif op.type in (common.OpType.SOURCE_BSDIFF, common.OpType.IMGDIFF): + self._ApplyDiffOperation(op, op_name, data, old_part_file, + new_part_file) + else: + raise PayloadError('%s: unknown operation type (%d)' % + (op_name, op.type)) + + def _ApplyToPartition(self, operations, part_name, base_name, + new_part_file_name, new_part_info, + old_part_file_name=None, old_part_info=None): + """Applies an update to a partition. + + Args: + operations: the sequence of update operations to apply + part_name: the name of the partition, for error reporting + base_name: the name of the operation sequence + new_part_file_name: file name to write partition data to + new_part_info: size and expected hash of dest partition + old_part_file_name: file name of source partition (optional) + old_part_info: size and expected hash of source partition (optional) + + Raises: + PayloadError if anything goes wrong with the update. + """ + # Do we have a source partition? + if old_part_file_name: + # Verify the source partition. + with open(old_part_file_name, 'rb') as old_part_file: + _VerifySha256(old_part_file, old_part_info.hash, + 'old ' + part_name, length=old_part_info.size) + new_part_file_mode = 'r+b' + if self.minor_version == common.INPLACE_MINOR_PAYLOAD_VERSION: + # Copy the src partition to the dst one; make sure we don't truncate it. + shutil.copyfile(old_part_file_name, new_part_file_name) + elif (self.minor_version == common.SOURCE_MINOR_PAYLOAD_VERSION or + self.minor_version == common.OPSRCHASH_MINOR_PAYLOAD_VERSION or + self.minor_version == common.IMGDIFF_MINOR_PAYLOAD_VERSION): + # In minor version >= 2, we don't want to copy the partitions, so + # instead just make the new partition file. + open(new_part_file_name, 'w').close() + else: + raise PayloadError("Unknown minor version: %d" % self.minor_version) + else: + # We need to create/truncate the dst partition file. + new_part_file_mode = 'w+b' + + # Apply operations. + with open(new_part_file_name, new_part_file_mode) as new_part_file: + old_part_file = (open(old_part_file_name, 'r+b') + if old_part_file_name else None) + try: + self._ApplyOperations(operations, base_name, old_part_file, + new_part_file, new_part_info.size) + finally: + if old_part_file: + old_part_file.close() + + # Truncate the result, if so instructed. + if self.truncate_to_expected_size: + new_part_file.seek(0, 2) + if new_part_file.tell() > new_part_info.size: + new_part_file.seek(new_part_info.size) + new_part_file.truncate() + + # Verify the resulting partition. + with open(new_part_file_name, 'rb') as new_part_file: + _VerifySha256(new_part_file, new_part_info.hash, + 'new ' + part_name, length=new_part_info.size) + + def Run(self, new_kernel_part, new_rootfs_part, old_kernel_part=None, + old_rootfs_part=None): + """Applier entry point, invoking all update operations. + + Args: + new_kernel_part: name of dest kernel partition file + new_rootfs_part: name of dest rootfs partition file + old_kernel_part: name of source kernel partition file (optional) + old_rootfs_part: name of source rootfs partition file (optional) + + Raises: + PayloadError if payload application failed. + """ + self.payload.ResetFile() + + # Make sure the arguments are sane and match the payload. + if not (new_kernel_part and new_rootfs_part): + raise PayloadError('missing dst {kernel,rootfs} partitions') + + if not (old_kernel_part or old_rootfs_part): + if not self.payload.IsFull(): + raise PayloadError('trying to apply a non-full update without src ' + '{kernel,rootfs} partitions') + elif old_kernel_part and old_rootfs_part: + if not self.payload.IsDelta(): + raise PayloadError('trying to apply a non-delta update onto src ' + '{kernel,rootfs} partitions') + else: + raise PayloadError('not all src partitions provided') + + # Apply update to rootfs. + self._ApplyToPartition( + self.payload.manifest.install_operations, 'rootfs', + 'install_operations', new_rootfs_part, + self.payload.manifest.new_rootfs_info, old_rootfs_part, + self.payload.manifest.old_rootfs_info) + + # Apply update to kernel update. + self._ApplyToPartition( + self.payload.manifest.kernel_install_operations, 'kernel', + 'kernel_install_operations', new_kernel_part, + self.payload.manifest.new_kernel_info, old_kernel_part, + self.payload.manifest.old_kernel_info)
diff --git a/update_engine/scripts/update_payload/block_tracer.py b/update_engine/scripts/update_payload/block_tracer.py new file mode 100644 index 0000000..f222b21 --- /dev/null +++ b/update_engine/scripts/update_payload/block_tracer.py
@@ -0,0 +1,113 @@ +# Copyright (c) 2013 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. + +"""Tracing block data source through a Chrome OS update payload. + +This module is used internally by the main Payload class for tracing block +content through an update payload. This is a useful feature in debugging +payload applying functionality in this package. The interface for invoking the +tracer is as follows: + + tracer = PayloadBlockTracer(payload) + tracer.Run(...) + +""" + +from __future__ import print_function + +import common + + +# +# Payload block tracing. +# +class PayloadBlockTracer(object): + """Tracing the origin of block data through update instructions. + + This is a short-lived object whose purpose is to isolate the logic used for + tracing the origin of destination partition blocks. + + """ + + def __init__(self, payload): + assert payload.is_init, 'uninitialized update payload' + self.payload = payload + + @staticmethod + def _TraceBlock(block, skip, trace_out_file, operations, base_name): + """Trace the origin of a given block through a sequence of operations. + + This method tries to map the given dest block to the corresponding source + block from which its content originates in the course of an update. It + further tries to trace transitive origins through MOVE operations. It is + rather efficient, doing the actual tracing by means of a single reverse + sweep through the operation sequence. It dumps a log of operations and + source blocks responsible for the data in the given dest block to the + provided output file. + + Args: + block: the block number to trace + skip: number of initial transitive origins to ignore + trace_out_file: a file object to dump the trace to + operations: the sequence of operations + base_name: name of the operation sequence + """ + # Traverse operations backwards. + for op, op_name in common.OperationIter(operations, base_name, + reverse=True): + total_block_offset = 0 + found = False + + # Is the traced block mentioned in the dest extents? + for dst_ex, dst_ex_name in common.ExtentIter(op.dst_extents, + op_name + '.dst_extents'): + if (block >= dst_ex.start_block + and block < dst_ex.start_block + dst_ex.num_blocks): + if skip: + skip -= 1 + else: + total_block_offset += block - dst_ex.start_block + trace_out_file.write( + '%d: %s: found %s (total block offset: %d)\n' % + (block, dst_ex_name, common.FormatExtent(dst_ex), + total_block_offset)) + found = True + break + + total_block_offset += dst_ex.num_blocks + + if found: + # Don't trace further, unless it's a MOVE. + if op.type != common.OpType.MOVE: + break + + # For MOVE, find corresponding source block and keep tracing. + for src_ex, src_ex_name in common.ExtentIter(op.src_extents, + op_name + '.src_extents'): + if total_block_offset < src_ex.num_blocks: + block = src_ex.start_block + total_block_offset + trace_out_file.write( + '%s: mapped to %s (%d)\n' % + (src_ex_name, common.FormatExtent(src_ex), block)) + break + + total_block_offset -= src_ex.num_blocks + + def Run(self, block, skip, trace_out_file, is_kernel): + """Block tracer entry point, invoking the actual search. + + Args: + block: the block number whose origin to trace + skip: the number of first origin mappings to skip + trace_out_file: file object to dump the trace to + is_kernel: trace through kernel (True) or rootfs (False) operations + """ + if is_kernel: + operations = self.payload.manifest.kernel_install_operations + base_name = 'kernel_install_operations' + else: + operations = self.payload.manifest.install_operations + base_name = 'install_operations' + + self._TraceBlock(block, skip, trace_out_file, operations, base_name)
diff --git a/update_engine/scripts/update_payload/checker.py b/update_engine/scripts/update_payload/checker.py new file mode 100644 index 0000000..e13ea13 --- /dev/null +++ b/update_engine/scripts/update_payload/checker.py
@@ -0,0 +1,1275 @@ +# Copyright (c) 2013 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. + +"""Verifying the integrity of a Chrome OS update payload. + +This module is used internally by the main Payload class for verifying the +integrity of an update payload. The interface for invoking the checks is as +follows: + + checker = PayloadChecker(payload) + checker.Run(...) +""" + +from __future__ import print_function + +import array +import base64 +import hashlib +import itertools +import os +import subprocess + +import common +import error +import format_utils +import histogram +import update_metadata_pb2 + + +# +# Constants. +# + +_CHECK_DST_PSEUDO_EXTENTS = 'dst-pseudo-extents' +_CHECK_MOVE_SAME_SRC_DST_BLOCK = 'move-same-src-dst-block' +_CHECK_PAYLOAD_SIG = 'payload-sig' +CHECKS_TO_DISABLE = ( + _CHECK_DST_PSEUDO_EXTENTS, + _CHECK_MOVE_SAME_SRC_DST_BLOCK, + _CHECK_PAYLOAD_SIG, +) + +_TYPE_FULL = 'full' +_TYPE_DELTA = 'delta' + +_DEFAULT_BLOCK_SIZE = 4096 + +_DEFAULT_PUBKEY_BASE_NAME = 'update-payload-key.pub.pem' +_DEFAULT_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__), + _DEFAULT_PUBKEY_BASE_NAME) + +# Supported minor version map to payload types allowed to be using them. +_SUPPORTED_MINOR_VERSIONS = { + 0: (_TYPE_FULL,), + 1: (_TYPE_DELTA,), + 2: (_TYPE_DELTA,), + 3: (_TYPE_DELTA,), + 4: (_TYPE_DELTA,), +} + +_OLD_DELTA_USABLE_PART_SIZE = 2 * 1024 * 1024 * 1024 + +# +# Helper functions. +# + +def _IsPowerOfTwo(val): + """Returns True iff val is a power of two.""" + return val > 0 and (val & (val - 1)) == 0 + + +def _AddFormat(format_func, value): + """Adds a custom formatted representation to ordinary string representation. + + Args: + format_func: A value formatter. + value: Value to be formatted and returned. + + Returns: + A string 'x (y)' where x = str(value) and y = format_func(value). + """ + ret = str(value) + formatted_str = format_func(value) + if formatted_str: + ret += ' (%s)' % formatted_str + return ret + + +def _AddHumanReadableSize(size): + """Adds a human readable representation to a byte size value.""" + return _AddFormat(format_utils.BytesToHumanReadable, size) + + +# +# Payload report generator. +# + +class _PayloadReport(object): + """A payload report generator. + + A report is essentially a sequence of nodes, which represent data points. It + is initialized to have a "global", untitled section. A node may be a + sub-report itself. + """ + + # Report nodes: Field, sub-report, section. + class Node(object): + """A report node interface.""" + + @staticmethod + def _Indent(indent, line): + """Indents a line by a given indentation amount. + + Args: + indent: The indentation amount. + line: The line content (string). + + Returns: + The properly indented line (string). + """ + return '%*s%s' % (indent, '', line) + + def GenerateLines(self, base_indent, sub_indent, curr_section): + """Generates the report lines for this node. + + Args: + base_indent: Base indentation for each line. + sub_indent: Additional indentation for sub-nodes. + curr_section: The current report section object. + + Returns: + A pair consisting of a list of properly indented report lines and a new + current section object. + """ + raise NotImplementedError + + class FieldNode(Node): + """A field report node, representing a (name, value) pair.""" + + def __init__(self, name, value, linebreak, indent): + super(_PayloadReport.FieldNode, self).__init__() + self.name = name + self.value = value + self.linebreak = linebreak + self.indent = indent + + def GenerateLines(self, base_indent, sub_indent, curr_section): + """Generates a properly formatted 'name : value' entry.""" + report_output = '' + if self.name: + report_output += self.name.ljust(curr_section.max_field_name_len) + ' :' + value_lines = str(self.value).splitlines() + if self.linebreak and self.name: + report_output += '\n' + '\n'.join( + ['%*s%s' % (self.indent, '', line) for line in value_lines]) + else: + if self.name: + report_output += ' ' + report_output += '%*s' % (self.indent, '') + cont_line_indent = len(report_output) + indented_value_lines = [value_lines[0]] + indented_value_lines.extend(['%*s%s' % (cont_line_indent, '', line) + for line in value_lines[1:]]) + report_output += '\n'.join(indented_value_lines) + + report_lines = [self._Indent(base_indent, line + '\n') + for line in report_output.split('\n')] + return report_lines, curr_section + + class SubReportNode(Node): + """A sub-report node, representing a nested report.""" + + def __init__(self, title, report): + super(_PayloadReport.SubReportNode, self).__init__() + self.title = title + self.report = report + + def GenerateLines(self, base_indent, sub_indent, curr_section): + """Recurse with indentation.""" + report_lines = [self._Indent(base_indent, self.title + ' =>\n')] + report_lines.extend(self.report.GenerateLines(base_indent + sub_indent, + sub_indent)) + return report_lines, curr_section + + class SectionNode(Node): + """A section header node.""" + + def __init__(self, title=None): + super(_PayloadReport.SectionNode, self).__init__() + self.title = title + self.max_field_name_len = 0 + + def GenerateLines(self, base_indent, sub_indent, curr_section): + """Dump a title line, return self as the (new) current section.""" + report_lines = [] + if self.title: + report_lines.append(self._Indent(base_indent, + '=== %s ===\n' % self.title)) + return report_lines, self + + def __init__(self): + self.report = [] + self.last_section = self.global_section = self.SectionNode() + self.is_finalized = False + + def GenerateLines(self, base_indent, sub_indent): + """Generates the lines in the report, properly indented. + + Args: + base_indent: The indentation used for root-level report lines. + sub_indent: The indentation offset used for sub-reports. + + Returns: + A list of indented report lines. + """ + report_lines = [] + curr_section = self.global_section + for node in self.report: + node_report_lines, curr_section = node.GenerateLines( + base_indent, sub_indent, curr_section) + report_lines.extend(node_report_lines) + + return report_lines + + def Dump(self, out_file, base_indent=0, sub_indent=2): + """Dumps the report to a file. + + Args: + out_file: File object to output the content to. + base_indent: Base indentation for report lines. + sub_indent: Added indentation for sub-reports. + """ + report_lines = self.GenerateLines(base_indent, sub_indent) + if report_lines and not self.is_finalized: + report_lines.append('(incomplete report)\n') + + for line in report_lines: + out_file.write(line) + + def AddField(self, name, value, linebreak=False, indent=0): + """Adds a field/value pair to the payload report. + + Args: + name: The field's name. + value: The field's value. + linebreak: Whether the value should be printed on a new line. + indent: Amount of extra indent for each line of the value. + """ + assert not self.is_finalized + if name and self.last_section.max_field_name_len < len(name): + self.last_section.max_field_name_len = len(name) + self.report.append(self.FieldNode(name, value, linebreak, indent)) + + def AddSubReport(self, title): + """Adds and returns a sub-report with a title.""" + assert not self.is_finalized + sub_report = self.SubReportNode(title, type(self)()) + self.report.append(sub_report) + return sub_report.report + + def AddSection(self, title): + """Adds a new section title.""" + assert not self.is_finalized + self.last_section = self.SectionNode(title) + self.report.append(self.last_section) + + def Finalize(self): + """Seals the report, marking it as complete.""" + self.is_finalized = True + + +# +# Payload verification. +# + +class PayloadChecker(object): + """Checking the integrity of an update payload. + + This is a short-lived object whose purpose is to isolate the logic used for + verifying the integrity of an update payload. + """ + + def __init__(self, payload, assert_type=None, block_size=0, + allow_unhashed=False, disabled_tests=()): + """Initialize the checker. + + Args: + payload: The payload object to check. + assert_type: Assert that payload is either 'full' or 'delta' (optional). + block_size: Expected filesystem / payload block size (optional). + allow_unhashed: Allow operations with unhashed data blobs. + disabled_tests: Sequence of tests to disable. + """ + if not payload.is_init: + raise ValueError('Uninitialized update payload.') + + # Set checker configuration. + self.payload = payload + self.block_size = block_size if block_size else _DEFAULT_BLOCK_SIZE + if not _IsPowerOfTwo(self.block_size): + raise error.PayloadError( + 'Expected block (%d) size is not a power of two.' % self.block_size) + if assert_type not in (None, _TYPE_FULL, _TYPE_DELTA): + raise error.PayloadError('Invalid assert_type value (%r).' % + assert_type) + self.payload_type = assert_type + self.allow_unhashed = allow_unhashed + + # Disable specific tests. + self.check_dst_pseudo_extents = ( + _CHECK_DST_PSEUDO_EXTENTS not in disabled_tests) + self.check_move_same_src_dst_block = ( + _CHECK_MOVE_SAME_SRC_DST_BLOCK not in disabled_tests) + self.check_payload_sig = _CHECK_PAYLOAD_SIG not in disabled_tests + + # Reset state; these will be assigned when the manifest is checked. + self.sigs_offset = 0 + self.sigs_size = 0 + self.old_rootfs_fs_size = 0 + self.old_kernel_fs_size = 0 + self.new_rootfs_fs_size = 0 + self.new_kernel_fs_size = 0 + self.minor_version = None + + @staticmethod + def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str, + msg_name=None, linebreak=False, indent=0): + """Adds an element from a protobuf message to the payload report. + + Checks to see whether a message contains a given element, and if so adds + the element value to the provided report. A missing mandatory element + causes an exception to be raised. + + Args: + msg: The message containing the element. + name: The name of the element. + report: A report object to add the element name/value to. + is_mandatory: Whether or not this element must be present. + is_submsg: Whether this element is itself a message. + convert: A function for converting the element value for reporting. + msg_name: The name of the message object (for error reporting). + linebreak: Whether the value report should induce a line break. + indent: Amount of indent used for reporting the value. + + Returns: + A pair consisting of the element value and the generated sub-report for + it (if the element is a sub-message, None otherwise). If the element is + missing, returns (None, None). + + Raises: + error.PayloadError if a mandatory element is missing. + """ + if not msg.HasField(name): + if is_mandatory: + raise error.PayloadError('%smissing mandatory %s %r.' % + (msg_name + ' ' if msg_name else '', + 'sub-message' if is_submsg else 'field', + name)) + return None, None + + value = getattr(msg, name) + if is_submsg: + return value, report and report.AddSubReport(name) + else: + if report: + report.AddField(name, convert(value), linebreak=linebreak, + indent=indent) + return value, None + + @staticmethod + def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str, + linebreak=False, indent=0): + """Adds a mandatory field; returning first component from _CheckElem.""" + return PayloadChecker._CheckElem(msg, field_name, report, True, False, + convert=convert, msg_name=msg_name, + linebreak=linebreak, indent=indent)[0] + + @staticmethod + def _CheckOptionalField(msg, field_name, report, convert=str, + linebreak=False, indent=0): + """Adds an optional field; returning first component from _CheckElem.""" + return PayloadChecker._CheckElem(msg, field_name, report, False, False, + convert=convert, linebreak=linebreak, + indent=indent)[0] + + @staticmethod + def _CheckMandatorySubMsg(msg, submsg_name, report, msg_name): + """Adds a mandatory sub-message; wrapper for _CheckElem.""" + return PayloadChecker._CheckElem(msg, submsg_name, report, True, True, + msg_name) + + @staticmethod + def _CheckOptionalSubMsg(msg, submsg_name, report): + """Adds an optional sub-message; wrapper for _CheckElem.""" + return PayloadChecker._CheckElem(msg, submsg_name, report, False, True) + + @staticmethod + def _CheckPresentIff(val1, val2, name1, name2, obj_name): + """Checks that val1 is None iff val2 is None. + + Args: + val1: first value to be compared. + val2: second value to be compared. + name1: name of object holding the first value. + name2: name of object holding the second value. + obj_name: Name of the object containing these values. + + Raises: + error.PayloadError if assertion does not hold. + """ + if None in (val1, val2) and val1 is not val2: + present, missing = (name1, name2) if val2 is None else (name2, name1) + raise error.PayloadError('%r present without %r%s.' % + (present, missing, + ' in ' + obj_name if obj_name else '')) + + @staticmethod + def _Run(cmd, send_data=None): + """Runs a subprocess, returns its output. + + Args: + cmd: Sequence of command-line argument for invoking the subprocess. + send_data: Data to feed to the process via its stdin. + + Returns: + A tuple containing the stdout and stderr output of the process. + """ + run_process = subprocess.Popen(cmd, stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + try: + result = run_process.communicate(input=send_data) + finally: + exit_code = run_process.wait() + + if exit_code: + raise RuntimeError('Subprocess %r failed with code %r.' % + (cmd, exit_code)) + + return result + + @staticmethod + def _CheckSha256Signature(sig_data, pubkey_file_name, actual_hash, sig_name): + """Verifies an actual hash against a signed one. + + Args: + sig_data: The raw signature data. + pubkey_file_name: Public key used for verifying signature. + actual_hash: The actual hash digest. + sig_name: Signature name for error reporting. + + Raises: + error.PayloadError if signature could not be verified. + """ + if len(sig_data) != 256: + raise error.PayloadError( + '%s: signature size (%d) not as expected (256).' % + (sig_name, len(sig_data))) + signed_data, _ = PayloadChecker._Run( + ['openssl', 'rsautl', '-verify', '-pubin', '-inkey', pubkey_file_name], + send_data=sig_data) + + if len(signed_data) != len(common.SIG_ASN1_HEADER) + 32: + raise error.PayloadError('%s: unexpected signed data length (%d).' % + (sig_name, len(signed_data))) + + if not signed_data.startswith(common.SIG_ASN1_HEADER): + raise error.PayloadError('%s: not containing standard ASN.1 prefix.' % + sig_name) + + signed_hash = signed_data[len(common.SIG_ASN1_HEADER):] + if signed_hash != actual_hash: + raise error.PayloadError( + '%s: signed hash (%s) different from actual (%s).' % + (sig_name, common.FormatSha256(signed_hash), + common.FormatSha256(actual_hash))) + + @staticmethod + def _CheckBlocksFitLength(length, num_blocks, block_size, length_name, + block_name=None): + """Checks that a given length fits given block space. + + This ensures that the number of blocks allocated is appropriate for the + length of the data residing in these blocks. + + Args: + length: The actual length of the data. + num_blocks: The number of blocks allocated for it. + block_size: The size of each block in bytes. + length_name: Name of length (used for error reporting). + block_name: Name of block (used for error reporting). + + Raises: + error.PayloadError if the aforementioned invariant is not satisfied. + """ + # Check: length <= num_blocks * block_size. + if length > num_blocks * block_size: + raise error.PayloadError( + '%s (%d) > num %sblocks (%d) * block_size (%d).' % + (length_name, length, block_name or '', num_blocks, block_size)) + + # Check: length > (num_blocks - 1) * block_size. + if length <= (num_blocks - 1) * block_size: + raise error.PayloadError( + '%s (%d) <= (num %sblocks - 1 (%d)) * block_size (%d).' % + (length_name, length, block_name or '', num_blocks - 1, block_size)) + + def _CheckManifestMinorVersion(self, report): + """Checks the payload manifest minor_version field. + + Args: + report: The report object to add to. + + Raises: + error.PayloadError if any of the checks fail. + """ + self.minor_version = self._CheckOptionalField(self.payload.manifest, + 'minor_version', report) + if self.minor_version in _SUPPORTED_MINOR_VERSIONS: + if self.payload_type not in _SUPPORTED_MINOR_VERSIONS[self.minor_version]: + raise error.PayloadError( + 'Minor version %d not compatible with payload type %s.' % + (self.minor_version, self.payload_type)) + elif self.minor_version is None: + raise error.PayloadError('Minor version is not set.') + else: + raise error.PayloadError('Unsupported minor version: %d' % + self.minor_version) + + def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0): + """Checks the payload manifest. + + Args: + report: A report object to add to. + rootfs_part_size: Size of the rootfs partition in bytes. + kernel_part_size: Size of the kernel partition in bytes. + + Returns: + A tuple consisting of the partition block size used during the update + (integer), the signatures block offset and size. + + Raises: + error.PayloadError if any of the checks fail. + """ + manifest = self.payload.manifest + report.AddSection('manifest') + + # Check: block_size must exist and match the expected value. + actual_block_size = self._CheckMandatoryField(manifest, 'block_size', + report, 'manifest') + if actual_block_size != self.block_size: + raise error.PayloadError('Block_size (%d) not as expected (%d).' % + (actual_block_size, self.block_size)) + + # Check: signatures_offset <==> signatures_size. + self.sigs_offset = self._CheckOptionalField(manifest, 'signatures_offset', + report) + self.sigs_size = self._CheckOptionalField(manifest, 'signatures_size', + report) + self._CheckPresentIff(self.sigs_offset, self.sigs_size, + 'signatures_offset', 'signatures_size', 'manifest') + + # Check: old_kernel_info <==> old_rootfs_info. + oki_msg, oki_report = self._CheckOptionalSubMsg(manifest, + 'old_kernel_info', report) + ori_msg, ori_report = self._CheckOptionalSubMsg(manifest, + 'old_rootfs_info', report) + self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info', + 'old_rootfs_info', 'manifest') + if oki_msg: # equivalently, ori_msg + # Assert/mark delta payload. + if self.payload_type == _TYPE_FULL: + raise error.PayloadError( + 'Apparent full payload contains old_{kernel,rootfs}_info.') + self.payload_type = _TYPE_DELTA + + # Check: {size, hash} present in old_{kernel,rootfs}_info. + self.old_kernel_fs_size = self._CheckMandatoryField( + oki_msg, 'size', oki_report, 'old_kernel_info') + self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info', + convert=common.FormatSha256) + self.old_rootfs_fs_size = self._CheckMandatoryField( + ori_msg, 'size', ori_report, 'old_rootfs_info') + self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info', + convert=common.FormatSha256) + + # Check: old_{kernel,rootfs} size must fit in respective partition. + if kernel_part_size and self.old_kernel_fs_size > kernel_part_size: + raise error.PayloadError( + 'Old kernel content (%d) exceed partition size (%d).' % + (self.old_kernel_fs_size, kernel_part_size)) + if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size: + raise error.PayloadError( + 'Old rootfs content (%d) exceed partition size (%d).' % + (self.old_rootfs_fs_size, rootfs_part_size)) + else: + # Assert/mark full payload. + if self.payload_type == _TYPE_DELTA: + raise error.PayloadError( + 'Apparent delta payload missing old_{kernel,rootfs}_info.') + self.payload_type = _TYPE_FULL + + # Check: new_kernel_info present; contains {size, hash}. + nki_msg, nki_report = self._CheckMandatorySubMsg( + manifest, 'new_kernel_info', report, 'manifest') + self.new_kernel_fs_size = self._CheckMandatoryField( + nki_msg, 'size', nki_report, 'new_kernel_info') + self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info', + convert=common.FormatSha256) + + # Check: new_rootfs_info present; contains {size, hash}. + nri_msg, nri_report = self._CheckMandatorySubMsg( + manifest, 'new_rootfs_info', report, 'manifest') + self.new_rootfs_fs_size = self._CheckMandatoryField( + nri_msg, 'size', nri_report, 'new_rootfs_info') + self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info', + convert=common.FormatSha256) + + # Check: new_{kernel,rootfs} size must fit in respective partition. + if kernel_part_size and self.new_kernel_fs_size > kernel_part_size: + raise error.PayloadError( + 'New kernel content (%d) exceed partition size (%d).' % + (self.new_kernel_fs_size, kernel_part_size)) + if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size: + raise error.PayloadError( + 'New rootfs content (%d) exceed partition size (%d).' % + (self.new_rootfs_fs_size, rootfs_part_size)) + + # Check: minor_version makes sense for the payload type. This check should + # run after the payload type has been set. + self._CheckManifestMinorVersion(report) + + def _CheckLength(self, length, total_blocks, op_name, length_name): + """Checks whether a length matches the space designated in extents. + + Args: + length: The total length of the data. + total_blocks: The total number of blocks in extents. + op_name: Operation name (for error reporting). + length_name: Length name (for error reporting). + + Raises: + error.PayloadError is there a problem with the length. + """ + # Check: length is non-zero. + if length == 0: + raise error.PayloadError('%s: %s is zero.' % (op_name, length_name)) + + # Check that length matches number of blocks. + self._CheckBlocksFitLength(length, total_blocks, self.block_size, + '%s: %s' % (op_name, length_name)) + + def _CheckExtents(self, extents, usable_size, block_counters, name, + allow_pseudo=False, allow_signature=False): + """Checks a sequence of extents. + + Args: + extents: The sequence of extents to check. + usable_size: The usable size of the partition to which the extents apply. + block_counters: Array of counters corresponding to the number of blocks. + name: The name of the extent block. + allow_pseudo: Whether or not pseudo block numbers are allowed. + allow_signature: Whether or not the extents are used for a signature. + + Returns: + The total number of blocks in the extents. + + Raises: + error.PayloadError if any of the entailed checks fails. + """ + total_num_blocks = 0 + for ex, ex_name in common.ExtentIter(extents, name): + # Check: Mandatory fields. + start_block = PayloadChecker._CheckMandatoryField(ex, 'start_block', + None, ex_name) + num_blocks = PayloadChecker._CheckMandatoryField(ex, 'num_blocks', None, + ex_name) + end_block = start_block + num_blocks + + # Check: num_blocks > 0. + if num_blocks == 0: + raise error.PayloadError('%s: extent length is zero.' % ex_name) + + if start_block != common.PSEUDO_EXTENT_MARKER: + # Check: Make sure we're within the partition limit. + if usable_size and end_block * self.block_size > usable_size: + raise error.PayloadError( + '%s: extent (%s) exceeds usable partition size (%d).' % + (ex_name, common.FormatExtent(ex, self.block_size), usable_size)) + + # Record block usage. + for i in xrange(start_block, end_block): + block_counters[i] += 1 + elif not (allow_pseudo or (allow_signature and len(extents) == 1)): + # Pseudo-extents must be allowed explicitly, or otherwise be part of a + # signature operation (in which case there has to be exactly one). + raise error.PayloadError('%s: unexpected pseudo-extent.' % ex_name) + + total_num_blocks += num_blocks + + return total_num_blocks + + def _CheckReplaceOperation(self, op, data_length, total_dst_blocks, op_name): + """Specific checks for REPLACE/REPLACE_BZ operations. + + Args: + op: The operation object from the manifest. + data_length: The length of the data blob associated with the operation. + total_dst_blocks: Total number of blocks in dst_extents. + op_name: Operation name for error reporting. + + Raises: + error.PayloadError if any check fails. + """ + # Check: Does not contain src extents. + if op.src_extents: + raise error.PayloadError('%s: contains src_extents.' % op_name) + + # Check: Contains data. + if data_length is None: + raise error.PayloadError('%s: missing data_{offset,length}.' % op_name) + + if op.type == common.OpType.REPLACE: + PayloadChecker._CheckBlocksFitLength(data_length, total_dst_blocks, + self.block_size, + op_name + '.data_length', 'dst') + else: + # Check: data_length must be smaller than the alotted dst blocks. + if data_length >= total_dst_blocks * self.block_size: + raise error.PayloadError( + '%s: data_length (%d) must be less than allotted dst block ' + 'space (%d * %d).' % + (op_name, data_length, total_dst_blocks, self.block_size)) + + def _CheckMoveOperation(self, op, data_offset, total_src_blocks, + total_dst_blocks, op_name): + """Specific checks for MOVE operations. + + Args: + op: The operation object from the manifest. + data_offset: The offset of a data blob for the operation. + total_src_blocks: Total number of blocks in src_extents. + total_dst_blocks: Total number of blocks in dst_extents. + op_name: Operation name for error reporting. + + Raises: + error.PayloadError if any check fails. + """ + # Check: No data_{offset,length}. + if data_offset is not None: + raise error.PayloadError('%s: contains data_{offset,length}.' % op_name) + + # Check: total_src_blocks == total_dst_blocks. + if total_src_blocks != total_dst_blocks: + raise error.PayloadError( + '%s: total src blocks (%d) != total dst blocks (%d).' % + (op_name, total_src_blocks, total_dst_blocks)) + + # Check: For all i, i-th src block index != i-th dst block index. + i = 0 + src_extent_iter = iter(op.src_extents) + dst_extent_iter = iter(op.dst_extents) + src_extent = dst_extent = None + src_idx = src_num = dst_idx = dst_num = 0 + while i < total_src_blocks: + # Get the next source extent, if needed. + if not src_extent: + try: + src_extent = src_extent_iter.next() + except StopIteration: + raise error.PayloadError('%s: ran out of src extents (%d/%d).' % + (op_name, i, total_src_blocks)) + src_idx = src_extent.start_block + src_num = src_extent.num_blocks + + # Get the next dest extent, if needed. + if not dst_extent: + try: + dst_extent = dst_extent_iter.next() + except StopIteration: + raise error.PayloadError('%s: ran out of dst extents (%d/%d).' % + (op_name, i, total_dst_blocks)) + dst_idx = dst_extent.start_block + dst_num = dst_extent.num_blocks + + # Check: start block is not 0. See crbug/480751; there are still versions + # of update_engine which fail when seeking to 0 in PReadAll and PWriteAll, + # so we need to fail payloads that try to MOVE to/from block 0. + if src_idx == 0 or dst_idx == 0: + raise error.PayloadError( + '%s: MOVE operation cannot have extent with start block 0' % + op_name) + + if self.check_move_same_src_dst_block and src_idx == dst_idx: + raise error.PayloadError( + '%s: src/dst block number %d is the same (%d).' % + (op_name, i, src_idx)) + + advance = min(src_num, dst_num) + i += advance + + src_idx += advance + src_num -= advance + if src_num == 0: + src_extent = None + + dst_idx += advance + dst_num -= advance + if dst_num == 0: + dst_extent = None + + # Make sure we've exhausted all src/dst extents. + if src_extent: + raise error.PayloadError('%s: excess src blocks.' % op_name) + if dst_extent: + raise error.PayloadError('%s: excess dst blocks.' % op_name) + + def _CheckAnyDiffOperation(self, data_length, total_dst_blocks, op_name): + """Specific checks for BSDIFF, SOURCE_BSDIFF and IMGDIFF operations. + + Args: + data_length: The length of the data blob associated with the operation. + total_dst_blocks: Total number of blocks in dst_extents. + op_name: Operation name for error reporting. + + Raises: + error.PayloadError if any check fails. + """ + # Check: data_{offset,length} present. + if data_length is None: + raise error.PayloadError('%s: missing data_{offset,length}.' % op_name) + + # Check: data_length is strictly smaller than the alotted dst blocks. + if data_length >= total_dst_blocks * self.block_size: + raise error.PayloadError( + '%s: data_length (%d) must be smaller than allotted dst space ' + '(%d * %d = %d).' % + (op_name, data_length, total_dst_blocks, self.block_size, + total_dst_blocks * self.block_size)) + + def _CheckSourceCopyOperation(self, data_offset, total_src_blocks, + total_dst_blocks, op_name): + """Specific checks for SOURCE_COPY. + + Args: + data_offset: The offset of a data blob for the operation. + total_src_blocks: Total number of blocks in src_extents. + total_dst_blocks: Total number of blocks in dst_extents. + op_name: Operation name for error reporting. + + Raises: + error.PayloadError if any check fails. + """ + # Check: No data_{offset,length}. + if data_offset is not None: + raise error.PayloadError('%s: contains data_{offset,length}.' % op_name) + + # Check: total_src_blocks == total_dst_blocks. + if total_src_blocks != total_dst_blocks: + raise error.PayloadError( + '%s: total src blocks (%d) != total dst blocks (%d).' % + (op_name, total_src_blocks, total_dst_blocks)) + + def _CheckAnySourceOperation(self, op, total_src_blocks, op_name): + """Specific checks for SOURCE_* operations. + + Args: + op: The operation object from the manifest. + total_src_blocks: Total number of blocks in src_extents. + op_name: Operation name for error reporting. + + Raises: + error.PayloadError if any check fails. + """ + # Check: total_src_blocks != 0. + if total_src_blocks == 0: + raise error.PayloadError('%s: no src blocks in a source op.' % op_name) + + # Check: src_sha256_hash present in minor version >= 3. + if self.minor_version >= 3 and op.src_sha256_hash is None: + raise error.PayloadError('%s: source hash missing.' % op_name) + + def _CheckOperation(self, op, op_name, is_last, old_block_counters, + new_block_counters, old_usable_size, new_usable_size, + prev_data_offset, allow_signature, blob_hash_counts): + """Checks a single update operation. + + Args: + op: The operation object. + op_name: Operation name string for error reporting. + is_last: Whether this is the last operation in the sequence. + old_block_counters: Arrays of block read counters. + new_block_counters: Arrays of block write counters. + old_usable_size: The overall usable size for src data in bytes. + new_usable_size: The overall usable size for dst data in bytes. + prev_data_offset: Offset of last used data bytes. + allow_signature: Whether this may be a signature operation. + blob_hash_counts: Counters for hashed/unhashed blobs. + + Returns: + The amount of data blob associated with the operation. + + Raises: + error.PayloadError if any check has failed. + """ + # Check extents. + total_src_blocks = self._CheckExtents( + op.src_extents, old_usable_size, old_block_counters, + op_name + '.src_extents', allow_pseudo=True) + allow_signature_in_extents = (allow_signature and is_last and + op.type == common.OpType.REPLACE) + total_dst_blocks = self._CheckExtents( + op.dst_extents, new_usable_size, new_block_counters, + op_name + '.dst_extents', + allow_pseudo=(not self.check_dst_pseudo_extents), + allow_signature=allow_signature_in_extents) + + # Check: data_offset present <==> data_length present. + data_offset = self._CheckOptionalField(op, 'data_offset', None) + data_length = self._CheckOptionalField(op, 'data_length', None) + self._CheckPresentIff(data_offset, data_length, 'data_offset', + 'data_length', op_name) + + # Check: At least one dst_extent. + if not op.dst_extents: + raise error.PayloadError('%s: dst_extents is empty.' % op_name) + + # Check {src,dst}_length, if present. + if op.HasField('src_length'): + self._CheckLength(op.src_length, total_src_blocks, op_name, 'src_length') + if op.HasField('dst_length'): + self._CheckLength(op.dst_length, total_dst_blocks, op_name, 'dst_length') + + if op.HasField('data_sha256_hash'): + blob_hash_counts['hashed'] += 1 + + # Check: Operation carries data. + if data_offset is None: + raise error.PayloadError( + '%s: data_sha256_hash present but no data_{offset,length}.' % + op_name) + + # Check: Hash verifies correctly. + # pylint cannot find the method in hashlib, for some reason. + # pylint: disable=E1101 + actual_hash = hashlib.sha256(self.payload.ReadDataBlob(data_offset, + data_length)) + if op.data_sha256_hash != actual_hash.digest(): + raise error.PayloadError( + '%s: data_sha256_hash (%s) does not match actual hash (%s).' % + (op_name, common.FormatSha256(op.data_sha256_hash), + common.FormatSha256(actual_hash.digest()))) + elif data_offset is not None: + if allow_signature_in_extents: + blob_hash_counts['signature'] += 1 + elif self.allow_unhashed: + blob_hash_counts['unhashed'] += 1 + else: + raise error.PayloadError('%s: unhashed operation not allowed.' % + op_name) + + if data_offset is not None: + # Check: Contiguous use of data section. + if data_offset != prev_data_offset: + raise error.PayloadError( + '%s: data offset (%d) not matching amount used so far (%d).' % + (op_name, data_offset, prev_data_offset)) + + # Type-specific checks. + if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ): + self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name) + elif op.type == common.OpType.MOVE and self.minor_version == 1: + self._CheckMoveOperation(op, data_offset, total_src_blocks, + total_dst_blocks, op_name) + elif op.type == common.OpType.BSDIFF and self.minor_version == 1: + self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name) + elif op.type == common.OpType.SOURCE_COPY and self.minor_version >= 2: + self._CheckSourceCopyOperation(data_offset, total_src_blocks, + total_dst_blocks, op_name) + self._CheckAnySourceOperation(op, total_src_blocks, op_name) + elif op.type == common.OpType.SOURCE_BSDIFF and self.minor_version >= 2: + self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name) + self._CheckAnySourceOperation(op, total_src_blocks, op_name) + elif op.type == common.OpType.IMGDIFF and self.minor_version >= 4: + self._CheckAnyDiffOperation(data_length, total_dst_blocks, op_name) + self._CheckAnySourceOperation(op, total_src_blocks, op_name) + else: + raise error.PayloadError( + 'Operation %s (type %d) not allowed in minor version %d' % + (op_name, op.type, self.minor_version)) + return data_length if data_length is not None else 0 + + def _SizeToNumBlocks(self, size): + """Returns the number of blocks needed to contain a given byte size.""" + return (size + self.block_size - 1) / self.block_size + + def _AllocBlockCounters(self, total_size): + """Returns a freshly initialized array of block counters. + + Note that the generated array is not portable as is due to byte-ordering + issues, hence it should not be serialized. + + Args: + total_size: The total block size in bytes. + + Returns: + An array of unsigned short elements initialized to zero, one for each of + the blocks necessary for containing the partition. + """ + return array.array('H', + itertools.repeat(0, self._SizeToNumBlocks(total_size))) + + def _CheckOperations(self, operations, report, base_name, old_fs_size, + new_fs_size, new_usable_size, prev_data_offset, + allow_signature): + """Checks a sequence of update operations. + + Args: + operations: The sequence of operations to check. + report: The report object to add to. + base_name: The name of the operation block. + old_fs_size: The old filesystem size in bytes. + new_fs_size: The new filesystem size in bytes. + new_usable_size: The overall usable size of the new partition in bytes. + prev_data_offset: Offset of last used data bytes. + allow_signature: Whether this sequence may contain signature operations. + + Returns: + The total data blob size used. + + Raises: + error.PayloadError if any of the checks fails. + """ + # The total size of data blobs used by operations scanned thus far. + total_data_used = 0 + # Counts of specific operation types. + op_counts = { + common.OpType.REPLACE: 0, + common.OpType.REPLACE_BZ: 0, + common.OpType.MOVE: 0, + common.OpType.BSDIFF: 0, + common.OpType.SOURCE_COPY: 0, + common.OpType.SOURCE_BSDIFF: 0, + common.OpType.IMGDIFF: 0, + } + # Total blob sizes for each operation type. + op_blob_totals = { + common.OpType.REPLACE: 0, + common.OpType.REPLACE_BZ: 0, + # MOVE operations don't have blobs. + common.OpType.BSDIFF: 0, + # SOURCE_COPY operations don't have blobs. + common.OpType.SOURCE_BSDIFF: 0, + common.OpType.IMGDIFF: 0, + } + # Counts of hashed vs unhashed operations. + blob_hash_counts = { + 'hashed': 0, + 'unhashed': 0, + } + if allow_signature: + blob_hash_counts['signature'] = 0 + + # Allocate old and new block counters. + old_block_counters = (self._AllocBlockCounters(new_usable_size) + if old_fs_size else None) + new_block_counters = self._AllocBlockCounters(new_usable_size) + + # Process and verify each operation. + op_num = 0 + for op, op_name in common.OperationIter(operations, base_name): + op_num += 1 + + # Check: Type is valid. + if op.type not in op_counts.keys(): + raise error.PayloadError('%s: invalid type (%d).' % (op_name, op.type)) + op_counts[op.type] += 1 + + is_last = op_num == len(operations) + curr_data_used = self._CheckOperation( + op, op_name, is_last, old_block_counters, new_block_counters, + new_usable_size if old_fs_size else 0, new_usable_size, + prev_data_offset + total_data_used, allow_signature, + blob_hash_counts) + if curr_data_used: + op_blob_totals[op.type] += curr_data_used + total_data_used += curr_data_used + + # Report totals and breakdown statistics. + report.AddField('total operations', op_num) + report.AddField( + None, + histogram.Histogram.FromCountDict(op_counts, + key_names=common.OpType.NAMES), + indent=1) + report.AddField('total blobs', sum(blob_hash_counts.values())) + report.AddField(None, + histogram.Histogram.FromCountDict(blob_hash_counts), + indent=1) + report.AddField('total blob size', _AddHumanReadableSize(total_data_used)) + report.AddField( + None, + histogram.Histogram.FromCountDict(op_blob_totals, + formatter=_AddHumanReadableSize, + key_names=common.OpType.NAMES), + indent=1) + + # Report read/write histograms. + if old_block_counters: + report.AddField('block read hist', + histogram.Histogram.FromKeyList(old_block_counters), + linebreak=True, indent=1) + + new_write_hist = histogram.Histogram.FromKeyList( + new_block_counters[:self._SizeToNumBlocks(new_fs_size)]) + report.AddField('block write hist', new_write_hist, linebreak=True, + indent=1) + + # Check: Full update must write each dst block once. + if self.payload_type == _TYPE_FULL and new_write_hist.GetKeys() != [1]: + raise error.PayloadError( + '%s: not all blocks written exactly once during full update.' % + base_name) + + return total_data_used + + def _CheckSignatures(self, report, pubkey_file_name): + """Checks a payload's signature block.""" + sigs_raw = self.payload.ReadDataBlob(self.sigs_offset, self.sigs_size) + sigs = update_metadata_pb2.Signatures() + sigs.ParseFromString(sigs_raw) + report.AddSection('signatures') + + # Check: At least one signature present. + # pylint cannot see through the protobuf object, it seems. + # pylint: disable=E1101 + if not sigs.signatures: + raise error.PayloadError('Signature block is empty.') + + last_ops_section = (self.payload.manifest.kernel_install_operations or + self.payload.manifest.install_operations) + fake_sig_op = last_ops_section[-1] + # Check: signatures_{offset,size} must match the last (fake) operation. + if not (fake_sig_op.type == common.OpType.REPLACE and + self.sigs_offset == fake_sig_op.data_offset and + self.sigs_size == fake_sig_op.data_length): + raise error.PayloadError( + 'Signatures_{offset,size} (%d+%d) does not match last operation ' + '(%d+%d).' % + (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset, + fake_sig_op.data_length)) + + # Compute the checksum of all data up to signature blob. + # TODO(garnold) we're re-reading the whole data section into a string + # just to compute the checksum; instead, we could do it incrementally as + # we read the blobs one-by-one, under the assumption that we're reading + # them in order (which currently holds). This should be reconsidered. + payload_hasher = self.payload.manifest_hasher.copy() + common.Read(self.payload.payload_file, self.sigs_offset, + offset=self.payload.data_offset, hasher=payload_hasher) + + for sig, sig_name in common.SignatureIter(sigs.signatures, 'signatures'): + sig_report = report.AddSubReport(sig_name) + + # Check: Signature contains mandatory fields. + self._CheckMandatoryField(sig, 'version', sig_report, sig_name) + self._CheckMandatoryField(sig, 'data', None, sig_name) + sig_report.AddField('data len', len(sig.data)) + + # Check: Signatures pertains to actual payload hash. + if sig.version == 1: + self._CheckSha256Signature(sig.data, pubkey_file_name, + payload_hasher.digest(), sig_name) + else: + raise error.PayloadError('Unknown signature version (%d).' % + sig.version) + + def Run(self, pubkey_file_name=None, metadata_sig_file=None, + rootfs_part_size=0, kernel_part_size=0, report_out_file=None): + """Checker entry point, invoking all checks. + + Args: + pubkey_file_name: Public key used for signature verification. + metadata_sig_file: Metadata signature, if verification is desired. + rootfs_part_size: The size of rootfs partitions in bytes (default: infer + based on payload type and version). + kernel_part_size: The size of kernel partitions in bytes (default: use + reported filesystem size). + report_out_file: File object to dump the report to. + + Raises: + error.PayloadError if payload verification failed. + """ + if not pubkey_file_name: + pubkey_file_name = _DEFAULT_PUBKEY_FILE_NAME + + report = _PayloadReport() + + # Get payload file size. + self.payload.payload_file.seek(0, 2) + payload_file_size = self.payload.payload_file.tell() + self.payload.ResetFile() + + try: + # Check metadata signature (if provided). + if metadata_sig_file: + metadata_sig = base64.b64decode(metadata_sig_file.read()) + self._CheckSha256Signature(metadata_sig, pubkey_file_name, + self.payload.manifest_hasher.digest(), + 'metadata signature') + + # Part 1: Check the file header. + report.AddSection('header') + # Check: Payload version is valid. + if self.payload.header.version != 1: + raise error.PayloadError('Unknown payload version (%d).' % + self.payload.header.version) + report.AddField('version', self.payload.header.version) + report.AddField('manifest len', self.payload.header.manifest_len) + + # Part 2: Check the manifest. + self._CheckManifest(report, rootfs_part_size, kernel_part_size) + assert self.payload_type, 'payload type should be known by now' + + # Infer the usable partition size when validating rootfs operations: + # - If rootfs partition size was provided, use that. + # - Otherwise, if this is an older delta (minor version < 2), stick with + # a known constant size. This is necessary because older deltas may + # exceed the filesystem size when moving data blocks around. + # - Otherwise, use the encoded filesystem size. + new_rootfs_usable_size = self.new_rootfs_fs_size + if rootfs_part_size: + new_rootfs_usable_size = rootfs_part_size + elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1): + new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE + + # Part 3: Examine rootfs operations. + # TODO(garnold)(chromium:243559) only default to the filesystem size if + # no explicit size provided *and* the partition size is not embedded in + # the payload; see issue for more details. + report.AddSection('rootfs operations') + total_blob_size = self._CheckOperations( + self.payload.manifest.install_operations, report, + 'install_operations', self.old_rootfs_fs_size, + self.new_rootfs_fs_size, new_rootfs_usable_size, 0, False) + + # Part 4: Examine kernel operations. + # TODO(garnold)(chromium:243559) as above. + report.AddSection('kernel operations') + total_blob_size += self._CheckOperations( + self.payload.manifest.kernel_install_operations, report, + 'kernel_install_operations', self.old_kernel_fs_size, + self.new_kernel_fs_size, + kernel_part_size if kernel_part_size else self.new_kernel_fs_size, + total_blob_size, True) + + # Check: Operations data reach the end of the payload file. + used_payload_size = self.payload.data_offset + total_blob_size + if used_payload_size != payload_file_size: + raise error.PayloadError( + 'Used payload size (%d) different from actual file size (%d).' % + (used_payload_size, payload_file_size)) + + # Part 5: Handle payload signatures message. + if self.check_payload_sig and self.sigs_size: + self._CheckSignatures(report, pubkey_file_name) + + # Part 6: Summary. + report.AddSection('summary') + report.AddField('update type', self.payload_type) + + report.Finalize() + finally: + if report_out_file: + report.Dump(report_out_file)
diff --git a/update_engine/scripts/update_payload/checker_unittest.py b/update_engine/scripts/update_payload/checker_unittest.py new file mode 100755 index 0000000..56b1a30 --- /dev/null +++ b/update_engine/scripts/update_payload/checker_unittest.py
@@ -0,0 +1,1326 @@ +#!/usr/bin/python2 +# +# Copyright (c) 2013 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. + +"""Unit testing checker.py.""" + +from __future__ import print_function + +import array +import collections +import cStringIO +import hashlib +import itertools +import os +import unittest + +# pylint cannot find mox. +# pylint: disable=F0401 +import mox + +import checker +import common +import payload as update_payload # Avoid name conflicts later. +import test_utils +import update_metadata_pb2 + + +def _OpTypeByName(op_name): + op_name_to_type = { + 'REPLACE': common.OpType.REPLACE, + 'REPLACE_BZ': common.OpType.REPLACE_BZ, + 'MOVE': common.OpType.MOVE, + 'BSDIFF': common.OpType.BSDIFF, + 'SOURCE_COPY': common.OpType.SOURCE_COPY, + 'SOURCE_BSDIFF': common.OpType.SOURCE_BSDIFF, + 'ZERO': common.OpType.ZERO, + 'DISCARD': common.OpType.DISCARD, + 'REPLACE_XZ': common.OpType.REPLACE_XZ, + 'IMGDIFF': common.OpType.IMGDIFF, + } + return op_name_to_type[op_name] + + +def _GetPayloadChecker(payload_gen_write_to_file_func, payload_gen_dargs=None, + checker_init_dargs=None): + """Returns a payload checker from a given payload generator.""" + if payload_gen_dargs is None: + payload_gen_dargs = {} + if checker_init_dargs is None: + checker_init_dargs = {} + + payload_file = cStringIO.StringIO() + payload_gen_write_to_file_func(payload_file, **payload_gen_dargs) + payload_file.seek(0) + payload = update_payload.Payload(payload_file) + payload.Init() + return checker.PayloadChecker(payload, **checker_init_dargs) + + +def _GetPayloadCheckerWithData(payload_gen): + """Returns a payload checker from a given payload generator.""" + payload_file = cStringIO.StringIO() + payload_gen.WriteToFile(payload_file) + payload_file.seek(0) + payload = update_payload.Payload(payload_file) + payload.Init() + return checker.PayloadChecker(payload) + + +# This class doesn't need an __init__(). +# pylint: disable=W0232 +# Unit testing is all about running protected methods. +# pylint: disable=W0212 +# Don't bark about missing members of classes you cannot import. +# pylint: disable=E1101 +class PayloadCheckerTest(mox.MoxTestBase): + """Tests the PayloadChecker class. + + In addition to ordinary testFoo() methods, which are automatically invoked by + the unittest framework, in this class we make use of DoBarTest() calls that + implement parametric tests of certain features. In order to invoke each test, + which embodies a unique combination of parameter values, as a complete unit + test, we perform explicit enumeration of the parameter space and create + individual invocation contexts for each, which are then bound as + testBar__param1=val1__param2=val2(). The enumeration of parameter spaces for + all such tests is done in AddAllParametricTests(). + """ + + def MockPayload(self): + """Create a mock payload object, complete with a mock manifest.""" + payload = self.mox.CreateMock(update_payload.Payload) + payload.is_init = True + payload.manifest = self.mox.CreateMock( + update_metadata_pb2.DeltaArchiveManifest) + return payload + + @staticmethod + def NewExtent(start_block, num_blocks): + """Returns an Extent message. + + Each of the provided fields is set iff it is >= 0; otherwise, it's left at + its default state. + + Args: + start_block: The starting block of the extent. + num_blocks: The number of blocks in the extent. + + Returns: + An Extent message. + """ + ex = update_metadata_pb2.Extent() + if start_block >= 0: + ex.start_block = start_block + if num_blocks >= 0: + ex.num_blocks = num_blocks + return ex + + @staticmethod + def NewExtentList(*args): + """Returns an list of extents. + + Args: + *args: (start_block, num_blocks) pairs defining the extents. + + Returns: + A list of Extent objects. + """ + ex_list = [] + for start_block, num_blocks in args: + ex_list.append(PayloadCheckerTest.NewExtent(start_block, num_blocks)) + return ex_list + + @staticmethod + def AddToMessage(repeated_field, field_vals): + for field_val in field_vals: + new_field = repeated_field.add() + new_field.CopyFrom(field_val) + + def SetupAddElemTest(self, is_present, is_submsg, convert=str, + linebreak=False, indent=0): + """Setup for testing of _CheckElem() and its derivatives. + + Args: + is_present: Whether or not the element is found in the message. + is_submsg: Whether the element is a sub-message itself. + convert: A representation conversion function. + linebreak: Whether or not a linebreak is to be used in the report. + indent: Indentation used for the report. + + Returns: + msg: A mock message object. + report: A mock report object. + subreport: A mock sub-report object. + name: An element name to check. + val: Expected element value. + """ + name = 'foo' + val = 'fake submsg' if is_submsg else 'fake field' + subreport = 'fake subreport' + + # Create a mock message. + msg = self.mox.CreateMock(update_metadata_pb2._message.Message) + msg.HasField(name).AndReturn(is_present) + setattr(msg, name, val) + + # Create a mock report. + report = self.mox.CreateMock(checker._PayloadReport) + if is_present: + if is_submsg: + report.AddSubReport(name).AndReturn(subreport) + else: + report.AddField(name, convert(val), linebreak=linebreak, indent=indent) + + self.mox.ReplayAll() + return (msg, report, subreport, name, val) + + def DoAddElemTest(self, is_present, is_mandatory, is_submsg, convert, + linebreak, indent): + """Parametric testing of _CheckElem(). + + Args: + is_present: Whether or not the element is found in the message. + is_mandatory: Whether or not it's a mandatory element. + is_submsg: Whether the element is a sub-message itself. + convert: A representation conversion function. + linebreak: Whether or not a linebreak is to be used in the report. + indent: Indentation used for the report. + """ + msg, report, subreport, name, val = self.SetupAddElemTest( + is_present, is_submsg, convert, linebreak, indent) + + args = (msg, name, report, is_mandatory, is_submsg) + kwargs = {'convert': convert, 'linebreak': linebreak, 'indent': indent} + if is_mandatory and not is_present: + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckElem, *args, **kwargs) + else: + ret_val, ret_subreport = checker.PayloadChecker._CheckElem(*args, + **kwargs) + self.assertEquals(val if is_present else None, ret_val) + self.assertEquals(subreport if is_present and is_submsg else None, + ret_subreport) + + def DoAddFieldTest(self, is_mandatory, is_present, convert, linebreak, + indent): + """Parametric testing of _Check{Mandatory,Optional}Field(). + + Args: + is_mandatory: Whether we're testing a mandatory call. + is_present: Whether or not the element is found in the message. + convert: A representation conversion function. + linebreak: Whether or not a linebreak is to be used in the report. + indent: Indentation used for the report. + """ + msg, report, _, name, val = self.SetupAddElemTest( + is_present, False, convert, linebreak, indent) + + # Prepare for invocation of the tested method. + args = [msg, name, report] + kwargs = {'convert': convert, 'linebreak': linebreak, 'indent': indent} + if is_mandatory: + args.append('bar') + tested_func = checker.PayloadChecker._CheckMandatoryField + else: + tested_func = checker.PayloadChecker._CheckOptionalField + + # Test the method call. + if is_mandatory and not is_present: + self.assertRaises(update_payload.PayloadError, tested_func, *args, + **kwargs) + else: + ret_val = tested_func(*args, **kwargs) + self.assertEquals(val if is_present else None, ret_val) + + def DoAddSubMsgTest(self, is_mandatory, is_present): + """Parametrized testing of _Check{Mandatory,Optional}SubMsg(). + + Args: + is_mandatory: Whether we're testing a mandatory call. + is_present: Whether or not the element is found in the message. + """ + msg, report, subreport, name, val = self.SetupAddElemTest(is_present, True) + + # Prepare for invocation of the tested method. + args = [msg, name, report] + if is_mandatory: + args.append('bar') + tested_func = checker.PayloadChecker._CheckMandatorySubMsg + else: + tested_func = checker.PayloadChecker._CheckOptionalSubMsg + + # Test the method call. + if is_mandatory and not is_present: + self.assertRaises(update_payload.PayloadError, tested_func, *args) + else: + ret_val, ret_subreport = tested_func(*args) + self.assertEquals(val if is_present else None, ret_val) + self.assertEquals(subreport if is_present else None, ret_subreport) + + def testCheckPresentIff(self): + """Tests _CheckPresentIff().""" + self.assertIsNone(checker.PayloadChecker._CheckPresentIff( + None, None, 'foo', 'bar', 'baz')) + self.assertIsNone(checker.PayloadChecker._CheckPresentIff( + 'a', 'b', 'foo', 'bar', 'baz')) + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckPresentIff, + 'a', None, 'foo', 'bar', 'baz') + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckPresentIff, + None, 'b', 'foo', 'bar', 'baz') + + def DoCheckSha256SignatureTest(self, expect_pass, expect_subprocess_call, + sig_data, sig_asn1_header, + returned_signed_hash, expected_signed_hash): + """Parametric testing of _CheckSha256SignatureTest(). + + Args: + expect_pass: Whether or not it should pass. + expect_subprocess_call: Whether to expect the openssl call to happen. + sig_data: The signature raw data. + sig_asn1_header: The ASN1 header. + returned_signed_hash: The signed hash data retuned by openssl. + expected_signed_hash: The signed hash data to compare against. + """ + try: + # Stub out the subprocess invocation. + self.mox.StubOutWithMock(checker.PayloadChecker, '_Run') + if expect_subprocess_call: + checker.PayloadChecker._Run( + mox.IsA(list), send_data=sig_data).AndReturn( + (sig_asn1_header + returned_signed_hash, None)) + + self.mox.ReplayAll() + if expect_pass: + self.assertIsNone(checker.PayloadChecker._CheckSha256Signature( + sig_data, 'foo', expected_signed_hash, 'bar')) + else: + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckSha256Signature, + sig_data, 'foo', expected_signed_hash, 'bar') + finally: + self.mox.UnsetStubs() + + def testCheckSha256Signature_Pass(self): + """Tests _CheckSha256Signature(); pass case.""" + sig_data = 'fake-signature'.ljust(256) + signed_hash = hashlib.sha256('fake-data').digest() + self.DoCheckSha256SignatureTest(True, True, sig_data, + common.SIG_ASN1_HEADER, signed_hash, + signed_hash) + + def testCheckSha256Signature_FailBadSignature(self): + """Tests _CheckSha256Signature(); fails due to malformed signature.""" + sig_data = 'fake-signature' # Malformed (not 256 bytes in length). + signed_hash = hashlib.sha256('fake-data').digest() + self.DoCheckSha256SignatureTest(False, False, sig_data, + common.SIG_ASN1_HEADER, signed_hash, + signed_hash) + + def testCheckSha256Signature_FailBadOutputLength(self): + """Tests _CheckSha256Signature(); fails due to unexpected output length.""" + sig_data = 'fake-signature'.ljust(256) + signed_hash = 'fake-hash' # Malformed (not 32 bytes in length). + self.DoCheckSha256SignatureTest(False, True, sig_data, + common.SIG_ASN1_HEADER, signed_hash, + signed_hash) + + def testCheckSha256Signature_FailBadAsnHeader(self): + """Tests _CheckSha256Signature(); fails due to bad ASN1 header.""" + sig_data = 'fake-signature'.ljust(256) + signed_hash = hashlib.sha256('fake-data').digest() + bad_asn1_header = 'bad-asn-header'.ljust(len(common.SIG_ASN1_HEADER)) + self.DoCheckSha256SignatureTest(False, True, sig_data, bad_asn1_header, + signed_hash, signed_hash) + + def testCheckSha256Signature_FailBadHash(self): + """Tests _CheckSha256Signature(); fails due to bad hash returned.""" + sig_data = 'fake-signature'.ljust(256) + expected_signed_hash = hashlib.sha256('fake-data').digest() + returned_signed_hash = hashlib.sha256('bad-fake-data').digest() + self.DoCheckSha256SignatureTest(False, True, sig_data, + common.SIG_ASN1_HEADER, + expected_signed_hash, returned_signed_hash) + + def testCheckBlocksFitLength_Pass(self): + """Tests _CheckBlocksFitLength(); pass case.""" + self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength( + 64, 4, 16, 'foo')) + self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength( + 60, 4, 16, 'foo')) + self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength( + 49, 4, 16, 'foo')) + self.assertIsNone(checker.PayloadChecker._CheckBlocksFitLength( + 48, 3, 16, 'foo')) + + def testCheckBlocksFitLength_TooManyBlocks(self): + """Tests _CheckBlocksFitLength(); fails due to excess blocks.""" + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckBlocksFitLength, + 64, 5, 16, 'foo') + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckBlocksFitLength, + 60, 5, 16, 'foo') + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckBlocksFitLength, + 49, 5, 16, 'foo') + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckBlocksFitLength, + 48, 4, 16, 'foo') + + def testCheckBlocksFitLength_TooFewBlocks(self): + """Tests _CheckBlocksFitLength(); fails due to insufficient blocks.""" + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckBlocksFitLength, + 64, 3, 16, 'foo') + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckBlocksFitLength, + 60, 3, 16, 'foo') + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckBlocksFitLength, + 49, 3, 16, 'foo') + self.assertRaises(update_payload.PayloadError, + checker.PayloadChecker._CheckBlocksFitLength, + 48, 2, 16, 'foo') + + def DoCheckManifestTest(self, fail_mismatched_block_size, fail_bad_sigs, + fail_mismatched_oki_ori, fail_bad_oki, fail_bad_ori, + fail_bad_nki, fail_bad_nri, fail_old_kernel_fs_size, + fail_old_rootfs_fs_size, fail_new_kernel_fs_size, + fail_new_rootfs_fs_size): + """Parametric testing of _CheckManifest(). + + Args: + fail_mismatched_block_size: Simulate a missing block_size field. + fail_bad_sigs: Make signatures descriptor inconsistent. + fail_mismatched_oki_ori: Make old rootfs/kernel info partially present. + fail_bad_oki: Tamper with old kernel info. + fail_bad_ori: Tamper with old rootfs info. + fail_bad_nki: Tamper with new kernel info. + fail_bad_nri: Tamper with new rootfs info. + fail_old_kernel_fs_size: Make old kernel fs size too big. + fail_old_rootfs_fs_size: Make old rootfs fs size too big. + fail_new_kernel_fs_size: Make new kernel fs size too big. + fail_new_rootfs_fs_size: Make new rootfs fs size too big. + """ + # Generate a test payload. For this test, we only care about the manifest + # and don't need any data blobs, hence we can use a plain paylaod generator + # (which also gives us more control on things that can be screwed up). + payload_gen = test_utils.PayloadGenerator() + + # Tamper with block size, if required. + if fail_mismatched_block_size: + payload_gen.SetBlockSize(test_utils.KiB(1)) + else: + payload_gen.SetBlockSize(test_utils.KiB(4)) + + # Add some operations. + payload_gen.AddOperation(False, common.OpType.MOVE, + src_extents=[(0, 16), (16, 497)], + dst_extents=[(16, 496), (0, 16)]) + payload_gen.AddOperation(True, common.OpType.MOVE, + src_extents=[(0, 8), (8, 8)], + dst_extents=[(8, 8), (0, 8)]) + + # Set an invalid signatures block (offset but no size), if required. + if fail_bad_sigs: + payload_gen.SetSignatures(32, None) + + # Set partition / filesystem sizes. + rootfs_part_size = test_utils.MiB(8) + kernel_part_size = test_utils.KiB(512) + old_rootfs_fs_size = new_rootfs_fs_size = rootfs_part_size + old_kernel_fs_size = new_kernel_fs_size = kernel_part_size + if fail_old_kernel_fs_size: + old_kernel_fs_size += 100 + if fail_old_rootfs_fs_size: + old_rootfs_fs_size += 100 + if fail_new_kernel_fs_size: + new_kernel_fs_size += 100 + if fail_new_rootfs_fs_size: + new_rootfs_fs_size += 100 + + # Add old kernel/rootfs partition info, as required. + if fail_mismatched_oki_ori or fail_old_kernel_fs_size or fail_bad_oki: + oki_hash = (None if fail_bad_oki + else hashlib.sha256('fake-oki-content').digest()) + payload_gen.SetPartInfo(True, False, old_kernel_fs_size, oki_hash) + if not fail_mismatched_oki_ori and (fail_old_rootfs_fs_size or + fail_bad_ori): + ori_hash = (None if fail_bad_ori + else hashlib.sha256('fake-ori-content').digest()) + payload_gen.SetPartInfo(False, False, old_rootfs_fs_size, ori_hash) + + # Add new kernel/rootfs partition info. + payload_gen.SetPartInfo( + True, True, new_kernel_fs_size, + None if fail_bad_nki else hashlib.sha256('fake-nki-content').digest()) + payload_gen.SetPartInfo( + False, True, new_rootfs_fs_size, + None if fail_bad_nri else hashlib.sha256('fake-nri-content').digest()) + + # Set the minor version. + payload_gen.SetMinorVersion(0) + + # Create the test object. + payload_checker = _GetPayloadChecker(payload_gen.WriteToFile) + report = checker._PayloadReport() + + should_fail = (fail_mismatched_block_size or fail_bad_sigs or + fail_mismatched_oki_ori or fail_bad_oki or fail_bad_ori or + fail_bad_nki or fail_bad_nri or fail_old_kernel_fs_size or + fail_old_rootfs_fs_size or fail_new_kernel_fs_size or + fail_new_rootfs_fs_size) + if should_fail: + self.assertRaises(update_payload.PayloadError, + payload_checker._CheckManifest, report, + rootfs_part_size, kernel_part_size) + else: + self.assertIsNone(payload_checker._CheckManifest(report, + rootfs_part_size, + kernel_part_size)) + + def testCheckLength(self): + """Tests _CheckLength().""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + block_size = payload_checker.block_size + + # Passes. + self.assertIsNone(payload_checker._CheckLength( + int(3.5 * block_size), 4, 'foo', 'bar')) + # Fails, too few blocks. + self.assertRaises(update_payload.PayloadError, + payload_checker._CheckLength, + int(3.5 * block_size), 3, 'foo', 'bar') + # Fails, too many blocks. + self.assertRaises(update_payload.PayloadError, + payload_checker._CheckLength, + int(3.5 * block_size), 5, 'foo', 'bar') + + def testCheckExtents(self): + """Tests _CheckExtents().""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + block_size = payload_checker.block_size + + # Passes w/ all real extents. + extents = self.NewExtentList((0, 4), (8, 3), (1024, 16)) + self.assertEquals( + 23, + payload_checker._CheckExtents(extents, (1024 + 16) * block_size, + collections.defaultdict(int), 'foo')) + + # Passes w/ pseudo-extents (aka sparse holes). + extents = self.NewExtentList((0, 4), (common.PSEUDO_EXTENT_MARKER, 5), + (8, 3)) + self.assertEquals( + 12, + payload_checker._CheckExtents(extents, (1024 + 16) * block_size, + collections.defaultdict(int), 'foo', + allow_pseudo=True)) + + # Passes w/ pseudo-extent due to a signature. + extents = self.NewExtentList((common.PSEUDO_EXTENT_MARKER, 2)) + self.assertEquals( + 2, + payload_checker._CheckExtents(extents, (1024 + 16) * block_size, + collections.defaultdict(int), 'foo', + allow_signature=True)) + + # Fails, extent missing a start block. + extents = self.NewExtentList((-1, 4), (8, 3), (1024, 16)) + self.assertRaises( + update_payload.PayloadError, payload_checker._CheckExtents, + extents, (1024 + 16) * block_size, collections.defaultdict(int), + 'foo') + + # Fails, extent missing block count. + extents = self.NewExtentList((0, -1), (8, 3), (1024, 16)) + self.assertRaises( + update_payload.PayloadError, payload_checker._CheckExtents, + extents, (1024 + 16) * block_size, collections.defaultdict(int), + 'foo') + + # Fails, extent has zero blocks. + extents = self.NewExtentList((0, 4), (8, 3), (1024, 0)) + self.assertRaises( + update_payload.PayloadError, payload_checker._CheckExtents, + extents, (1024 + 16) * block_size, collections.defaultdict(int), + 'foo') + + # Fails, extent exceeds partition boundaries. + extents = self.NewExtentList((0, 4), (8, 3), (1024, 16)) + self.assertRaises( + update_payload.PayloadError, payload_checker._CheckExtents, + extents, (1024 + 15) * block_size, collections.defaultdict(int), + 'foo') + + def testCheckReplaceOperation(self): + """Tests _CheckReplaceOperation() where op.type == REPLACE.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + block_size = payload_checker.block_size + data_length = 10000 + + op = self.mox.CreateMock( + update_metadata_pb2.InstallOperation) + op.type = common.OpType.REPLACE + + # Pass. + op.src_extents = [] + self.assertIsNone( + payload_checker._CheckReplaceOperation( + op, data_length, (data_length + block_size - 1) / block_size, + 'foo')) + + # Fail, src extents founds. + op.src_extents = ['bar'] + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckReplaceOperation, + op, data_length, (data_length + block_size - 1) / block_size, 'foo') + + # Fail, missing data. + op.src_extents = [] + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckReplaceOperation, + op, None, (data_length + block_size - 1) / block_size, 'foo') + + # Fail, length / block number mismatch. + op.src_extents = ['bar'] + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckReplaceOperation, + op, data_length, (data_length + block_size - 1) / block_size + 1, 'foo') + + def testCheckReplaceBzOperation(self): + """Tests _CheckReplaceOperation() where op.type == REPLACE_BZ.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + block_size = payload_checker.block_size + data_length = block_size * 3 + + op = self.mox.CreateMock( + update_metadata_pb2.InstallOperation) + op.type = common.OpType.REPLACE_BZ + + # Pass. + op.src_extents = [] + self.assertIsNone( + payload_checker._CheckReplaceOperation( + op, data_length, (data_length + block_size - 1) / block_size + 5, + 'foo')) + + # Fail, src extents founds. + op.src_extents = ['bar'] + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckReplaceOperation, + op, data_length, (data_length + block_size - 1) / block_size + 5, 'foo') + + # Fail, missing data. + op.src_extents = [] + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckReplaceOperation, + op, None, (data_length + block_size - 1) / block_size, 'foo') + + # Fail, too few blocks to justify BZ. + op.src_extents = [] + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckReplaceOperation, + op, data_length, (data_length + block_size - 1) / block_size, 'foo') + + def testCheckMoveOperation_Pass(self): + """Tests _CheckMoveOperation(); pass case.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + op = update_metadata_pb2.InstallOperation() + op.type = common.OpType.MOVE + + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 4), (12, 2), (1024, 128))) + self.AddToMessage(op.dst_extents, + self.NewExtentList((16, 128), (512, 6))) + self.assertIsNone( + payload_checker._CheckMoveOperation(op, None, 134, 134, 'foo')) + + def testCheckMoveOperation_FailContainsData(self): + """Tests _CheckMoveOperation(); fails, message contains data.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + op = update_metadata_pb2.InstallOperation() + op.type = common.OpType.MOVE + + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 4), (12, 2), (1024, 128))) + self.AddToMessage(op.dst_extents, + self.NewExtentList((16, 128), (512, 6))) + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckMoveOperation, + op, 1024, 134, 134, 'foo') + + def testCheckMoveOperation_FailInsufficientSrcBlocks(self): + """Tests _CheckMoveOperation(); fails, not enough actual src blocks.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + op = update_metadata_pb2.InstallOperation() + op.type = common.OpType.MOVE + + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 4), (12, 2), (1024, 127))) + self.AddToMessage(op.dst_extents, + self.NewExtentList((16, 128), (512, 6))) + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckMoveOperation, + op, None, 134, 134, 'foo') + + def testCheckMoveOperation_FailInsufficientDstBlocks(self): + """Tests _CheckMoveOperation(); fails, not enough actual dst blocks.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + op = update_metadata_pb2.InstallOperation() + op.type = common.OpType.MOVE + + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 4), (12, 2), (1024, 128))) + self.AddToMessage(op.dst_extents, + self.NewExtentList((16, 128), (512, 5))) + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckMoveOperation, + op, None, 134, 134, 'foo') + + def testCheckMoveOperation_FailExcessSrcBlocks(self): + """Tests _CheckMoveOperation(); fails, too many actual src blocks.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + op = update_metadata_pb2.InstallOperation() + op.type = common.OpType.MOVE + + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 4), (12, 2), (1024, 128))) + self.AddToMessage(op.dst_extents, + self.NewExtentList((16, 128), (512, 5))) + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckMoveOperation, + op, None, 134, 134, 'foo') + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 4), (12, 2), (1024, 129))) + self.AddToMessage(op.dst_extents, + self.NewExtentList((16, 128), (512, 6))) + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckMoveOperation, + op, None, 134, 134, 'foo') + + def testCheckMoveOperation_FailExcessDstBlocks(self): + """Tests _CheckMoveOperation(); fails, too many actual dst blocks.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + op = update_metadata_pb2.InstallOperation() + op.type = common.OpType.MOVE + + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 4), (12, 2), (1024, 128))) + self.AddToMessage(op.dst_extents, + self.NewExtentList((16, 128), (512, 7))) + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckMoveOperation, + op, None, 134, 134, 'foo') + + def testCheckMoveOperation_FailStagnantBlocks(self): + """Tests _CheckMoveOperation(); fails, there are blocks that do not move.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + op = update_metadata_pb2.InstallOperation() + op.type = common.OpType.MOVE + + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 4), (12, 2), (1024, 128))) + self.AddToMessage(op.dst_extents, + self.NewExtentList((8, 128), (512, 6))) + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckMoveOperation, + op, None, 134, 134, 'foo') + + def testCheckMoveOperation_FailZeroStartBlock(self): + """Tests _CheckMoveOperation(); fails, has extent with start block 0.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + op = update_metadata_pb2.InstallOperation() + op.type = common.OpType.MOVE + + self.AddToMessage(op.src_extents, + self.NewExtentList((0, 4), (12, 2), (1024, 128))) + self.AddToMessage(op.dst_extents, + self.NewExtentList((8, 128), (512, 6))) + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckMoveOperation, + op, None, 134, 134, 'foo') + + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 4), (12, 2), (1024, 128))) + self.AddToMessage(op.dst_extents, + self.NewExtentList((0, 128), (512, 6))) + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckMoveOperation, + op, None, 134, 134, 'foo') + + def testCheckAnyDiff(self): + """Tests _CheckAnyDiffOperation().""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + + # Pass. + self.assertIsNone( + payload_checker._CheckAnyDiffOperation(10000, 3, 'foo')) + + # Fail, missing data blob. + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckAnyDiffOperation, + None, 3, 'foo') + + # Fail, too big of a diff blob (unjustified). + self.assertRaises( + update_payload.PayloadError, + payload_checker._CheckAnyDiffOperation, + 10000, 2, 'foo') + + def testCheckSourceCopyOperation_Pass(self): + """Tests _CheckSourceCopyOperation(); pass case.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + self.assertIsNone( + payload_checker._CheckSourceCopyOperation(None, 134, 134, 'foo')) + + def testCheckSourceCopyOperation_FailContainsData(self): + """Tests _CheckSourceCopyOperation(); message contains data.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + self.assertRaises(update_payload.PayloadError, + payload_checker._CheckSourceCopyOperation, + 134, 0, 0, 'foo') + + def testCheckSourceCopyOperation_FailBlockCountsMismatch(self): + """Tests _CheckSourceCopyOperation(); src and dst block totals not equal.""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + self.assertRaises(update_payload.PayloadError, + payload_checker._CheckSourceCopyOperation, + None, 0, 1, 'foo') + + def DoCheckOperationTest(self, op_type_name, is_last, allow_signature, + allow_unhashed, fail_src_extents, fail_dst_extents, + fail_mismatched_data_offset_length, + fail_missing_dst_extents, fail_src_length, + fail_dst_length, fail_data_hash, + fail_prev_data_offset, fail_bad_minor_version): + """Parametric testing of _CheckOperation(). + + Args: + op_type_name: 'REPLACE', 'REPLACE_BZ', 'MOVE', 'BSDIFF', 'SOURCE_COPY', + or 'SOURCE_BSDIFF'. + is_last: Whether we're testing the last operation in a sequence. + allow_signature: Whether we're testing a signature-capable operation. + allow_unhashed: Whether we're allowing to not hash the data. + fail_src_extents: Tamper with src extents. + fail_dst_extents: Tamper with dst extents. + fail_mismatched_data_offset_length: Make data_{offset,length} + inconsistent. + fail_missing_dst_extents: Do not include dst extents. + fail_src_length: Make src length inconsistent. + fail_dst_length: Make dst length inconsistent. + fail_data_hash: Tamper with the data blob hash. + fail_prev_data_offset: Make data space uses incontiguous. + fail_bad_minor_version: Make minor version incompatible with op. + """ + op_type = _OpTypeByName(op_type_name) + + # Create the test object. + payload = self.MockPayload() + payload_checker = checker.PayloadChecker(payload, + allow_unhashed=allow_unhashed) + block_size = payload_checker.block_size + + # Create auxiliary arguments. + old_part_size = test_utils.MiB(4) + new_part_size = test_utils.MiB(8) + old_block_counters = array.array( + 'B', [0] * ((old_part_size + block_size - 1) / block_size)) + new_block_counters = array.array( + 'B', [0] * ((new_part_size + block_size - 1) / block_size)) + prev_data_offset = 1876 + blob_hash_counts = collections.defaultdict(int) + + # Create the operation object for the test. + op = update_metadata_pb2.InstallOperation() + op.type = op_type + + total_src_blocks = 0 + if op_type in (common.OpType.MOVE, common.OpType.BSDIFF, + common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF): + if fail_src_extents: + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 0))) + else: + self.AddToMessage(op.src_extents, + self.NewExtentList((1, 16))) + total_src_blocks = 16 + + if op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ): + payload_checker.minor_version = 0 + elif op_type in (common.OpType.MOVE, common.OpType.BSDIFF): + payload_checker.minor_version = 2 if fail_bad_minor_version else 1 + elif op_type in (common.OpType.SOURCE_COPY, common.OpType.SOURCE_BSDIFF): + payload_checker.minor_version = 1 if fail_bad_minor_version else 2 + + if op_type not in (common.OpType.MOVE, common.OpType.SOURCE_COPY): + if not fail_mismatched_data_offset_length: + op.data_length = 16 * block_size - 8 + if fail_prev_data_offset: + op.data_offset = prev_data_offset + 16 + else: + op.data_offset = prev_data_offset + + fake_data = 'fake-data'.ljust(op.data_length) + if not (allow_unhashed or (is_last and allow_signature and + op_type == common.OpType.REPLACE)): + if not fail_data_hash: + # Create a valid data blob hash. + op.data_sha256_hash = hashlib.sha256(fake_data).digest() + payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn( + fake_data) + elif fail_data_hash: + # Create an invalid data blob hash. + op.data_sha256_hash = hashlib.sha256( + fake_data.replace(' ', '-')).digest() + payload.ReadDataBlob(op.data_offset, op.data_length).AndReturn( + fake_data) + + total_dst_blocks = 0 + if not fail_missing_dst_extents: + total_dst_blocks = 16 + if fail_dst_extents: + self.AddToMessage(op.dst_extents, + self.NewExtentList((4, 16), (32, 0))) + else: + self.AddToMessage(op.dst_extents, + self.NewExtentList((4, 8), (64, 8))) + + if total_src_blocks: + if fail_src_length: + op.src_length = total_src_blocks * block_size + 8 + else: + op.src_length = total_src_blocks * block_size + elif fail_src_length: + # Add an orphaned src_length. + op.src_length = 16 + + if total_dst_blocks: + if fail_dst_length: + op.dst_length = total_dst_blocks * block_size + 8 + else: + op.dst_length = total_dst_blocks * block_size + + self.mox.ReplayAll() + should_fail = (fail_src_extents or fail_dst_extents or + fail_mismatched_data_offset_length or + fail_missing_dst_extents or fail_src_length or + fail_dst_length or fail_data_hash or fail_prev_data_offset or + fail_bad_minor_version) + args = (op, 'foo', is_last, old_block_counters, new_block_counters, + old_part_size, new_part_size, prev_data_offset, allow_signature, + blob_hash_counts) + if should_fail: + self.assertRaises(update_payload.PayloadError, + payload_checker._CheckOperation, *args) + else: + self.assertEqual(op.data_length if op.HasField('data_length') else 0, + payload_checker._CheckOperation(*args)) + + def testAllocBlockCounters(self): + """Tests _CheckMoveOperation().""" + payload_checker = checker.PayloadChecker(self.MockPayload()) + block_size = payload_checker.block_size + + # Check allocation for block-aligned partition size, ensure it's integers. + result = payload_checker._AllocBlockCounters(16 * block_size) + self.assertEqual(16, len(result)) + self.assertEqual(int, type(result[0])) + + # Check allocation of unaligned partition sizes. + result = payload_checker._AllocBlockCounters(16 * block_size - 1) + self.assertEqual(16, len(result)) + result = payload_checker._AllocBlockCounters(16 * block_size + 1) + self.assertEqual(17, len(result)) + + def DoCheckOperationsTest(self, fail_nonexhaustive_full_update): + # Generate a test payload. For this test, we only care about one + # (arbitrary) set of operations, so we'll only be generating kernel and + # test with them. + payload_gen = test_utils.PayloadGenerator() + + block_size = test_utils.KiB(4) + payload_gen.SetBlockSize(block_size) + + rootfs_part_size = test_utils.MiB(8) + + # Fake rootfs operations in a full update, tampered with as required. + rootfs_op_type = common.OpType.REPLACE + rootfs_data_length = rootfs_part_size + if fail_nonexhaustive_full_update: + rootfs_data_length -= block_size + + payload_gen.AddOperation(False, rootfs_op_type, + dst_extents=[(0, rootfs_data_length / block_size)], + data_offset=0, + data_length=rootfs_data_length) + + # Create the test object. + payload_checker = _GetPayloadChecker(payload_gen.WriteToFile, + checker_init_dargs={ + 'allow_unhashed': True}) + payload_checker.payload_type = checker._TYPE_FULL + report = checker._PayloadReport() + + args = (payload_checker.payload.manifest.install_operations, report, + 'foo', 0, rootfs_part_size, rootfs_part_size, 0, False) + if fail_nonexhaustive_full_update: + self.assertRaises(update_payload.PayloadError, + payload_checker._CheckOperations, *args) + else: + self.assertEqual(rootfs_data_length, + payload_checker._CheckOperations(*args)) + + def DoCheckSignaturesTest(self, fail_empty_sigs_blob, fail_missing_pseudo_op, + fail_mismatched_pseudo_op, fail_sig_missing_fields, + fail_unknown_sig_version, fail_incorrect_sig): + # Generate a test payload. For this test, we only care about the signature + # block and how it relates to the payload hash. Therefore, we're generating + # a random (otherwise useless) payload for this purpose. + payload_gen = test_utils.EnhancedPayloadGenerator() + block_size = test_utils.KiB(4) + payload_gen.SetBlockSize(block_size) + rootfs_part_size = test_utils.MiB(2) + kernel_part_size = test_utils.KiB(16) + payload_gen.SetPartInfo(False, True, rootfs_part_size, + hashlib.sha256('fake-new-rootfs-content').digest()) + payload_gen.SetPartInfo(True, True, kernel_part_size, + hashlib.sha256('fake-new-kernel-content').digest()) + payload_gen.SetMinorVersion(0) + payload_gen.AddOperationWithData( + False, common.OpType.REPLACE, + dst_extents=[(0, rootfs_part_size / block_size)], + data_blob=os.urandom(rootfs_part_size)) + + do_forge_pseudo_op = (fail_missing_pseudo_op or fail_mismatched_pseudo_op) + do_forge_sigs_data = (do_forge_pseudo_op or fail_empty_sigs_blob or + fail_sig_missing_fields or fail_unknown_sig_version + or fail_incorrect_sig) + + sigs_data = None + if do_forge_sigs_data: + sigs_gen = test_utils.SignaturesGenerator() + if not fail_empty_sigs_blob: + if fail_sig_missing_fields: + sig_data = None + else: + sig_data = test_utils.SignSha256('fake-payload-content', + test_utils._PRIVKEY_FILE_NAME) + sigs_gen.AddSig(5 if fail_unknown_sig_version else 1, sig_data) + + sigs_data = sigs_gen.ToBinary() + payload_gen.SetSignatures(payload_gen.curr_offset, len(sigs_data)) + + if do_forge_pseudo_op: + assert sigs_data is not None, 'should have forged signatures blob by now' + sigs_len = len(sigs_data) + payload_gen.AddOperation( + False, common.OpType.REPLACE, + data_offset=payload_gen.curr_offset / 2, + data_length=sigs_len / 2, + dst_extents=[(0, (sigs_len / 2 + block_size - 1) / block_size)]) + + # Generate payload (complete w/ signature) and create the test object. + payload_checker = _GetPayloadChecker( + payload_gen.WriteToFileWithData, + payload_gen_dargs={ + 'sigs_data': sigs_data, + 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME, + 'do_add_pseudo_operation': not do_forge_pseudo_op}) + payload_checker.payload_type = checker._TYPE_FULL + report = checker._PayloadReport() + + # We have to check the manifest first in order to set signature attributes. + payload_checker._CheckManifest(report, rootfs_part_size, kernel_part_size) + + should_fail = (fail_empty_sigs_blob or fail_missing_pseudo_op or + fail_mismatched_pseudo_op or fail_sig_missing_fields or + fail_unknown_sig_version or fail_incorrect_sig) + args = (report, test_utils._PUBKEY_FILE_NAME) + if should_fail: + self.assertRaises(update_payload.PayloadError, + payload_checker._CheckSignatures, *args) + else: + self.assertIsNone(payload_checker._CheckSignatures(*args)) + + def DoCheckManifestMinorVersionTest(self, minor_version, payload_type): + """Parametric testing for CheckManifestMinorVersion(). + + Args: + minor_version: The payload minor version to test with. + payload_type: The type of the payload we're testing, delta or full. + """ + # Create the test object. + payload = self.MockPayload() + payload.manifest.minor_version = minor_version + payload_checker = checker.PayloadChecker(payload) + payload_checker.payload_type = payload_type + report = checker._PayloadReport() + + should_succeed = ( + (minor_version == 0 and payload_type == checker._TYPE_FULL) or + (minor_version == 1 and payload_type == checker._TYPE_DELTA) or + (minor_version == 2 and payload_type == checker._TYPE_DELTA) or + (minor_version == 3 and payload_type == checker._TYPE_DELTA) or + (minor_version == 4 and payload_type == checker._TYPE_DELTA)) + args = (report,) + + if should_succeed: + self.assertIsNone(payload_checker._CheckManifestMinorVersion(*args)) + else: + self.assertRaises(update_payload.PayloadError, + payload_checker._CheckManifestMinorVersion, *args) + + def DoRunTest(self, rootfs_part_size_provided, kernel_part_size_provided, + fail_wrong_payload_type, fail_invalid_block_size, + fail_mismatched_block_size, fail_excess_data, + fail_rootfs_part_size_exceeded, + fail_kernel_part_size_exceeded): + # Generate a test payload. For this test, we generate a full update that + # has sample kernel and rootfs operations. Since most testing is done with + # internal PayloadChecker methods that are tested elsewhere, here we only + # tamper with what's actually being manipulated and/or tested in the Run() + # method itself. Note that the checker doesn't verify partition hashes, so + # they're safe to fake. + payload_gen = test_utils.EnhancedPayloadGenerator() + block_size = test_utils.KiB(4) + payload_gen.SetBlockSize(block_size) + kernel_filesystem_size = test_utils.KiB(16) + rootfs_filesystem_size = test_utils.MiB(2) + payload_gen.SetPartInfo(False, True, rootfs_filesystem_size, + hashlib.sha256('fake-new-rootfs-content').digest()) + payload_gen.SetPartInfo(True, True, kernel_filesystem_size, + hashlib.sha256('fake-new-kernel-content').digest()) + payload_gen.SetMinorVersion(0) + + rootfs_part_size = 0 + if rootfs_part_size_provided: + rootfs_part_size = rootfs_filesystem_size + block_size + rootfs_op_size = rootfs_part_size or rootfs_filesystem_size + if fail_rootfs_part_size_exceeded: + rootfs_op_size += block_size + payload_gen.AddOperationWithData( + False, common.OpType.REPLACE, + dst_extents=[(0, rootfs_op_size / block_size)], + data_blob=os.urandom(rootfs_op_size)) + + kernel_part_size = 0 + if kernel_part_size_provided: + kernel_part_size = kernel_filesystem_size + block_size + kernel_op_size = kernel_part_size or kernel_filesystem_size + if fail_kernel_part_size_exceeded: + kernel_op_size += block_size + payload_gen.AddOperationWithData( + True, common.OpType.REPLACE, + dst_extents=[(0, kernel_op_size / block_size)], + data_blob=os.urandom(kernel_op_size)) + + # Generate payload (complete w/ signature) and create the test object. + if fail_invalid_block_size: + use_block_size = block_size + 5 # Not a power of two. + elif fail_mismatched_block_size: + use_block_size = block_size * 2 # Different that payload stated. + else: + use_block_size = block_size + + kwargs = { + 'payload_gen_dargs': { + 'privkey_file_name': test_utils._PRIVKEY_FILE_NAME, + 'do_add_pseudo_operation': True, + 'is_pseudo_in_kernel': True, + 'padding': os.urandom(1024) if fail_excess_data else None}, + 'checker_init_dargs': { + 'assert_type': 'delta' if fail_wrong_payload_type else 'full', + 'block_size': use_block_size}} + if fail_invalid_block_size: + self.assertRaises(update_payload.PayloadError, _GetPayloadChecker, + payload_gen.WriteToFileWithData, **kwargs) + else: + payload_checker = _GetPayloadChecker(payload_gen.WriteToFileWithData, + **kwargs) + + kwargs = {'pubkey_file_name': test_utils._PUBKEY_FILE_NAME, + 'rootfs_part_size': rootfs_part_size, + 'kernel_part_size': kernel_part_size} + should_fail = (fail_wrong_payload_type or fail_mismatched_block_size or + fail_excess_data or + fail_rootfs_part_size_exceeded or + fail_kernel_part_size_exceeded) + if should_fail: + self.assertRaises(update_payload.PayloadError, payload_checker.Run, + **kwargs) + else: + self.assertIsNone(payload_checker.Run(**kwargs)) + +# This implements a generic API, hence the occasional unused args. +# pylint: disable=W0613 +def ValidateCheckOperationTest(op_type_name, is_last, allow_signature, + allow_unhashed, fail_src_extents, + fail_dst_extents, + fail_mismatched_data_offset_length, + fail_missing_dst_extents, fail_src_length, + fail_dst_length, fail_data_hash, + fail_prev_data_offset, fail_bad_minor_version): + """Returns True iff the combination of arguments represents a valid test.""" + op_type = _OpTypeByName(op_type_name) + + # REPLACE/REPLACE_BZ operations don't read data from src partition. They are + # compatible with all valid minor versions, so we don't need to check that. + if (op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ) and ( + fail_src_extents or fail_src_length or fail_bad_minor_version)): + return False + + # MOVE and SOURCE_COPY operations don't carry data. + if (op_type in (common.OpType.MOVE, common.OpType.SOURCE_COPY) and ( + fail_mismatched_data_offset_length or fail_data_hash or + fail_prev_data_offset)): + return False + + return True + + +def TestMethodBody(run_method_name, run_dargs): + """Returns a function that invokes a named method with named arguments.""" + return lambda self: getattr(self, run_method_name)(**run_dargs) + + +def AddParametricTests(tested_method_name, arg_space, validate_func=None): + """Enumerates and adds specific parametric tests to PayloadCheckerTest. + + This function enumerates a space of test parameters (defined by arg_space), + then binds a new, unique method name in PayloadCheckerTest to a test function + that gets handed the said parameters. This is a preferable approach to doing + the enumeration and invocation during the tests because this way each test is + treated as a complete run by the unittest framework, and so benefits from the + usual setUp/tearDown mechanics. + + Args: + tested_method_name: Name of the tested PayloadChecker method. + arg_space: A dictionary containing variables (keys) and lists of values + (values) associated with them. + validate_func: A function used for validating test argument combinations. + """ + for value_tuple in itertools.product(*arg_space.itervalues()): + run_dargs = dict(zip(arg_space.iterkeys(), value_tuple)) + if validate_func and not validate_func(**run_dargs): + continue + run_method_name = 'Do%sTest' % tested_method_name + test_method_name = 'test%s' % tested_method_name + for arg_key, arg_val in run_dargs.iteritems(): + if arg_val or type(arg_val) is int: + test_method_name += '__%s=%s' % (arg_key, arg_val) + setattr(PayloadCheckerTest, test_method_name, + TestMethodBody(run_method_name, run_dargs)) + + +def AddAllParametricTests(): + """Enumerates and adds all parametric tests to PayloadCheckerTest.""" + # Add all _CheckElem() test cases. + AddParametricTests('AddElem', + {'linebreak': (True, False), + 'indent': (0, 1, 2), + 'convert': (str, lambda s: s[::-1]), + 'is_present': (True, False), + 'is_mandatory': (True, False), + 'is_submsg': (True, False)}) + + # Add all _Add{Mandatory,Optional}Field tests. + AddParametricTests('AddField', + {'is_mandatory': (True, False), + 'linebreak': (True, False), + 'indent': (0, 1, 2), + 'convert': (str, lambda s: s[::-1]), + 'is_present': (True, False)}) + + # Add all _Add{Mandatory,Optional}SubMsg tests. + AddParametricTests('AddSubMsg', + {'is_mandatory': (True, False), + 'is_present': (True, False)}) + + # Add all _CheckManifest() test cases. + AddParametricTests('CheckManifest', + {'fail_mismatched_block_size': (True, False), + 'fail_bad_sigs': (True, False), + 'fail_mismatched_oki_ori': (True, False), + 'fail_bad_oki': (True, False), + 'fail_bad_ori': (True, False), + 'fail_bad_nki': (True, False), + 'fail_bad_nri': (True, False), + 'fail_old_kernel_fs_size': (True, False), + 'fail_old_rootfs_fs_size': (True, False), + 'fail_new_kernel_fs_size': (True, False), + 'fail_new_rootfs_fs_size': (True, False)}) + + # Add all _CheckOperation() test cases. + AddParametricTests('CheckOperation', + {'op_type_name': ('REPLACE', 'REPLACE_BZ', 'MOVE', + 'BSDIFF', 'SOURCE_COPY', + 'SOURCE_BSDIFF'), + 'is_last': (True, False), + 'allow_signature': (True, False), + 'allow_unhashed': (True, False), + 'fail_src_extents': (True, False), + 'fail_dst_extents': (True, False), + 'fail_mismatched_data_offset_length': (True, False), + 'fail_missing_dst_extents': (True, False), + 'fail_src_length': (True, False), + 'fail_dst_length': (True, False), + 'fail_data_hash': (True, False), + 'fail_prev_data_offset': (True, False), + 'fail_bad_minor_version': (True, False)}, + validate_func=ValidateCheckOperationTest) + + # Add all _CheckOperations() test cases. + AddParametricTests('CheckOperations', + {'fail_nonexhaustive_full_update': (True, False)}) + + # Add all _CheckOperations() test cases. + AddParametricTests('CheckSignatures', + {'fail_empty_sigs_blob': (True, False), + 'fail_missing_pseudo_op': (True, False), + 'fail_mismatched_pseudo_op': (True, False), + 'fail_sig_missing_fields': (True, False), + 'fail_unknown_sig_version': (True, False), + 'fail_incorrect_sig': (True, False)}) + + # Add all _CheckManifestMinorVersion() test cases. + AddParametricTests('CheckManifestMinorVersion', + {'minor_version': (None, 0, 1, 2, 3, 4, 555), + 'payload_type': (checker._TYPE_FULL, + checker._TYPE_DELTA)}) + + # Add all Run() test cases. + AddParametricTests('Run', + {'rootfs_part_size_provided': (True, False), + 'kernel_part_size_provided': (True, False), + 'fail_wrong_payload_type': (True, False), + 'fail_invalid_block_size': (True, False), + 'fail_mismatched_block_size': (True, False), + 'fail_excess_data': (True, False), + 'fail_rootfs_part_size_exceeded': (True, False), + 'fail_kernel_part_size_exceeded': (True, False)}) + + +if __name__ == '__main__': + AddAllParametricTests() + unittest.main()
diff --git a/update_engine/scripts/update_payload/common.py b/update_engine/scripts/update_payload/common.py new file mode 100644 index 0000000..678fc5d --- /dev/null +++ b/update_engine/scripts/update_payload/common.py
@@ -0,0 +1,204 @@ +# Copyright (c) 2013 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. + +"""Utilities for update payload processing.""" + +from __future__ import print_function + +from error import PayloadError +import update_metadata_pb2 + + +# +# Constants. +# +PSEUDO_EXTENT_MARKER = (1L << 64) - 1 # UINT64_MAX + +SIG_ASN1_HEADER = ( + '\x30\x31\x30\x0d\x06\x09\x60\x86' + '\x48\x01\x65\x03\x04\x02\x01\x05' + '\x00\x04\x20' +) + +CHROMEOS_MAJOR_PAYLOAD_VERSION = 1 +BRILLO_MAJOR_PAYLOAD_VERSION = 2 + +INPLACE_MINOR_PAYLOAD_VERSION = 1 +SOURCE_MINOR_PAYLOAD_VERSION = 2 +OPSRCHASH_MINOR_PAYLOAD_VERSION = 3 +IMGDIFF_MINOR_PAYLOAD_VERSION = 4 + +# +# Payload operation types. +# +class OpType(object): + """Container for operation type constants.""" + _CLASS = update_metadata_pb2.InstallOperation + # pylint: disable=E1101 + REPLACE = _CLASS.REPLACE + REPLACE_BZ = _CLASS.REPLACE_BZ + MOVE = _CLASS.MOVE + BSDIFF = _CLASS.BSDIFF + SOURCE_COPY = _CLASS.SOURCE_COPY + SOURCE_BSDIFF = _CLASS.SOURCE_BSDIFF + ZERO = _CLASS.ZERO + DISCARD = _CLASS.DISCARD + REPLACE_XZ = _CLASS.REPLACE_XZ + IMGDIFF = _CLASS.IMGDIFF + ALL = (REPLACE, REPLACE_BZ, MOVE, BSDIFF, SOURCE_COPY, SOURCE_BSDIFF, ZERO, + DISCARD, REPLACE_XZ, IMGDIFF) + NAMES = { + REPLACE: 'REPLACE', + REPLACE_BZ: 'REPLACE_BZ', + MOVE: 'MOVE', + BSDIFF: 'BSDIFF', + SOURCE_COPY: 'SOURCE_COPY', + SOURCE_BSDIFF: 'SOURCE_BSDIFF', + ZERO: 'ZERO', + DISCARD: 'DISCARD', + REPLACE_XZ: 'REPLACE_XZ', + IMGDIFF: 'IMGDIFF', + } + + def __init__(self): + pass + + +# +# Checked and hashed reading of data. +# +def IntPackingFmtStr(size, is_unsigned): + """Returns an integer format string for use by the struct module. + + Args: + size: the integer size in bytes (2, 4 or 8) + is_unsigned: whether it is signed or not + + Returns: + A format string for packing/unpacking integer values; assumes network byte + order (big-endian). + + Raises: + PayloadError if something is wrong with the arguments. + """ + # Determine the base conversion format. + if size == 2: + fmt = 'h' + elif size == 4: + fmt = 'i' + elif size == 8: + fmt = 'q' + else: + raise PayloadError('unsupport numeric field size (%s)' % size) + + # Signed or unsigned? + if is_unsigned: + fmt = fmt.upper() + + # Make it network byte order (big-endian). + fmt = '!' + fmt + + return fmt + + +def Read(file_obj, length, offset=None, hasher=None): + """Reads binary data from a file. + + Args: + file_obj: an open file object + length: the length of the data to read + offset: an offset to seek to prior to reading; this is an absolute offset + from either the beginning (non-negative) or end (negative) of the + file. (optional) + hasher: a hashing object to pass the read data through (optional) + + Returns: + A string containing the read data. + + Raises: + PayloadError if a read error occurred or not enough data was read. + """ + if offset is not None: + if offset >= 0: + file_obj.seek(offset) + else: + file_obj.seek(offset, 2) + + try: + data = file_obj.read(length) + except IOError, e: + raise PayloadError('error reading from file (%s): %s' % (file_obj.name, e)) + + if len(data) != length: + raise PayloadError( + 'reading from file (%s) too short (%d instead of %d bytes)' % + (file_obj.name, len(data), length)) + + if hasher: + hasher.update(data) + + return data + + +# +# Formatting functions. +# +def FormatExtent(ex, block_size=0): + end_block = ex.start_block + ex.num_blocks + if block_size: + return '%d->%d * %d' % (ex.start_block, end_block, block_size) + else: + return '%d->%d' % (ex.start_block, end_block) + + +def FormatSha256(digest): + """Returns a canonical string representation of a SHA256 digest.""" + return digest.encode('base64').strip() + + +# +# Useful iterators. +# +def _ObjNameIter(items, base_name, reverse=False, name_format_func=None): + """A generic (item, name) tuple iterators. + + Args: + items: the sequence of objects to iterate on + base_name: the base name for all objects + reverse: whether iteration should be in reverse order + name_format_func: a function to apply to the name string + + Yields: + An iterator whose i-th invocation returns (items[i], name), where name == + base_name + '[i]' (with a formatting function optionally applied to it). + """ + idx, inc = (len(items), -1) if reverse else (1, 1) + if reverse: + items = reversed(items) + for item in items: + item_name = '%s[%d]' % (base_name, idx) + if name_format_func: + item_name = name_format_func(item, item_name) + yield (item, item_name) + idx += inc + + +def _OperationNameFormatter(op, op_name): + return '%s(%s)' % (op_name, OpType.NAMES.get(op.type, '?')) + + +def OperationIter(operations, base_name, reverse=False): + """An (item, name) iterator for update operations.""" + return _ObjNameIter(operations, base_name, reverse=reverse, + name_format_func=_OperationNameFormatter) + + +def ExtentIter(extents, base_name, reverse=False): + """An (item, name) iterator for operation extents.""" + return _ObjNameIter(extents, base_name, reverse=reverse) + + +def SignatureIter(sigs, base_name, reverse=False): + """An (item, name) iterator for signatures.""" + return _ObjNameIter(sigs, base_name, reverse=reverse)
diff --git a/update_engine/scripts/update_payload/error.py b/update_engine/scripts/update_payload/error.py new file mode 100644 index 0000000..8b9cadd --- /dev/null +++ b/update_engine/scripts/update_payload/error.py
@@ -0,0 +1,9 @@ +# Copyright (c) 2013 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. + +"""Payload handling errors.""" + + +class PayloadError(Exception): + """An update payload general processing error."""
diff --git a/update_engine/scripts/update_payload/format_utils.py b/update_engine/scripts/update_payload/format_utils.py new file mode 100644 index 0000000..2c3775c --- /dev/null +++ b/update_engine/scripts/update_payload/format_utils.py
@@ -0,0 +1,97 @@ +# Copyright (c) 2013 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. + +"""Various formatting functions.""" + + +def NumToPercent(num, total, min_precision=1, max_precision=5): + """Returns the percentage (string) of |num| out of |total|. + + If the percentage includes a fraction, it will be computed down to the least + precision that yields a non-zero and ranging between |min_precision| and + |max_precision|. Values are always rounded down. All arithmetic operations + are integer built-ins. Examples (using default precision): + + (1, 1) => 100% + (3, 10) => 30% + (3, 9) => 33.3% + (3, 900) => 0.3% + (3, 9000000) => 0.00003% + (3, 900000000) => 0% + (5, 2) => 250% + + Args: + num: the value of the part + total: the value of the whole + min_precision: minimum precision for fractional percentage + max_precision: maximum precision for fractional percentage + Returns: + Percentage string, or None if percent cannot be computed (i.e. total is + zero). + + """ + if total == 0: + return None + + percent = 0 + precision = min(min_precision, max_precision) + factor = 10 ** precision + while precision <= max_precision: + percent = num * 100 * factor / total + if percent: + break + factor *= 10 + precision += 1 + + whole, frac = divmod(percent, factor) + while frac and not frac % 10: + frac /= 10 + precision -= 1 + + return '%d%s%%' % (whole, '.%0*d' % (precision, frac) if frac else '') + + +def BytesToHumanReadable(size, precision=1, decimal=False): + """Returns a human readable representation of a given |size|. + + The returned string includes unit notations in either binary (KiB, MiB, etc) + or decimal (kB, MB, etc), based on the value of |decimal|. The chosen unit is + the largest that yields a whole (or mixed) number. It may contain up to + |precision| fractional digits. Values are always rounded down. Largest unit + is an exabyte. All arithmetic operations are integer built-ins. Examples + (using default precision and binary units): + + 4096 => 4 KiB + 5000 => 4.8 KiB + 500000 => 488.2 KiB + 5000000 => 4.7 MiB + + Args: + size: the size in bytes + precision: the number of digits past the decimal point + decimal: whether to compute/present decimal or binary units + Returns: + Readable size string, or None if no conversion is applicable (i.e. size is + less than the smallest unit). + + """ + constants = ( + (('KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'), 1024), + (('kB', 'MB', 'GB', 'TB', 'PB', 'EB'), 1000) + ) + suffixes, base = constants[decimal] + exp, magnitude = 0, 1 + while exp < len(suffixes): + next_magnitude = magnitude * base + if size < next_magnitude: + break + exp += 1 + magnitude = next_magnitude + + if exp != 0: + whole = size / magnitude + frac = (size % magnitude) * (10 ** precision) / magnitude + while frac and not frac % 10: + frac /= 10 + return '%d%s %s' % (whole, '.%d' % frac if frac else '', suffixes[exp - 1])
diff --git a/update_engine/scripts/update_payload/format_utils_unittest.py b/update_engine/scripts/update_payload/format_utils_unittest.py new file mode 100755 index 0000000..8c5ba8e --- /dev/null +++ b/update_engine/scripts/update_payload/format_utils_unittest.py
@@ -0,0 +1,76 @@ +#!/usr/bin/python +# +# Copyright (c) 2013 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. + +"""Unit tests for format_utils.py.""" + +import unittest + +import format_utils + + +class NumToPercentTest(unittest.TestCase): + def testHundredPercent(self): + self.assertEqual(format_utils.NumToPercent(1, 1), '100%') + + def testOverHundredPercent(self): + self.assertEqual(format_utils.NumToPercent(5, 2), '250%') + + def testWholePercent(self): + self.assertEqual(format_utils.NumToPercent(3, 10), '30%') + + def testDefaultMinPrecision(self): + self.assertEqual(format_utils.NumToPercent(3, 9), '33.3%') + self.assertEqual(format_utils.NumToPercent(3, 900), '0.3%') + + def testDefaultMaxPrecision(self): + self.assertEqual(format_utils.NumToPercent(3, 9000000), '0.00003%') + self.assertEqual(format_utils.NumToPercent(3, 90000000), '0%') + + def testCustomMinPrecision(self): + self.assertEqual(format_utils.NumToPercent(3, 9, min_precision=3), + '33.333%') + self.assertEqual(format_utils.NumToPercent(3, 9, min_precision=0), + '33%') + + def testCustomMaxPrecision(self): + self.assertEqual(format_utils.NumToPercent(3, 900, max_precision=1), + '0.3%') + self.assertEqual(format_utils.NumToPercent(3, 9000, max_precision=1), + '0%') + + +class BytesToHumanReadableTest(unittest.TestCase): + def testBaseTwo(self): + self.assertEqual(format_utils.BytesToHumanReadable(0x1000), '4 KiB') + self.assertEqual(format_utils.BytesToHumanReadable(0x400000), '4 MiB') + self.assertEqual(format_utils.BytesToHumanReadable(0x100000000), '4 GiB') + self.assertEqual(format_utils.BytesToHumanReadable(0x40000000000), '4 TiB') + + def testDecimal(self): + self.assertEqual(format_utils.BytesToHumanReadable(5000, decimal=True), + '5 kB') + self.assertEqual(format_utils.BytesToHumanReadable(5000000, decimal=True), + '5 MB') + self.assertEqual(format_utils.BytesToHumanReadable(5000000000, + decimal=True), + '5 GB') + + def testDefaultPrecision(self): + self.assertEqual(format_utils.BytesToHumanReadable(5000), '4.8 KiB') + self.assertEqual(format_utils.BytesToHumanReadable(500000), '488.2 KiB') + self.assertEqual(format_utils.BytesToHumanReadable(5000000), '4.7 MiB') + + def testCustomPrecision(self): + self.assertEqual(format_utils.BytesToHumanReadable(5000, precision=3), + '4.882 KiB') + self.assertEqual(format_utils.BytesToHumanReadable(500000, precision=0), + '488 KiB') + self.assertEqual(format_utils.BytesToHumanReadable(5000000, precision=5), + '4.76837 MiB') + + +if __name__ == '__main__': + unittest.main()
diff --git a/update_engine/scripts/update_payload/histogram.py b/update_engine/scripts/update_payload/histogram.py new file mode 100644 index 0000000..9916329 --- /dev/null +++ b/update_engine/scripts/update_payload/histogram.py
@@ -0,0 +1,117 @@ +# Copyright (c) 2013 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. + +"""Histogram generation tools.""" + +from collections import defaultdict + +import format_utils + + +class Histogram(object): + """A histogram generating object. + + This object serves the sole purpose of formatting (key, val) pairs as an + ASCII histogram, including bars and percentage markers, and taking care of + label alignment, scaling, etc. In addition to the standard __init__ + interface, two static methods are provided for conveniently converting data + in different formats into a histogram. Histogram generation is exported via + its __str__ method, and looks as follows: + + Yes |################ | 5 (83.3%) + No |### | 1 (16.6%) + + TODO(garnold) we may want to add actual methods for adding data or tweaking + the output layout and formatting. For now, though, this is fine. + + """ + + def __init__(self, data, scale=20, formatter=None): + """Initialize a histogram object. + + Args: + data: list of (key, count) pairs constituting the histogram + scale: number of characters used to indicate 100% + formatter: function used for formatting raw histogram values + + """ + self.data = data + self.scale = scale + self.formatter = formatter or str + self.max_key_len = max([len(str(key)) for key, count in self.data]) + self.total = sum([count for key, count in self.data]) + + @staticmethod + def FromCountDict(count_dict, scale=20, formatter=None, key_names=None): + """Takes a dictionary of counts and returns a histogram object. + + This simply converts a mapping from names to counts into a list of (key, + count) pairs, optionally translating keys into name strings, then + generating and returning a histogram for them. This is a useful convenience + call for clients that update a dictionary of counters as they (say) scan a + data stream. + + Args: + count_dict: dictionary mapping keys to occurrence counts + scale: number of characters used to indicate 100% + formatter: function used for formatting raw histogram values + key_names: dictionary mapping keys to name strings + Returns: + A histogram object based on the given data. + + """ + namer = None + if key_names: + namer = lambda key: key_names[key] + else: + namer = lambda key: key + + hist = [(namer(key), count) for key, count in count_dict.items()] + return Histogram(hist, scale, formatter) + + @staticmethod + def FromKeyList(key_list, scale=20, formatter=None, key_names=None): + """Takes a list of (possibly recurring) keys and returns a histogram object. + + This converts the list into a dictionary of counters, then uses + FromCountDict() to generate the actual histogram. For example: + + ['a', 'a', 'b', 'a', 'b'] --> {'a': 3, 'b': 2} --> ... + + Args: + key_list: list of (possibly recurring) keys + scale: number of characters used to indicate 100% + formatter: function used for formatting raw histogram values + key_names: dictionary mapping keys to name strings + Returns: + A histogram object based on the given data. + + """ + count_dict = defaultdict(int) # Unset items default to zero + for key in key_list: + count_dict[key] += 1 + return Histogram.FromCountDict(count_dict, scale, formatter, key_names) + + def __str__(self): + hist_lines = [] + hist_bar = '|' + for key, count in self.data: + if self.total: + bar_len = count * self.scale / self.total + hist_bar = '|%s|' % ('#' * bar_len).ljust(self.scale) + + line = '%s %s %s' % ( + str(key).ljust(self.max_key_len), + hist_bar, + self.formatter(count)) + percent_str = format_utils.NumToPercent(count, self.total) + if percent_str: + line += ' (%s)' % percent_str + hist_lines.append(line) + + return '\n'.join(hist_lines) + + def GetKeys(self): + """Returns the keys of the histogram.""" + return [key for key, _ in self.data]
diff --git a/update_engine/scripts/update_payload/histogram_unittest.py b/update_engine/scripts/update_payload/histogram_unittest.py new file mode 100755 index 0000000..421ff20 --- /dev/null +++ b/update_engine/scripts/update_payload/histogram_unittest.py
@@ -0,0 +1,60 @@ +#!/usr/bin/python +# +# Copyright (c) 2013 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. + +"""Unit tests for histogram.py.""" + +import unittest + +import format_utils +import histogram + + +class HistogramTest(unittest.TestCase): + + @staticmethod + def AddHumanReadableSize(size): + fmt = format_utils.BytesToHumanReadable(size) + return '%s (%s)' % (size, fmt) if fmt else str(size) + + def CompareToExpectedDefault(self, actual_str): + expected_str = ( + 'Yes |################ | 5 (83.3%)\n' + 'No |### | 1 (16.6%)' + ) + self.assertEqual(actual_str, expected_str) + + def testExampleHistogram(self): + self.CompareToExpectedDefault(str(histogram.Histogram( + [('Yes', 5), ('No', 1)]))) + + def testFromCountDict(self): + self.CompareToExpectedDefault(str(histogram.Histogram.FromCountDict( + {'Yes': 5, 'No': 1}))) + + def testFromKeyList(self): + self.CompareToExpectedDefault(str(histogram.Histogram.FromKeyList( + ['Yes', 'Yes', 'No', 'Yes', 'Yes', 'Yes']))) + + def testCustomScale(self): + expected_str = ( + 'Yes |#### | 5 (83.3%)\n' + 'No | | 1 (16.6%)' + ) + actual_str = str(histogram.Histogram([('Yes', 5), ('No', 1)], scale=5)) + self.assertEqual(actual_str, expected_str) + + def testCustomFormatter(self): + expected_str = ( + 'Yes |################ | 5000 (4.8 KiB) (83.3%)\n' + 'No |### | 1000 (16.6%)' + ) + actual_str = str(histogram.Histogram( + [('Yes', 5000), ('No', 1000)], formatter=self.AddHumanReadableSize)) + self.assertEqual(actual_str, expected_str) + + +if __name__ == '__main__': + unittest.main()
diff --git a/update_engine/scripts/update_payload/payload-test-key.pem b/update_engine/scripts/update_payload/payload-test-key.pem new file mode 100644 index 0000000..342e923 --- /dev/null +++ b/update_engine/scripts/update_payload/payload-test-key.pem
@@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAvtGHtqO21Uhy2wGz9fluIpIUR8G7dZoCZhZukGkm4mlfgL71 +xPSArjx02/w/FhYxOusV6/XQeKgL3i8cni3HCkCOurZLpi2L5Ver6qrxKFh6WBVZ +0Dj7N6P/Mf5jZdhfvVyweLlsNK8Ypeb+RazfrsXhd4cy3dBMxouGwH7R7QQXTFCo +Cc8kgJBTxILl3jfvY8OrNKgYiCETa7tQdFkP0bfPwH9cAXuMjHXiZatim0tF+ivp +kM2v/6LTxtD6Rq1wks/N6CHi8efrRaviFp7c0mNmBNFaV54cHEUW2SlNIiRun7L0 +1nAz/D8kuoHfx4E3Mtj0DbvngZJMX/X+rJQ5cQIDAQABAoIBADmE2X7hbJxwAUcp +BUExFdTP6dMTf9lcOjrhqiRXvgPjtYkOhvD+rsdWq/cf2zhiKibTdEEzUMr+BM3N +r7eyntvlR+DaUIVgF1pjigvryVPbD837aZ5NftRv194PC5FInttq1Dsf0ZEz8p8X +uS/xg1+ggG1SUK/yOSJkLpNZ5xelbclQJ9bnJST8PR8XbEieA83xt5M2DcooPzq0 +/99m/daA5hmSWs6n8sFrIZDQxDhLyyW4J72jjoNTE87eCpwK855yXMelpEPDZNQi +nB3x5Y/bGbl81PInqL2q14lekrVYdYZ7bOBVlsmyvz6f1e4OOE1aaAM+w6ArA4az +6elZQE0CgYEA4GOU6BBu9jLqFdqV9jIkWsgz5ZWINz8PLJPtZzk5I9KO1m+GAUy2 +h/1IGGR6qRQR49hMtq4C0lUifxquq0xivzJ87U9oxKC9yEeTxkmDe5csVHsnAtqT +xRgVM7Ysrut5NLU1zm0q3jBmkDu7d99LvscM/3n7eJ6RiYpnA54O6I8CgYEA2bNA +34PTvxBS2deRoxKQNlVU14FtirE+q0+k0wcE85wr7wIMpR13al8T1TpE8J1yvvZM +92HMGFGfYNDB46b8VfJ5AxEUFwdruec6sTVVfkMZMOqM/A08yiaLzQ1exDxNwaja +fLuG5FAVRD/2g7fLBcsmosyNgcgNr1XA8Q/nvf8CgYEAwaSOg7py19rWcqehlMZu +4z00tCNYWzz7LmA2l0clzYlPJTU3MvXt6+ujhRFpXXJpgfRPN7Nx0ewQihoPtNqF +uTSr5OwLoOyK+0Tx/UPByS2L3xgscWUJ8yQ2X9sOMqIZhmf/mDZTsU2ZpU03GlrE +dk43JF4zq0NEm6qp/dAwU3cCgYEAvECl+KKmmLIk8vvWlI2Y52Mi2rixYR2kc7+L +aHDJd1+1HhlHlgDFItbU765Trz5322phZArN0rnCeJYNFC9yRWBIBL7gAIoKPdgW +iOb15xlez04EXHGV/7kVa1wEdu0u0CiTxwjivMwDl+E36u8kQP5LirwYIgI800H0 +doCqhUECgYEAjvA38OS7hy56Q4LQtmHFBuRIn4E5SrIGMwNIH6TGbEKQix3ajTCQ +0fSoLDGTkU6dH+T4v0WheveN2a2Kofqm0UQx5V2rfnY/Ut1fAAWgL/lsHLDnzPUZ +bvTOANl8TbT49xAfNXTaGWe7F7nYz+bK0UDif1tJNDLQw7USD5I8lbQ= +-----END RSA PRIVATE KEY-----
diff --git a/update_engine/scripts/update_payload/payload-test-key.pub b/update_engine/scripts/update_payload/payload-test-key.pub new file mode 100644 index 0000000..fdae963 --- /dev/null +++ b/update_engine/scripts/update_payload/payload-test-key.pub
@@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvtGHtqO21Uhy2wGz9flu +IpIUR8G7dZoCZhZukGkm4mlfgL71xPSArjx02/w/FhYxOusV6/XQeKgL3i8cni3H +CkCOurZLpi2L5Ver6qrxKFh6WBVZ0Dj7N6P/Mf5jZdhfvVyweLlsNK8Ypeb+Razf +rsXhd4cy3dBMxouGwH7R7QQXTFCoCc8kgJBTxILl3jfvY8OrNKgYiCETa7tQdFkP +0bfPwH9cAXuMjHXiZatim0tF+ivpkM2v/6LTxtD6Rq1wks/N6CHi8efrRaviFp7c +0mNmBNFaV54cHEUW2SlNIiRun7L01nAz/D8kuoHfx4E3Mtj0DbvngZJMX/X+rJQ5 +cQIDAQAB +-----END PUBLIC KEY-----
diff --git a/update_engine/scripts/update_payload/payload.py b/update_engine/scripts/update_payload/payload.py new file mode 100644 index 0000000..f76c0de --- /dev/null +++ b/update_engine/scripts/update_payload/payload.py
@@ -0,0 +1,341 @@ +# Copyright (c) 2013 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. + +"""Tools for reading, verifying and applying Chrome OS update payloads.""" + +from __future__ import print_function + +import hashlib +import struct + +import applier +import block_tracer +import checker +import common +from error import PayloadError +import update_metadata_pb2 + + +# +# Helper functions. +# +def _ReadInt(file_obj, size, is_unsigned, hasher=None): + """Reads a binary-encoded integer from a file. + + It will do the correct conversion based on the reported size and whether or + not a signed number is expected. Assumes a network (big-endian) byte + ordering. + + Args: + file_obj: a file object + size: the integer size in bytes (2, 4 or 8) + is_unsigned: whether it is signed or not + hasher: an optional hasher to pass the value through + + Returns: + An "unpacked" (Python) integer value. + + Raises: + PayloadError if an read error occurred. + """ + return struct.unpack(common.IntPackingFmtStr(size, is_unsigned), + common.Read(file_obj, size, hasher=hasher))[0] + + +# +# Update payload. +# +class Payload(object): + """Chrome OS update payload processor.""" + + class _PayloadHeader(object): + """Update payload header struct.""" + + # Header constants; sizes are in bytes. + _MAGIC = 'CrAU' + _VERSION_SIZE = 8 + _MANIFEST_LEN_SIZE = 8 + _METADATA_SIGNATURE_LEN_SIZE = 4 + + def __init__(self): + self.version = None + self.manifest_len = None + self.metadata_signature_len = None + self.size = None + + def ReadFromPayload(self, payload_file, hasher=None): + """Reads the payload header from a file. + + Reads the payload header from the |payload_file| and updates the |hasher| + if one is passed. The parsed header is stored in the _PayloadHeader + instance attributes. + + Args: + payload_file: a file object + hasher: an optional hasher to pass the value through + + Returns: + None. + + Raises: + PayloadError if a read error occurred or the header is invalid. + """ + # Verify magic + magic = common.Read(payload_file, len(self._MAGIC), hasher=hasher) + if magic != self._MAGIC: + raise PayloadError('invalid payload magic: %s' % magic) + + self.version = _ReadInt(payload_file, self._VERSION_SIZE, True, + hasher=hasher) + self.manifest_len = _ReadInt(payload_file, self._MANIFEST_LEN_SIZE, True, + hasher=hasher) + self.size = (len(self._MAGIC) + self._VERSION_SIZE + + self._MANIFEST_LEN_SIZE) + self.metadata_signature_len = 0 + + if self.version == common.BRILLO_MAJOR_PAYLOAD_VERSION: + self.size += self._METADATA_SIGNATURE_LEN_SIZE + self.metadata_signature_len = _ReadInt( + payload_file, self._METADATA_SIGNATURE_LEN_SIZE, True, + hasher=hasher) + + + def __init__(self, payload_file): + """Initialize the payload object. + + Args: + payload_file: update payload file object open for reading + """ + self.payload_file = payload_file + self.manifest_hasher = None + self.is_init = False + self.header = None + self.manifest = None + self.data_offset = None + self.metadata_signature = None + self.metadata_size = None + + def _ReadHeader(self): + """Reads and returns the payload header. + + Returns: + A payload header object. + + Raises: + PayloadError if a read error occurred. + """ + header = self._PayloadHeader() + header.ReadFromPayload(self.payload_file, self.manifest_hasher) + return header + + def _ReadManifest(self): + """Reads and returns the payload manifest. + + Returns: + A string containing the payload manifest in binary form. + + Raises: + PayloadError if a read error occurred. + """ + if not self.header: + raise PayloadError('payload header not present') + + return common.Read(self.payload_file, self.header.manifest_len, + hasher=self.manifest_hasher) + + def _ReadMetadataSignature(self): + """Reads and returns the metadata signatures. + + Returns: + A string containing the metadata signatures protobuf in binary form or + an empty string if no metadata signature found in the payload. + + Raises: + PayloadError if a read error occurred. + """ + if not self.header: + raise PayloadError('payload header not present') + + return common.Read( + self.payload_file, self.header.metadata_signature_len, + offset=self.header.size + self.header.manifest_len) + + def ReadDataBlob(self, offset, length): + """Reads and returns a single data blob from the update payload. + + Args: + offset: offset to the beginning of the blob from the end of the manifest + length: the blob's length + + Returns: + A string containing the raw blob data. + + Raises: + PayloadError if a read error occurred. + """ + return common.Read(self.payload_file, length, + offset=self.data_offset + offset) + + def Init(self): + """Initializes the payload object. + + This is a prerequisite for any other public API call. + + Raises: + PayloadError if object already initialized or fails to initialize + correctly. + """ + if self.is_init: + raise PayloadError('payload object already initialized') + + # Initialize hash context. + # pylint: disable=E1101 + self.manifest_hasher = hashlib.sha256() + + # Read the file header. + self.header = self._ReadHeader() + + # Read the manifest. + manifest_raw = self._ReadManifest() + self.manifest = update_metadata_pb2.DeltaArchiveManifest() + self.manifest.ParseFromString(manifest_raw) + + # Read the metadata signature (if any). + metadata_signature_raw = self._ReadMetadataSignature() + if metadata_signature_raw: + self.metadata_signature = update_metadata_pb2.Signatures() + self.metadata_signature.ParseFromString(metadata_signature_raw) + + self.metadata_size = self.header.size + self.header.manifest_len + self.data_offset = self.metadata_size + self.header.metadata_signature_len + + self.is_init = True + + def Describe(self): + """Emits the payload embedded description data to standard output.""" + def _DescribeImageInfo(description, image_info): + def _DisplayIndentedValue(name, value): + print(' {:<14} {}'.format(name+':', value)) + + print('%s:' % description) + _DisplayIndentedValue('Channel', image_info.channel) + _DisplayIndentedValue('Board', image_info.board) + _DisplayIndentedValue('Version', image_info.version) + _DisplayIndentedValue('Key', image_info.key) + + if image_info.build_channel != image_info.channel: + _DisplayIndentedValue('Build channel', image_info.build_channel) + + if image_info.build_version != image_info.version: + _DisplayIndentedValue('Build version', image_info.build_version) + + if self.manifest.HasField('old_image_info'): + # pylint: disable=E1101 + _DescribeImageInfo('Old Image', self.manifest.old_image_info) + + if self.manifest.HasField('new_image_info'): + # pylint: disable=E1101 + _DescribeImageInfo('New Image', self.manifest.new_image_info) + + def _AssertInit(self): + """Raises an exception if the object was not initialized.""" + if not self.is_init: + raise PayloadError('payload object not initialized') + + def ResetFile(self): + """Resets the offset of the payload file to right past the manifest.""" + self.payload_file.seek(self.data_offset) + + def IsDelta(self): + """Returns True iff the payload appears to be a delta.""" + self._AssertInit() + return (self.manifest.HasField('old_kernel_info') or + self.manifest.HasField('old_rootfs_info') or + any(partition.HasField('old_partition_info') + for partition in self.manifest.partitions)) + + def IsFull(self): + """Returns True iff the payload appears to be a full.""" + return not self.IsDelta() + + def Check(self, pubkey_file_name=None, metadata_sig_file=None, + report_out_file=None, assert_type=None, block_size=0, + rootfs_part_size=0, kernel_part_size=0, allow_unhashed=False, + disabled_tests=()): + """Checks the payload integrity. + + Args: + pubkey_file_name: public key used for signature verification + metadata_sig_file: metadata signature, if verification is desired + report_out_file: file object to dump the report to + assert_type: assert that payload is either 'full' or 'delta' + block_size: expected filesystem / payload block size + rootfs_part_size: the size of (physical) rootfs partitions in bytes + kernel_part_size: the size of (physical) kernel partitions in bytes + allow_unhashed: allow unhashed operation blobs + disabled_tests: list of tests to disable + + Raises: + PayloadError if payload verification failed. + """ + self._AssertInit() + + # Create a short-lived payload checker object and run it. + helper = checker.PayloadChecker( + self, assert_type=assert_type, block_size=block_size, + allow_unhashed=allow_unhashed, disabled_tests=disabled_tests) + helper.Run(pubkey_file_name=pubkey_file_name, + metadata_sig_file=metadata_sig_file, + rootfs_part_size=rootfs_part_size, + kernel_part_size=kernel_part_size, + report_out_file=report_out_file) + + def Apply(self, new_kernel_part, new_rootfs_part, old_kernel_part=None, + old_rootfs_part=None, bsdiff_in_place=True, bspatch_path=None, + truncate_to_expected_size=True): + """Applies the update payload. + + Args: + new_kernel_part: name of dest kernel partition file + new_rootfs_part: name of dest rootfs partition file + old_kernel_part: name of source kernel partition file (optional) + old_rootfs_part: name of source rootfs partition file (optional) + bsdiff_in_place: whether to perform BSDIFF operations in-place (optional) + bspatch_path: path to the bspatch binary (optional) + truncate_to_expected_size: whether to truncate the resulting partitions + to their expected sizes, as specified in the + payload (optional) + + Raises: + PayloadError if payload application failed. + """ + self._AssertInit() + + # Create a short-lived payload applier object and run it. + helper = applier.PayloadApplier( + self, bsdiff_in_place=bsdiff_in_place, bspatch_path=bspatch_path, + truncate_to_expected_size=truncate_to_expected_size) + helper.Run(new_kernel_part, new_rootfs_part, + old_kernel_part=old_kernel_part, + old_rootfs_part=old_rootfs_part) + + def TraceBlock(self, block, skip, trace_out_file, is_kernel): + """Traces the origin(s) of a given dest partition block. + + The tracing tries to find origins transitively, when possible (it currently + only works for move operations, where the mapping of src/dst is + one-to-one). It will dump a list of operations and source blocks + responsible for the data in the given dest block. + + Args: + block: the block number whose origin to trace + skip: the number of first origin mappings to skip + trace_out_file: file object to dump the trace to + is_kernel: trace through kernel (True) or rootfs (False) operations + """ + self._AssertInit() + + # Create a short-lived payload block tracer object and run it. + helper = block_tracer.PayloadBlockTracer(self) + helper.Run(block, skip, trace_out_file, is_kernel)
diff --git a/update_engine/scripts/update_payload/test_utils.py b/update_engine/scripts/update_payload/test_utils.py new file mode 100644 index 0000000..61a91f5 --- /dev/null +++ b/update_engine/scripts/update_payload/test_utils.py
@@ -0,0 +1,364 @@ +# Copyright (c) 2013 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. + +"""Utilities for unit testing.""" + +from __future__ import print_function + +import cStringIO +import hashlib +import os +import struct +import subprocess + +import common +import payload +import update_metadata_pb2 + + +class TestError(Exception): + """An error during testing of update payload code.""" + + +# Private/public RSA keys used for testing. +_PRIVKEY_FILE_NAME = os.path.join(os.path.dirname(__file__), + 'payload-test-key.pem') +_PUBKEY_FILE_NAME = os.path.join(os.path.dirname(__file__), + 'payload-test-key.pub') + + +def KiB(count): + return count << 10 + + +def MiB(count): + return count << 20 + + +def GiB(count): + return count << 30 + + +def _WriteInt(file_obj, size, is_unsigned, val): + """Writes a binary-encoded integer to a file. + + It will do the correct conversion based on the reported size and whether or + not a signed number is expected. Assumes a network (big-endian) byte + ordering. + + Args: + file_obj: a file object + size: the integer size in bytes (2, 4 or 8) + is_unsigned: whether it is signed or not + val: integer value to encode + + Raises: + PayloadError if a write error occurred. + """ + try: + file_obj.write(struct.pack(common.IntPackingFmtStr(size, is_unsigned), val)) + except IOError, e: + raise payload.PayloadError('error writing to file (%s): %s' % + (file_obj.name, e)) + + +def _SetMsgField(msg, field_name, val): + """Sets or clears a field in a protobuf message.""" + if val is None: + msg.ClearField(field_name) + else: + setattr(msg, field_name, val) + + +def SignSha256(data, privkey_file_name): + """Signs the data's SHA256 hash with an RSA private key. + + Args: + data: the data whose SHA256 hash we want to sign + privkey_file_name: private key used for signing data + + Returns: + The signature string, prepended with an ASN1 header. + + Raises: + TestError if something goes wrong. + """ + # pylint: disable=E1101 + data_sha256_hash = common.SIG_ASN1_HEADER + hashlib.sha256(data).digest() + sign_cmd = ['openssl', 'rsautl', '-sign', '-inkey', privkey_file_name] + try: + sign_process = subprocess.Popen(sign_cmd, stdin=subprocess.PIPE, + stdout=subprocess.PIPE) + sig, _ = sign_process.communicate(input=data_sha256_hash) + except Exception as e: + raise TestError('signing subprocess failed: %s' % e) + + return sig + + +class SignaturesGenerator(object): + """Generates a payload signatures data block.""" + + def __init__(self): + self.sigs = update_metadata_pb2.Signatures() + + def AddSig(self, version, data): + """Adds a signature to the signature sequence. + + Args: + version: signature version (None means do not assign) + data: signature binary data (None means do not assign) + """ + # Pylint fails to identify a member of the Signatures message. + # pylint: disable=E1101 + sig = self.sigs.signatures.add() + if version is not None: + sig.version = version + if data is not None: + sig.data = data + + def ToBinary(self): + """Returns the binary representation of the signature block.""" + return self.sigs.SerializeToString() + + +class PayloadGenerator(object): + """Generates an update payload allowing low-level control. + + Attributes: + manifest: the protobuf containing the payload manifest + version: the payload version identifier + block_size: the block size pertaining to update operations + + """ + + def __init__(self, version=1): + self.manifest = update_metadata_pb2.DeltaArchiveManifest() + self.version = version + self.block_size = 0 + + @staticmethod + def _WriteExtent(ex, val): + """Returns an Extent message.""" + start_block, num_blocks = val + _SetMsgField(ex, 'start_block', start_block) + _SetMsgField(ex, 'num_blocks', num_blocks) + + @staticmethod + def _AddValuesToRepeatedField(repeated_field, values, write_func): + """Adds values to a repeated message field.""" + if values: + for val in values: + new_item = repeated_field.add() + write_func(new_item, val) + + @staticmethod + def _AddExtents(extents_field, values): + """Adds extents to an extents field.""" + PayloadGenerator._AddValuesToRepeatedField( + extents_field, values, PayloadGenerator._WriteExtent) + + def SetBlockSize(self, block_size): + """Sets the payload's block size.""" + self.block_size = block_size + _SetMsgField(self.manifest, 'block_size', block_size) + + def SetPartInfo(self, is_kernel, is_new, part_size, part_hash): + """Set the partition info entry. + + Args: + is_kernel: whether this is kernel partition info + is_new: whether to set old (False) or new (True) info + part_size: the partition size (in fact, filesystem size) + part_hash: the partition hash + """ + if is_kernel: + # pylint: disable=E1101 + part_info = (self.manifest.new_kernel_info if is_new + else self.manifest.old_kernel_info) + else: + # pylint: disable=E1101 + part_info = (self.manifest.new_rootfs_info if is_new + else self.manifest.old_rootfs_info) + _SetMsgField(part_info, 'size', part_size) + _SetMsgField(part_info, 'hash', part_hash) + + def AddOperation(self, is_kernel, op_type, data_offset=None, + data_length=None, src_extents=None, src_length=None, + dst_extents=None, dst_length=None, data_sha256_hash=None): + """Adds an InstallOperation entry.""" + # pylint: disable=E1101 + operations = (self.manifest.kernel_install_operations if is_kernel + else self.manifest.install_operations) + + op = operations.add() + op.type = op_type + + _SetMsgField(op, 'data_offset', data_offset) + _SetMsgField(op, 'data_length', data_length) + + self._AddExtents(op.src_extents, src_extents) + _SetMsgField(op, 'src_length', src_length) + + self._AddExtents(op.dst_extents, dst_extents) + _SetMsgField(op, 'dst_length', dst_length) + + _SetMsgField(op, 'data_sha256_hash', data_sha256_hash) + + def SetSignatures(self, sigs_offset, sigs_size): + """Set the payload's signature block descriptors.""" + _SetMsgField(self.manifest, 'signatures_offset', sigs_offset) + _SetMsgField(self.manifest, 'signatures_size', sigs_size) + + def SetMinorVersion(self, minor_version): + """Set the payload's minor version field.""" + _SetMsgField(self.manifest, 'minor_version', minor_version) + + def _WriteHeaderToFile(self, file_obj, manifest_len): + """Writes a payload heaer to a file.""" + # We need to access protected members in Payload for writing the header. + # pylint: disable=W0212 + file_obj.write(payload.Payload._PayloadHeader._MAGIC) + _WriteInt(file_obj, payload.Payload._PayloadHeader._VERSION_SIZE, True, + self.version) + _WriteInt(file_obj, payload.Payload._PayloadHeader._MANIFEST_LEN_SIZE, True, + manifest_len) + + def WriteToFile(self, file_obj, manifest_len=-1, data_blobs=None, + sigs_data=None, padding=None): + """Writes the payload content to a file. + + Args: + file_obj: a file object open for writing + manifest_len: manifest len to dump (otherwise computed automatically) + data_blobs: a list of data blobs to be concatenated to the payload + sigs_data: a binary Signatures message to be concatenated to the payload + padding: stuff to dump past the normal data blobs provided (optional) + """ + manifest = self.manifest.SerializeToString() + if manifest_len < 0: + manifest_len = len(manifest) + self._WriteHeaderToFile(file_obj, manifest_len) + file_obj.write(manifest) + if data_blobs: + for data_blob in data_blobs: + file_obj.write(data_blob) + if sigs_data: + file_obj.write(sigs_data) + if padding: + file_obj.write(padding) + + +class EnhancedPayloadGenerator(PayloadGenerator): + """Payload generator with automatic handling of data blobs. + + Attributes: + data_blobs: a list of blobs, in the order they were added + curr_offset: the currently consumed offset of blobs added to the payload + """ + + def __init__(self): + super(EnhancedPayloadGenerator, self).__init__() + self.data_blobs = [] + self.curr_offset = 0 + + def AddData(self, data_blob): + """Adds a (possibly orphan) data blob.""" + data_length = len(data_blob) + data_offset = self.curr_offset + self.curr_offset += data_length + self.data_blobs.append(data_blob) + return data_length, data_offset + + def AddOperationWithData(self, is_kernel, op_type, src_extents=None, + src_length=None, dst_extents=None, dst_length=None, + data_blob=None, do_hash_data_blob=True): + """Adds an install operation and associated data blob. + + This takes care of obtaining a hash of the data blob (if so instructed) + and appending it to the internally maintained list of blobs, including the + necessary offset/length accounting. + + Args: + is_kernel: whether this is a kernel (True) or rootfs (False) operation + op_type: one of REPLACE, REPLACE_BZ, MOVE or BSDIFF + src_extents: list of (start, length) pairs indicating src block ranges + src_length: size of the src data in bytes (needed for BSDIFF) + dst_extents: list of (start, length) pairs indicating dst block ranges + dst_length: size of the dst data in bytes (needed for BSDIFF) + data_blob: a data blob associated with this operation + do_hash_data_blob: whether or not to compute and add a data blob hash + """ + data_offset = data_length = data_sha256_hash = None + if data_blob is not None: + if do_hash_data_blob: + # pylint: disable=E1101 + data_sha256_hash = hashlib.sha256(data_blob).digest() + data_length, data_offset = self.AddData(data_blob) + + self.AddOperation(is_kernel, op_type, data_offset=data_offset, + data_length=data_length, src_extents=src_extents, + src_length=src_length, dst_extents=dst_extents, + dst_length=dst_length, data_sha256_hash=data_sha256_hash) + + def WriteToFileWithData(self, file_obj, sigs_data=None, + privkey_file_name=None, + do_add_pseudo_operation=False, + is_pseudo_in_kernel=False, padding=None): + """Writes the payload content to a file, optionally signing the content. + + Args: + file_obj: a file object open for writing + sigs_data: signatures blob to be appended to the payload (optional; + payload signature fields assumed to be preset by the caller) + privkey_file_name: key used for signing the payload (optional; used only + if explicit signatures blob not provided) + do_add_pseudo_operation: whether a pseudo-operation should be added to + account for the signature blob + is_pseudo_in_kernel: whether the pseudo-operation should be added to + kernel (True) or rootfs (False) operations + padding: stuff to dump past the normal data blobs provided (optional) + + Raises: + TestError: if arguments are inconsistent or something goes wrong. + """ + sigs_len = len(sigs_data) if sigs_data else 0 + + # Do we need to generate a genuine signatures blob? + do_generate_sigs_data = sigs_data is None and privkey_file_name + + if do_generate_sigs_data: + # First, sign some arbitrary data to obtain the size of a signature blob. + fake_sig = SignSha256('fake-payload-data', privkey_file_name) + fake_sigs_gen = SignaturesGenerator() + fake_sigs_gen.AddSig(1, fake_sig) + sigs_len = len(fake_sigs_gen.ToBinary()) + + # Update the payload with proper signature attributes. + self.SetSignatures(self.curr_offset, sigs_len) + + # Add a pseudo-operation to account for the signature blob, if requested. + if do_add_pseudo_operation: + if not self.block_size: + raise TestError('cannot add pseudo-operation without knowing the ' + 'payload block size') + self.AddOperation( + is_pseudo_in_kernel, common.OpType.REPLACE, + data_offset=self.curr_offset, data_length=sigs_len, + dst_extents=[(common.PSEUDO_EXTENT_MARKER, + (sigs_len + self.block_size - 1) / self.block_size)]) + + if do_generate_sigs_data: + # Once all payload fields are updated, dump and sign it. + temp_payload_file = cStringIO.StringIO() + self.WriteToFile(temp_payload_file, data_blobs=self.data_blobs) + sig = SignSha256(temp_payload_file.getvalue(), privkey_file_name) + sigs_gen = SignaturesGenerator() + sigs_gen.AddSig(1, sig) + sigs_data = sigs_gen.ToBinary() + assert len(sigs_data) == sigs_len, 'signature blob lengths mismatch' + + # Dump the whole thing, complete with data and signature blob, to a file. + self.WriteToFile(file_obj, data_blobs=self.data_blobs, sigs_data=sigs_data, + padding=padding)
diff --git a/update_engine/scripts/update_payload/update-payload-key.pub.pem b/update_engine/scripts/update_payload/update-payload-key.pub.pem new file mode 100644 index 0000000..7ac369f --- /dev/null +++ b/update_engine/scripts/update_payload/update-payload-key.pub.pem
@@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1Bg9BnjWhX3jJyECeXqF +O28nkYTF1NHWLlFHgzAGg+ysva22BL3S5LlsNejnYVg/xzx3izvAQyOF3I1TJVOy +2fH1DoZOWyKuckMyUrFQbO6OV1VIvPUPKckHadWcXSsHj2lBdDPH9xRDEBsXeztf +nAGBD8GlAyTU7iH+Bf+xzyK9k4BmITf4Nx4xWhRZ6gm2Fc2SEP3x5N5fohkLv5ZP +kFr0fj5wUK+0XF95rkGFBLIq2XACS3dmxMFToFl1HMM1HonUg9TAH+3dVH93zue1 +y81mkTuGnNX+zYya5ov2kD8zW1V10iTOSJfOlho5T8FpKbG37o3yYcUiyMHKO1Iv +PQIDAQAB +-----END PUBLIC KEY-----
diff --git a/update_engine/scripts/update_payload/update_metadata_pb2.py b/update_engine/scripts/update_payload/update_metadata_pb2.py new file mode 100644 index 0000000..46c475e --- /dev/null +++ b/update_engine/scripts/update_payload/update_metadata_pb2.py
@@ -0,0 +1,620 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: update_metadata.proto + +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import descriptor_pb2 +# @@protoc_insertion_point(imports) + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='update_metadata.proto', + package='chromeos_update_engine', + serialized_pb='\n\x15update_metadata.proto\x12\x16\x63hromeos_update_engine\"1\n\x06\x45xtent\x12\x13\n\x0bstart_block\x18\x01 \x01(\x04\x12\x12\n\nnum_blocks\x18\x02 \x01(\x04\"z\n\nSignatures\x12@\n\nsignatures\x18\x01 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x1a*\n\tSignature\x12\x0f\n\x07version\x18\x01 \x01(\r\x12\x0c\n\x04\x64\x61ta\x18\x02 \x01(\x0c\"+\n\rPartitionInfo\x12\x0c\n\x04size\x18\x01 \x01(\x04\x12\x0c\n\x04hash\x18\x02 \x01(\x0c\"w\n\tImageInfo\x12\r\n\x05\x62oard\x18\x01 \x01(\t\x12\x0b\n\x03key\x18\x02 \x01(\t\x12\x0f\n\x07\x63hannel\x18\x03 \x01(\t\x12\x0f\n\x07version\x18\x04 \x01(\t\x12\x15\n\rbuild_channel\x18\x05 \x01(\t\x12\x15\n\rbuild_version\x18\x06 \x01(\t\"\xd2\x03\n\x10InstallOperation\x12;\n\x04type\x18\x01 \x02(\x0e\x32-.chromeos_update_engine.InstallOperation.Type\x12\x13\n\x0b\x64\x61ta_offset\x18\x02 \x01(\r\x12\x13\n\x0b\x64\x61ta_length\x18\x03 \x01(\r\x12\x33\n\x0bsrc_extents\x18\x04 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\nsrc_length\x18\x05 \x01(\x04\x12\x33\n\x0b\x64st_extents\x18\x06 \x03(\x0b\x32\x1e.chromeos_update_engine.Extent\x12\x12\n\ndst_length\x18\x07 \x01(\x04\x12\x18\n\x10\x64\x61ta_sha256_hash\x18\x08 \x01(\x0c\x12\x17\n\x0fsrc_sha256_hash\x18\t \x01(\x0c\"\x91\x01\n\x04Type\x12\x0b\n\x07REPLACE\x10\x00\x12\x0e\n\nREPLACE_BZ\x10\x01\x12\x08\n\x04MOVE\x10\x02\x12\n\n\x06\x42SDIFF\x10\x03\x12\x0f\n\x0bSOURCE_COPY\x10\x04\x12\x11\n\rSOURCE_BSDIFF\x10\x05\x12\x08\n\x04ZERO\x10\x06\x12\x0b\n\x07\x44ISCARD\x10\x07\x12\x0e\n\nREPLACE_XZ\x10\x08\x12\x0b\n\x07IMGDIFF\x10\t\"\x88\x03\n\x0fPartitionUpdate\x12\x16\n\x0epartition_name\x18\x01 \x02(\t\x12\x17\n\x0frun_postinstall\x18\x02 \x01(\x08\x12\x18\n\x10postinstall_path\x18\x03 \x01(\t\x12\x17\n\x0f\x66ilesystem_type\x18\x04 \x01(\t\x12M\n\x17new_partition_signature\x18\x05 \x03(\x0b\x32,.chromeos_update_engine.Signatures.Signature\x12\x41\n\x12old_partition_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12\x41\n\x12new_partition_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12<\n\noperations\x18\x08 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\"\xc4\x05\n\x14\x44\x65ltaArchiveManifest\x12\x44\n\x12install_operations\x18\x01 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\x12K\n\x19kernel_install_operations\x18\x02 \x03(\x0b\x32(.chromeos_update_engine.InstallOperation\x12\x18\n\nblock_size\x18\x03 \x01(\r:\x04\x34\x30\x39\x36\x12\x19\n\x11signatures_offset\x18\x04 \x01(\x04\x12\x17\n\x0fsignatures_size\x18\x05 \x01(\x04\x12>\n\x0fold_kernel_info\x18\x06 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12>\n\x0fnew_kernel_info\x18\x07 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12>\n\x0fold_rootfs_info\x18\x08 \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12>\n\x0fnew_rootfs_info\x18\t \x01(\x0b\x32%.chromeos_update_engine.PartitionInfo\x12\x39\n\x0eold_image_info\x18\n \x01(\x0b\x32!.chromeos_update_engine.ImageInfo\x12\x39\n\x0enew_image_info\x18\x0b \x01(\x0b\x32!.chromeos_update_engine.ImageInfo\x12\x18\n\rminor_version\x18\x0c \x01(\r:\x01\x30\x12;\n\npartitions\x18\r \x03(\x0b\x32\'.chromeos_update_engine.PartitionUpdateB\x02H\x03') + + + +_INSTALLOPERATION_TYPE = _descriptor.EnumDescriptor( + name='Type', + full_name='chromeos_update_engine.InstallOperation.Type', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='REPLACE', index=0, number=0, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='REPLACE_BZ', index=1, number=1, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MOVE', index=2, number=2, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='BSDIFF', index=3, number=3, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SOURCE_COPY', index=4, number=4, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SOURCE_BSDIFF', index=5, number=5, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='ZERO', index=6, number=6, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='DISCARD', index=7, number=7, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='REPLACE_XZ', index=8, number=8, + options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='IMGDIFF', index=9, number=9, + options=None, + type=None), + ], + containing_type=None, + options=None, + serialized_start=712, + serialized_end=857, +) + + +_EXTENT = _descriptor.Descriptor( + name='Extent', + full_name='chromeos_update_engine.Extent', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='start_block', full_name='chromeos_update_engine.Extent.start_block', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='num_blocks', full_name='chromeos_update_engine.Extent.num_blocks', index=1, + number=2, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=49, + serialized_end=98, +) + + +_SIGNATURES_SIGNATURE = _descriptor.Descriptor( + name='Signature', + full_name='chromeos_update_engine.Signatures.Signature', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='version', full_name='chromeos_update_engine.Signatures.Signature.version', index=0, + number=1, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='data', full_name='chromeos_update_engine.Signatures.Signature.data', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=180, + serialized_end=222, +) + +_SIGNATURES = _descriptor.Descriptor( + name='Signatures', + full_name='chromeos_update_engine.Signatures', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='signatures', full_name='chromeos_update_engine.Signatures.signatures', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[_SIGNATURES_SIGNATURE, ], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=100, + serialized_end=222, +) + + +_PARTITIONINFO = _descriptor.Descriptor( + name='PartitionInfo', + full_name='chromeos_update_engine.PartitionInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='size', full_name='chromeos_update_engine.PartitionInfo.size', index=0, + number=1, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='hash', full_name='chromeos_update_engine.PartitionInfo.hash', index=1, + number=2, type=12, cpp_type=9, label=1, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=224, + serialized_end=267, +) + + +_IMAGEINFO = _descriptor.Descriptor( + name='ImageInfo', + full_name='chromeos_update_engine.ImageInfo', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='board', full_name='chromeos_update_engine.ImageInfo.board', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='key', full_name='chromeos_update_engine.ImageInfo.key', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='channel', full_name='chromeos_update_engine.ImageInfo.channel', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='version', full_name='chromeos_update_engine.ImageInfo.version', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='build_channel', full_name='chromeos_update_engine.ImageInfo.build_channel', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='build_version', full_name='chromeos_update_engine.ImageInfo.build_version', index=5, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=269, + serialized_end=388, +) + + +_INSTALLOPERATION = _descriptor.Descriptor( + name='InstallOperation', + full_name='chromeos_update_engine.InstallOperation', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='type', full_name='chromeos_update_engine.InstallOperation.type', index=0, + number=1, type=14, cpp_type=8, label=2, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='data_offset', full_name='chromeos_update_engine.InstallOperation.data_offset', index=1, + number=2, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='data_length', full_name='chromeos_update_engine.InstallOperation.data_length', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='src_extents', full_name='chromeos_update_engine.InstallOperation.src_extents', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='src_length', full_name='chromeos_update_engine.InstallOperation.src_length', index=4, + number=5, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='dst_extents', full_name='chromeos_update_engine.InstallOperation.dst_extents', index=5, + number=6, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='dst_length', full_name='chromeos_update_engine.InstallOperation.dst_length', index=6, + number=7, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='data_sha256_hash', full_name='chromeos_update_engine.InstallOperation.data_sha256_hash', index=7, + number=8, type=12, cpp_type=9, label=1, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='src_sha256_hash', full_name='chromeos_update_engine.InstallOperation.src_sha256_hash', index=8, + number=9, type=12, cpp_type=9, label=1, + has_default_value=False, default_value="", + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + _INSTALLOPERATION_TYPE, + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=391, + serialized_end=857, +) + + +_PARTITIONUPDATE = _descriptor.Descriptor( + name='PartitionUpdate', + full_name='chromeos_update_engine.PartitionUpdate', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='partition_name', full_name='chromeos_update_engine.PartitionUpdate.partition_name', index=0, + number=1, type=9, cpp_type=9, label=2, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='run_postinstall', full_name='chromeos_update_engine.PartitionUpdate.run_postinstall', index=1, + number=2, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='postinstall_path', full_name='chromeos_update_engine.PartitionUpdate.postinstall_path', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='filesystem_type', full_name='chromeos_update_engine.PartitionUpdate.filesystem_type', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=unicode("", "utf-8"), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='new_partition_signature', full_name='chromeos_update_engine.PartitionUpdate.new_partition_signature', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='old_partition_info', full_name='chromeos_update_engine.PartitionUpdate.old_partition_info', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='new_partition_info', full_name='chromeos_update_engine.PartitionUpdate.new_partition_info', index=6, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='operations', full_name='chromeos_update_engine.PartitionUpdate.operations', index=7, + number=8, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=860, + serialized_end=1252, +) + + +_DELTAARCHIVEMANIFEST = _descriptor.Descriptor( + name='DeltaArchiveManifest', + full_name='chromeos_update_engine.DeltaArchiveManifest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='install_operations', full_name='chromeos_update_engine.DeltaArchiveManifest.install_operations', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='kernel_install_operations', full_name='chromeos_update_engine.DeltaArchiveManifest.kernel_install_operations', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='block_size', full_name='chromeos_update_engine.DeltaArchiveManifest.block_size', index=2, + number=3, type=13, cpp_type=3, label=1, + has_default_value=True, default_value=4096, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='signatures_offset', full_name='chromeos_update_engine.DeltaArchiveManifest.signatures_offset', index=3, + number=4, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='signatures_size', full_name='chromeos_update_engine.DeltaArchiveManifest.signatures_size', index=4, + number=5, type=4, cpp_type=4, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='old_kernel_info', full_name='chromeos_update_engine.DeltaArchiveManifest.old_kernel_info', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='new_kernel_info', full_name='chromeos_update_engine.DeltaArchiveManifest.new_kernel_info', index=6, + number=7, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='old_rootfs_info', full_name='chromeos_update_engine.DeltaArchiveManifest.old_rootfs_info', index=7, + number=8, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='new_rootfs_info', full_name='chromeos_update_engine.DeltaArchiveManifest.new_rootfs_info', index=8, + number=9, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='old_image_info', full_name='chromeos_update_engine.DeltaArchiveManifest.old_image_info', index=9, + number=10, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='new_image_info', full_name='chromeos_update_engine.DeltaArchiveManifest.new_image_info', index=10, + number=11, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='minor_version', full_name='chromeos_update_engine.DeltaArchiveManifest.minor_version', index=11, + number=12, type=13, cpp_type=3, label=1, + has_default_value=True, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + _descriptor.FieldDescriptor( + name='partitions', full_name='chromeos_update_engine.DeltaArchiveManifest.partitions', index=12, + number=13, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + options=None), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + options=None, + is_extendable=False, + extension_ranges=[], + serialized_start=1255, + serialized_end=1963, +) + +_SIGNATURES_SIGNATURE.containing_type = _SIGNATURES; +_SIGNATURES.fields_by_name['signatures'].message_type = _SIGNATURES_SIGNATURE +_INSTALLOPERATION.fields_by_name['type'].enum_type = _INSTALLOPERATION_TYPE +_INSTALLOPERATION.fields_by_name['src_extents'].message_type = _EXTENT +_INSTALLOPERATION.fields_by_name['dst_extents'].message_type = _EXTENT +_INSTALLOPERATION_TYPE.containing_type = _INSTALLOPERATION; +_PARTITIONUPDATE.fields_by_name['new_partition_signature'].message_type = _SIGNATURES_SIGNATURE +_PARTITIONUPDATE.fields_by_name['old_partition_info'].message_type = _PARTITIONINFO +_PARTITIONUPDATE.fields_by_name['new_partition_info'].message_type = _PARTITIONINFO +_PARTITIONUPDATE.fields_by_name['operations'].message_type = _INSTALLOPERATION +_DELTAARCHIVEMANIFEST.fields_by_name['install_operations'].message_type = _INSTALLOPERATION +_DELTAARCHIVEMANIFEST.fields_by_name['kernel_install_operations'].message_type = _INSTALLOPERATION +_DELTAARCHIVEMANIFEST.fields_by_name['old_kernel_info'].message_type = _PARTITIONINFO +_DELTAARCHIVEMANIFEST.fields_by_name['new_kernel_info'].message_type = _PARTITIONINFO +_DELTAARCHIVEMANIFEST.fields_by_name['old_rootfs_info'].message_type = _PARTITIONINFO +_DELTAARCHIVEMANIFEST.fields_by_name['new_rootfs_info'].message_type = _PARTITIONINFO +_DELTAARCHIVEMANIFEST.fields_by_name['old_image_info'].message_type = _IMAGEINFO +_DELTAARCHIVEMANIFEST.fields_by_name['new_image_info'].message_type = _IMAGEINFO +_DELTAARCHIVEMANIFEST.fields_by_name['partitions'].message_type = _PARTITIONUPDATE +DESCRIPTOR.message_types_by_name['Extent'] = _EXTENT +DESCRIPTOR.message_types_by_name['Signatures'] = _SIGNATURES +DESCRIPTOR.message_types_by_name['PartitionInfo'] = _PARTITIONINFO +DESCRIPTOR.message_types_by_name['ImageInfo'] = _IMAGEINFO +DESCRIPTOR.message_types_by_name['InstallOperation'] = _INSTALLOPERATION +DESCRIPTOR.message_types_by_name['PartitionUpdate'] = _PARTITIONUPDATE +DESCRIPTOR.message_types_by_name['DeltaArchiveManifest'] = _DELTAARCHIVEMANIFEST + +class Extent(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType + DESCRIPTOR = _EXTENT + + # @@protoc_insertion_point(class_scope:chromeos_update_engine.Extent) + +class Signatures(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType + + class Signature(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType + DESCRIPTOR = _SIGNATURES_SIGNATURE + + # @@protoc_insertion_point(class_scope:chromeos_update_engine.Signatures.Signature) + DESCRIPTOR = _SIGNATURES + + # @@protoc_insertion_point(class_scope:chromeos_update_engine.Signatures) + +class PartitionInfo(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType + DESCRIPTOR = _PARTITIONINFO + + # @@protoc_insertion_point(class_scope:chromeos_update_engine.PartitionInfo) + +class ImageInfo(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType + DESCRIPTOR = _IMAGEINFO + + # @@protoc_insertion_point(class_scope:chromeos_update_engine.ImageInfo) + +class InstallOperation(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType + DESCRIPTOR = _INSTALLOPERATION + + # @@protoc_insertion_point(class_scope:chromeos_update_engine.InstallOperation) + +class PartitionUpdate(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType + DESCRIPTOR = _PARTITIONUPDATE + + # @@protoc_insertion_point(class_scope:chromeos_update_engine.PartitionUpdate) + +class DeltaArchiveManifest(_message.Message): + __metaclass__ = _reflection.GeneratedProtocolMessageType + DESCRIPTOR = _DELTAARCHIVEMANIFEST + + # @@protoc_insertion_point(class_scope:chromeos_update_engine.DeltaArchiveManifest) + + +DESCRIPTOR.has_options = True +DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), 'H\003') +# @@protoc_insertion_point(module_scope)
diff --git a/update_engine/service_delegate_android_interface.h b/update_engine/service_delegate_android_interface.h new file mode 100644 index 0000000..7dae40f --- /dev/null +++ b/update_engine/service_delegate_android_interface.h
@@ -0,0 +1,79 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_SERVICE_DELEGATE_ANDROID_INTERFACE_H_ +#define UPDATE_ENGINE_SERVICE_DELEGATE_ANDROID_INTERFACE_H_ + +#include <inttypes.h> + +#include <string> +#include <vector> + +#include <brillo/errors/error.h> + +namespace chromeos_update_engine { + +// This class defines the interface exposed by the Android version of the +// daemon service. This interface only includes the method calls that such +// daemon exposes. For asynchronous events initiated by a class implementing +// this interface see the ServiceObserverInterface class. +class ServiceDelegateAndroidInterface { + public: + virtual ~ServiceDelegateAndroidInterface() = default; + + // Start an update attempt to download an apply the provided |payload_url| if + // no other update is running. The extra |key_value_pair_headers| will be + // included when fetching the payload. Returns whether the update was started + // successfully, which means that no other update was running and the passed + // parameters were correct, but not necessarily that the update finished + // correctly. + virtual bool ApplyPayload( + const std::string& payload_url, + int64_t payload_offset, + int64_t payload_size, + const std::vector<std::string>& key_value_pair_headers, + brillo::ErrorPtr* error) = 0; + + // Suspend an ongoing update. Returns true if there was an update ongoing and + // it was suspended. In case of failure, it returns false and sets |error| + // accordingly. + virtual bool SuspendUpdate(brillo::ErrorPtr* error) = 0; + + // Resumes an update suspended with SuspendUpdate(). The update can't be + // suspended after it finished and this method will fail in that case. + // Returns whether the resume operation was successful, which only implies + // that there was a suspended update. In case of error, returns false and sets + // |error| accordingly. + virtual bool ResumeUpdate(brillo::ErrorPtr* error) = 0; + + // Cancel the ongoing update. The update could be running or suspended, but it + // can't be canceled after it was done. In case of error, returns false and + // sets |error| accordingly. + virtual bool CancelUpdate(brillo::ErrorPtr* error) = 0; + + // Reset the already applied update back to an idle state. This method can + // only be called when no update attempt is going on, and it will reset the + // status back to idle, deleting the currently applied update if any. In case + // of error, returns false and sets |error| accordingly. + virtual bool ResetStatus(brillo::ErrorPtr* error) = 0; + + protected: + ServiceDelegateAndroidInterface() = default; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_SERVICE_DELEGATE_ANDROID_INTERFACE_H_
diff --git a/update_engine/service_observer_interface.h b/update_engine/service_observer_interface.h new file mode 100644 index 0000000..75a739f --- /dev/null +++ b/update_engine/service_observer_interface.h
@@ -0,0 +1,52 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_SERVICE_OBSERVER_INTERFACE_H_ +#define UPDATE_ENGINE_SERVICE_OBSERVER_INTERFACE_H_ + +#include <memory> +#include <string> + +#include "update_engine/client_library/include/update_engine/update_status.h" +#include "update_engine/common/error_code.h" + +namespace chromeos_update_engine { + +class ServiceObserverInterface { + public: + virtual ~ServiceObserverInterface() = default; + + // Called whenever the value of these parameters changes. For |progress| + // value changes, this method will be called only if it changes significantly. + virtual void SendStatusUpdate(int64_t last_checked_time, + double progress, + update_engine::UpdateStatus status, + const std::string& new_version, + int64_t new_size) = 0; + + // Called whenever an update attempt is completed. + virtual void SendPayloadApplicationComplete(ErrorCode error_code) = 0; + + // Called whenever the channel we are tracking changes. + virtual void SendChannelChangeUpdate(const std::string& tracking_channel) = 0; + + protected: + ServiceObserverInterface() = default; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_SERVICE_OBSERVER_INTERFACE_H_
diff --git a/update_engine/shill_proxy.cc b/update_engine/shill_proxy.cc new file mode 100644 index 0000000..d398bba --- /dev/null +++ b/update_engine/shill_proxy.cc
@@ -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. +// + +#include "update_engine/shill_proxy.h" + +#include "update_engine/dbus_connection.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() + : bus_(DBusConnection::Get()->GetDBus()), + manager_proxy_(new ManagerProxy(bus_)) {} + +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/update_engine/shill_proxy.h b/update_engine/shill_proxy.h new file mode 100644 index 0000000..4b466c9 --- /dev/null +++ b/update_engine/shill_proxy.h
@@ -0,0 +1,54 @@ +// +// 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: + ShillProxy(); + ~ShillProxy() override = default; + + // 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/update_engine/shill_proxy_interface.h b/update_engine/shill_proxy_interface.h new file mode 100644 index 0000000..5f6b44e --- /dev/null +++ b/update_engine/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/update_engine/sideload_main.cc b/update_engine/sideload_main.cc new file mode 100644 index 0000000..d02af0e --- /dev/null +++ b/update_engine/sideload_main.cc
@@ -0,0 +1,226 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <xz.h> + +#include <string> +#include <vector> + +#include <base/command_line.h> +#include <base/logging.h> +#include <base/strings/string_split.h> +#include <base/strings/stringprintf.h> +#include <brillo/asynchronous_signal_handler.h> +#include <brillo/flag_helper.h> +#include <brillo/make_unique_ptr.h> +#include <brillo/message_loops/base_message_loop.h> +#include <brillo/streams/file_stream.h> +#include <brillo/streams/stream.h> + +#include "update_engine/common/boot_control.h" +#include "update_engine/common/error_code_utils.h" +#include "update_engine/common/hardware.h" +#include "update_engine/common/prefs.h" +#include "update_engine/common/subprocess.h" +#include "update_engine/common/terminator.h" +#include "update_engine/common/utils.h" +#include "update_engine/update_attempter_android.h" + +using std::string; +using std::vector; +using update_engine::UpdateStatus; + +namespace { +// The root directory used for temporary files in update_engine_sideload. +const char kSideloadRootTempDir[] = "/tmp/update_engine_sideload"; +} // namespace + +namespace chromeos_update_engine { +namespace { + +void SetupLogging() { + string log_file; + logging::LoggingSettings log_settings; + log_settings.lock_log = logging::DONT_LOCK_LOG_FILE; + log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE; + log_settings.log_file = nullptr; + log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; + + logging::InitLogging(log_settings); +} + +class SideloadDaemonState : public DaemonStateInterface, + public ServiceObserverInterface { + public: + explicit SideloadDaemonState(brillo::StreamPtr status_stream) + : status_stream_(std::move(status_stream)) { + // Add this class as the only observer. + observers_.insert(this); + } + ~SideloadDaemonState() override = default; + + // DaemonStateInterface overrides. + bool StartUpdater() override { return true; } + void AddObserver(ServiceObserverInterface* observer) override {} + void RemoveObserver(ServiceObserverInterface* observer) override {} + const std::set<ServiceObserverInterface*>& service_observers() override { + return observers_; + } + + // ServiceObserverInterface overrides. + void SendStatusUpdate(int64_t last_checked_time, + double progress, + UpdateStatus status, + const string& new_version, + int64_t new_size) override { + if (status_ != status && (status == UpdateStatus::DOWNLOADING || + status == UpdateStatus::FINALIZING)) { + // Split the progress bar in two parts for the two stages DOWNLOADING and + // FINALIZING. + ReportStatus(base::StringPrintf( + "ui_print Step %d/2", status == UpdateStatus::DOWNLOADING ? 1 : 2)); + ReportStatus(base::StringPrintf("progress 0.5 0")); + } + if (status_ != status || fabs(progress - progress_) > 0.005) { + ReportStatus(base::StringPrintf("set_progress %.lf", progress)); + } + progress_ = progress; + status_ = status; + } + + void SendPayloadApplicationComplete(ErrorCode error_code) override { + if (error_code != ErrorCode::kSuccess) { + ReportStatus( + base::StringPrintf("ui_print Error applying update: %d (%s)", + error_code, + utils::ErrorCodeToString(error_code).c_str())); + } + error_code_ = error_code; + brillo::MessageLoop::current()->BreakLoop(); + } + + void SendChannelChangeUpdate(const string& tracking_channel) override {} + + // Getters. + UpdateStatus status() { return status_; } + ErrorCode error_code() { return error_code_; } + + private: + // Report a status message in the status_stream_, if any. These messages + // should conform to the specification defined in the Android recovery. + void ReportStatus(const string& message) { + if (!status_stream_) + return; + string status_line = message + "\n"; + status_stream_->WriteAllBlocking( + status_line.data(), status_line.size(), nullptr); + } + + std::set<ServiceObserverInterface*> observers_; + brillo::StreamPtr status_stream_; + + // The last status and error code reported. + UpdateStatus status_{UpdateStatus::IDLE}; + ErrorCode error_code_{ErrorCode::kSuccess}; + double progress_{-1.}; +}; + +// Apply an update payload directly from the given payload URI. +bool ApplyUpdatePayload(const string& payload, + int64_t payload_offset, + int64_t payload_size, + const vector<string>& headers, + int64_t status_fd) { + brillo::BaseMessageLoop loop; + loop.SetAsCurrent(); + + // Setup the subprocess handler. + brillo::AsynchronousSignalHandler handler; + handler.Init(); + Subprocess subprocess; + subprocess.Init(&handler); + + SideloadDaemonState sideload_daemon_state( + brillo::FileStream::FromFileDescriptor(status_fd, true, nullptr)); + + // During the sideload we don't access the prefs persisted on disk but instead + // use a temporary memory storage. + MemoryPrefs prefs; + + std::unique_ptr<BootControlInterface> boot_control = + boot_control::CreateBootControl(); + if (!boot_control) { + LOG(ERROR) << "Error initializing the BootControlInterface."; + return false; + } + + std::unique_ptr<HardwareInterface> hardware = hardware::CreateHardware(); + if (!hardware) { + LOG(ERROR) << "Error initializing the HardwareInterface."; + return false; + } + + UpdateAttempterAndroid update_attempter( + &sideload_daemon_state, &prefs, boot_control.get(), hardware.get()); + update_attempter.Init(); + + TEST_AND_RETURN_FALSE(update_attempter.ApplyPayload( + payload, payload_offset, payload_size, headers, nullptr)); + + loop.Run(); + return sideload_daemon_state.status() == UpdateStatus::UPDATED_NEED_REBOOT; +} + +} // namespace +} // namespace chromeos_update_engine + +int main(int argc, char** argv) { + DEFINE_string(payload, + "file:///data/payload.bin", + "The URI to the update payload to use."); + DEFINE_int64( + offset, 0, "The offset in the payload where the CrAU update starts. "); + DEFINE_int64(size, + 0, + "The size of the CrAU part of the payload. If 0 is passed, it " + "will be autodetected."); + DEFINE_string(headers, + "", + "A list of key-value pairs, one element of the list per line."); + DEFINE_int64(status_fd, -1, "A file descriptor to notify the update status."); + + chromeos_update_engine::Terminator::Init(); + chromeos_update_engine::SetupLogging(); + brillo::FlagHelper::Init(argc, argv, "Update Engine Sideload"); + + LOG(INFO) << "Update Engine Sideloading starting"; + + // xz-embedded requires to initialize its CRC-32 table once on startup. + xz_crc32_init(); + + // When called from recovery, /data is not accessible, so we need to use + // /tmp for temporary files. + chromeos_update_engine::utils::SetRootTempDir(kSideloadRootTempDir); + + vector<string> headers = base::SplitString( + FLAGS_headers, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + + if (!chromeos_update_engine::ApplyUpdatePayload( + FLAGS_payload, FLAGS_offset, FLAGS_size, headers, FLAGS_status_fd)) + return 1; + + return 0; +}
diff --git a/update_engine/system_state.h b/update_engine/system_state.h new file mode 100644 index 0000000..4d040ec --- /dev/null +++ b/update_engine/system_state.h
@@ -0,0 +1,121 @@ +// +// 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_ + +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 PowerManagerInterface; +class PrefsInterface; +class UpdateAttempter; +class WeaveServiceInterface; + +// 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 WeaveServiceInterface class or nullptr if none. + virtual WeaveServiceInterface* weave_service() = 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; + + // Gets the power manager object. Mocked during test. + virtual PowerManagerInterface* power_manager() = 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/update_engine/tar_bunzip2.gypi b/update_engine/tar_bunzip2.gypi new file mode 100644 index 0000000..8c6614a --- /dev/null +++ b/update_engine/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/update_engine/test_http_server.cc b/update_engine/test_http_server.cc new file mode 100644 index 0000000..2955e79 --- /dev/null +++ b/update_engine/test_http_server.cc
@@ -0,0 +1,647 @@ +// +// 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 { + string raw_headers; + string host; + string url; + off_t start_offset{0}; + off_t end_offset{0}; // non-inclusive, zero indicates unspecified. + HttpResponseCode return_code{kHttpResponseOk}; +}; + +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, base::CompareCase::SENSITIVE)); + + LOG(INFO) << "got headers:\n--8<------8<------8<------8<----\n" + << headers + << "\n--8<------8<------8<------8<----"; + request->raw_headers = headers; + + // 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::SplitString(lines[0], base::kWhitespaceASCII, + base::KEEP_WHITESPACE, + base::SPLIT_WANT_NONEMPTY); + 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++) { + terms = base::SplitString(lines[i], base::kWhitespaceASCII, + base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + + 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::StartsWith(range, "bytes=", base::CompareCase::SENSITIVE) && + 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); + } +} + +// Returns a valid response echoing in the body of the response all the headers +// sent by the client. +void HandleEchoHeaders(int fd, const HttpRequest& request) { + WriteHeaders(fd, 0, request.raw_headers.size(), kHttpResponseOk); + WriteString(fd, request.raw_headers); +} + +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 preceding slash. + terms = base::SplitString(url.substr(1), "/", base::KEEP_WHITESPACE, + base::SPLIT_WANT_ALL); + + // 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::StartsWith( + url, "/download/", base::CompareCase::SENSITIVE)) { + const UrlTerms terms(url, 2); + HandleGet(fd, request, terms.GetSizeT(1)); + } else if (base::StartsWith(url, "/flaky/", base::CompareCase::SENSITIVE)) { + 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::StartsWith(url, "/error-if-offset/", + base::CompareCase::SENSITIVE)) { + const UrlTerms terms(url, 3); + HandleErrorIfOffset(fd, request, terms.GetSizeT(1), terms.GetInt(2)); + } else if (url == "/echo-headers") { + HandleEchoHeaders(fd, request); + } 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/update_engine/test_subprocess.cc b/update_engine/test_subprocess.cc new file mode 100644 index 0000000..c7f4a37 --- /dev/null +++ b/update_engine/test_subprocess.cc
@@ -0,0 +1,59 @@ +// +// 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 is a simple program used to test interaction with update_engine when +// executing other programs. This program receives pre-programmed actions in the +// command line and executes them in order. + +#include <errno.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <string> + +#define EX_USAGE_ERROR 100 + +void usage(const char* program, const char* error) { + if (error) + fprintf(stderr, "ERROR: %s\n", error); + fprintf(stderr, "Usage: %s <cmd> [args..]\n", program); + exit(EX_USAGE_ERROR); +} + +int main(int argc, char** argv, char** envp) { + if (argc < 2) + usage(argv[0], "No command passed"); + + std::string cmd(argv[1]); + if (cmd == "fstat") { + // Call fstat on the passed file descriptor number + if (argc < 3) + usage(argv[0], "No fd passed to fstat"); + int fd = atoi(argv[2]); + struct stat buf; + int rc = fstat(fd, &buf); + if (rc < 0) { + int ret = errno; + perror("fstat"); + return ret; + } + return 0; + } + + usage(argv[0], "Unknown command"); +}
diff --git a/update_engine/testrunner.cc b/update_engine/testrunner.cc new file mode 100644 index 0000000..934ea91 --- /dev/null +++ b/update_engine/testrunner.cc
@@ -0,0 +1,64 @@ +// +// 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 <string> + +#include <xz.h> + +#include <base/at_exit.h> +#include <base/command_line.h> +#include <base/environment.h> +#include <brillo/test_helpers.h> +#include <gtest/gtest.h> + +#include "update_engine/common/terminator.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/payload_generator/xz.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(); + // The LZMA SDK-based Xz compressor used in the payload generation requires + // this one-time initialization. + chromeos_update_engine::XzCompressInit(); + // 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); + // In Android bsdiff is located in update_engine_unittests, add it to PATH. +#ifdef __ANDROID__ + std::unique_ptr<base::Environment> env(base::Environment::Create()); + std::string path_env; + CHECK(env->GetVar("PATH", &path_env)); + path_env += + ":" + chromeos_update_engine::test_utils::GetBuildArtifactsPath().value(); + CHECK(env->SetVar("PATH", path_env)); +#endif + 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/update_engine/unittest_key.pem b/update_engine/unittest_key.pem new file mode 100644 index 0000000..224d3c3 --- /dev/null +++ b/update_engine/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/update_engine/unittest_key2.pem b/update_engine/unittest_key2.pem new file mode 100644 index 0000000..d1f9a78 --- /dev/null +++ b/update_engine/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_engine/update_attempter.cc b/update_engine/update_attempter.cc new file mode 100644 index 0000000..8afc395 --- /dev/null +++ b/update_engine/update_attempter.cc
@@ -0,0 +1,1592 @@ +// +// 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/errors/error_codes.h> +#include <brillo/make_unique_ptr.h> +#include <brillo/message_loops/message_loop.h> +#include <policy/device_policy.h> +#include <policy/libpolicy.h> +#include <update_engine/dbus-constants.h> + +#include "update_engine/certificate_checker.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/clock_interface.h" +#include "update_engine/common/constants.h" +#include "update_engine/common/hardware_interface.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/libcurl_http_fetcher.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/power_manager_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; + +// Minimum threshold to broadcast an status update in progress and time. +const double kBroadcastThresholdProgress = 0.01; // 1% +const int kBroadcastThresholdSeconds = 10; + +// 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) + : processor_(new ActionProcessor()), + system_state_(system_state), +#if USE_LIBCROS + cert_checker_(cert_checker), + chrome_proxy_resolver_(libcros_proxy) { +#else + cert_checker_(cert_checker) { +#endif // USE_LIBCROS +} + +UpdateAttempter::~UpdateAttempter() { + // CertificateChecker might not be initialized in unittests. + if (cert_checker_) + cert_checker_->SetObserver(nullptr); + // 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; + +#if USE_LIBCROS + chrome_proxy_resolver_.Init(); +#endif // USE_LIBCROS +} + +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; + } + // Notify observers the target channel change. + BroadcastChannel(); + + // 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()->IsOOBEEnabled() && + !system_state_->hardware()->IsOOBEComplete(nullptr)) { + LOG(INFO) << "Scattering disabled since OOBE is enabled but 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(), + system_state_->hardware())); + postinstall_runner_action->set_delegate(this); + 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<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_->boot_control(), + system_state_->hardware(), + 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> filesystem_verifier_action( + new FilesystemVerifierAction()); + 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>(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>(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(), + download_action.get()); + BondActions(download_action.get(), + filesystem_verifier_action.get()); + BuildPostInstallActions(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 (system_state_->power_manager()->RequestReboot()) + 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::RebootDirectly() { + vector<string> command; + command.push_back("/sbin/shutdown"); + command.push_back("-r"); + command.push_back("now"); + LOG(INFO) << "Running \"" << base::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."; + + Update(forced_app_version_, forced_omaha_url_, params.target_channel, + params.target_version_prefix, false, params.is_interactive); + // Always clear the forced app_version and omaha_url after an update attempt + // so the next update uses the defaults. + forced_app_version_.clear(); + forced_omaha_url_.clear(); + } 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. + cpu_limiter_.StopLimiter(); + + 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. + cpu_limiter_.StopLimiter(); + 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(); + cpu_limiter_.StartLimiter(); + 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 = 0; + if (total) + progress = static_cast<double>(bytes_received) / static_cast<double>(total); + if (status_ != UpdateStatus::DOWNLOADING || bytes_received == total) { + download_progress_ = progress; + SetStatusAndNotify(UpdateStatus::DOWNLOADING); + } else { + ProgressUpdate(progress); + } +} + +void UpdateAttempter::DownloadComplete() { + system_state_->payload_state()->DownloadComplete(); +} + +bool UpdateAttempter::OnCheckForUpdates(brillo::ErrorPtr* error) { + CheckForUpdate( + "" /* app_version */, "" /* omaha_url */, true /* interactive */); + return true; +} + +bool UpdateAttempter::OnTrackChannel(const string& channel, + brillo::ErrorPtr* error) { + LOG(INFO) << "Setting destination channel to: " << channel; + string error_message; + if (!system_state_->request_params()->SetTargetChannel( + channel, false /* powerwash_allowed */, &error_message)) { + brillo::Error::AddTo(error, + FROM_HERE, + brillo::errors::dbus::kDomain, + "set_target_error", + error_message); + return false; + } + // Notify observers the target channel change. + BroadcastChannel(); + return true; +} + +bool UpdateAttempter::GetWeaveState(int64_t* last_checked_time, + double* progress, + UpdateStatus* update_status, + string* current_channel, + string* tracking_channel) { + *last_checked_time = last_checked_time_; + *progress = download_progress_; + *update_status = status_; + OmahaRequestParams* rp = system_state_->request_params(); + *current_channel = rp->current_channel(); + *tracking_channel = rp->target_channel(); + return true; +} + +void UpdateAttempter::ProgressUpdate(double progress) { + // Self throttle based on progress. Also send notifications if progress is + // too slow. + if (progress == 1.0 || + progress - download_progress_ >= kBroadcastThresholdProgress || + TimeTicks::Now() - last_notify_time_ >= + TimeDelta::FromSeconds(kBroadcastThresholdSeconds)) { + download_progress_ = progress; + BroadcastStatus(); + } +} + +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() { + for (const auto& observer : service_observers_) { + observer->SendStatusUpdate(last_checked_time_, + download_progress_, + status_, + new_version_, + new_payload_size_); + } + last_notify_time_ = TimeTicks::Now(); +} + +void UpdateAttempter::BroadcastChannel() { + for (const auto& observer : service_observers_) { + observer->SendChannelChangeUpdate( + system_state_->request_params()->target_channel()); + } +} + +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::ScheduleProcessingStart() { + LOG(INFO) << "Scheduling an action processor start."; + start_action_processor_ = false; + MessageLoop::current()->PostTask( + FROM_HERE, + Bind([](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(processor_.get()))); +} + +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; + int64_t manifest_signature_size = 0; + prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size); + prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size); + fetcher->AddRange(0, manifest_metadata_size + manifest_signature_size); + // If there're remaining unprocessed data blobs, fetch them. Be careful not + // to request data beyond the end of the payload to avoid 416 HTTP response + // error codes. + int64_t next_data_offset = 0; + prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset); + uint64_t resume_offset = + manifest_metadata_size + manifest_signature_size + next_data_offset; + if (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; + } + + if (system_state_->hardware()->AreDevFeaturesEnabled()) { + LOG(INFO) << "Developer features enabled; allowing custom update sources."; + return true; + } + + LOG(INFO) + << "Developer features disabled; disallowing custom update sources."; + return false; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/update_attempter.h b/update_engine/update_attempter.h new file mode 100644 index 0000000..104975c --- /dev/null +++ b/update_engine/update_attempter.h
@@ -0,0 +1,519 @@ +// +// 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 <set> +#include <string> +#include <utility> +#include <vector> + +#include <base/bind.h> +#include <base/time/time.h> +#include <gtest/gtest_prod.h> // for FRIEND_TEST + +#if USE_LIBCROS +#include "update_engine/chrome_browser_proxy_resolver.h" +#endif // USE_LIBCROS +#include "update_engine/certificate_checker.h" +#include "update_engine/client_library/include/update_engine/update_status.h" +#include "update_engine/common/action_processor.h" +#include "update_engine/common/cpu_limiter.h" +#include "update_engine/omaha_request_params.h" +#include "update_engine/omaha_response_handler_action.h" +#include "update_engine/payload_consumer/download_action.h" +#include "update_engine/payload_consumer/postinstall_runner_action.h" +#include "update_engine/proxy_resolver.h" +#include "update_engine/service_observer_interface.h" +#include "update_engine/system_state.h" +#include "update_engine/update_manager/policy.h" +#include "update_engine/update_manager/update_manager.h" +#include "update_engine/weave_service_interface.h" + +class MetricsLibraryInterface; + +namespace policy { +class PolicyProvider; +} + +namespace chromeos_update_engine { + +class LibCrosProxy; +class UpdateEngineAdaptor; + +class UpdateAttempter : public ActionProcessorDelegate, + public DownloadActionDelegate, + public CertificateChecker::Observer, + public WeaveServiceInterface::DelegateInterface, + public PostinstallRunnerAction::DelegateInterface { + public: + using UpdateStatus = update_engine::UpdateStatus; + static const int kMaxDeltaUpdateFailures; + + UpdateAttempter(SystemState* system_state, + CertificateChecker* cert_checker, + LibCrosProxy* libcros_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; + + // WeaveServiceInterface::DelegateInterface overrides. + bool OnCheckForUpdates(brillo::ErrorPtr* error) override; + bool OnTrackChannel(const std::string& channel, + brillo::ErrorPtr* error) override; + bool GetWeaveState(int64_t* last_checked_time, + double* progress, + UpdateStatus* update_status, + std::string* current_channel, + std::string* tracking_channel) override; + + // PostinstallRunnerAction::DelegateInterface + void ProgressUpdate(double progress) 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; } + + // 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 to all observers. + void BroadcastStatus(); + + // Broadcasts the current tracking channel to all observers. + void BroadcastChannel(); + + // 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(); + + // Add and remove a service observer. + void AddObserver(ServiceObserverInterface* observer) { + service_observers_.insert(observer); + } + void RemoveObserver(ServiceObserverInterface* observer) { + service_observers_.erase(observer); + } + + const std::set<ServiceObserverInterface*>& service_observers() { + return service_observers_; + } + + // Remove all the observers. + void ClearObservers() { service_observers_.clear(); } + + 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(); + + // 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() { +#if USE_LIBCROS + return obeying_proxies_ ? + reinterpret_cast<ProxyResolver*>(&chrome_proxy_resolver_) : + reinterpret_cast<ProxyResolver*>(&direct_proxy_resolver_); +#else + return &direct_proxy_resolver_; +#endif // USE_LIBCROS + } + + // 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(); + + // 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_; + + // The list of services observing changes in the updater. + std::set<ServiceObserverInterface*> service_observers_; + + // 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; + + // CPU limiter during the update. + CPULimiter cpu_limiter_; + + // 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_; +#if USE_LIBCROS + ChromeBrowserProxyResolver chrome_proxy_resolver_; +#endif // USE_LIBCROS + + // 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_; + + DISALLOW_COPY_AND_ASSIGN(UpdateAttempter); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_UPDATE_ATTEMPTER_H_
diff --git a/update_engine/update_attempter_android.cc b/update_engine/update_attempter_android.cc new file mode 100644 index 0000000..2de2667 --- /dev/null +++ b/update_engine/update_attempter_android.cc
@@ -0,0 +1,542 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/update_attempter_android.h" + +#include <algorithm> +#include <map> +#include <utility> + +#include <base/bind.h> +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <brillo/bind_lambda.h> +#include <brillo/message_loops/message_loop.h> +#include <brillo/strings/string_utils.h> + +#include "update_engine/common/constants.h" +#include "update_engine/common/file_fetcher.h" +#include "update_engine/common/multi_range_http_fetcher.h" +#include "update_engine/common/utils.h" +#include "update_engine/daemon_state_interface.h" +#include "update_engine/network_selector.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/update_status_utils.h" + +#ifndef _UE_SIDELOAD +// Do not include support for external HTTP(s) urls when building +// update_engine_sideload. +#include "update_engine/libcurl_http_fetcher.h" +#endif + +using base::Bind; +using base::TimeDelta; +using base::TimeTicks; +using std::shared_ptr; +using std::string; +using std::vector; + +namespace chromeos_update_engine { + +namespace { + +// Minimum threshold to broadcast an status update in progress and time. +const double kBroadcastThresholdProgress = 0.01; // 1% +const int kBroadcastThresholdSeconds = 10; + +const char* const kErrorDomain = "update_engine"; +// TODO(deymo): Convert the different errors to a numeric value to report them +// back on the service error. +const char* const kGenericError = "generic_error"; + +// Log and set the error on the passed ErrorPtr. +bool LogAndSetError(brillo::ErrorPtr* error, + const tracked_objects::Location& location, + const string& reason) { + brillo::Error::AddTo(error, location, kErrorDomain, kGenericError, reason); + LOG(ERROR) << "Replying with failure: " << location.ToString() << ": " + << reason; + return false; +} + +} // namespace + +UpdateAttempterAndroid::UpdateAttempterAndroid( + DaemonStateInterface* daemon_state, + PrefsInterface* prefs, + BootControlInterface* boot_control, + HardwareInterface* hardware) + : daemon_state_(daemon_state), + prefs_(prefs), + boot_control_(boot_control), + hardware_(hardware), + processor_(new ActionProcessor()) { + network_selector_ = network::CreateNetworkSelector(); +} + +UpdateAttempterAndroid::~UpdateAttempterAndroid() { + // Release ourselves as the ActionProcessor's delegate to prevent + // re-scheduling the updates due to the processing stopped. + processor_->set_delegate(nullptr); +} + +void UpdateAttempterAndroid::Init() { + // In case of update_engine restart without a reboot we need to restore the + // reboot needed state. + if (UpdateCompletedOnThisBoot()) + SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT); + else + SetStatusAndNotify(UpdateStatus::IDLE); +} + +bool UpdateAttempterAndroid::ApplyPayload( + const string& payload_url, + int64_t payload_offset, + int64_t payload_size, + const vector<string>& key_value_pair_headers, + brillo::ErrorPtr* error) { + if (status_ == UpdateStatus::UPDATED_NEED_REBOOT) { + return LogAndSetError( + error, FROM_HERE, "An update already applied, waiting for reboot"); + } + if (ongoing_update_) { + return LogAndSetError( + error, FROM_HERE, "Already processing an update, cancel it first."); + } + DCHECK(status_ == UpdateStatus::IDLE); + + std::map<string, string> headers; + for (const string& key_value_pair : key_value_pair_headers) { + string key; + string value; + if (!brillo::string_utils::SplitAtFirst( + key_value_pair, "=", &key, &value, false)) { + return LogAndSetError( + error, FROM_HERE, "Passed invalid header: " + key_value_pair); + } + if (!headers.emplace(key, value).second) + return LogAndSetError(error, FROM_HERE, "Passed repeated key: " + key); + } + + // Unique identifier for the payload. An empty string means that the payload + // can't be resumed. + string payload_id = (headers[kPayloadPropertyFileHash] + + headers[kPayloadPropertyMetadataHash]); + + // Setup the InstallPlan based on the request. + install_plan_ = InstallPlan(); + + install_plan_.download_url = payload_url; + install_plan_.version = ""; + base_offset_ = payload_offset; + install_plan_.payload_size = payload_size; + if (!install_plan_.payload_size) { + if (!base::StringToUint64(headers[kPayloadPropertyFileSize], + &install_plan_.payload_size)) { + install_plan_.payload_size = 0; + } + } + install_plan_.payload_hash = headers[kPayloadPropertyFileHash]; + if (!base::StringToUint64(headers[kPayloadPropertyMetadataSize], + &install_plan_.metadata_size)) { + install_plan_.metadata_size = 0; + } + install_plan_.metadata_signature = ""; + // The |public_key_rsa| key would override the public key stored on disk. + install_plan_.public_key_rsa = ""; + + install_plan_.hash_checks_mandatory = hardware_->IsOfficialBuild(); + install_plan_.is_resume = !payload_id.empty() && + DeltaPerformer::CanResumeUpdate(prefs_, payload_id); + if (!install_plan_.is_resume) { + if (!DeltaPerformer::ResetUpdateProgress(prefs_, false)) { + LOG(WARNING) << "Unable to reset the update progress."; + } + if (!prefs_->SetString(kPrefsUpdateCheckResponseHash, payload_id)) { + LOG(WARNING) << "Unable to save the update check response hash."; + } + } + // The |payload_type| is not used anymore since minor_version 3. + install_plan_.payload_type = InstallPayloadType::kUnknown; + + install_plan_.source_slot = boot_control_->GetCurrentSlot(); + install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0; + + int data_wipe = 0; + install_plan_.powerwash_required = + base::StringToInt(headers[kPayloadPropertyPowerwash], &data_wipe) && + data_wipe != 0; + + NetworkId network_id = kDefaultNetworkId; + if (!headers[kPayloadPropertyNetworkId].empty()) { + if (!base::StringToUint64(headers[kPayloadPropertyNetworkId], + &network_id)) { + return LogAndSetError( + error, + FROM_HERE, + "Invalid network_id: " + headers[kPayloadPropertyNetworkId]); + } + if (!network_selector_->SetProcessNetwork(network_id)) { + LOG(WARNING) << "Unable to set network_id, continuing with the update."; + } + } + + LOG(INFO) << "Using this install plan:"; + install_plan_.Dump(); + + BuildUpdateActions(payload_url); + SetupDownload(); + // Setup extra headers. + HttpFetcher* fetcher = download_action_->http_fetcher(); + if (!headers[kPayloadPropertyAuthorization].empty()) + fetcher->SetHeader("Authorization", headers[kPayloadPropertyAuthorization]); + if (!headers[kPayloadPropertyUserAgent].empty()) + fetcher->SetHeader("User-Agent", headers[kPayloadPropertyUserAgent]); + + cpu_limiter_.StartLimiter(); + SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE); + ongoing_update_ = true; + + // Just in case we didn't update boot flags yet, make sure they're updated + // before any update processing starts. This will start the update process. + UpdateBootFlags(); + return true; +} + +bool UpdateAttempterAndroid::SuspendUpdate(brillo::ErrorPtr* error) { + if (!ongoing_update_) + return LogAndSetError(error, FROM_HERE, "No ongoing update to suspend."); + processor_->SuspendProcessing(); + return true; +} + +bool UpdateAttempterAndroid::ResumeUpdate(brillo::ErrorPtr* error) { + if (!ongoing_update_) + return LogAndSetError(error, FROM_HERE, "No ongoing update to resume."); + processor_->ResumeProcessing(); + return true; +} + +bool UpdateAttempterAndroid::CancelUpdate(brillo::ErrorPtr* error) { + if (!ongoing_update_) + return LogAndSetError(error, FROM_HERE, "No ongoing update to cancel."); + processor_->StopProcessing(); + return true; +} + +bool UpdateAttempterAndroid::ResetStatus(brillo::ErrorPtr* error) { + LOG(INFO) << "Attempting to reset state from " + << UpdateStatusToString(status_) << " to UpdateStatus::IDLE"; + + switch (status_) { + case UpdateStatus::IDLE: + return true; + + case UpdateStatus::UPDATED_NEED_REBOOT: { + // Remove the reboot marker so that if the machine is rebooted + // after resetting to idle state, it doesn't go back to + // UpdateStatus::UPDATED_NEED_REBOOT state. + bool ret_value = prefs_->Delete(kPrefsUpdateCompletedOnBootId); + + // Update the boot flags so the current slot has higher priority. + if (!boot_control_->SetActiveBootSlot(boot_control_->GetCurrentSlot())) + ret_value = false; + + if (!ret_value) { + return LogAndSetError( + error, + FROM_HERE, + "Failed to reset the status to "); + } + + SetStatusAndNotify(UpdateStatus::IDLE); + LOG(INFO) << "Reset status successful"; + return true; + } + + default: + return LogAndSetError( + error, + FROM_HERE, + "Reset not allowed in this state. Cancel the ongoing update first"); + } +} + +void UpdateAttempterAndroid::ProcessingDone(const ActionProcessor* processor, + ErrorCode code) { + LOG(INFO) << "Processing Done."; + + switch (code) { + case ErrorCode::kSuccess: + // Update succeeded. + WriteUpdateCompletedMarker(); + prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0); + DeltaPerformer::ResetUpdateProgress(prefs_, false); + + LOG(INFO) << "Update successfully applied, waiting to reboot."; + break; + + case ErrorCode::kFilesystemCopierError: + case ErrorCode::kNewRootfsVerificationError: + case ErrorCode::kNewKernelVerificationError: + case ErrorCode::kFilesystemVerifierError: + case ErrorCode::kDownloadStateInitializationError: + // Reset the ongoing update for these errors so it starts from the + // beginning next time. + DeltaPerformer::ResetUpdateProgress(prefs_, false); + LOG(INFO) << "Resetting update progress."; + break; + + default: + // Ignore all other error codes. + break; + } + + TerminateUpdateAndNotify(code); +} + +void UpdateAttempterAndroid::ProcessingStopped( + const ActionProcessor* processor) { + TerminateUpdateAndNotify(ErrorCode::kUserCanceled); +} + +void UpdateAttempterAndroid::ActionCompleted(ActionProcessor* processor, + AbstractAction* action, + ErrorCode code) { + // Reset download progress regardless of whether or not the download + // action succeeded. + const string type = action->Type(); + if (type == DownloadAction::StaticType()) { + download_progress_ = 0; + } + if (code != ErrorCode::kSuccess) { + // If an action failed, the ActionProcessor will cancel the whole thing. + return; + } + if (type == DownloadAction::StaticType()) { + SetStatusAndNotify(UpdateStatus::FINALIZING); + } +} + +void UpdateAttempterAndroid::BytesReceived(uint64_t bytes_progressed, + uint64_t bytes_received, + uint64_t total) { + double progress = 0; + if (total) + progress = static_cast<double>(bytes_received) / static_cast<double>(total); + if (status_ != UpdateStatus::DOWNLOADING || bytes_received == total) { + download_progress_ = progress; + SetStatusAndNotify(UpdateStatus::DOWNLOADING); + } else { + ProgressUpdate(progress); + } +} + +bool UpdateAttempterAndroid::ShouldCancel(ErrorCode* cancel_reason) { + // TODO(deymo): Notify the DownloadAction that it should cancel the update + // download. + return false; +} + +void UpdateAttempterAndroid::DownloadComplete() { + // Nothing needs to be done when the download completes. +} + +void UpdateAttempterAndroid::ProgressUpdate(double progress) { + // Self throttle based on progress. Also send notifications if progress is + // too slow. + if (progress == 1.0 || + progress - download_progress_ >= kBroadcastThresholdProgress || + TimeTicks::Now() - last_notify_time_ >= + TimeDelta::FromSeconds(kBroadcastThresholdSeconds)) { + download_progress_ = progress; + SetStatusAndNotify(status_); + } +} + +void UpdateAttempterAndroid::UpdateBootFlags() { + if (updated_boot_flags_) { + LOG(INFO) << "Already updated boot flags. Skipping."; + CompleteUpdateBootFlags(true); + return; + } + // This is purely best effort. + LOG(INFO) << "Marking booted slot as good."; + if (!boot_control_->MarkBootSuccessfulAsync( + Bind(&UpdateAttempterAndroid::CompleteUpdateBootFlags, + base::Unretained(this)))) { + LOG(ERROR) << "Failed to mark current boot as successful."; + CompleteUpdateBootFlags(false); + } +} + +void UpdateAttempterAndroid::CompleteUpdateBootFlags(bool successful) { + updated_boot_flags_ = true; + ScheduleProcessingStart(); +} + +void UpdateAttempterAndroid::ScheduleProcessingStart() { + LOG(INFO) << "Scheduling an action processor start."; + brillo::MessageLoop::current()->PostTask( + FROM_HERE, + Bind([](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(processor_.get()))); +} + +void UpdateAttempterAndroid::TerminateUpdateAndNotify(ErrorCode error_code) { + if (status_ == UpdateStatus::IDLE) { + LOG(ERROR) << "No ongoing update, but TerminatedUpdate() called."; + return; + } + + // Reset cpu shares back to normal. + cpu_limiter_.StopLimiter(); + download_progress_ = 0; + actions_.clear(); + UpdateStatus new_status = + (error_code == ErrorCode::kSuccess ? UpdateStatus::UPDATED_NEED_REBOOT + : UpdateStatus::IDLE); + SetStatusAndNotify(new_status); + ongoing_update_ = false; + + for (auto observer : daemon_state_->service_observers()) + observer->SendPayloadApplicationComplete(error_code); +} + +void UpdateAttempterAndroid::SetStatusAndNotify(UpdateStatus status) { + status_ = status; + for (auto observer : daemon_state_->service_observers()) { + observer->SendStatusUpdate( + 0, download_progress_, status_, "", install_plan_.payload_size); + } + last_notify_time_ = TimeTicks::Now(); +} + +void UpdateAttempterAndroid::BuildUpdateActions(const string& url) { + CHECK(!processor_->IsRunning()); + processor_->set_delegate(this); + + // Actions: + shared_ptr<InstallPlanAction> install_plan_action( + new InstallPlanAction(install_plan_)); + + HttpFetcher* download_fetcher = nullptr; + if (FileFetcher::SupportedUrl(url)) { + DLOG(INFO) << "Using FileFetcher for file URL."; + download_fetcher = new FileFetcher(); + } else { +#ifdef _UE_SIDELOAD + LOG(FATAL) << "Unsupported sideload URI: " << url; +#else + LibcurlHttpFetcher* libcurl_fetcher = + new LibcurlHttpFetcher(&proxy_resolver_, hardware_); + libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload); + download_fetcher = libcurl_fetcher; +#endif // _UE_SIDELOAD + } + shared_ptr<DownloadAction> download_action(new DownloadAction( + prefs_, + boot_control_, + hardware_, + nullptr, // system_state, not used. + new MultiRangeHttpFetcher(download_fetcher))); // passes ownership + shared_ptr<FilesystemVerifierAction> filesystem_verifier_action( + new FilesystemVerifierAction()); + + shared_ptr<PostinstallRunnerAction> postinstall_runner_action( + new PostinstallRunnerAction(boot_control_, hardware_)); + + download_action->set_delegate(this); + download_action_ = download_action; + postinstall_runner_action->set_delegate(this); + + actions_.push_back(shared_ptr<AbstractAction>(install_plan_action)); + actions_.push_back(shared_ptr<AbstractAction>(download_action)); + actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action)); + actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action)); + + // Bond them together. We have to use the leaf-types when calling + // BondActions(). + BondActions(install_plan_action.get(), download_action.get()); + BondActions(download_action.get(), filesystem_verifier_action.get()); + BondActions(filesystem_verifier_action.get(), + postinstall_runner_action.get()); + + // Enqueue the actions. + for (const shared_ptr<AbstractAction>& action : actions_) + processor_->EnqueueAction(action.get()); +} + +void UpdateAttempterAndroid::SetupDownload() { + MultiRangeHttpFetcher* fetcher = + static_cast<MultiRangeHttpFetcher*>(download_action_->http_fetcher()); + fetcher->ClearRanges(); + if (install_plan_.is_resume) { + // Resuming an update so fetch the update manifest metadata first. + int64_t manifest_metadata_size = 0; + int64_t manifest_signature_size = 0; + prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size); + prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size); + fetcher->AddRange(base_offset_, + manifest_metadata_size + manifest_signature_size); + // If there're remaining unprocessed data blobs, fetch them. Be careful not + // to request data beyond the end of the payload to avoid 416 HTTP response + // error codes. + int64_t next_data_offset = 0; + prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset); + uint64_t resume_offset = + manifest_metadata_size + manifest_signature_size + next_data_offset; + if (!install_plan_.payload_size) { + fetcher->AddRange(base_offset_ + resume_offset); + } else if (resume_offset < install_plan_.payload_size) { + fetcher->AddRange(base_offset_ + resume_offset, + install_plan_.payload_size - resume_offset); + } + } else { + if (install_plan_.payload_size) { + fetcher->AddRange(base_offset_, install_plan_.payload_size); + } else { + // If no payload size is passed we assume we read until the end of the + // stream. + fetcher->AddRange(base_offset_); + } + } +} + +bool UpdateAttempterAndroid::WriteUpdateCompletedMarker() { + string boot_id; + TEST_AND_RETURN_FALSE(utils::GetBootId(&boot_id)); + prefs_->SetString(kPrefsUpdateCompletedOnBootId, boot_id); + return true; +} + +bool UpdateAttempterAndroid::UpdateCompletedOnThisBoot() { + // 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; + return (prefs_->Exists(kPrefsUpdateCompletedOnBootId) && + prefs_->GetString(kPrefsUpdateCompletedOnBootId, + &update_completed_on_boot_id) && + update_completed_on_boot_id == boot_id); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/update_attempter_android.h b/update_engine/update_attempter_android.h new file mode 100644 index 0000000..2617318 --- /dev/null +++ b/update_engine/update_attempter_android.h
@@ -0,0 +1,178 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_UPDATE_ATTEMPTER_ANDROID_H_ +#define UPDATE_ENGINE_UPDATE_ATTEMPTER_ANDROID_H_ + +#include <stdint.h> + +#include <memory> +#include <string> +#include <vector> + +#include <base/time/time.h> + +#include "update_engine/client_library/include/update_engine/update_status.h" +#include "update_engine/common/action_processor.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/cpu_limiter.h" +#include "update_engine/common/hardware_interface.h" +#include "update_engine/common/prefs_interface.h" +#include "update_engine/daemon_state_interface.h" +#include "update_engine/network_selector_interface.h" +#include "update_engine/payload_consumer/download_action.h" +#include "update_engine/payload_consumer/postinstall_runner_action.h" +#include "update_engine/service_delegate_android_interface.h" +#include "update_engine/service_observer_interface.h" + +namespace chromeos_update_engine { + +class UpdateAttempterAndroid + : public ServiceDelegateAndroidInterface, + public ActionProcessorDelegate, + public DownloadActionDelegate, + public PostinstallRunnerAction::DelegateInterface { + public: + using UpdateStatus = update_engine::UpdateStatus; + + UpdateAttempterAndroid(DaemonStateInterface* daemon_state, + PrefsInterface* prefs, + BootControlInterface* boot_control_, + HardwareInterface* hardware_); + ~UpdateAttempterAndroid() override; + + // Further initialization to be done post construction. + void Init(); + + // ServiceDelegateAndroidInterface overrides. + bool ApplyPayload(const std::string& payload_url, + int64_t payload_offset, + int64_t payload_size, + const std::vector<std::string>& key_value_pair_headers, + brillo::ErrorPtr* error) override; + bool SuspendUpdate(brillo::ErrorPtr* error) override; + bool ResumeUpdate(brillo::ErrorPtr* error) override; + bool CancelUpdate(brillo::ErrorPtr* error) override; + bool ResetStatus(brillo::ErrorPtr* error) override; + + // 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; + + // DownloadActionDelegate overrides. + void BytesReceived(uint64_t bytes_progressed, + uint64_t bytes_received, + uint64_t total) override; + bool ShouldCancel(ErrorCode* cancel_reason) override; + void DownloadComplete() override; + + // PostinstallRunnerAction::DelegateInterface + void ProgressUpdate(double progress) override; + + private: + // Asynchronously marks the current slot as successful if needed. If already + // marked as good, CompleteUpdateBootFlags() is called starting the action + // processor. + void UpdateBootFlags(); + + // Called when the boot flags have been updated. + void CompleteUpdateBootFlags(bool success); + + // Schedules an event loop callback to start the action processor. This is + // scheduled asynchronously to unblock the event loop. + void ScheduleProcessingStart(); + + // Notifies an update request completed with the given error |code| to all + // observers. + void TerminateUpdateAndNotify(ErrorCode error_code); + + // Sets the status to the given |status| and notifies a status update to + // all observers. + void SetStatusAndNotify(UpdateStatus status); + + // Helper method to construct the sequence of actions to be performed for + // applying an update from the given |url|. + void BuildUpdateActions(const std::string& url); + + // Sets up the download parameters based on the update requested on the + // |install_plan_|. + void SetupDownload(); + + // Writes to the processing completed marker. Does nothing if + // |update_completed_marker_| is empty. + bool WriteUpdateCompletedMarker(); + + // Returns whether an update was completed in the current boot. + bool UpdateCompletedOnThisBoot(); + + DaemonStateInterface* daemon_state_; + + // DaemonStateAndroid pointers. + PrefsInterface* prefs_; + BootControlInterface* boot_control_; + HardwareInterface* hardware_; + + // 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_; + + // The list of actions and action processor that runs them asynchronously. + // Only used when |ongoing_update_| is true. + std::vector<std::shared_ptr<AbstractAction>> actions_; + std::unique_ptr<ActionProcessor> processor_; + + // Pointer to the DownloadAction in the actions_ vector. + std::shared_ptr<DownloadAction> download_action_; + + // Whether there is an ongoing update. This implies that an update was started + // but not finished yet. This value will be true even if the update was + // suspended. + bool ongoing_update_{false}; + + // The InstallPlan used during the ongoing update. + InstallPlan install_plan_; + + // For status: + UpdateStatus status_{UpdateStatus::IDLE}; + double download_progress_{0.0}; + + // The offset in the payload file where the CrAU part starts. + int64_t base_offset_{0}; + + // Only direct proxy supported. + DirectProxyResolver proxy_resolver_; + + // CPU limiter during the update. + CPULimiter cpu_limiter_; + + // Helper class to select the network to use during the update. + std::unique_ptr<NetworkSelectorInterface> network_selector_; + + // Whether we have marked the current slot as good. This step is required + // before applying an update to the other slot. + bool updated_boot_flags_ = false; + + DISALLOW_COPY_AND_ASSIGN(UpdateAttempterAndroid); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_UPDATE_ATTEMPTER_ANDROID_H_
diff --git a/update_engine/update_attempter_nestlabs.cc b/update_engine/update_attempter_nestlabs.cc new file mode 100644 index 0000000..047778a --- /dev/null +++ b/update_engine/update_attempter_nestlabs.cc
@@ -0,0 +1,841 @@ +// +// 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_nestlabs.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/errors/error_codes.h> +#include <brillo/make_unique_ptr.h> +#include <brillo/message_loops/message_loop.h> +#include <policy/device_policy.h> +#include <policy/libpolicy.h> +#include <update_engine/dbus-constants-nestlabs.h> + +#include "update_engine/certificate_checker.h" +#include "update_engine/common/boot_control_interface.h" +#include "update_engine/common/clock_interface.h" +#include "update_engine/common/constants.h" +#include "update_engine/common/hardware_interface.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/libcurl_http_fetcher.h" +#include "update_engine/metrics.h" +#include "update_engine/payload_properties_request_action.h" +#include "update_engine/payload_properties_handler_action.h" +#include "update_engine/p2p_manager.h" +#include "update_engine/payload_consumer/download_action.h" +#include "update_engine/payload_consumer/filesystem_verifier_action.h" +#include "update_engine/payload_consumer/postinstall_runner_action.h" +#include "update_engine/payload_state_interface.h" +#include "update_engine/power_manager_interface.h" +#include "update_engine/system_state.h" +#include "update_engine/update_manager/policy.h" +#include "update_engine/update_manager/update_manager.h" +#include "update_engine/update_status_utils.h" + +#define PAYLOAD_PROPERTIES_OFFSET 0 +#define PAYLOAD_PROPERTIES_SIZE 512 + + +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 { +// Minimum threshold to broadcast an status update in progress and time. +const double kBroadcastThresholdProgress = 0.01; // 1% +const int kBroadcastThresholdSeconds = 10; +} // namespace + +UpdateAttempter::UpdateAttempter(SystemState* system_state, + CertificateChecker* cert_checker, + LibCrosProxy* libcros_proxy) + : processor_(new ActionProcessor()), + system_state_(system_state), + cert_checker_(cert_checker) { +} + +UpdateAttempter::~UpdateAttempter() { + // CertificateChecker might not be initialized in unittests. + if (cert_checker_) + cert_checker_->SetObserver(nullptr); + // 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; +} + +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); +} + +void UpdateAttempter::Update(const string& app_version, + const string& url, + const string& target_channel, + const string& target_version_prefix, + bool obey_proxies, + bool interactive) { + + // Notify of the new update attempt, clearing prior interactive requests. + if (forced_update_pending_callback_.get()) + forced_update_pending_callback_->Run(false, 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"; + metrics::ReportUpdateCheckMetrics(system_state_, + metrics::CheckResult::kRebootPending, + metrics::CheckReaction::kUnset, + metrics::DownloadErrorCode::kUnset); + return; + } + if (status_ != UpdateStatus::IDLE) { + // Update in progress. Do nothing + return; + } + + // 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(); + + if (!CalculateUpdateParams(app_version ,url)) { + return; + } + + BuildUpdateActions(interactive); + + SetStatusAndNotify(UpdateStatus::CHECKING_FOR_UPDATE); + + // 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(); +} + +bool UpdateAttempter::CalculateUpdateParams(const string& version, + const string& url) { + + PayloadStateInterface* const payload_state = system_state_->payload_state(); + + // Refresh the policy before computing all the update parameters. + RefreshDevicePolicy(); + + payload_properties_.payload_urls.clear(); + + // Skip update if `url' is empty (update_engine was not asked for update via DBUS), + // and URL from payload_state is empty (there is no restored URL + // from previos update) + if (url != "") + { + payload_properties_.payload_urls.push_back(url); + } + else if (payload_state->GetCurrentUrl() != "") + { + payload_properties_.payload_urls.push_back(payload_state->GetCurrentUrl()); + } + else + { + LOG(INFO) + << "URL is not present. Nothing to do now. retrying."; + ScheduleUpdates(); + return false; + } + + // turn P2P off + payload_state->SetUsingP2PForDownloading(false); + payload_state->SetUsingP2PForSharing(false); + + // reset code + http_response_code_ = 0; + + return true; +} + +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::BuildPostInstallActions( + InstallPlanAction* previous_action) { + shared_ptr<PostinstallRunnerAction> postinstall_runner_action( + new PostinstallRunnerAction(system_state_->boot_control(), + system_state_->hardware())); + postinstall_runner_action->set_delegate(this); + actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action)); + BondActions(previous_action, + postinstall_runner_action.get()); +} + +#define MAX_RETRIES 3 + +void UpdateAttempter::BuildUpdateActions(bool interactive) { + CHECK(!processor_->IsRunning()); + processor_->set_delegate(this); + + // Actions: + LibcurlHttpFetcher * pp_base_fetcher = + new LibcurlHttpFetcher(GetProxyResolver(), system_state_->hardware()); + MultiRangeHttpFetcher * payload_properties_fetcher = + new MultiRangeHttpFetcher(pp_base_fetcher); + + pp_base_fetcher->set_server_to_check(ServerToCheck::kDownload); + pp_base_fetcher->set_no_network_max_retries(MAX_RETRIES); + payload_properties_fetcher->AddRange(PAYLOAD_PROPERTIES_OFFSET, PAYLOAD_PROPERTIES_SIZE); + shared_ptr<PayloadPropertiesRequestAction> payload_properties_request_action( + new PayloadPropertiesRequestAction(system_state_, + &payload_properties_, + (LibcurlHttpFetcher *)payload_properties_fetcher)); + payload_properties_request_action_ = payload_properties_request_action; + + shared_ptr<PayloadPropertiesHandlerAction> payload_properties_handler_action( + new PayloadPropertiesHandlerAction(system_state_)); + payload_properties_handler_action_ = payload_properties_handler_action; + + 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_->boot_control(), + system_state_->hardware(), + system_state_, + new MultiRangeHttpFetcher(download_fetcher))); // passes ownership + download_action->set_delegate(this); + download_action_ = download_action; + + shared_ptr<FilesystemVerifierAction> filesystem_verifier_action( + new FilesystemVerifierAction()); + + actions_.push_back(shared_ptr<AbstractAction>(payload_properties_request_action)); + actions_.push_back(shared_ptr<AbstractAction>(payload_properties_handler_action)); + actions_.push_back(shared_ptr<AbstractAction>(download_action)); + actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action)); + + // Bond them together. We have to use the leaf-types when calling + // BondActions(). + BondActions(payload_properties_request_action.get(), + payload_properties_handler_action.get()); + BondActions(payload_properties_handler_action.get(), + download_action.get()); + BondActions(download_action.get(), + filesystem_verifier_action.get()); + BuildPostInstallActions(filesystem_verifier_action.get()); + + // Enqueue the actions + for (const shared_ptr<AbstractAction>& action : actions_) { + processor_->EnqueueAction(action.get()); + } +} + +bool UpdateAttempter::Rollback(bool powerwash) { + if (!CanRollback()) { + return false; + } + + processor_->set_delegate(this); + + 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& url, + bool interactive) { + LOG(INFO) << "Update requested."; + app_version_ = app_version; + url_ = url; + + 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 (system_state_->power_manager()->RequestReboot()) + 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::RebootDirectly() { + vector<string> command; + command.push_back("/system/bin/reboot"); + LOG(INFO) << "Running \"" << base::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 update."; + + Update(app_version_, url_); + // Always clear the forced app_version and url after an update attempt + // so the next update uses the defaults. + app_version_.clear(); + url_.clear(); + } 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. + cpu_limiter_.StopLimiter(); + + if (code != ErrorCode::kSuccess) { + // update metrics + system_state_->payload_state()->UpdateFailed(code); + DeltaPerformer::ResetUpdateProgress(prefs_, false); + // send ERROR event and go into IDLE state otherwise update_engine + // will not able to handle other requests + // REPORTING_ERROR_EVENT is recoverable state which is used just for + // notifying about error. + SetStatusAndNotify(UpdateStatus::REPORTING_ERROR_EVENT); + // go back to IDLE + SetStatusAndNotify(UpdateStatus::IDLE); + ScheduleUpdates(); + return; + } + + WriteUpdateCompletedMarker(); + prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0); + prefs_->SetString(kPrefsPreviousVersion, + payload_properties_.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); + LOG(INFO) << "Update successfully applied, waiting to reboot."; + return; +} + +void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) { + // Reset cpu shares back to normal. + cpu_limiter_.StopLimiter(); + download_progress_ = 0.0; + SetStatusAndNotify(UpdateStatus::IDLE); + ScheduleUpdates(); + actions_.clear(); +} + +// Called whenever an action has finished processing, either successfully +// or otherwise. +void UpdateAttempter::ActionCompleted(ActionProcessor* processor, + AbstractAction* action, + ErrorCode code) { + const string type = action->Type(); + + // 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). + if (type == DownloadAction::StaticType()) { + download_progress_ = 0.0; + http_response_code_ = download_action_->GetHTTPResponseCode(); + } + else if (type == PayloadPropertiesRequestAction::StaticType()) + { + http_response_code_ = payload_properties_request_action_->GetHTTPResponseCode(); + } + 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(); + } + return; + } + + if (type == PayloadPropertiesHandlerAction::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 == payload_properties_handler_action_.get()); + const InstallPlan& plan = payload_properties_handler_action_->install_plan(); + UpdateLastCheckedTime(); + new_version_ = plan.version; + new_payload_size_ = plan.payload_size; + SetupDownload(); + cpu_limiter_.StartLimiter(); + 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 = 0; + if (total) + progress = static_cast<double>(bytes_received) / static_cast<double>(total); + if (status_ != UpdateStatus::DOWNLOADING || bytes_received == total) { + download_progress_ = progress; + SetStatusAndNotify(UpdateStatus::DOWNLOADING); + } else { + ProgressUpdate(progress); + } +} + +void UpdateAttempter::DownloadComplete() { + system_state_->payload_state()->DownloadComplete(); +} + +void UpdateAttempter::ProgressUpdate(double progress) { + // Self throttle based on progress. Also send notifications if progress is + // too slow. + if (progress == 1.0 || + progress - download_progress_ >= kBroadcastThresholdProgress || + TimeTicks::Now() - last_notify_time_ >= + TimeDelta::FromSeconds(kBroadcastThresholdSeconds)) { + download_progress_ = progress; + BroadcastStatus(); + } +} + +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() { + for (const auto& observer : service_observers_) { + observer->SendStatusUpdate(last_checked_time_, + download_progress_, + status_, + new_version_, + new_payload_size_); + } + last_notify_time_ = TimeTicks::Now(); +} + +void UpdateAttempter::SetStatusAndNotify(UpdateStatus status) { + status_ = status; + BroadcastStatus(); +} + +void UpdateAttempter::ScheduleProcessingStart() { + LOG(INFO) << "Scheduling an action processor start."; + start_action_processor_ = false; + MessageLoop::current()->PostTask( + FROM_HERE, + Bind([](ActionProcessor* processor) { processor->StartProcessing(); }, + base::Unretained(processor_.get()))); +} + +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) { + if (payload_properties_handler_action_->install_plan().is_resume) { + // Resuming an update so fetch the update manifest metadata first. + int64_t manifest_metadata_size = 0; + int64_t manifest_signature_size = 0; + prefs_->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size); + prefs_->GetInt64(kPrefsManifestSignatureSize, &manifest_signature_size); + fetcher->AddRange(PAYLOAD_PROPERTIES_SIZE, manifest_metadata_size + manifest_signature_size); + // If there're remaining unprocessed data blobs, fetch them. Be careful not + // to request data beyond the end of the payload to avoid 416 HTTP response + // error codes. + int64_t next_data_offset = 0; + prefs_->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset); + uint64_t resume_offset = + manifest_metadata_size + manifest_signature_size + next_data_offset; + if (resume_offset < payload_properties_handler_action_->install_plan().payload_size) { + fetcher->AddRange(resume_offset); + } + } else { + fetcher->AddRange(PAYLOAD_PROPERTIES_SIZE); + } +} + + +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(); +} + +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_); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/update_attempter_nestlabs.h b/update_engine/update_attempter_nestlabs.h new file mode 100644 index 0000000..d490d74 --- /dev/null +++ b/update_engine/update_attempter_nestlabs.h
@@ -0,0 +1,397 @@ +// +// 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_NESTLABS_H_ +#define UPDATE_ENGINE_UPDATE_ATTEMPTER_NESTLABS_H_ + +#include <time.h> + +#include <memory> +#include <set> +#include <string> +#include <utility> +#include <vector> + +#include <base/bind.h> +#include <base/time/time.h> +#include <gtest/gtest_prod.h> // for FRIEND_TEST + +#include "update_engine/certificate_checker.h" +#include "update_engine/client_library/include/update_engine/update_status.h" +#include "update_engine/common/action_processor.h" +#include "update_engine/common/cpu_limiter.h" +#include "update_engine/payload_properties_request_action.h" +#include "update_engine/payload_properties_handler_action.h" +#include "update_engine/payload_properties.h" +#include "update_engine/payload_consumer/download_action.h" +#include "update_engine/payload_consumer/postinstall_runner_action.h" +#include "update_engine/proxy_resolver.h" +#include "update_engine/service_observer_interface.h" +#include "update_engine/system_state.h" +#include "update_engine/update_manager/policy.h" +#include "update_engine/update_manager/update_manager.h" +#include "update_engine/weave_service_interface.h" + +class MetricsLibraryInterface; + +namespace policy { +class PolicyProvider; +} + +namespace chromeos_update_engine { + +class LibCrosProxy; +class UpdateEngineAdaptor; + +class UpdateAttempter : public ActionProcessorDelegate, + public DownloadActionDelegate, + public CertificateChecker::Observer, + public WeaveServiceInterface::DelegateInterface, // TODO remove + public PostinstallRunnerAction::DelegateInterface { + public: + using UpdateStatus = update_engine::UpdateStatus; + static const int kMaxDeltaUpdateFailures; + + UpdateAttempter(SystemState* system_state, + CertificateChecker* cert_checker, + LibCrosProxy* libcros_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& url, + const std::string& target_channel = "", + const std::string& target_version_prefix = "", + bool obey_proxies = false, + bool interactive = false); + + // 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; + // WeaveServiceInterface::DelegateInterface overrides. + bool OnCheckForUpdates(brillo::ErrorPtr* error) override { return false; }; + bool OnTrackChannel(const std::string& channel, + brillo::ErrorPtr* error) override { return false; }; + bool GetWeaveState(int64_t* last_checked_time, + double* progress, + UpdateStatus* update_status, + std::string* current_channel, + std::string* tracking_channel) override { return false;}; + + // PostinstallRunnerAction::DelegateInterface + void ProgressUpdate(double progress) 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; } + + // 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& 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. + // TODO (dmitryya) figire out why do we need it + bool ShouldCancel(ErrorCode* cancel_reason) override { return false;}; + + void DownloadComplete() override; + + // Broadcasts the current status to all observers. + void BroadcastStatus(); + + // 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_; } + + // 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); + } + + // Add and remove a service observer. + void AddObserver(ServiceObserverInterface* observer) { + service_observers_.insert(observer); + } + void RemoveObserver(ServiceObserverInterface* observer) { + service_observers_.erase(observer); + } + + const std::set<ServiceObserverInterface*>& service_observers() { + return service_observers_; + } + + // Remove all the observers. + void ClearObservers() { service_observers_.clear(); } + + + // =================== TO REMOVE + // Broadcasts the current tracking channel to all observers. + void BroadcastChannel() {}; + + // Returns the poll interval dictated by Omaha, if provided; zero otherwise. + virtual unsigned int server_dictated_poll_interval() const { + return 0; + } + + // Returns the number of consecutive failed update checks. + virtual unsigned int consecutive_failed_update_checks() const { + return 0; + } + + 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, MarkDeltaUpdateFailureTest); + FRIEND_TEST(UpdateAttempterTest, UpdateTest); + FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics); + FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile); + +private: + bool CalculateUpdateParams(const std::string& version, + const std::string& url); + + // CertificateChecker::Observer method. + // Report metrics about the certificate being checked. + void CertificateChecked(ServerToCheck server_to_check, + CertificateCheckResult result) override; + + // 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(); + + // Schedules an event loop callback to start the action processor. This is + // scheduled asynchronously to unblock the event loop. + void ScheduleProcessingStart(); + + // 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 &direct_proxy_resolver_; + } + + // 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); + + // Writes to the processing completed marker. Does nothing if + // |update_completed_marker_| is empty. + void WriteUpdateCompletedMarker(); + + // 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_; + + // The list of services observing changes in the updater. + std::set<ServiceObserverInterface*> service_observers_; + + // Pointer to the DownloadAction in the actions_ vector. + std::shared_ptr<DownloadAction> download_action_; + + std::shared_ptr<PayloadPropertiesHandlerAction> payload_properties_handler_action_; + std::shared_ptr<PayloadPropertiesRequestAction> payload_properties_request_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; + + // HTTP server response code from the last HTTP request action. + int http_response_code_ = 0; + + // CPU limiter during the update. + CPULimiter cpu_limiter_; + + // 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_ = "0000000"; + int64_t new_payload_size_ = 0; + + // Our two proxy resolvers + DirectProxyResolver direct_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_; + + // 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_; + + std::string app_version_; + std::string url_; + + PayloadProperties payload_properties_; + + DISALLOW_COPY_AND_ASSIGN(UpdateAttempter); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_UPDATE_ATTEMPTER_NESTLABS_H_
diff --git a/update_engine/update_attempter_unittest.cc b/update_engine/update_attempter_unittest.cc new file mode 100644 index 0000000..94a1b3c --- /dev/null +++ b/update_engine/update_attempter_unittest.cc
@@ -0,0 +1,981 @@ +// +// 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 <gtest/gtest.h> +#include <policy/libpolicy.h> +#include <policy/mock_device_policy.h> + +#if USE_LIBCROS +#include "libcros/dbus-proxies.h" +#include "libcros/dbus-proxy-mocks.h" +#include "update_engine/libcros_proxy.h" +#endif // USE_LIBCROS +#include "update_engine/common/fake_clock.h" +#include "update_engine/common/fake_prefs.h" +#include "update_engine/common/mock_action.h" +#include "update_engine/common/mock_action_processor.h" +#include "update_engine/common/mock_http_fetcher.h" +#include "update_engine/common/mock_prefs.h" +#include "update_engine/common/platform_constants.h" +#include "update_engine/common/prefs.h" +#include "update_engine/common/test_utils.h" +#include "update_engine/common/utils.h" +#include "update_engine/fake_system_state.h" +#include "update_engine/mock_p2p_manager.h" +#include "update_engine/mock_payload_state.h" +#include "update_engine/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; +#if USE_LIBCROS +using org::chromium::LibCrosServiceInterfaceProxyMock; +using org::chromium::UpdateEngineLibcrosProxyResolvedInterfaceProxyMock; +#endif // USE_LIBCROS +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) + : UpdateAttempter(system_state, nullptr, libcros_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() + : +#if USE_LIBCROS + 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_)), +#endif // USE_LIBCROS + 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 { + EXPECT_NE(nullptr, attempter_.system_state_); + EXPECT_EQ(0, attempter_.http_response_code_); + EXPECT_EQ(UpdateStatus::IDLE, attempter_.status_); + EXPECT_EQ(0.0, attempter_.download_progress_); + EXPECT_EQ(0, attempter_.last_checked_time_); + EXPECT_EQ("0.0.0.0", attempter_.new_version_); + EXPECT_EQ(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_)); + } + + 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_; +#if USE_LIBCROS + LibCrosServiceInterfaceProxyMock* service_interface_mock_; + UpdateEngineLibcrosProxyResolvedInterfaceProxyMock* + ue_proxy_resolved_interface_mock_; + LibCrosProxy libcros_proxy_; + UpdateAttempterUnderTest attempter_{&fake_system_state_, &libcros_proxy_}; +#else + UpdateAttempterUnderTest attempter_{&fake_system_state_, nullptr}; +#endif // USE_LIBCROS + OpenSSLWrapper openssl_wrapper_; + CertificateChecker certificate_checker_; + + NiceMock<MockActionProcessor>* processor_; + NiceMock<MockPrefs>* prefs_; // Shortcut to fake_system_state_->mock_prefs(). + NiceMock<MockConnectionManager> mock_connection_manager; + + bool actual_using_p2p_for_downloading_; + bool actual_using_p2p_for_sharing_; +}; + +void UpdateAttempterTest::ScheduleQuitMainLoop() { + loop_.PostTask( + FROM_HERE, + base::Bind([](brillo::BaseMessageLoop* loop) { loop->BreakLoop(); }, + base::Unretained(&loop_))); +} + +TEST_F(UpdateAttempterTest, ActionCompletedDownloadTest) { + unique_ptr<MockHttpFetcher> fetcher(new MockHttpFetcher("", 0, nullptr)); + fetcher->FailTransfer(503); // Sets the HTTP response code. + DownloadAction action(prefs_, nullptr, nullptr, nullptr, fetcher.release()); + 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(234U, attempter_.server_dictated_poll_interval_); + ASSERT_TRUE(attempter_.error_event_.get() == nullptr); +} + +TEST_F(UpdateAttempterTest, ConstructWithUpdatedMarkerTest) { + FakePrefs fake_prefs; + string boot_id; + EXPECT_TRUE(utils::GetBootId(&boot_id)); + fake_prefs.SetString(kPrefsUpdateCompletedOnBootId, boot_id); + fake_system_state_.set_prefs(&fake_prefs); + attempter_.Init(); + EXPECT_EQ(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status()); +} + +TEST_F(UpdateAttempterTest, GetErrorCodeForActionTest) { + 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; + EXPECT_EQ(ErrorCode::kFilesystemVerifierError, + GetErrorCodeForAction(&filesystem_verifier_action, + ErrorCode::kError)); + PostinstallRunnerAction postinstall_runner_action( + fake_system_state.fake_boot_control(), fake_system_state.fake_hardware()); + EXPECT_EQ(ErrorCode::kPostinstallRunnerError, + GetErrorCodeForAction(&postinstall_runner_action, + ErrorCode::kError)); + MockAction action_mock; + EXPECT_CALL(action_mock, Type()).WillOnce(Return("MockAction")); + EXPECT_EQ(ErrorCode::kError, + GetErrorCodeForAction(&action_mock, ErrorCode::kError)); +} + +TEST_F(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest) { + attempter_.omaha_request_params_->set_delta_okay(true); + EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)) + .WillOnce(Return(false)); + attempter_.DisableDeltaUpdateIfNeeded(); + EXPECT_TRUE(attempter_.omaha_request_params_->delta_okay()); + EXPECT_CALL(*prefs_, GetInt64(kPrefsDeltaUpdateFailures, _)) + .WillOnce(DoAll( + 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(), + 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()); + AbstractAction* action_3 = attempter_.actions_[3].get(); + ASSERT_NE(nullptr, action_3); + ASSERT_EQ(DownloadAction::StaticType(), action_3->Type()); + DownloadAction* download_action = static_cast<DownloadAction*>(action_3); + 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()); + AbstractAction* action_0 = attempter_.actions_[0].get(); + ASSERT_NE(nullptr, action_0); + ASSERT_EQ(InstallPlanAction::StaticType(), action_0->Type()); + InstallPlanAction* install_plan_action = + static_cast<InstallPlanAction*>(action_0); + InstallPlan* install_plan = install_plan_action->install_plan(); + EXPECT_EQ(0U, 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) { + FakeClock fake_clock; + fake_clock.SetBootTime(Time::FromTimeT(42)); + fake_system_state_.set_clock(&fake_clock); + FakePrefs fake_prefs; + fake_system_state_.set_prefs(&fake_prefs); + attempter_.Init(); + + Time boot_time; + EXPECT_FALSE(attempter_.GetBootTimeAtUpdate(&boot_time)); + + attempter_.WriteUpdateCompletedMarker(); + + EXPECT_TRUE(attempter_.GetBootTimeAtUpdate(&boot_time)); + EXPECT_EQ(boot_time.ToTimeT(), 42); +} + +TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedUnofficial) { + fake_system_state_.fake_hardware()->SetIsOfficialBuild(false); + EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed()); +} + +TEST_F(UpdateAttempterTest, AnyUpdateSourceAllowedOfficialDevmode) { + fake_system_state_.fake_hardware()->SetIsOfficialBuild(true); + fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(true); + EXPECT_TRUE(attempter_.IsAnyUpdateSourceAllowed()); +} + +TEST_F(UpdateAttempterTest, AnyUpdateSourceDisallowedOfficialNormal) { + fake_system_state_.fake_hardware()->SetIsOfficialBuild(true); + fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false); + EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed()); +} + +TEST_F(UpdateAttempterTest, CheckForUpdateAUTest) { + fake_system_state_.fake_hardware()->SetIsOfficialBuild(true); + fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false); + 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()->SetAreDevFeaturesEnabled(false); + attempter_.CheckForUpdate("", "autest-scheduled", true); + EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url()); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/update_engine-client.gyp b/update_engine/update_engine-client.gyp new file mode 100644 index 0000000..588fc63 --- /dev/null +++ b/update_engine/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/update_engine.conf b/update_engine/update_engine.conf new file mode 100644 index 0000000..449e669 --- /dev/null +++ b/update_engine/update_engine.conf
@@ -0,0 +1,2 @@ +PAYLOAD_MAJOR_VERSION=2 +PAYLOAD_MINOR_VERSION=3
diff --git a/update_engine/update_engine.gyp b/update_engine/update_engine.gyp new file mode 100644 index 0000000..38d6ba1 --- /dev/null +++ b/update_engine/update_engine.gyp
@@ -0,0 +1,581 @@ +# +# 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_binder%': '0', + 'USE_dbus%': '1', + 'USE_hwid_override%': '0', + 'USE_libcros%': '1', + 'USE_mtd%': '0', + 'USE_power_management%': '0', + 'USE_buffet%': '0', + }, + 'cflags': [ + '-g', + '-ffunction-sections', + '-Wall', + '-Wextra', + '-Werror', + '-Wno-unused-parameter', + ], + 'cflags_cc': [ + '-fno-strict-aliasing', + '-Wnon-virtual-dtor', + ], + 'ldflags': [ + '-Wl,--gc-sections', + ], + 'defines': [ + '__CHROMEOS__', + '_FILE_OFFSET_BITS=64', + '_POSIX_C_SOURCE=199309L', + 'USE_BINDER=<(USE_binder)', + 'USE_DBUS=<(USE_dbus)', + 'USE_HWID_OVERRIDE=<(USE_hwid_override)', + 'USE_LIBCROS=<(USE_libcros)', + 'USE_MTD=<(USE_mtd)', + 'USE_OMAHA=1', + 'USE_SHILL=1', + 'USE_WEAVE=<(USE_buffet)', + ], + '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', + 'libimgpatch', + '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/clock.cc', + 'common/constants.cc', + 'common/cpu_limiter.cc', + 'common/error_code_utils.cc', + 'common/hash_calculator.cc', + 'common/http_common.cc', + 'common/http_fetcher.cc', + 'common/hwid_override.cc', + 'common/multi_range_http_fetcher.cc', + 'common/platform_constants_chromeos.cc', + 'common/prefs.cc', + 'common/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', + 'expat', + 'libcurl', + 'libdebugd-client', + 'libmetrics-<(libbase_ver)', + 'libpower_manager-client', + 'libsession_manager-client', + 'libshill-client', + 'libssl', + 'libupdate_engine-client', + ], + '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', + 'certificate_checker.cc', + 'common_service.cc', + 'connection_manager.cc', + 'connection_utils.cc', + 'daemon.cc', + 'dbus_connection.cc', + 'dbus_service.cc', + 'hardware_chromeos.cc', + 'image_properties_chromeos.cc', + 'libcros_proxy.cc', + 'libcurl_http_fetcher.cc', + 'metrics.cc', + 'metrics_utils.cc', + 'omaha_request_action.cc', + 'omaha_request_params.cc', + 'omaha_response_handler_action.cc', + 'omaha_utils.cc', + 'p2p_manager.cc', + 'payload_state.cc', + 'power_manager_chromeos.cc', + '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', + 'weave_service_factory.cc', + ], + 'conditions': [ + ['USE_buffet == 1', { + 'sources': [ + 'weave_service.cc', + ], + 'variables': { + 'exported_deps': [ + 'libweave-<(libbase_ver)', + ], + }, + }], + ['USE_libcros == 1', { + 'dependencies': [ + 'update_engine-other-dbus-proxies', + ], + 'sources': [ + 'chrome_browser_proxy_resolver.cc', + ], + }], + ], + }, + # update_engine daemon. + { + 'target_name': 'update_engine', + 'type': 'executable', + 'dependencies': [ + 'libupdate_engine', + ], + 'sources': [ + 'main.cc', + ], + }, + # update_engine client library. + { + 'target_name': 'libupdate_engine_client', + 'type': 'static_library', + 'variables': { + 'deps': [ + 'dbus-1', + 'libupdate_engine-client', + ], + }, + 'sources': [ + 'client_library/client.cc', + 'client_library/client_dbus.cc', + 'update_status_utils.cc', + ], + 'include_dirs': [ + 'client_library/include', + ], + }, + # update_engine console client. + { + 'target_name': 'update_engine_client', + 'type': 'executable', + 'dependencies': [ + 'libupdate_engine_client', + ], + 'sources': [ + 'common/error_code_utils.cc', + 'omaha_utils.cc', + 'update_engine_client.cc', + ], + }, + # server-side code. This is used for delta_generator and unittests but not + # for any client code. + { + 'target_name': 'libpayload_generator', + 'type': 'static_library', + 'dependencies': [ + 'libpayload_consumer', + 'update_metadata-protos', + ], + 'variables': { + 'exported_deps': [ + 'ext2fs', + ], + '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', + 'payload_generator/xz_chromeos.cc', + ], + }, + # server-side delta generator. + { + 'target_name': 'delta_generator', + 'type': 'executable', + 'dependencies': [ + 'libpayload_consumer', + 'libpayload_generator', + ], + 'link_settings': { + 'ldflags!': [ + '-pie', + ], + }, + 'sources': [ + 'payload_generator/generate_delta_main.cc', + ], + }, + ], + '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', + 'sources': [ + 'common/http_common.cc', + 'test_http_server.cc', + ], + }, + # Test subprocess helper. + { + 'target_name': 'test_subprocess', + 'type': 'executable', + 'sources': [ + 'test_subprocess.cc', + ], + }, + # Main unittest file. + { + 'target_name': 'update_engine_unittests', + 'type': 'executable', + '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': [ + 'boot_control_chromeos_unittest.cc', + 'certificate_checker_unittest.cc', + 'common/action_pipe_unittest.cc', + 'common/action_processor_unittest.cc', + 'common/action_unittest.cc', + 'common/cpu_limiter_unittest.cc', + 'common/fake_prefs.cc', + 'common/file_fetcher.cc', # Only required for tests. + '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', + 'common_service_unittest.cc', + 'connection_manager_unittest.cc', + 'fake_shill_proxy.cc', + 'fake_system_state.cc', + 'hardware_chromeos_unittest.cc', + 'image_properties_chromeos_unittest.cc', + 'metrics_utils_unittest.cc', + 'omaha_request_action_unittest.cc', + 'omaha_request_params_unittest.cc', + 'omaha_response_handler_action_unittest.cc', + 'omaha_utils_unittest.cc', + 'p2p_manager_unittest.cc', + 'payload_consumer/bzip_extent_writer_unittest.cc', + 'payload_consumer/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_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', + ], + 'conditions': [ + ['USE_libcros == 1', { + 'sources': [ + 'chrome_browser_proxy_resolver_unittest.cc', + ], + }], + ], + }, + ], + }], + ], +}
diff --git a/update_engine/update_engine.rc b/update_engine/update_engine.rc new file mode 100644 index 0000000..e6f1eba --- /dev/null +++ b/update_engine/update_engine.rc
@@ -0,0 +1,15 @@ +service update_engine /system/bin/update_engine --logtostderr --foreground + class late_start + user root + group root system wakelock dbus inet cache + + writepid /dev/cpuctl/update_engine/tasks + +on post-fs + # update_engine + mkdir /dev/cpuctl/update_engine + chown system system /dev/cpuctl/update_engine/tasks + chmod 0666 /dev/cpuctl/update_engine/tasks + write /dev/cpuctl/update_engine/cpu.shares 1024 + write /dev/cpuctl/update_engine/cpu.rt_runtime_us 700000 + write /dev/cpuctl/update_engine/cpu.rt_period_us 1000000
diff --git a/update_engine/update_engine_client.cc b/update_engine/update_engine_client.cc new file mode 100644 index 0000000..31a1fe2 --- /dev/null +++ b/update_engine/update_engine_client.cc
@@ -0,0 +1,639 @@ +// +// 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 <memory> +#include <string> +#include <vector> + +#include <base/bind.h> +#include <base/command_line.h> +#include <base/logging.h> +#include <base/macros.h> +#include <brillo/daemons/daemon.h> +#include <brillo/flag_helper.h> + +#include "update_engine/client.h" +#include "update_engine/common/error_code.h" +#include "update_engine/common/error_code_utils.h" +#include "update_engine/omaha_utils.h" +#include "update_engine/status_update_handler.h" +#include "update_engine/update_status.h" +#include "update_engine/update_status_utils.h" + +using chromeos_update_engine::EolStatus; +using chromeos_update_engine::ErrorCode; +using chromeos_update_engine::UpdateStatusToString; +using chromeos_update_engine::utils::ErrorCodeToString; +using std::string; +using std::unique_ptr; +using std::vector; +using update_engine::UpdateStatus; + +namespace { + +// Constant to signal that we need to continue running the daemon after +// initialization. +const int kContinueRunning = -1; + +class UpdateEngineClient : public brillo::Daemon { + public: + UpdateEngineClient(int argc, char** argv) : argc_(argc), argv_(argv) { + } + + ~UpdateEngineClient() override = default; + + protected: + int OnInit() override { + int ret = Daemon::OnInit(); + if (ret != EX_OK) return ret; + + client_ = update_engine::UpdateEngineClient::CreateInstance(); + + if (!client_) { + LOG(ERROR) << "UpdateEngineService not available."; + return 1; + } + + // We can't call QuitWithExitCode from OnInit(), so we delay the execution + // of the ProcessFlags method after the Daemon initialization is done. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&UpdateEngineClient::ProcessFlagsAndExit, + base::Unretained(this))); + return EX_OK; + } + + private: + // Show the status of the update engine in stdout. + bool ShowStatus(); + + // Return whether we need to reboot. 0 if reboot is needed, 1 if an error + // occurred, 2 if no reboot is needed. + int GetNeedReboot(); + + // Main method that parses and triggers all the actions based on the passed + // flags. Returns the exit code of the program of kContinueRunning if it + // should not exit. + int ProcessFlags(); + + // Processes the flags and exits the program accordingly. + void ProcessFlagsAndExit(); + + // Copy of argc and argv passed to main(). + int argc_; + char** argv_; + + // Library-based client + unique_ptr<update_engine::UpdateEngineClient> client_; + + // Pointers to handlers for cleanup + vector<unique_ptr<update_engine::StatusUpdateHandler>> handlers_; + + DISALLOW_COPY_AND_ASSIGN(UpdateEngineClient); +}; + +class ExitingStatusUpdateHandler : public update_engine::StatusUpdateHandler { + public: + ~ExitingStatusUpdateHandler() override = default; + + void IPCError(const string& error) override; +}; + +void ExitingStatusUpdateHandler::IPCError(const string& error) { + LOG(ERROR) << error; + exit(1); +} + +class WatchingStatusUpdateHandler : public ExitingStatusUpdateHandler { + public: + ~WatchingStatusUpdateHandler() override = default; + + void HandleStatusUpdate(int64_t last_checked_time, + double progress, + UpdateStatus current_operation, + const string& new_version, + int64_t new_size) override; +}; + +void WatchingStatusUpdateHandler::HandleStatusUpdate( + int64_t last_checked_time, double progress, UpdateStatus current_operation, + const string& new_version, int64_t new_size) { + LOG(INFO) << "Got status update:"; + LOG(INFO) << " last_checked_time: " << last_checked_time; + LOG(INFO) << " progress: " << progress; + LOG(INFO) << " current_operation: " + << UpdateStatusToString(current_operation); + LOG(INFO) << " new_version: " << new_version; + LOG(INFO) << " new_size: " << new_size; +} + +bool UpdateEngineClient::ShowStatus() { + int64_t last_checked_time = 0; + double progress = 0.0; + UpdateStatus current_op; + string new_version; + int64_t new_size = 0; + + if (!client_->GetStatus(&last_checked_time, &progress, ¤t_op, + &new_version, &new_size)) { + return false; + } + + printf("LAST_CHECKED_TIME=%" PRIi64 + "\nPROGRESS=%f\nCURRENT_OP=%s\n" + "NEW_VERSION=%s\nNEW_SIZE=%" PRIi64 "\n", + last_checked_time, progress, UpdateStatusToString(current_op), + new_version.c_str(), new_size); + + return true; +} + +int UpdateEngineClient::GetNeedReboot() { + int64_t last_checked_time = 0; + double progress = 0.0; + UpdateStatus current_op; + string new_version; + int64_t new_size = 0; + + if (!client_->GetStatus(&last_checked_time, &progress, ¤t_op, + &new_version, &new_size)) { + return 1; + } + + if (current_op == UpdateStatus::UPDATED_NEED_REBOOT) { + return 0; + } + + return 2; +} + +class UpdateWaitHandler : public ExitingStatusUpdateHandler { + public: + explicit UpdateWaitHandler(bool exit_on_error, + update_engine::UpdateEngineClient* client) + : exit_on_error_(exit_on_error), client_(client) {} + + ~UpdateWaitHandler() override = default; + + void HandleStatusUpdate(int64_t last_checked_time, + double progress, + UpdateStatus current_operation, + const string& new_version, + int64_t new_size) override; + + private: + bool exit_on_error_; + update_engine::UpdateEngineClient* client_; +}; + +void UpdateWaitHandler::HandleStatusUpdate(int64_t /* last_checked_time */, + double /* progress */, + UpdateStatus current_operation, + const string& /* new_version */, + int64_t /* new_size */) { + if (exit_on_error_ && current_operation == UpdateStatus::IDLE) { + int last_attempt_error; + ErrorCode code = ErrorCode::kSuccess; + if (client_ && client_->GetLastAttemptError(&last_attempt_error)) + code = static_cast<ErrorCode>(last_attempt_error); + + LOG(ERROR) << "Update failed, current operation is " + << UpdateStatusToString(current_operation) + << ", last error code is " << ErrorCodeToString(code) << "(" + << last_attempt_error << ")"; + exit(1); + } + if (current_operation == UpdateStatus::UPDATED_NEED_REBOOT) { + LOG(INFO) << "Update succeeded -- reboot needed."; + exit(0); + } +} + +int UpdateEngineClient::ProcessFlags() { + DEFINE_string(app_version, "", "Force the current app version."); +#ifndef USE_NESTLABS + 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_string( + cohort_hint, "", "Set the current cohort hint to the passed value."); +#endif + 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."); +#ifdef USE_NESTLABS + DEFINE_string(url, "", "The URL of OTA update."); + DEFINE_bool(mark_boot_successful, false, "Mark current boot successful"); +#else + 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."); +#endif + 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."); +#ifndef USE_NESTLABS + DEFINE_bool(show_channel, false, "Show the current and target channels."); + DEFINE_bool(show_cohort_hint, false, "Show the current cohort hint."); + 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."); +#endif + DEFINE_bool(status, false, "Print the status to stdout."); +#ifndef USE_NESTLABS + 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."); +#endif + 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."); + DEFINE_bool(last_attempt_error, false, "Show the last attempt error."); +#ifndef USE_NESTLABS + DEFINE_bool(eol_status, false, "Show the current end-of-life status."); +#endif + + // Boilerplate init commands. + base::CommandLine::Init(argc_, argv_); +#ifndef USE_NESTLABS + brillo::FlagHelper::Init(argc_, argv_, "Chromium OS Update Engine Client"); +#else + brillo::FlagHelper::Init(argc_, argv_, "Nestlabs Update Engine Client"); +#endif + + // Ensure there are no positional arguments. + const 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 ..."; + + if (client_->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;)"; + } else { + LOG(ERROR) << "ResetStatus failed"; + return 1; + } + } + +#ifndef USE_NESTLABS + // 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 { + if (!client_->SetUpdateOverCellularPermission(allowed)) { + LOG(ERROR) << "Error setting the update over cellular setting."; + return 1; + } + } + } + + // Show the current update over cellular network setting. + if (FLAGS_show_update_over_cellular) { + bool allowed; + + if (!client_->GetUpdateOverCellularPermission(&allowed)) { + LOG(ERROR) << "Error getting the update over cellular setting."; + return 1; + } + + LOG(INFO) << "Current update over cellular network setting: " + << (allowed ? "ENABLED" : "DISABLED"); + } + + // Change/show the cohort hint. + bool set_cohort_hint = + base::CommandLine::ForCurrentProcess()->HasSwitch("cohort_hint"); + if (set_cohort_hint) { + LOG(INFO) << "Setting cohort hint to: \"" << FLAGS_cohort_hint << "\""; + if (!client_->SetCohortHint(FLAGS_cohort_hint)) { + LOG(ERROR) << "Error setting the cohort hint."; + return 1; + } + } + + if (FLAGS_show_cohort_hint || set_cohort_hint) { + string cohort_hint; + if (!client_->GetCohortHint(&cohort_hint)) { + LOG(ERROR) << "Error getting the cohort hint."; + return 1; + } + + LOG(INFO) << "Current cohort hint: \"" << cohort_hint << "\""; + } + + 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 { + if (!client_->SetP2PUpdatePermission(enabled)) { + LOG(ERROR) << "Error setting the peer-to-peer update setting."; + return 1; + } + } + } +#endif + + // Show the rollback availability. + if (FLAGS_can_rollback) { + string rollback_partition; + + if (!client_->GetRollbackPartition(&rollback_partition)) { + LOG(ERROR) << "Error while querying rollback partition availabilty."; + return 1; + } + + 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; + } + } + +#ifndef USE_NESTLABS + // Show the current P2P enabled setting. + if (FLAGS_show_p2p_update) { + bool enabled; + + if (!client_->GetP2PUpdatePermission(&enabled)) { + LOG(ERROR) << "Error getting the peer-to-peer update setting."; + return 1; + } + + LOG(INFO) << "Current update using P2P setting: " + << (enabled ? "ENABLED" : "DISABLED"); + } + + // First, update the target channel if requested. + if (!FLAGS_channel.empty()) { + if (!client_->SetTargetChannel(FLAGS_channel, FLAGS_powerwash)) { + LOG(ERROR) << "Error setting the channel."; + return 1; + } + + LOG(INFO) << "Channel permanently set to: " << FLAGS_channel; + } + + // Show the current and target channels if requested. + if (FLAGS_show_channel) { + string current_channel; + string target_channel; + + if (!client_->GetChannel(¤t_channel)) { + LOG(ERROR) << "Error getting the current channel."; + return 1; + } + + if (!client_->GetTargetChannel(&target_channel)) { + LOG(ERROR) << "Error getting the target channel."; + return 1; + } + + LOG(INFO) << "Current Channel: " << current_channel; + + if (!target_channel.empty()) + LOG(INFO) << "Target Channel (pending update): " << target_channel; + } +#endif + +#ifndef USE_NESTLABS + bool do_update_request = FLAGS_check_for_update | FLAGS_update | + !FLAGS_app_version.empty() | + !FLAGS_omaha_url.empty(); + + if (FLAGS_update) FLAGS_follow = true; +#else + bool do_update_request = !FLAGS_app_version.empty() | + !FLAGS_url.empty(); +#endif + + 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."; + if (!client_->Rollback(FLAGS_powerwash)) { + LOG(ERROR) << "Rollback request failed."; + return 1; + } + } + +#ifndef USE_NESTLABS + // 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."; + if (!client_->AttemptUpdate(app_version, FLAGS_omaha_url, + FLAGS_interactive)) { + LOG(ERROR) << "Error checking for update."; + return 1; + } + } +#else + // Initiate an update check, if necessary. + if (do_update_request) { + LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored."; + string app_version = FLAGS_app_version; + LOG(INFO) << "Initiating update check and install."; + if (!client_->AttemptUpdate(app_version, FLAGS_url, + FLAGS_interactive)) { + LOG(ERROR) << "Error checking for update."; + return 1; + } + } + + if (FLAGS_mark_boot_successful) { + LOG(INFO) << "Mark current slot successful"; + if (!client_->MarkBootSuccessful()) { + LOG(ERROR) << "Error marking the current slot successful"; + return 1; + } + } +#endif + + // 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..."; + if (!ShowStatus()) { + LOG(ERROR) << "Failed to query status"; + return 1; + } + return 0; + } + + if (FLAGS_follow) { + LOG(INFO) << "Waiting for update to complete."; + auto handler = new UpdateWaitHandler(true, client_.get()); + handlers_.emplace_back(handler); + client_->RegisterStatusUpdateHandler(handler); + return kContinueRunning; + } + + if (FLAGS_watch_for_updates) { + LOG(INFO) << "Watching for status updates."; + auto handler = new WatchingStatusUpdateHandler(); + handlers_.emplace_back(handler); + client_->RegisterStatusUpdateHandler(handler); + return kContinueRunning; + } + + if (FLAGS_reboot) { + LOG(INFO) << "Requesting a reboot..."; + client_->RebootIfNeeded(); + return 0; + } + + if (FLAGS_prev_version) { + string prev_version; + + if (!client_->GetPrevVersion(&prev_version)) { + LOG(ERROR) << "Error getting previous version."; + } else { + LOG(INFO) << "Previous version = " << prev_version; + } + } + + if (FLAGS_is_reboot_needed) { + int ret = GetNeedReboot(); + + if (ret == 1) { + LOG(ERROR) << "Could not query the current operation."; + } + + return ret; + } + + if (FLAGS_block_until_reboot_is_needed) { + auto handler = new UpdateWaitHandler(false, nullptr); + handlers_.emplace_back(handler); + client_->RegisterStatusUpdateHandler(handler); + return kContinueRunning; + } + + if (FLAGS_last_attempt_error) { + int last_attempt_error; + if (!client_->GetLastAttemptError(&last_attempt_error)) { + LOG(ERROR) << "Error getting last attempt error."; + } else { + ErrorCode code = static_cast<ErrorCode>(last_attempt_error); + printf( + "ERROR_CODE=%i\n" + "ERROR_MESSAGE=%s\n", + last_attempt_error, + ErrorCodeToString(code).c_str()); + } + } + +#ifndef USE_NESTLABS + if (FLAGS_eol_status) { + int eol_status; + if (!client_->GetEolStatus(&eol_status)) { + LOG(ERROR) << "Error getting the end-of-life status."; + } else { + EolStatus eol_status_code = static_cast<EolStatus>(eol_status); + printf("EOL_STATUS=%s\n", EolStatusToString(eol_status_code)); + } + } +#endif + + return 0; +} + +void UpdateEngineClient::ProcessFlagsAndExit() { + int ret = ProcessFlags(); + if (ret != kContinueRunning) + QuitWithExitCode(ret); +} + +} // namespace + +int main(int argc, char** argv) { + UpdateEngineClient client(argc, argv); + return client.Run(); +}
diff --git a/update_engine/update_engine_client_android.cc b/update_engine/update_engine_client_android.cc new file mode 100644 index 0000000..989a97e --- /dev/null +++ b/update_engine/update_engine_client_android.cc
@@ -0,0 +1,250 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include <sysexits.h> +#include <unistd.h> + +#include <string> +#include <vector> + +#include <base/bind.h> +#include <base/callback.h> +#include <base/command_line.h> +#include <base/logging.h> +#include <base/strings/string_split.h> +#include <binder/IServiceManager.h> +#include <binderwrapper/binder_wrapper.h> +#include <brillo/binder_watcher.h> +#include <brillo/daemons/daemon.h> +#include <brillo/flag_helper.h> +#include <brillo/message_loops/message_loop.h> +#include <brillo/syslog_logging.h> +#include <utils/String16.h> +#include <utils/StrongPointer.h> + +#include "android/os/BnUpdateEngineCallback.h" +#include "android/os/IUpdateEngine.h" +#include "update_engine/client_library/include/update_engine/update_status.h" +#include "update_engine/common/error_code.h" +#include "update_engine/common/error_code_utils.h" +#include "update_engine/update_status_utils.h" + +using android::binder::Status; + +namespace chromeos_update_engine { +namespace internal { + +class UpdateEngineClientAndroid : public brillo::Daemon { + public: + UpdateEngineClientAndroid(int argc, char** argv) : argc_(argc), argv_(argv) { + } + + int ExitWhenIdle(const Status& status); + int ExitWhenIdle(int return_code); + + private: + class UECallback : public android::os::BnUpdateEngineCallback { + public: + explicit UECallback(UpdateEngineClientAndroid* client) : client_(client) {} + + // android::os::BnUpdateEngineCallback overrides. + Status onStatusUpdate(int status_code, float progress) override; + Status onPayloadApplicationComplete(int error_code) override; + + private: + UpdateEngineClientAndroid* client_; + }; + + int OnInit() override; + + // Called whenever the UpdateEngine daemon dies. + void UpdateEngineServiceDied(); + + // Copy of argc and argv passed to main(). + int argc_; + char** argv_; + + android::sp<android::os::IUpdateEngine> service_; + android::sp<android::os::BnUpdateEngineCallback> callback_; + + brillo::BinderWatcher binder_watcher_; +}; + +Status UpdateEngineClientAndroid::UECallback::onStatusUpdate( + int status_code, float progress) { + update_engine::UpdateStatus status = + static_cast<update_engine::UpdateStatus>(status_code); + LOG(INFO) << "onStatusUpdate(" << UpdateStatusToString(status) << " (" + << status_code << "), " << progress << ")"; + return Status::ok(); +} + +Status UpdateEngineClientAndroid::UECallback::onPayloadApplicationComplete( + int error_code) { + ErrorCode code = static_cast<ErrorCode>(error_code); + LOG(INFO) << "onPayloadApplicationComplete(" << utils::ErrorCodeToString(code) + << " (" << error_code << "))"; + client_->ExitWhenIdle(code == ErrorCode::kSuccess ? EX_OK : 1); + return Status::ok(); +} + +int UpdateEngineClientAndroid::OnInit() { + int ret = Daemon::OnInit(); + if (ret != EX_OK) + return ret; + + DEFINE_bool(update, false, "Start a new update, if no update in progress."); + DEFINE_string(payload, + "http://127.0.0.1:8080/payload", + "The URI to the update payload to use."); + DEFINE_int64(offset, 0, + "The offset in the payload where the CrAU update starts. " + "Used when --update is passed."); + DEFINE_int64(size, 0, + "The size of the CrAU part of the payload. If 0 is passed, it " + "will be autodetected. Used when --update is passed."); + DEFINE_string(headers, + "", + "A list of key-value pairs, one element of the list per line. " + "Used when --update is passed."); + + DEFINE_bool(suspend, false, "Suspend an ongoing update and exit."); + DEFINE_bool(resume, false, "Resume a suspended update."); + DEFINE_bool(cancel, false, "Cancel the ongoing update and exit."); + DEFINE_bool(reset_status, false, "Reset an already applied update and exit."); + DEFINE_bool(follow, + false, + "Follow status update changes until a final state is reached. " + "Exit status is 0 if the update succeeded, and 1 otherwise."); + + // Boilerplate init commands. + base::CommandLine::Init(argc_, argv_); + brillo::FlagHelper::Init(argc_, argv_, "Android Update Engine Client"); + if (argc_ == 1) { + LOG(ERROR) << "Nothing to do. Run with --help for help."; + return 1; + } + + // Ensure there are no positional arguments. + const std::vector<std::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; + } + + bool keep_running = false; + brillo::InitLog(brillo::kLogToStderr); + + // Initialize a binder watcher early in the process before any interaction + // with the binder driver. + binder_watcher_.Init(); + + android::status_t status = android::getService( + android::String16("android.os.UpdateEngineService"), &service_); + if (status != android::OK) { + LOG(ERROR) << "Failed to get IUpdateEngine binder from service manager: " + << Status::fromStatusT(status).toString8(); + return ExitWhenIdle(1); + } + + if (FLAGS_suspend) { + return ExitWhenIdle(service_->suspend()); + } + + if (FLAGS_resume) { + return ExitWhenIdle(service_->resume()); + } + + if (FLAGS_cancel) { + return ExitWhenIdle(service_->cancel()); + } + + if (FLAGS_reset_status) { + return ExitWhenIdle(service_->resetStatus()); + } + + if (FLAGS_follow) { + // Register a callback object with the service. + callback_ = new UECallback(this); + bool bound; + if (!service_->bind(callback_, &bound).isOk() || !bound) { + LOG(ERROR) << "Failed to bind() the UpdateEngine daemon."; + return 1; + } + keep_running = true; + } + + if (FLAGS_update) { + std::vector<std::string> headers = base::SplitString( + FLAGS_headers, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); + std::vector<android::String16> and_headers; + for (const auto& header : headers) { + and_headers.push_back(android::String16{header.data(), header.size()}); + } + Status status = service_->applyPayload( + android::String16{FLAGS_payload.data(), FLAGS_payload.size()}, + FLAGS_offset, + FLAGS_size, + and_headers); + if (!status.isOk()) + return ExitWhenIdle(status); + } + + if (!keep_running) + return ExitWhenIdle(EX_OK); + + // When following updates status changes, exit if the update_engine daemon + // dies. + android::BinderWrapper::Create(); + android::BinderWrapper::Get()->RegisterForDeathNotifications( + android::os::IUpdateEngine::asBinder(service_), + base::Bind(&UpdateEngineClientAndroid::UpdateEngineServiceDied, + base::Unretained(this))); + + return EX_OK; +} + +int UpdateEngineClientAndroid::ExitWhenIdle(const Status& status) { + if (status.isOk()) + return ExitWhenIdle(EX_OK); + LOG(ERROR) << status.toString8(); + return ExitWhenIdle(status.exceptionCode()); +} + +int UpdateEngineClientAndroid::ExitWhenIdle(int return_code) { + auto delayed_exit = base::Bind( + &Daemon::QuitWithExitCode, base::Unretained(this), return_code); + if (!brillo::MessageLoop::current()->PostTask(delayed_exit)) + return 1; + return EX_OK; +} + +void UpdateEngineClientAndroid::UpdateEngineServiceDied() { + LOG(ERROR) << "UpdateEngineService died."; + QuitWithExitCode(1); +} + +} // namespace internal +} // namespace chromeos_update_engine + +int main(int argc, char** argv) { + chromeos_update_engine::internal::UpdateEngineClientAndroid client( + argc, argv); + return client.Run(); +}
diff --git a/update_engine/update_manager/boxed_value.cc b/update_engine/update_manager/boxed_value.cc new file mode 100644 index 0000000..9758d33 --- /dev/null +++ b/update_engine/update_manager/boxed_value.cc
@@ -0,0 +1,179 @@ +// +// 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/connection_utils.h" +#include "update_engine/update_manager/shill_provider.h" +#include "update_engine/update_manager/updater_provider.h" + +using chromeos_update_engine::ConnectionTethering; +using chromeos_update_engine::ConnectionType; +using chromeos_update_engine::connection_utils::StringForConnectionType; +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); +} + +template<> +string BoxedValue::ValuePrinter<ConnectionType>(const void* value) { + const ConnectionType* val = reinterpret_cast<const ConnectionType*>(value); + return StringForConnectionType(*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 += StringForConnectionType(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_engine/update_manager/boxed_value.h b/update_engine/update_manager/boxed_value.h new file mode 100644 index 0000000..5f41835 --- /dev/null +++ b/update_engine/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_engine/update_manager/boxed_value_unittest.cc b/update_engine/update_manager/boxed_value_unittest.cc new file mode 100644 index 0000000..2a086a6 --- /dev/null +++ b/update_engine/update_manager/boxed_value_unittest.cc
@@ -0,0 +1,234 @@ +// +// 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 chromeos_update_engine::ConnectionTethering; +using chromeos_update_engine::ConnectionType; +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_engine/update_manager/chromeos_policy.cc b/update_engine/update_manager/chromeos_policy.cc new file mode 100644 index 0000000..ec2b9f0 --- /dev/null +++ b/update_engine/update_manager/chromeos_policy.cc
@@ -0,0 +1,979 @@ +// +// 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/error_code_utils.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::ConnectionTethering; +using chromeos_update_engine::ConnectionType; +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::ErrorCodeToString(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::ErrorCodeToString(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::kNonCriticalUpdateInOOBE: + case ErrorCode::kOmahaUpdateDeferredForBackoff: + case ErrorCode::kPostinstallPowerwashError: + case ErrorCode::kUpdateCanceledByChannelChange: + case ErrorCode::kOmahaRequestXMLHasEntityDecl: + case ErrorCode::kFilesystemVerifierError: + case ErrorCode::kUserCanceled: + LOG(INFO) << "Not changing URL index or failure count due to error " + << chromeos_update_engine::utils::ErrorCodeToString(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::ErrorCodeToString(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::StartsWith(url, "http://", + base::CompareCase::INSENSITIVE_ASCII); +} + +} // 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) { + bool kiosk_app_control_chrome_version = false; + + // 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) { + // Check whether allow kiosk app to control chrome version policy. This + // policy is only effective when AU is disabled by admin. + const bool* allow_kiosk_app_control_chrome_version_p = ec->GetValue( + dp_provider->var_allow_kiosk_app_control_chrome_version()); + kiosk_app_control_chrome_version = + allow_kiosk_app_control_chrome_version_p && + *allow_kiosk_app_control_chrome_version_p; + if (!kiosk_app_control_chrome_version) { + // No kiosk pin chrome version policy. AU is really disabled. + LOG(INFO) << "Updates disabled by policy, blocking update checks."; + return EvalStatus::kAskMeAgainLater; + } + } + + if (kiosk_app_control_chrome_version) { + // Get the required platform version from Chrome. + const string* kiosk_required_platform_version_p = + ec->GetValue(system_provider->var_kiosk_required_platform_version()); + if (!kiosk_required_platform_version_p) { + LOG(INFO) << "Kiosk app required platform version is not fetched, " + "blocking update checks"; + return EvalStatus::kAskMeAgainLater; + } + + result->target_version_prefix = *kiosk_required_platform_version_p; + LOG(INFO) << "Allow kiosk app to control Chrome version policy is set," + << ", target version is " + << (kiosk_required_platform_version_p + ? *kiosk_required_platform_version_p + : std::string("latest")); + } else { + // 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_engine/update_manager/chromeos_policy.h b/update_engine/update_manager/chromeos_policy.h new file mode 100644 index 0000000..b4370c4 --- /dev/null +++ b/update_engine/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_engine/update_manager/chromeos_policy_unittest.cc b/update_engine/update_manager/chromeos_policy_unittest.cc new file mode 100644 index 0000000..0c38700 --- /dev/null +++ b/update_engine/update_manager/chromeos_policy_unittest.cc
@@ -0,0 +1,1699 @@ +// +// 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::ConnectionTethering; +using chromeos_update_engine::ConnectionType; +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, UpdateCheckAllowedKioskPin) { + // Update check is allowed. + SetUpdateCheckAllowed(true); + + // A typical setup for kiosk pin policy: AU disabled, allow kiosk to pin + // and there is a kiosk required platform version. + fake_state_.device_policy_provider()->var_update_disabled()->reset( + new bool(true)); + fake_state_.device_policy_provider() + ->var_allow_kiosk_app_control_chrome_version() + ->reset(new bool(true)); + fake_state_.system_provider()->var_kiosk_required_platform_version()->reset( + new string("1234.0.0")); + + UpdateCheckParams result; + ExpectPolicyStatus(EvalStatus::kSucceeded, + &Policy::UpdateCheckAllowed, &result); + EXPECT_TRUE(result.updates_enabled); + EXPECT_EQ("1234.0.0", result.target_version_prefix); + EXPECT_FALSE(result.is_interactive); +} + +TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedDisabledWhenNoKioskPin) { + // Update check is allowed. + SetUpdateCheckAllowed(true); + + // Disable AU policy is set but kiosk pin policy is set to false. Update is + // disabled in such case. + fake_state_.device_policy_provider()->var_update_disabled()->reset( + new bool(true)); + fake_state_.device_policy_provider() + ->var_allow_kiosk_app_control_chrome_version() + ->reset(new bool(false)); + + UpdateCheckParams result; + ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, + &Policy::UpdateCheckAllowed, &result); +} + +TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedKioskPinWithNoRequiredVersion) { + // Update check is allowed. + SetUpdateCheckAllowed(true); + + // AU disabled, allow kiosk to pin but there is no kiosk required platform + // version (i.e. app does not provide the info). Update to latest in such + // case. + fake_state_.device_policy_provider()->var_update_disabled()->reset( + new bool(true)); + fake_state_.device_policy_provider() + ->var_allow_kiosk_app_control_chrome_version() + ->reset(new bool(true)); + fake_state_.system_provider()->var_kiosk_required_platform_version()->reset( + new string()); + + UpdateCheckParams result; + ExpectPolicyStatus(EvalStatus::kSucceeded, + &Policy::UpdateCheckAllowed, &result); + EXPECT_TRUE(result.updates_enabled); + EXPECT_TRUE(result.target_version_prefix.empty()); + EXPECT_FALSE(result.is_interactive); +} + +TEST_F(UmChromeOSPolicyTest, + UpdateCheckAllowedKioskPinWithFailedGetRequiredVersionCall) { + // AU disabled, allow kiosk to pin but D-Bus call to get required platform + // version failed. Defer update check in this case. + fake_state_.device_policy_provider()->var_update_disabled()->reset( + new bool(true)); + fake_state_.device_policy_provider() + ->var_allow_kiosk_app_control_chrome_version() + ->reset(new bool(true)); + fake_state_.system_provider()->var_kiosk_required_platform_version()->reset( + nullptr); + + UpdateCheckParams result; + ExpectPolicyStatus(EvalStatus::kAskMeAgainLater, + &Policy::UpdateCheckAllowed, &result); +} + +TEST_F(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_engine/update_manager/config_provider.h b/update_engine/update_manager/config_provider.h new file mode 100644 index 0000000..36d57a7 --- /dev/null +++ b/update_engine/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_engine/update_manager/default_policy.cc b/update_engine/update_manager/default_policy.cc new file mode 100644 index 0000000..9a5ce7e --- /dev/null +++ b/update_engine/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_engine/update_manager/default_policy.h b/update_engine/update_manager/default_policy.h new file mode 100644 index 0000000..3f41178 --- /dev/null +++ b/update_engine/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_engine/update_manager/device_policy_provider.h b/update_engine/update_manager/device_policy_provider.h new file mode 100644 index 0000000..3537d13 --- /dev/null +++ b/update_engine/update_manager/device_policy_provider.h
@@ -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. +// + +#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<chromeos_update_engine::ConnectionType>>* + var_allowed_connection_types_for_update() = 0; + + // Variable stating the name of the device owner. For enterprise enrolled + // devices, this will be an empty string. + virtual Variable<std::string>* var_owner() = 0; + + virtual Variable<bool>* var_http_downloads_enabled() = 0; + + virtual Variable<bool>* var_au_p2p_enabled() = 0; + + virtual Variable<bool>* var_allow_kiosk_app_control_chrome_version() = 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_engine/update_manager/evaluation_context-inl.h b/update_engine/update_manager/evaluation_context-inl.h new file mode 100644 index 0000000..937adf4 --- /dev/null +++ b/update_engine/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_engine/update_manager/evaluation_context.cc b/update_engine/update_manager/evaluation_context.cc new file mode 100644 index 0000000..63f7d9b --- /dev/null +++ b/update_engine/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 std::move(callback_); +} + +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_engine/update_manager/evaluation_context.h b/update_engine/update_manager/evaluation_context.h new file mode 100644 index 0000000..df5816a --- /dev/null +++ b/update_engine/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_engine/update_manager/evaluation_context_unittest.cc b/update_engine/update_manager/evaluation_context_unittest.cc new file mode 100644 index 0000000..1e61db7 --- /dev/null +++ b/update_engine/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_engine/update_manager/fake_config_provider.h b/update_engine/update_manager/fake_config_provider.h new file mode 100644 index 0000000..6a324df --- /dev/null +++ b/update_engine/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_engine/update_manager/fake_device_policy_provider.h b/update_engine/update_manager/fake_device_policy_provider.h new file mode 100644 index 0000000..9e4f5b7 --- /dev/null +++ b/update_engine/update_manager/fake_device_policy_provider.h
@@ -0,0 +1,106 @@ +// +// 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<chromeos_update_engine::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_; + } + + FakeVariable<bool>* var_allow_kiosk_app_control_chrome_version() override { + return &var_allow_kiosk_app_control_chrome_version_; + } + + 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<chromeos_update_engine::ConnectionType>> + var_allowed_connection_types_for_update_{ + "allowed_connection_types_for_update", kVariableModePoll}; + FakeVariable<std::string> var_owner_{"owner", kVariableModePoll}; + FakeVariable<bool> var_http_downloads_enabled_{ + "http_downloads_enabled", kVariableModePoll}; + FakeVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled", kVariableModePoll}; + FakeVariable<bool> var_allow_kiosk_app_control_chrome_version_{ + "allow_kiosk_app_control_chrome_version", kVariableModePoll}; + + DISALLOW_COPY_AND_ASSIGN(FakeDevicePolicyProvider); +}; + +} // namespace chromeos_update_manager + +#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_engine/update_manager/fake_random_provider.h b/update_engine/update_manager/fake_random_provider.h new file mode 100644 index 0000000..643a194 --- /dev/null +++ b/update_engine/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_engine/update_manager/fake_shill_provider.h b/update_engine/update_manager/fake_shill_provider.h new file mode 100644 index 0000000..7f1c8f5 --- /dev/null +++ b/update_engine/update_manager/fake_shill_provider.h
@@ -0,0 +1,62 @@ +// +// 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<chromeos_update_engine::ConnectionType>* var_conn_type() + override { + return &var_conn_type_; + } + + FakeVariable<chromeos_update_engine::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<chromeos_update_engine::ConnectionType> var_conn_type_{ + "conn_type", kVariableModePoll}; + FakeVariable<chromeos_update_engine::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_engine/update_manager/fake_state.h b/update_engine/update_manager/fake_state.h new file mode 100644 index 0000000..fd7a88c --- /dev/null +++ b/update_engine/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_engine/update_manager/fake_system_provider.h b/update_engine/update_manager/fake_system_provider.h new file mode 100644 index 0000000..0f4dff4 --- /dev/null +++ b/update_engine/update_manager/fake_system_provider.h
@@ -0,0 +1,66 @@ +// +// 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_; + } + + FakeVariable<std::string>* var_kiosk_required_platform_version() override { + return &var_kiosk_required_platform_version_; + } + + 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}; + FakeVariable<std::string> var_kiosk_required_platform_version_{ + "kiosk_required_platform_version", kVariableModePoll}; + + DISALLOW_COPY_AND_ASSIGN(FakeSystemProvider); +}; + +} // namespace chromeos_update_manager + +#endif // UPDATE_ENGINE_UPDATE_MANAGER_FAKE_SYSTEM_PROVIDER_H_
diff --git a/update_engine/update_manager/fake_time_provider.h b/update_engine/update_manager/fake_time_provider.h new file mode 100644 index 0000000..2aea2e7 --- /dev/null +++ b/update_engine/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_engine/update_manager/fake_update_manager.h b/update_engine/update_manager/fake_update_manager.h new file mode 100644 index 0000000..2ea00b6 --- /dev/null +++ b/update_engine/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_engine/update_manager/fake_updater_provider.h b/update_engine/update_manager/fake_updater_provider.h new file mode 100644 index 0000000..44389f4 --- /dev/null +++ b/update_engine/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_engine/update_manager/fake_variable.h b/update_engine/update_manager/fake_variable.h new file mode 100644 index 0000000..2f8e079 --- /dev/null +++ b/update_engine/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_engine/update_manager/generic_variables.h b/update_engine/update_manager/generic_variables.h new file mode 100644 index 0000000..f87a05e --- /dev/null +++ b/update_engine/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_engine/update_manager/generic_variables_unittest.cc b/update_engine/update_manager/generic_variables_unittest.cc new file mode 100644 index 0000000..cb0c48f --- /dev/null +++ b/update_engine/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_engine/update_manager/mock_policy.h b/update_engine/update_manager/mock_policy.h new file mode 100644 index 0000000..14470e9 --- /dev/null +++ b/update_engine/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_engine/update_manager/mock_variable.h b/update_engine/update_manager/mock_variable.h new file mode 100644 index 0000000..1493491 --- /dev/null +++ b/update_engine/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_engine/update_manager/policy.cc b/update_engine/update_manager/policy.cc new file mode 100644 index 0000000..151c225 --- /dev/null +++ b/update_engine/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_engine/update_manager/policy.h b/update_engine/update_manager/policy.h new file mode 100644 index 0000000..fae1494 --- /dev/null +++ b/update_engine/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_engine/update_manager/policy_utils.h b/update_engine/update_manager/policy_utils.h new file mode 100644 index 0000000..960987e --- /dev/null +++ b/update_engine/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_engine/update_manager/prng.h b/update_engine/update_manager/prng.h new file mode 100644 index 0000000..64f886c --- /dev/null +++ b/update_engine/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_engine/update_manager/prng_unittest.cc b/update_engine/update_manager/prng_unittest.cc new file mode 100644 index 0000000..2a3f689 --- /dev/null +++ b/update_engine/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_engine/update_manager/provider.h b/update_engine/update_manager/provider.h new file mode 100644 index 0000000..84335a2 --- /dev/null +++ b/update_engine/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_engine/update_manager/random_provider.h b/update_engine/update_manager/random_provider.h new file mode 100644 index 0000000..60df62d --- /dev/null +++ b/update_engine/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_engine/update_manager/real_config_provider.cc b/update_engine/update_manager/real_config_provider.cc new file mode 100644 index 0000000..97e624e --- /dev/null +++ b/update_engine/update_manager/real_config_provider.cc
@@ -0,0 +1,30 @@ +// +// 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 "update_engine/update_manager/generic_variables.h" + +namespace chromeos_update_manager { + +bool RealConfigProvider::Init() { + var_is_oobe_enabled_.reset(new ConstCopyVariable<bool>( + "is_oobe_enabled", hardware_->IsOOBEEnabled())); + + return true; +} + +} // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_config_provider.h b/update_engine/update_manager/real_config_provider.h new file mode 100644 index 0000000..e79ae60 --- /dev/null +++ b/update_engine/update_manager/real_config_provider.h
@@ -0,0 +1,52 @@ +// +// 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 "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: + std::unique_ptr<ConstCopyVariable<bool>> var_is_oobe_enabled_; + + chromeos_update_engine::HardwareInterface* hardware_; + + DISALLOW_COPY_AND_ASSIGN(RealConfigProvider); +}; + +} // namespace chromeos_update_manager + +#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_CONFIG_PROVIDER_H_
diff --git a/update_engine/update_manager/real_device_policy_provider.cc b/update_engine/update_manager/real_device_policy_provider.cc new file mode 100644 index 0000000..d9880c3 --- /dev/null +++ b/update_engine/update_manager/real_device_policy_provider.cc
@@ -0,0 +1,192 @@ +// +// 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/connection_utils.h" +#include "update_engine/update_manager/generic_variables.h" + +using base::TimeDelta; +using brillo::MessageLoop; +using chromeos_update_engine::ConnectionType; +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(); + +#if USE_DBUS + // 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))); +#endif // USE_DBUS + 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 = + chromeos_update_engine::connection_utils::ParseConnectionType(type_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); + UpdateVariable(&var_allow_kiosk_app_control_chrome_version_, + &DevicePolicy::GetAllowKioskAppControlChromeVersion); +} + +} // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_device_policy_provider.h b/update_engine/update_manager/real_device_policy_provider.h new file mode 100644 index 0000000..5b5ee58 --- /dev/null +++ b/update_engine/update_manager/real_device_policy_provider.h
@@ -0,0 +1,182 @@ +// +// 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 <memory> +#include <set> +#include <string> + +#include <brillo/message_loops/message_loop.h> +#include <gtest/gtest_prod.h> // for FRIEND_TEST +#include <policy/libpolicy.h> +#if USE_DBUS +#include <session_manager/dbus-proxies.h> +#endif // USE_DBUS + +#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: +#if USE_DBUS + RealDevicePolicyProvider( + std::unique_ptr<org::chromium::SessionManagerInterfaceProxyInterface> + session_manager_proxy, + policy::PolicyProvider* policy_provider) + : policy_provider_(policy_provider), + session_manager_proxy_(std::move(session_manager_proxy)) {} +#endif // USE_DBUS + explicit RealDevicePolicyProvider(policy::PolicyProvider* policy_provider) + : policy_provider_(policy_provider) {} + ~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<chromeos_update_engine::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_; + } + + Variable<bool>* var_allow_kiosk_app_control_chrome_version() override { + return &var_allow_kiosk_app_control_chrome_version_; + } + + 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<chromeos_update_engine::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}; + +#if USE_DBUS + // The DBus (mockable) session manager proxy. + std::unique_ptr<org::chromium::SessionManagerInterfaceProxyInterface> + session_manager_proxy_; +#endif // USE_DBUS + + // 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<chromeos_update_engine::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"}; + AsyncCopyVariable<bool> var_allow_kiosk_app_control_chrome_version_{ + "allow_kiosk_app_control_chrome_version"}; + + DISALLOW_COPY_AND_ASSIGN(RealDevicePolicyProvider); +}; + +} // namespace chromeos_update_manager + +#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_DEVICE_POLICY_PROVIDER_H_
diff --git a/update_engine/update_manager/real_device_policy_provider_unittest.cc b/update_engine/update_manager/real_device_policy_provider_unittest.cc new file mode 100644 index 0000000..71c95bb --- /dev/null +++ b/update_engine/update_manager/real_device_policy_provider_unittest.cc
@@ -0,0 +1,272 @@ +// +// 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/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 <gmock/gmock.h> +#include <gtest/gtest.h> +#include <policy/mock_device_policy.h> +#include <policy/mock_libpolicy.h> +#if USE_DBUS +#include <session_manager/dbus-proxies.h> +#include <session_manager/dbus-proxy-mocks.h> +#endif // USE_DBUS + +#include "update_engine/common/test_utils.h" +#if USE_DBUS +#include "update_engine/dbus_test_utils.h" +#endif // USE_DBUS +#include "update_engine/update_manager/umtest_utils.h" + +using base::TimeDelta; +using brillo::MessageLoop; +using chromeos_update_engine::ConnectionType; +#if USE_DBUS +using chromeos_update_engine::dbus_test_utils::MockSignalHandler; +#endif // USE_DBUS +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(); +#if USE_DBUS + auto session_manager_proxy_mock = + new org::chromium::SessionManagerInterfaceProxyMock(); + provider_.reset(new RealDevicePolicyProvider( + brillo::make_unique_ptr(session_manager_proxy_mock), + &mock_policy_provider_)); +#else + provider_.reset(new RealDevicePolicyProvider(&mock_policy_provider_)); +#endif // USE_DBUS + // By default, we have a device policy loaded. Tests can call + // SetUpNonExistentDevicePolicy() to override this. + SetUpExistentDevicePolicy(); + +#if USE_DBUS + // 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); +#endif // USE_DBUS + } + + 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}; + testing::NiceMock<policy::MockDevicePolicy> mock_device_policy_; + testing::NiceMock<policy::MockPolicyProvider> mock_policy_provider_; + unique_ptr<RealDevicePolicyProvider> provider_; + +#if USE_DBUS + // The registered signal handler for the signal. + MockSignalHandler<void(const string&)> property_change_complete_; +#endif // USE_DBUS +}; + +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_); + // We won't be notified that signal is connected without DBus. +#if USE_DBUS + EXPECT_CALL(mock_policy_provider_, Reload()); +#endif // USE_DBUS + loop_.RunOnce(false); +} + +TEST_F(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded) { + // Checks that the policy is reloaded by RefreshDevicePolicy(). + SetUpNonExistentDevicePolicy(); + // We won't be notified that signal is connected without DBus. +#if USE_DBUS + EXPECT_CALL(mock_policy_provider_, Reload()).Times(3); +#else + EXPECT_CALL(mock_policy_provider_, Reload()).Times(2); +#endif // USE_DBUS + EXPECT_TRUE(provider_->Init()); + loop_.RunOnce(false); + // Force the policy refresh. + provider_->RefreshDevicePolicy(); +} + +#if USE_DBUS +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"); +} +#endif // USE_DBUS + +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()); + UmTestUtils::ExpectVariableNotSet( + provider_->var_allow_kiosk_app_control_chrome_version()); +} + +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)); + EXPECT_CALL(mock_device_policy_, GetAllowKioskAppControlChromeVersion(_)) + .WillOnce(DoAll(SetArgPointee<0>(true), Return(true))); + + 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()); + UmTestUtils::ExpectVariableHasValue( + true, provider_->var_allow_kiosk_app_control_chrome_version()); +} + +TEST_F(UmRealDevicePolicyProviderTest, ScatterFactorConverted) { + SetUpExistentDevicePolicy(); + EXPECT_CALL(mock_device_policy_, GetScatterFactorInSeconds(_)) +#if USE_DBUS + .Times(2) +#else + .Times(1) +#endif // USE_DBUS + .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(_)) +#if USE_DBUS + .Times(2) +#else + .Times(1) +#endif // USE_DBUS + .WillRepeatedly(DoAll(SetArgPointee<0>(-1), Return(true))); + EXPECT_TRUE(provider_->Init()); + loop_.RunOnce(false); + + UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor()); +} + +TEST_F(UmRealDevicePolicyProviderTest, AllowedTypesConverted) { + SetUpExistentDevicePolicy(); + EXPECT_CALL(mock_device_policy_, GetAllowedConnectionTypesForUpdate(_)) +#if USE_DBUS + .Times(2) +#else + .Times(1) +#endif // USE_DBUS + .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_engine/update_manager/real_random_provider.cc b/update_engine/update_manager/real_random_provider.cc new file mode 100644 index 0000000..ed0eb4d --- /dev/null +++ b/update_engine/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_engine/update_manager/real_random_provider.h b/update_engine/update_manager/real_random_provider.h new file mode 100644 index 0000000..14ce7a3 --- /dev/null +++ b/update_engine/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_engine/update_manager/real_random_provider_unittest.cc b/update_engine/update_manager/real_random_provider_unittest.cc new file mode 100644 index 0000000..ca67da6 --- /dev/null +++ b/update_engine/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_engine/update_manager/real_shill_provider.cc b/update_engine/update_manager/real_shill_provider.cc new file mode 100644 index 0000000..2c58a7e --- /dev/null +++ b/update_engine/update_manager/real_shill_provider.cc
@@ -0,0 +1,167 @@ +// +// 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 chromeos_update_engine::connection_utils::ParseConnectionType; +using org::chromium::flimflam::ManagerProxyInterface; +using org::chromium::flimflam::ServiceProxyInterface; +using std::string; + +namespace chromeos_update_manager { + +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( + chromeos_update_engine::connection_utils::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( + chromeos_update_engine::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_engine/update_manager/real_shill_provider.h b/update_engine/update_manager/real_shill_provider.h new file mode 100644 index 0000000..e7708c8 --- /dev/null +++ b/update_engine/update_manager/real_shill_provider.h
@@ -0,0 +1,101 @@ +// +// 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 <memory> +#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<chromeos_update_engine::ConnectionType>* var_conn_type() override { + return &var_conn_type_; + } + + Variable<chromeos_update_engine::ConnectionTethering>* var_conn_tethering() override { + return &var_conn_tethering_; + } + + Variable<base::Time>* var_conn_last_changed() override { + return &var_conn_last_changed_; + } + + 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. + std::unique_ptr<chromeos_update_engine::ShillProxyInterface> shill_proxy_; + + // A clock abstraction (mockable). + chromeos_update_engine::ClockInterface* const clock_; + + // The provider's variables. + AsyncCopyVariable<bool> var_is_connected_{"is_connected"}; + AsyncCopyVariable<chromeos_update_engine::ConnectionType> var_conn_type_{ + "conn_type"}; + AsyncCopyVariable<chromeos_update_engine::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_engine/update_manager/real_shill_provider_unittest.cc b/update_engine/update_manager/real_shill_provider_unittest.cc new file mode 100644 index 0000000..e821dc7 --- /dev/null +++ b/update_engine/update_manager/real_shill_provider_unittest.cc
@@ -0,0 +1,533 @@ +// +// 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::ConnectionTethering; +using chromeos_update_engine::ConnectionType; +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(); + fake_shill_proxy_ = new chromeos_update_engine::FakeShillProxy(); + 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_engine/update_manager/real_state.h b/update_engine/update_manager/real_state.h new file mode 100644 index 0000000..e83c49d --- /dev/null +++ b/update_engine/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_engine/update_manager/real_system_provider.cc b/update_engine/update_manager/real_system_provider.cc new file mode 100644 index 0000000..44d5566 --- /dev/null +++ b/update_engine/update_manager/real_system_provider.cc
@@ -0,0 +1,141 @@ +// +// 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 <base/bind.h> +#include <base/callback.h> +#include <base/logging.h> +#include <base/time/time.h> + +#include "update_engine/common/utils.h" +#if USE_LIBCROS +#include "update_engine/libcros_proxy.h" +#endif +#include "update_engine/update_manager/generic_variables.h" +#include "update_engine/update_manager/variable.h" + +using std::string; + +namespace chromeos_update_manager { + +namespace { + +// The maximum number of consecutive failures before returning the default +// constructor value for T instead of failure. +const int kRetryPollVariableMaxRetry = 5; + +// The polling interval to be used whenever GetValue() returns an error. +const int kRetryPollVariableRetryIntervalSeconds = 5 * 60; + +// The RetryPollVariable variable is a polling variable that allows the function +// returning the value to fail a few times and shortens the polling rate when +// that happens. +template <typename T> +class RetryPollVariable : public Variable<T> { + public: + RetryPollVariable(const string& name, + const base::TimeDelta poll_interval, + base::Callback<bool(T* res)> func) + : Variable<T>(name, poll_interval), + func_(func), + base_interval_(poll_interval) { + DCHECK_LT(kRetryPollVariableRetryIntervalSeconds, + base_interval_.InSeconds()); + } + + protected: + // Variable override. + const T* GetValue(base::TimeDelta /* timeout */, + string* /* errmsg */) override { + std::unique_ptr<T> result(new T()); + if (!func_.Run(result.get())) { + if (failed_attempts_ >= kRetryPollVariableMaxRetry) { + // Give up on the retries, set back the desired polling interval and + // return the default. + this->SetPollInterval(base_interval_); + return result.release(); + } + this->SetPollInterval( + base::TimeDelta::FromSeconds(kRetryPollVariableRetryIntervalSeconds)); + failed_attempts_++; + return nullptr; + } + failed_attempts_ = 0; + this->SetPollInterval(base_interval_); + return result.release(); + } + + private: + // The function to be called, stored as a base::Callback. + base::Callback<bool(T*)> func_; + + // The desired polling interval when |func_| works and returns true. + base::TimeDelta base_interval_; + + // The number of consecutive failed attempts made. + int failed_attempts_ = 0; + + DISALLOW_COPY_AND_ASSIGN(RetryPollVariable); +}; + +} // namespace + +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())); + + var_kiosk_required_platform_version_.reset(new RetryPollVariable<string>( + "kiosk_required_platform_version", + base::TimeDelta::FromHours(5), // Same as Chrome's CWS poll. + base::Bind(&RealSystemProvider::GetKioskAppRequiredPlatformVersion, + base::Unretained(this)))); + + return true; +} + +bool RealSystemProvider::GetKioskAppRequiredPlatformVersion( + string* required_platform_version) { +#if USE_LIBCROS + brillo::ErrorPtr error; + if (!libcros_proxy_->service_interface_proxy() + ->GetKioskAppRequiredPlatformVersion(required_platform_version, + &error)) { + LOG(WARNING) << "Failed to get kiosk required platform version"; + required_platform_version->clear(); + return false; + } +#endif + + return true; +} + +} // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_system_provider.h b/update_engine/update_manager/real_system_provider.h new file mode 100644 index 0000000..083943b --- /dev/null +++ b/update_engine/update_manager/real_system_provider.h
@@ -0,0 +1,85 @@ +// +// 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_engine { +class LibCrosProxy; +} + +namespace chromeos_update_manager { + +// SystemProvider concrete implementation. +class RealSystemProvider : public SystemProvider { + public: + RealSystemProvider(chromeos_update_engine::HardwareInterface* hardware, + chromeos_update_engine::BootControlInterface* boot_control, + chromeos_update_engine::LibCrosProxy* libcros_proxy) + : hardware_(hardware), + boot_control_(boot_control), + libcros_proxy_(libcros_proxy) {} + + // 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(); + } + + Variable<std::string>* var_kiosk_required_platform_version() override { + return var_kiosk_required_platform_version_.get(); + } + + private: + bool GetKioskAppRequiredPlatformVersion( + std::string* required_platform_version); + + 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_; + std::unique_ptr<Variable<std::string>> var_kiosk_required_platform_version_; + + chromeos_update_engine::HardwareInterface* const hardware_; + chromeos_update_engine::BootControlInterface* const boot_control_; + chromeos_update_engine::LibCrosProxy* const libcros_proxy_ ALLOW_UNUSED_TYPE; + + DISALLOW_COPY_AND_ASSIGN(RealSystemProvider); +}; + +} // namespace chromeos_update_manager + +#endif // UPDATE_ENGINE_UPDATE_MANAGER_REAL_SYSTEM_PROVIDER_H_
diff --git a/update_engine/update_manager/real_system_provider_unittest.cc b/update_engine/update_manager/real_system_provider_unittest.cc new file mode 100644 index 0000000..c997ad8 --- /dev/null +++ b/update_engine/update_manager/real_system_provider_unittest.cc
@@ -0,0 +1,144 @@ +// +// 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 <brillo/make_unique_ptr.h> +#include <gmock/gmock.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" +#if USE_LIBCROS +#include "libcros/dbus-proxies.h" +#include "libcros/dbus-proxy-mocks.h" +#include "update_engine/libcros_proxy.h" + +using org::chromium::LibCrosServiceInterfaceProxyMock; +#endif // USE_LIBCROS +using std::unique_ptr; +using testing::_; +using testing::DoAll; +using testing::Return; +using testing::SetArgPointee; + +#if USE_LIBCROS +namespace { +const char kRequiredPlatformVersion[] ="1234.0.0"; +} // namespace +#endif // USE_LIBCROS + +namespace chromeos_update_manager { + +class UmRealSystemProviderTest : public ::testing::Test { + protected: + void SetUp() override { +#if USE_LIBCROS + service_interface_mock_ = new LibCrosServiceInterfaceProxyMock(); + libcros_proxy_.reset(new chromeos_update_engine::LibCrosProxy( + brillo::make_unique_ptr(service_interface_mock_), + unique_ptr< + org::chromium:: + UpdateEngineLibcrosProxyResolvedInterfaceProxyInterface>())); + ON_CALL(*service_interface_mock_, + GetKioskAppRequiredPlatformVersion(_, _, _)) + .WillByDefault( + DoAll(SetArgPointee<0>(kRequiredPlatformVersion), Return(true))); + + provider_.reset(new RealSystemProvider( + &fake_hardware_, &fake_boot_control_, libcros_proxy_.get())); +#else + provider_.reset( + new RealSystemProvider(&fake_hardware_, &fake_boot_control_, nullptr)); +#endif // USE_LIBCROS + EXPECT_TRUE(provider_->Init()); + } + + chromeos_update_engine::FakeHardware fake_hardware_; + chromeos_update_engine::FakeBootControl fake_boot_control_; + unique_ptr<RealSystemProvider> provider_; + +#if USE_LIBCROS + // Local pointers to the mocks. The instances are owned by the + // |libcros_proxy_|. + LibCrosServiceInterfaceProxyMock* service_interface_mock_; + + unique_ptr<chromeos_update_engine::LibCrosProxy> libcros_proxy_; +#endif // USE_LIBCROS +}; + +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()); + EXPECT_NE(nullptr, provider_->var_kiosk_required_platform_version()); +} + +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()); +} + +#if USE_LIBCROS +TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersion) { + UmTestUtils::ExpectVariableHasValue( + std::string(kRequiredPlatformVersion), + provider_->var_kiosk_required_platform_version()); +} + +TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersionFailure) { + EXPECT_CALL(*service_interface_mock_, + GetKioskAppRequiredPlatformVersion(_, _, _)) + .WillOnce(Return(false)); + + UmTestUtils::ExpectVariableNotSet( + provider_->var_kiosk_required_platform_version()); +} + +TEST_F(UmRealSystemProviderTest, + KioskRequiredPlatformVersionRecoveryFromFailure) { + EXPECT_CALL(*service_interface_mock_, + GetKioskAppRequiredPlatformVersion(_, _, _)) + .WillOnce(Return(false)); + UmTestUtils::ExpectVariableNotSet( + provider_->var_kiosk_required_platform_version()); + testing::Mock::VerifyAndClearExpectations(service_interface_mock_); + + EXPECT_CALL(*service_interface_mock_, + GetKioskAppRequiredPlatformVersion(_, _, _)) + .WillOnce( + DoAll(SetArgPointee<0>(kRequiredPlatformVersion), Return(true))); + UmTestUtils::ExpectVariableHasValue( + std::string(kRequiredPlatformVersion), + provider_->var_kiosk_required_platform_version()); +} +#else +TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersion) { + UmTestUtils::ExpectVariableHasValue( + std::string(), provider_->var_kiosk_required_platform_version()); +} +#endif + +} // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/real_time_provider.cc b/update_engine/update_manager/real_time_provider.cc new file mode 100644 index 0000000..ca3acad --- /dev/null +++ b/update_engine/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_engine/update_manager/real_time_provider.h b/update_engine/update_manager/real_time_provider.h new file mode 100644 index 0000000..e7cae94 --- /dev/null +++ b/update_engine/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_engine/update_manager/real_time_provider_unittest.cc b/update_engine/update_manager/real_time_provider_unittest.cc new file mode 100644 index 0000000..0e1ef34 --- /dev/null +++ b/update_engine/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_engine/update_manager/real_updater_provider.cc b/update_engine/update_manager/real_updater_provider.cc new file mode 100644 index 0000000..096b067 --- /dev/null +++ b/update_engine/update_manager/real_updater_provider.cc
@@ -0,0 +1,458 @@ +// +// 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/common/clock_interface.h" +#include "update_engine/common/prefs.h" +#include "update_engine/omaha_request_params.h" +#if USE_NESTLABS +#include <update_engine/dbus-constants-nestlabs.h> +#include "update_engine/update_attempter_nestlabs.h" +#else +#include <update_engine/dbus-constants.h> +#include "update_engine/update_attempter.h" +#endif + +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_engine/update_manager/real_updater_provider.h b/update_engine/update_manager/real_updater_provider.h new file mode 100644 index 0000000..b99bcc5 --- /dev/null +++ b/update_engine/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_engine/update_manager/real_updater_provider_unittest.cc b/update_engine/update_manager/real_updater_provider_unittest.cc new file mode 100644 index 0000000..bfbacb8 --- /dev/null +++ b/update_engine/update_manager/real_updater_provider_unittest.cc
@@ -0,0 +1,447 @@ +// +// 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> +#ifdef USE_NESTLABS +#include <update_engine/dbus-constants-nestlabs.h> +#else +#include <update_engine/dbus-constants.h> +#endif + +#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_engine/update_manager/shill_provider.h b/update_engine/update_manager/shill_provider.h new file mode 100644 index 0000000..e6f4628 --- /dev/null +++ b/update_engine/update_manager/shill_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_SHILL_PROVIDER_H_ +#define UPDATE_ENGINE_UPDATE_MANAGER_SHILL_PROVIDER_H_ + +#include <base/time/time.h> + +#include "update_engine/connection_utils.h" +#include "update_engine/update_manager/provider.h" +#include "update_engine/update_manager/variable.h" + +namespace chromeos_update_manager { + +// 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<chromeos_update_engine::ConnectionType>* var_conn_type() = 0; + + // A variable returning the tethering mode of a network connection. Unknown if + // not connected. + virtual Variable<chromeos_update_engine::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_engine/update_manager/state.h b/update_engine/update_manager/state.h new file mode 100644 index 0000000..d428059 --- /dev/null +++ b/update_engine/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_engine/update_manager/state_factory.cc b/update_engine/update_manager/state_factory.cc new file mode 100644 index 0000000..2b3ce63 --- /dev/null +++ b/update_engine/update_manager/state_factory.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/state_factory.h" + +#include <memory> + +#include <base/logging.h> +#include <brillo/make_unique_ptr.h> +#if USE_DBUS +#include <session_manager/dbus-proxies.h> +#endif // USE_DBUS + +#include "update_engine/common/clock_interface.h" +#if USE_DBUS +#include "update_engine/dbus_connection.h" +#endif // USE_DBUS +#include "update_engine/update_manager/fake_shill_provider.h" +#include "update_engine/update_manager/real_config_provider.h" +#include "update_engine/update_manager/real_device_policy_provider.h" +#include "update_engine/update_manager/real_random_provider.h" +#include "update_engine/update_manager/real_state.h" +#include "update_engine/update_manager/real_system_provider.h" +#include "update_engine/update_manager/real_time_provider.h" +#include "update_engine/update_manager/real_updater_provider.h" +#if USE_SHILL +#include "update_engine/shill_proxy.h" +#include "update_engine/update_manager/real_shill_provider.h" +#endif // USE_SHILL + +using std::unique_ptr; + +namespace chromeos_update_manager { + +State* DefaultStateFactory( + policy::PolicyProvider* policy_provider, + chromeos_update_engine::LibCrosProxy* libcros_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())); +#if USE_DBUS + scoped_refptr<dbus::Bus> bus = + chromeos_update_engine::DBusConnection::Get()->GetDBus(); + unique_ptr<RealDevicePolicyProvider> device_policy_provider( + new RealDevicePolicyProvider( + brillo::make_unique_ptr( + new org::chromium::SessionManagerInterfaceProxy(bus)), + policy_provider)); +#else + unique_ptr<RealDevicePolicyProvider> device_policy_provider( + new RealDevicePolicyProvider(policy_provider)); +#endif // USE_DBUS +#if USE_SHILL + unique_ptr<RealShillProvider> shill_provider( + new RealShillProvider(new chromeos_update_engine::ShillProxy(), clock)); +#else + unique_ptr<FakeShillProvider> shill_provider(new FakeShillProvider()); +#endif // USE_SHILL + unique_ptr<RealRandomProvider> random_provider(new RealRandomProvider()); + unique_ptr<RealSystemProvider> system_provider(new RealSystemProvider( + system_state->hardware(), system_state->boot_control(), libcros_proxy)); + 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() && +#if USE_SHILL + shill_provider->Init() && +#endif // USE_SHILL + 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_engine/update_manager/state_factory.h b/update_engine/update_manager/state_factory.h new file mode 100644 index 0000000..f1b576c --- /dev/null +++ b/update_engine/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 "update_engine/system_state.h" +#include "update_engine/update_manager/state.h" + +namespace chromeos_update_engine { +class LibCrosProxy; +} + +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::LibCrosProxy* libcros_proxy, + chromeos_update_engine::SystemState* system_state); + +} // namespace chromeos_update_manager + +#endif // UPDATE_ENGINE_UPDATE_MANAGER_STATE_FACTORY_H_
diff --git a/update_engine/update_manager/system_provider.h b/update_engine/update_manager/system_provider.h new file mode 100644 index 0000000..13e188b --- /dev/null +++ b/update_engine/update_manager/system_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_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; + + // Returns the required platform version of the configured auto launch + // with zero delay kiosk app if any. + virtual Variable<std::string>* var_kiosk_required_platform_version() = 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_engine/update_manager/time_provider.h b/update_engine/update_manager/time_provider.h new file mode 100644 index 0000000..663ec2c --- /dev/null +++ b/update_engine/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_engine/update_manager/umtest_utils.cc b/update_engine/update_manager/umtest_utils.cc new file mode 100644 index 0000000..aa88141 --- /dev/null +++ b/update_engine/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_engine/update_manager/umtest_utils.h b/update_engine/update_manager/umtest_utils.h new file mode 100644 index 0000000..80693db --- /dev/null +++ b/update_engine/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_engine/update_manager/update_manager-inl.h b/update_engine/update_manager/update_manager-inl.h new file mode 100644 index 0000000..77224cf --- /dev/null +++ b/update_engine/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_engine/update_manager/update_manager.cc b/update_engine/update_manager/update_manager.cc new file mode 100644 index 0000000..8e9b221 --- /dev/null +++ b/update_engine/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_engine/update_manager/update_manager.conf.example b/update_engine/update_manager/update_manager.conf.example new file mode 100644 index 0000000..2d77974 --- /dev/null +++ b/update_engine/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_engine/update_manager/update_manager.h b/update_engine/update_manager/update_manager.h new file mode 100644 index 0000000..a2f35df --- /dev/null +++ b/update_engine/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_engine/update_manager/update_manager_unittest.cc b/update_engine/update_manager/update_manager_unittest.cc new file mode 100644 index 0000000..03f1610 --- /dev/null +++ b/update_engine/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(0U, calls.size()); + MessageLoopRunMaxIterations(MessageLoop::current(), 100); + EXPECT_EQ(1U, 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(1U, 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(1U, 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(0U, 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(0U, 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(1U, calls.size()); + EXPECT_EQ(EvalStatus::kSucceeded, calls[0].first); +} + +} // namespace chromeos_update_manager
diff --git a/update_engine/update_manager/updater_provider.h b/update_engine/update_manager/updater_provider.h new file mode 100644 index 0000000..8048d38 --- /dev/null +++ b/update_engine/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_engine/update_manager/variable.h b/update_engine/update_manager/variable.h new file mode 100644 index 0000000..7109692 --- /dev/null +++ b/update_engine/update_manager/variable.h
@@ -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. +// + +#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) {} + + // Reset the poll interval on a polling variable to the given one. + void SetPollInterval(base::TimeDelta poll_interval) { + DCHECK_EQ(kVariableModePoll, mode_) << "Can't set the poll_interval on a " + << mode_ << " variable"; + poll_interval_ = 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. + 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_engine/update_manager/variable_unittest.cc b/update_engine/update_manager/variable_unittest.cc new file mode 100644 index 0000000..144002a --- /dev/null +++ b/update_engine/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(1U, var.observer_list_.size()); + var.AddObserver(&observer); + EXPECT_EQ(1U, var.observer_list_.size()); + var.RemoveObserver(&observer); + EXPECT_EQ(0U, var.observer_list_.size()); + var.RemoveObserver(&observer); + EXPECT_EQ(0U, var.observer_list_.size()); +} + +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(0U, observer1.calls_.size()); + MessageLoopRunMaxIterations(MessageLoop::current(), 100); + + ASSERT_EQ(1U, 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(2U, observer1.calls_.size()); + EXPECT_EQ(1U, 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_engine/update_metadata.proto b/update_engine/update_metadata.proto new file mode 100644 index 0000000..454c736 --- /dev/null +++ b/update_engine/update_metadata.proto
@@ -0,0 +1,284 @@ +// +// 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. + + // On minor version 4 or newer, these operations are supported: + IMGDIFF = 9; // The data is in imgdiff format. + } + 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; + + // Whether a failure in the postinstall step for this partition should be + // ignored. + optional bool postinstall_optional = 9; +} + +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_engine/update_payload_key/README b/update_engine/update_payload_key/README new file mode 100644 index 0000000..cf87148 --- /dev/null +++ b/update_engine/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_engine/update_payload_key/brillo-update-payload-key.pub.pem b/update_engine/update_payload_key/brillo-update-payload-key.pub.pem new file mode 100644 index 0000000..7e5164c --- /dev/null +++ b/update_engine/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_engine/update_status_utils.cc b/update_engine/update_status_utils.cc new file mode 100644 index 0000000..97408ef --- /dev/null +++ b/update_engine/update_status_utils.cc
@@ -0,0 +1,136 @@ +// +// 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> +#ifdef USE_NESTLABS +#include <update_engine/dbus-constants-nestlabs.h> +#else +#include <update_engine/dbus-constants.h> +#endif + +using update_engine::UpdateStatus; + +namespace { + +const char kWeaveStatusIdle[] = "idle"; +const char kWeaveStatusCheckingForUpdate[] = "checkingForUpdate"; +const char kWeaveStatusUpdateAvailable[] = "updateAvailable"; +const char kWeaveStatusDownloading[] = "downloading"; +const char kWeaveStatusVerifying[] = "verifying"; +const char kWeaveStatusFinalizing[] = "finalizing"; +const char kWeaveStatusUpdatedNeedReboot[] = "updatedNeedReboot"; +const char kWeaveStatusReportingErrorEvent[] = "reportingErrorEvent"; +const char kWeaveStatusAttemptingRollback[] = "attemptingRollback"; +const char kWeaveStatusDisabled[] = "disabled"; + +} // namespace + +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; +} + +const char* UpdateStatusToWeaveStatus(const UpdateStatus& status) { + switch (status) { + case UpdateStatus::IDLE: + return kWeaveStatusIdle; + case UpdateStatus::CHECKING_FOR_UPDATE: + return kWeaveStatusCheckingForUpdate; + case UpdateStatus::UPDATE_AVAILABLE: + return kWeaveStatusUpdateAvailable; + case UpdateStatus::DOWNLOADING: + return kWeaveStatusDownloading; + case UpdateStatus::VERIFYING: + return kWeaveStatusVerifying; + case UpdateStatus::FINALIZING: + return kWeaveStatusFinalizing; + case UpdateStatus::UPDATED_NEED_REBOOT: + return kWeaveStatusUpdatedNeedReboot; + case UpdateStatus::REPORTING_ERROR_EVENT: + return kWeaveStatusReportingErrorEvent; + case UpdateStatus::ATTEMPTING_ROLLBACK: + return kWeaveStatusAttemptingRollback; + case UpdateStatus::DISABLED: + return kWeaveStatusDisabled; + } + + 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_engine/update_status_utils.h b/update_engine/update_status_utils.h new file mode 100644 index 0000000..9d85144 --- /dev/null +++ b/update_engine/update_status_utils.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_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); + +// Convert the UpdateStatus |status| to the string reported in the weave status. +const char* UpdateStatusToWeaveStatus( + 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_
diff --git a/update_engine/utils_android.cc b/update_engine/utils_android.cc new file mode 100644 index 0000000..a4f1ea8 --- /dev/null +++ b/update_engine/utils_android.cc
@@ -0,0 +1,71 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/utils_android.h" + +#include <cutils/properties.h> +#include <fs_mgr.h> + +using std::string; + +namespace chromeos_update_engine { + +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 utils { + +bool DeviceForMountPoint(const string& mount_point, base::FilePath* device) { + struct fstab* fstab; + struct fstab_rec* record; + + fstab = OpenFSTab(); + if (fstab == nullptr) { + LOG(ERROR) << "Error opening fstab file."; + return false; + } + record = fs_mgr_get_entry_for_mount_point(fstab, mount_point.c_str()); + if (record == nullptr) { + LOG(ERROR) << "Error finding " << mount_point << " entry in fstab file."; + fs_mgr_free_fstab(fstab); + return false; + } + + *device = base::FilePath(record->blk_device); + fs_mgr_free_fstab(fstab); + return true; +} + +} // namespace utils + +} // namespace chromeos_update_engine
diff --git a/update_engine/utils_android.h b/update_engine/utils_android.h new file mode 100644 index 0000000..18dd8ab --- /dev/null +++ b/update_engine/utils_android.h
@@ -0,0 +1,37 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef UPDATE_ENGINE_UTILS_ANDROID_H_ +#define UPDATE_ENGINE_UTILS_ANDROID_H_ + +#include <string> + +#include <base/files/file_util.h> + +namespace chromeos_update_engine { + +namespace utils { + +// Find the block device that should be mounted in the |mount_point| path and +// store it in |device|. Returns whether a device was found on the fstab. +bool DeviceForMountPoint(const std::string& mount_point, + base::FilePath* device); + +} // namespace utils + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_UTILS_ANDROID_H_
diff --git a/update_engine/weave_service.cc b/update_engine/weave_service.cc new file mode 100644 index 0000000..0a145e4 --- /dev/null +++ b/update_engine/weave_service.cc
@@ -0,0 +1,133 @@ +// +// 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/weave_service.h" + +#include <cmath> +#include <string> + +#include <base/bind.h> +#include <brillo/errors/error.h> +#include <brillo/message_loops/message_loop.h> + +#include "update_engine/update_status_utils.h" + +using std::string; + +namespace { + +const char kWeaveComponent[] = "updater"; +const char kWeaveTrait[] = "_updater"; + +} // namespace + +namespace chromeos_update_engine { + +bool WeaveService::Init(DelegateInterface* delegate) { + delegate_ = delegate; + weave_service_subscription_ = weaved::Service::Connect( + brillo::MessageLoop::current(), + base::Bind(&WeaveService::OnWeaveServiceConnected, + base::Unretained(this))); + return true; +} + +void WeaveService::OnWeaveServiceConnected( + const std::weak_ptr<weaved::Service>& service) { + weave_service_ = service; + auto weave_service = weave_service_.lock(); + if (!weave_service) + return; + + weave_service->AddComponent(kWeaveComponent, {kWeaveTrait}, nullptr); + weave_service->AddCommandHandler( + kWeaveComponent, kWeaveTrait, "checkForUpdates", + base::Bind(&WeaveService::OnCheckForUpdates, base::Unretained(this))); + weave_service->AddCommandHandler( + kWeaveComponent, kWeaveTrait, "trackChannel", + base::Bind(&WeaveService::OnTrackChannel, base::Unretained(this))); + UpdateWeaveState(); +} + +void WeaveService::SendStatusUpdate(int64_t /* last_checked_time */, + double /* progress */, + update_engine::UpdateStatus /* status */, + const string& /* new_version */, + int64_t /* new_size */) { + // We query the Weave + UpdateWeaveState(); +} + +void WeaveService::SendChannelChangeUpdate( + const string& /* tracking_channel */) { + UpdateWeaveState(); +} + +void WeaveService::UpdateWeaveState() { + auto weave_service = weave_service_.lock(); + if (!weave_service || !delegate_) + return; + + int64_t last_checked_time; + double progress; + update_engine::UpdateStatus update_status; + string current_channel; + string tracking_channel; + + if (!delegate_->GetWeaveState(&last_checked_time, + &progress, + &update_status, + ¤t_channel, + &tracking_channel)) + return; + + // Round to progress to 1% (0.01) to avoid excessive and meaningless state + // changes. + progress = std::floor(progress * 100.) / 100.; + + base::DictionaryValue state; + state.SetString("_updater.currentChannel", current_channel); + state.SetString("_updater.trackingChannel", tracking_channel); + state.SetString("_updater.status", UpdateStatusToWeaveStatus(update_status)); + state.SetDouble("_updater.progress", progress); + state.SetDouble("_updater.lastUpdateCheckTimestamp", + static_cast<double>(last_checked_time)); + + if (!weave_service->SetStateProperties(kWeaveComponent, state, nullptr)) { + LOG(ERROR) << "Failed to update _updater state."; + } +} + +void WeaveService::OnCheckForUpdates(std::unique_ptr<weaved::Command> command) { + brillo::ErrorPtr error; + if (!delegate_->OnCheckForUpdates(&error)) { + command->AbortWithCustomError(error.get(), nullptr); + return; + } + command->Complete({}, nullptr); +} + +void WeaveService::OnTrackChannel(std::unique_ptr<weaved::Command> command) { + string channel = command->GetParameter<string>("channel"); + brillo::ErrorPtr error; + if (!delegate_->OnTrackChannel(channel, &error)) { + command->AbortWithCustomError(error.get(), nullptr); + return; + } + command->Complete({}, nullptr); +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/weave_service.h b/update_engine/weave_service.h new file mode 100644 index 0000000..5a3aff3 --- /dev/null +++ b/update_engine/weave_service.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_WEAVE_SERVICE_H_ +#define UPDATE_ENGINE_WEAVE_SERVICE_H_ + +#include <memory> +#include <string> + +#include <base/memory/weak_ptr.h> +#include <libweaved/command.h> +#include <libweaved/service.h> + +#include "update_engine/weave_service_interface.h" + +namespace chromeos_update_engine { + +class WeaveService : public WeaveServiceInterface { + public: + WeaveService() = default; + ~WeaveService() override = default; + + bool Init(DelegateInterface* delegate); + + // ServiceObserverInterface overrides. + void SendStatusUpdate(int64_t last_checked_time, + double progress, + update_engine::UpdateStatus status, + const std::string& new_version, + int64_t new_size) override; + void SendChannelChangeUpdate(const std::string& tracking_channel) override; + void SendPayloadApplicationComplete(ErrorCode error_code) override {} + + private: + // Force a weave update. + void UpdateWeaveState(); + + void OnWeaveServiceConnected(const std::weak_ptr<weaved::Service>& service); + + // Weave command handlers. These are called from the message loop whenever a + // command is received and dispatch the synchronous call to the |delegate_|. + void OnCheckForUpdates(std::unique_ptr<weaved::Command> cmd); + void OnTrackChannel(std::unique_ptr<weaved::Command> cmd); + + WeaveServiceInterface::DelegateInterface* delegate_{nullptr}; + + std::unique_ptr<weaved::Service::Subscription> weave_service_subscription_; + std::weak_ptr<weaved::Service> weave_service_; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_WEAVE_SERVICE_H_
diff --git a/update_engine/weave_service_factory.cc b/update_engine/weave_service_factory.cc new file mode 100644 index 0000000..24b9b79 --- /dev/null +++ b/update_engine/weave_service_factory.cc
@@ -0,0 +1,40 @@ +// +// 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/weave_service_factory.h" + +#if USE_WEAVE +#include "update_engine/weave_service.h" +#endif + +namespace chromeos_update_engine { + +std::unique_ptr<WeaveServiceInterface> ConstructWeaveService( + WeaveServiceInterface::DelegateInterface* delegate) { + std::unique_ptr<WeaveServiceInterface> result; + if (!delegate) + return result; + +#if USE_WEAVE + WeaveService* weave_service = new WeaveService(); + result.reset(weave_service); + if (!weave_service->Init(delegate)) + result.reset(); +#endif + return result; +} + +} // namespace chromeos_update_engine
diff --git a/update_engine/weave_service_factory.h b/update_engine/weave_service_factory.h new file mode 100644 index 0000000..7b129ab --- /dev/null +++ b/update_engine/weave_service_factory.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_WEAVE_SERVICE_FACTORY_H_ +#define UPDATE_ENGINE_WEAVE_SERVICE_FACTORY_H_ + +#include <memory> + +#include <base/memory/ref_counted.h> + +#include "update_engine/weave_service_interface.h" + +namespace chromeos_update_engine { + +// Create a new WeaveServiceInterface instance. In case of error or when weaved +// is disabled, returns an empty pointer. +std::unique_ptr<WeaveServiceInterface> ConstructWeaveService( + WeaveServiceInterface::DelegateInterface* delegate); + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_WEAVE_SERVICE_FACTORY_H_
diff --git a/update_engine/weave_service_interface.h b/update_engine/weave_service_interface.h new file mode 100644 index 0000000..a7e603e --- /dev/null +++ b/update_engine/weave_service_interface.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_WEAVE_SERVICE_INTERFACE_H_ +#define UPDATE_ENGINE_WEAVE_SERVICE_INTERFACE_H_ + +#include <string> + +#include <brillo/errors/error.h> + +#include "update_engine/client_library/include/update_engine/update_status.h" +#include "update_engine/service_observer_interface.h" + +namespace chromeos_update_engine { + +// A WeaveServiceInterface instance allows to register the daemon with weaved, +// handle commands and update the weave status. This class only handles the +// registration with weaved and the connection, the actual work to handle the +// commands is implemented by the DelegateInterface, which will be called from +// this class. +class WeaveServiceInterface : public ServiceObserverInterface { + public: + // The delegate class that actually handles the command execution from + class DelegateInterface { + public: + virtual ~DelegateInterface() = default; + + virtual bool OnCheckForUpdates(brillo::ErrorPtr* error) = 0; + + virtual bool OnTrackChannel(const std::string& channel, + brillo::ErrorPtr* error) = 0; + + // Return the current status. + virtual bool GetWeaveState(int64_t* last_checked_time, + double* progress, + update_engine::UpdateStatus* update_status, + std::string* current_channel, + std::string* tracking_channel) = 0; + }; + + virtual ~WeaveServiceInterface() = default; + + protected: + WeaveServiceInterface() = default; +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_WEAVE_SERVICE_INTERFACE_H_
diff --git a/update_engine/weaved/traits/updater.json b/update_engine/weaved/traits/updater.json new file mode 100644 index 0000000..52b1a55 --- /dev/null +++ b/update_engine/weaved/traits/updater.json
@@ -0,0 +1,52 @@ +{ + "_updater": { + "commands": { + "checkForUpdates": { + "minimalRole": "manager", + "parameters": {} + }, + "trackChannel": { + "minimalRole": "manager", + "parameters": { + "channel": { + "type": "string", + "enum": ["stable-channel", "beta-channel", "dev-channel", "canary-channel"] + } + } + } + }, + "state": { + "currentChannel": { + "type": "string", + "enum": ["stable-channel", "beta-channel", "dev-channel", "canary-channel"] + }, + "trackingChannel": { + "type": "string", + "enum": ["stable-channel", "beta-channel", "dev-channel", "canary-channel"] + }, + "status": { + "type": "string", + "enum": [ + "idle", + "checkingForUpdate", + "updateAvailable", + "downloading", + "verifying", + "finalizing", + "updatedNeedReboot", + "reportingErrorEvent", + "attemptingRollback", + "disabled" + ] + }, + "progress": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "lastUpdateCheckTimestamp": { + "type": "number" + } + } + } +}