// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chromeos/dbus/update_engine_client.h" #include #include "base/bind.h" #include "base/command_line.h" #include "base/location.h" #include "base/macros.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_util.h" #include "base/thread_task_runner_handle.h" #include "chromeos/chromeos_switches.h" #include "dbus/bus.h" #include "dbus/message.h" #include "dbus/object_path.h" #include "dbus/object_proxy.h" #include "third_party/cros_system_api/dbus/service_constants.h" namespace chromeos { namespace { const char kReleaseChannelDev[] = "dev-channel"; const char kReleaseChannelBeta[] = "beta-channel"; const char kReleaseChannelStable[] = "stable-channel"; // List of release channels ordered by stability. const char* kReleaseChannelsList[] = {kReleaseChannelDev, kReleaseChannelBeta, kReleaseChannelStable}; // Delay between successive state transitions during AU. const int kStateTransitionDefaultDelayMs = 3000; // Delay between successive notifications about downloading progress // during fake AU. const int kStateTransitionDownloadingDelayMs = 250; // Size of parts of a "new" image which are downloaded each // |kStateTransitionDownloadingDelayMs| during fake AU. const int64_t kDownloadSizeDelta = 1 << 19; // Returns UPDATE_STATUS_ERROR on error. UpdateEngineClient::UpdateStatusOperation UpdateStatusFromString( const std::string& str) { VLOG(1) << "UpdateStatusFromString got " << str << " as input."; if (str == update_engine::kUpdateStatusIdle) return UpdateEngineClient::UPDATE_STATUS_IDLE; if (str == update_engine::kUpdateStatusCheckingForUpdate) return UpdateEngineClient::UPDATE_STATUS_CHECKING_FOR_UPDATE; if (str == update_engine::kUpdateStatusUpdateAvailable) return UpdateEngineClient::UPDATE_STATUS_UPDATE_AVAILABLE; if (str == update_engine::kUpdateStatusDownloading) return UpdateEngineClient::UPDATE_STATUS_DOWNLOADING; if (str == update_engine::kUpdateStatusVerifying) return UpdateEngineClient::UPDATE_STATUS_VERIFYING; if (str == update_engine::kUpdateStatusFinalizing) return UpdateEngineClient::UPDATE_STATUS_FINALIZING; if (str == update_engine::kUpdateStatusUpdatedNeedReboot) return UpdateEngineClient::UPDATE_STATUS_UPDATED_NEED_REBOOT; if (str == update_engine::kUpdateStatusReportingErrorEvent) return UpdateEngineClient::UPDATE_STATUS_REPORTING_ERROR_EVENT; if (str == update_engine::kUpdateStatusAttemptingRollback) return UpdateEngineClient::UPDATE_STATUS_ATTEMPTING_ROLLBACK; return UpdateEngineClient::UPDATE_STATUS_ERROR; } // Used in UpdateEngineClient::EmptyUpdateCheckCallback(). void EmptyUpdateCheckCallbackBody( UpdateEngineClient::UpdateCheckResult unused_result) { } bool IsValidChannel(const std::string& channel) { return channel == kReleaseChannelDev || channel == kReleaseChannelBeta || channel == kReleaseChannelStable; } } // namespace // The UpdateEngineClient implementation used in production. class UpdateEngineClientImpl : public UpdateEngineClient { public: UpdateEngineClientImpl() : update_engine_proxy_(NULL), last_status_(), weak_ptr_factory_(this) {} ~UpdateEngineClientImpl() override {} // UpdateEngineClient implementation: void AddObserver(Observer* observer) override { observers_.AddObserver(observer); } void RemoveObserver(Observer* observer) override { observers_.RemoveObserver(observer); } bool HasObserver(const Observer* observer) const override { return observers_.HasObserver(observer); } void RequestUpdateCheck(const UpdateCheckCallback& callback) override { dbus::MethodCall method_call( update_engine::kUpdateEngineInterface, update_engine::kAttemptUpdate); dbus::MessageWriter writer(&method_call); writer.AppendString(""); // Unused. writer.AppendString(""); // Unused. VLOG(1) << "Requesting an update check"; update_engine_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, base::Bind(&UpdateEngineClientImpl::OnRequestUpdateCheck, weak_ptr_factory_.GetWeakPtr(), callback)); } void RebootAfterUpdate() override { dbus::MethodCall method_call( update_engine::kUpdateEngineInterface, update_engine::kRebootIfNeeded); VLOG(1) << "Requesting a reboot"; update_engine_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, base::Bind(&UpdateEngineClientImpl::OnRebootAfterUpdate, weak_ptr_factory_.GetWeakPtr())); } void Rollback() override { VLOG(1) << "Requesting a rollback"; dbus::MethodCall method_call( update_engine::kUpdateEngineInterface, update_engine::kAttemptRollback); dbus::MessageWriter writer(&method_call); writer.AppendBool(true /* powerwash */); update_engine_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, base::Bind(&UpdateEngineClientImpl::OnRollback, weak_ptr_factory_.GetWeakPtr())); } void CanRollbackCheck(const RollbackCheckCallback& callback) override { dbus::MethodCall method_call( update_engine::kUpdateEngineInterface, update_engine::kCanRollback); VLOG(1) << "Requesting to get rollback availability status"; update_engine_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, base::Bind(&UpdateEngineClientImpl::OnCanRollbackCheck, weak_ptr_factory_.GetWeakPtr(), callback)); } Status GetLastStatus() override { return last_status_; } void SetChannel(const std::string& target_channel, bool is_powerwash_allowed) override { if (!IsValidChannel(target_channel)) { LOG(ERROR) << "Invalid channel name: " << target_channel; return; } dbus::MethodCall method_call( update_engine::kUpdateEngineInterface, update_engine::kSetChannel); dbus::MessageWriter writer(&method_call); writer.AppendString(target_channel); writer.AppendBool(is_powerwash_allowed); VLOG(1) << "Requesting to set channel: " << "target_channel=" << target_channel << ", " << "is_powerwash_allowed=" << is_powerwash_allowed; update_engine_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, base::Bind(&UpdateEngineClientImpl::OnSetChannel, weak_ptr_factory_.GetWeakPtr())); } void GetChannel(bool get_current_channel, const GetChannelCallback& callback) override { dbus::MethodCall method_call( update_engine::kUpdateEngineInterface, update_engine::kGetChannel); dbus::MessageWriter writer(&method_call); writer.AppendBool(get_current_channel); VLOG(1) << "Requesting to get channel, get_current_channel=" << get_current_channel; update_engine_proxy_->CallMethod( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, base::Bind(&UpdateEngineClientImpl::OnGetChannel, weak_ptr_factory_.GetWeakPtr(), callback)); } protected: void Init(dbus::Bus* bus) override { update_engine_proxy_ = bus->GetObjectProxy( update_engine::kUpdateEngineServiceName, dbus::ObjectPath(update_engine::kUpdateEngineServicePath)); // Monitor the D-Bus signal for brightness changes. Only the power // manager knows the actual brightness level. We don't cache the // brightness level in Chrome as it will make things less reliable. update_engine_proxy_->ConnectToSignal( update_engine::kUpdateEngineInterface, update_engine::kStatusUpdate, base::Bind(&UpdateEngineClientImpl::StatusUpdateReceived, weak_ptr_factory_.GetWeakPtr()), base::Bind(&UpdateEngineClientImpl::StatusUpdateConnected, weak_ptr_factory_.GetWeakPtr())); // Get update engine status for the initial status. Update engine won't // send StatusUpdate signal unless there is a status change. If chrome // crashes after UPDATE_STATUS_UPDATED_NEED_REBOOT status is set, // restarted chrome would not get this status. See crbug.com/154104. GetUpdateEngineStatus(); } private: void GetUpdateEngineStatus() { dbus::MethodCall method_call( update_engine::kUpdateEngineInterface, update_engine::kGetStatus); update_engine_proxy_->CallMethodWithErrorCallback( &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, base::Bind(&UpdateEngineClientImpl::OnGetStatus, weak_ptr_factory_.GetWeakPtr()), base::Bind(&UpdateEngineClientImpl::OnGetStatusError, weak_ptr_factory_.GetWeakPtr())); } // Called when a response for RequestUpdateCheck() is received. void OnRequestUpdateCheck(const UpdateCheckCallback& callback, dbus::Response* response) { if (!response) { LOG(ERROR) << "Failed to request update check"; callback.Run(UPDATE_RESULT_FAILED); return; } callback.Run(UPDATE_RESULT_SUCCESS); } // Called when a response for RebootAfterUpdate() is received. void OnRebootAfterUpdate(dbus::Response* response) { if (!response) { LOG(ERROR) << "Failed to request rebooting after update"; return; } } // Called when a response for Rollback() is received. void OnRollback(dbus::Response* response) { if (!response) { LOG(ERROR) << "Failed to rollback"; return; } } // Called when a response for CanRollbackCheck() is received. void OnCanRollbackCheck(const RollbackCheckCallback& callback, dbus::Response* response) { if (!response) { LOG(ERROR) << "Failed to request rollback availability status"; callback.Run(false); return; } dbus::MessageReader reader(response); bool can_rollback; if (!reader.PopBool(&can_rollback)) { LOG(ERROR) << "Incorrect response: " << response->ToString(); callback.Run(false); return; } VLOG(1) << "Rollback availability status received: " << can_rollback; callback.Run(can_rollback); } // Called when a response for GetStatus is received. void OnGetStatus(dbus::Response* response) { if (!response) { LOG(ERROR) << "Failed to get response for GetStatus request."; return; } dbus::MessageReader reader(response); std::string current_operation; Status status; if (!(reader.PopInt64(&status.last_checked_time) && reader.PopDouble(&status.download_progress) && reader.PopString(¤t_operation) && reader.PopString(&status.new_version) && reader.PopInt64(&status.new_size))) { LOG(ERROR) << "GetStatus had incorrect response: " << response->ToString(); return; } status.status = UpdateStatusFromString(current_operation); last_status_ = status; FOR_EACH_OBSERVER(Observer, observers_, UpdateStatusChanged(status)); } // Called when GetStatus call failed. void OnGetStatusError(dbus::ErrorResponse* error) { LOG(ERROR) << "GetStatus request failed with error: " << (error ? error->ToString() : ""); } // Called when a response for SetReleaseChannel() is received. void OnSetChannel(dbus::Response* response) { if (!response) { LOG(ERROR) << "Failed to request setting channel"; return; } VLOG(1) << "Succeeded to set channel"; } // Called when a response for GetChannel() is received. void OnGetChannel(const GetChannelCallback& callback, dbus::Response* response) { if (!response) { LOG(ERROR) << "Failed to request getting channel"; callback.Run(""); return; } dbus::MessageReader reader(response); std::string channel; if (!reader.PopString(&channel)) { LOG(ERROR) << "Incorrect response: " << response->ToString(); callback.Run(""); return; } VLOG(1) << "The channel received: " << channel; callback.Run(channel); } // Called when a status update signal is received. void StatusUpdateReceived(dbus::Signal* signal) { VLOG(1) << "Status update signal received: " << signal->ToString(); dbus::MessageReader reader(signal); int64 last_checked_time = 0; double progress = 0.0; std::string current_operation; std::string new_version; int64_t new_size = 0; if (!(reader.PopInt64(&last_checked_time) && reader.PopDouble(&progress) && reader.PopString(¤t_operation) && reader.PopString(&new_version) && reader.PopInt64(&new_size))) { LOG(ERROR) << "Status changed signal had incorrect parameters: " << signal->ToString(); return; } Status status; status.last_checked_time = last_checked_time; status.download_progress = progress; status.status = UpdateStatusFromString(current_operation); status.new_version = new_version; status.new_size = new_size; last_status_ = status; FOR_EACH_OBSERVER(Observer, observers_, UpdateStatusChanged(status)); } // Called when the status update signal is initially connected. void StatusUpdateConnected(const std::string& interface_name, const std::string& signal_name, bool success) { LOG_IF(WARNING, !success) << "Failed to connect to status updated signal."; } dbus::ObjectProxy* update_engine_proxy_; base::ObserverList observers_; Status last_status_; // Note: This should remain the last member so it'll be destroyed and // invalidate its weak pointers before any other members are destroyed. base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(UpdateEngineClientImpl); }; // The UpdateEngineClient implementation used on Linux desktop, // which does nothing. class UpdateEngineClientStubImpl : public UpdateEngineClient { public: UpdateEngineClientStubImpl() : current_channel_(kReleaseChannelBeta), target_channel_(kReleaseChannelBeta) {} // UpdateEngineClient implementation: void Init(dbus::Bus* bus) override {} void AddObserver(Observer* observer) override {} void RemoveObserver(Observer* observer) override {} bool HasObserver(const Observer* observer) const override { return false; } void RequestUpdateCheck(const UpdateCheckCallback& callback) override { callback.Run(UPDATE_RESULT_NOTIMPLEMENTED); } void RebootAfterUpdate() override {} void Rollback() override {} void CanRollbackCheck(const RollbackCheckCallback& callback) override { callback.Run(true); } Status GetLastStatus() override { return Status(); } void SetChannel(const std::string& target_channel, bool is_powerwash_allowed) override { VLOG(1) << "Requesting to set channel: " << "target_channel=" << target_channel << ", " << "is_powerwash_allowed=" << is_powerwash_allowed; target_channel_ = target_channel; } void GetChannel(bool get_current_channel, const GetChannelCallback& callback) override { VLOG(1) << "Requesting to get channel, get_current_channel=" << get_current_channel; if (get_current_channel) callback.Run(current_channel_); else callback.Run(target_channel_); } std::string current_channel_; std::string target_channel_; }; // The UpdateEngineClient implementation used on Linux desktop, which // tries to emulate real update engine client. class UpdateEngineClientFakeImpl : public UpdateEngineClientStubImpl { public: UpdateEngineClientFakeImpl() : weak_factory_(this) { } ~UpdateEngineClientFakeImpl() override {} // UpdateEngineClient implementation: void AddObserver(Observer* observer) override { if (observer) observers_.AddObserver(observer); } void RemoveObserver(Observer* observer) override { if (observer) observers_.RemoveObserver(observer); } bool HasObserver(const Observer* observer) const override { return observers_.HasObserver(observer); } void RequestUpdateCheck(const UpdateCheckCallback& callback) override { if (last_status_.status != UPDATE_STATUS_IDLE) { callback.Run(UPDATE_RESULT_FAILED); return; } callback.Run(UPDATE_RESULT_SUCCESS); last_status_.status = UPDATE_STATUS_CHECKING_FOR_UPDATE; last_status_.download_progress = 0.0; last_status_.last_checked_time = 0; last_status_.new_size = 0; base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&UpdateEngineClientFakeImpl::StateTransition, weak_factory_.GetWeakPtr()), base::TimeDelta::FromMilliseconds(kStateTransitionDefaultDelayMs)); } Status GetLastStatus() override { return last_status_; } private: void StateTransition() { UpdateStatusOperation next_status = UPDATE_STATUS_ERROR; int delay_ms = kStateTransitionDefaultDelayMs; switch (last_status_.status) { case UPDATE_STATUS_ERROR: case UPDATE_STATUS_IDLE: case UPDATE_STATUS_UPDATED_NEED_REBOOT: case UPDATE_STATUS_REPORTING_ERROR_EVENT: case UPDATE_STATUS_ATTEMPTING_ROLLBACK: return; case UPDATE_STATUS_CHECKING_FOR_UPDATE: next_status = UPDATE_STATUS_UPDATE_AVAILABLE; break; case UPDATE_STATUS_UPDATE_AVAILABLE: next_status = UPDATE_STATUS_DOWNLOADING; break; case UPDATE_STATUS_DOWNLOADING: if (last_status_.download_progress >= 1.0) { next_status = UPDATE_STATUS_VERIFYING; } else { next_status = UPDATE_STATUS_DOWNLOADING; last_status_.download_progress += 0.01; last_status_.new_size = kDownloadSizeDelta; delay_ms = kStateTransitionDownloadingDelayMs; } break; case UPDATE_STATUS_VERIFYING: next_status = UPDATE_STATUS_FINALIZING; break; case UPDATE_STATUS_FINALIZING: next_status = UPDATE_STATUS_IDLE; break; } last_status_.status = next_status; FOR_EACH_OBSERVER(Observer, observers_, UpdateStatusChanged(last_status_)); if (last_status_.status != UPDATE_STATUS_IDLE) { base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::Bind(&UpdateEngineClientFakeImpl::StateTransition, weak_factory_.GetWeakPtr()), base::TimeDelta::FromMilliseconds(delay_ms)); } } base::ObserverList observers_; Status last_status_; base::WeakPtrFactory weak_factory_; DISALLOW_COPY_AND_ASSIGN(UpdateEngineClientFakeImpl); }; UpdateEngineClient::UpdateEngineClient() { } UpdateEngineClient::~UpdateEngineClient() { } // static UpdateEngineClient::UpdateCheckCallback UpdateEngineClient::EmptyUpdateCheckCallback() { return base::Bind(&EmptyUpdateCheckCallbackBody); } // static UpdateEngineClient* UpdateEngineClient::Create( DBusClientImplementationType type) { if (type == REAL_DBUS_CLIENT_IMPLEMENTATION) return new UpdateEngineClientImpl(); DCHECK_EQ(STUB_DBUS_CLIENT_IMPLEMENTATION, type); if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kTestAutoUpdateUI)) return new UpdateEngineClientFakeImpl(); else return new UpdateEngineClientStubImpl(); } // static bool UpdateEngineClient::IsTargetChannelMoreStable( const std::string& current_channel, const std::string& target_channel) { auto cix = std::find(kReleaseChannelsList, kReleaseChannelsList + arraysize(kReleaseChannelsList), current_channel); auto tix = std::find(kReleaseChannelsList, kReleaseChannelsList + arraysize(kReleaseChannelsList), target_channel); return tix > cix; } } // namespace chromeos