| // 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 "base/bind.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/strings/string_util.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"; |
| |
| // 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) {} |
| |
| virtual ~UpdateEngineClientImpl() { |
| } |
| |
| // UpdateEngineClient implementation: |
| virtual void AddObserver(Observer* observer) OVERRIDE { |
| observers_.AddObserver(observer); |
| } |
| |
| virtual void RemoveObserver(Observer* observer) OVERRIDE { |
| observers_.RemoveObserver(observer); |
| } |
| |
| virtual bool HasObserver(Observer* observer) OVERRIDE { |
| return observers_.HasObserver(observer); |
| } |
| |
| virtual 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)); |
| } |
| |
| virtual 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())); |
| } |
| |
| virtual 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())); |
| } |
| |
| |
| virtual 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)); |
| } |
| |
| virtual Status GetLastStatus() OVERRIDE { |
| return last_status_; |
| } |
| |
| virtual 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())); |
| } |
| |
| virtual 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: |
| virtual 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_; |
| ObserverList<Observer> 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<UpdateEngineClientImpl> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(UpdateEngineClientImpl); |
| }; |
| |
| // The UpdateEngineClient implementation used on Linux desktop, |
| // which does nothing. |
| class UpdateEngineClientStubImpl : public UpdateEngineClient { |
| // UpdateEngineClient implementation: |
| virtual void Init(dbus::Bus* bus) OVERRIDE {} |
| virtual void AddObserver(Observer* observer) OVERRIDE {} |
| virtual void RemoveObserver(Observer* observer) OVERRIDE {} |
| virtual bool HasObserver(Observer* observer) OVERRIDE { return false; } |
| |
| virtual void RequestUpdateCheck( |
| const UpdateCheckCallback& callback) OVERRIDE { |
| callback.Run(UPDATE_RESULT_NOTIMPLEMENTED); |
| } |
| virtual void RebootAfterUpdate() OVERRIDE {} |
| virtual void Rollback() OVERRIDE {} |
| virtual void CanRollbackCheck( |
| const RollbackCheckCallback& callback) OVERRIDE { |
| callback.Run(true); |
| } |
| virtual Status GetLastStatus() OVERRIDE { return Status(); } |
| virtual 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; |
| } |
| virtual void GetChannel(bool get_current_channel, |
| const GetChannelCallback& callback) OVERRIDE { |
| VLOG(1) << "Requesting to get channel, get_current_channel=" |
| << get_current_channel; |
| callback.Run(kReleaseChannelBeta); |
| } |
| }; |
| |
| // The UpdateEngineClient implementation used on Linux desktop, which |
| // tries to emulate real update engine client. |
| class UpdateEngineClientFakeImpl : public UpdateEngineClientStubImpl { |
| public: |
| UpdateEngineClientFakeImpl() : weak_factory_(this) { |
| } |
| |
| virtual ~UpdateEngineClientFakeImpl() { |
| } |
| |
| // UpdateEngineClient implementation: |
| virtual void AddObserver(Observer* observer) OVERRIDE { |
| if (observer) |
| observers_.AddObserver(observer); |
| } |
| |
| virtual void RemoveObserver(Observer* observer) OVERRIDE { |
| if (observer) |
| observers_.RemoveObserver(observer); |
| } |
| |
| virtual bool HasObserver(Observer* observer) OVERRIDE { |
| return observers_.HasObserver(observer); |
| } |
| |
| virtual 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::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&UpdateEngineClientFakeImpl::StateTransition, |
| weak_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(kStateTransitionDefaultDelayMs)); |
| } |
| |
| virtual 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::MessageLoop::current()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&UpdateEngineClientFakeImpl::StateTransition, |
| weak_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(delay_ms)); |
| } |
| } |
| |
| ObserverList<Observer> observers_; |
| Status last_status_; |
| |
| base::WeakPtrFactory<UpdateEngineClientFakeImpl> 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 (CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestAutoUpdateUI)) |
| return new UpdateEngineClientFakeImpl(); |
| else |
| return new UpdateEngineClientStubImpl(); |
| } |
| |
| } // namespace chromeos |