update_attempter.cc revision f42cc1c604fe5b0be29847a24f9bd5acf42ba394
1// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "update_engine/update_attempter.h" 6 7// From 'man clock_gettime': feature test macro: _POSIX_C_SOURCE >= 199309L 8#ifndef _POSIX_C_SOURCE 9#define _POSIX_C_SOURCE 199309L 10#endif // _POSIX_C_SOURCE 11#include <time.h> 12 13#include <tr1/memory> 14#include <string> 15#include <vector> 16 17#include <glib.h> 18#include <metrics/metrics_library.h> 19 20#include "update_engine/dbus_service.h" 21#include "update_engine/download_action.h" 22#include "update_engine/filesystem_copier_action.h" 23#include "update_engine/libcurl_http_fetcher.h" 24#include "update_engine/omaha_request_action.h" 25#include "update_engine/omaha_request_params.h" 26#include "update_engine/omaha_response_handler_action.h" 27#include "update_engine/postinstall_runner_action.h" 28#include "update_engine/set_bootable_flag_action.h" 29#include "update_engine/update_check_scheduler.h" 30 31using base::TimeDelta; 32using base::TimeTicks; 33using std::tr1::shared_ptr; 34using std::string; 35using std::vector; 36 37namespace chromeos_update_engine { 38 39const char* kUpdateCompletedMarker = "/tmp/update_engine_autoupdate_completed"; 40 41const char* UpdateStatusToString(UpdateStatus status) { 42 switch (status) { 43 case UPDATE_STATUS_IDLE: 44 return "UPDATE_STATUS_IDLE"; 45 case UPDATE_STATUS_CHECKING_FOR_UPDATE: 46 return "UPDATE_STATUS_CHECKING_FOR_UPDATE"; 47 case UPDATE_STATUS_UPDATE_AVAILABLE: 48 return "UPDATE_STATUS_UPDATE_AVAILABLE"; 49 case UPDATE_STATUS_DOWNLOADING: 50 return "UPDATE_STATUS_DOWNLOADING"; 51 case UPDATE_STATUS_VERIFYING: 52 return "UPDATE_STATUS_VERIFYING"; 53 case UPDATE_STATUS_FINALIZING: 54 return "UPDATE_STATUS_FINALIZING"; 55 case UPDATE_STATUS_UPDATED_NEED_REBOOT: 56 return "UPDATE_STATUS_UPDATED_NEED_REBOOT"; 57 case UPDATE_STATUS_REPORTING_ERROR_EVENT: 58 return "UPDATE_STATUS_REPORTING_ERROR_EVENT"; 59 default: 60 return "unknown status"; 61 } 62} 63 64// Turns a generic kActionCodeError to a generic error code specific 65// to |action| (e.g., kActionCodeFilesystemCopierError). If |code| is 66// not kActionCodeError, or the action is not matched, returns |code| 67// unchanged. 68ActionExitCode GetErrorCodeForAction(AbstractAction* action, 69 ActionExitCode code) { 70 if (code != kActionCodeError) 71 return code; 72 73 const string type = action->Type(); 74 if (type == OmahaRequestAction::StaticType()) 75 return kActionCodeOmahaRequestError; 76 if (type == OmahaResponseHandlerAction::StaticType()) 77 return kActionCodeOmahaResponseHandlerError; 78 if (type == FilesystemCopierAction::StaticType()) 79 return kActionCodeFilesystemCopierError; 80 if (type == PostinstallRunnerAction::StaticType()) 81 return kActionCodePostinstallRunnerError; 82 if (type == SetBootableFlagAction::StaticType()) 83 return kActionCodeSetBootableFlagError; 84 85 return code; 86} 87 88UpdateAttempter::UpdateAttempter(PrefsInterface* prefs, 89 MetricsLibraryInterface* metrics_lib) 90 : processor_(new ActionProcessor()), 91 dbus_service_(NULL), 92 prefs_(prefs), 93 metrics_lib_(metrics_lib), 94 update_check_scheduler_(NULL), 95 http_response_code_(0), 96 priority_(utils::kProcessPriorityNormal), 97 manage_priority_source_(NULL), 98 download_active_(false), 99 status_(UPDATE_STATUS_IDLE), 100 download_progress_(0.0), 101 last_checked_time_(0), 102 new_version_("0.0.0.0"), 103 new_size_(0) { 104 if (utils::FileExists(kUpdateCompletedMarker)) 105 status_ = UPDATE_STATUS_UPDATED_NEED_REBOOT; 106} 107 108UpdateAttempter::~UpdateAttempter() { 109 CleanupPriorityManagement(); 110} 111 112void UpdateAttempter::Update(const std::string& app_version, 113 const std::string& omaha_url) { 114 if (status_ == UPDATE_STATUS_UPDATED_NEED_REBOOT) { 115 LOG(INFO) << "Not updating b/c we already updated and we're waiting for " 116 << "reboot"; 117 return; 118 } 119 if (status_ != UPDATE_STATUS_IDLE) { 120 // Update in progress. Do nothing 121 return; 122 } 123 http_response_code_ = 0; 124 if (!omaha_request_params_.Init(app_version, omaha_url)) { 125 LOG(ERROR) << "Unable to initialize Omaha request device params."; 126 return; 127 } 128 CHECK(!processor_->IsRunning()); 129 processor_->set_delegate(this); 130 131 // Actions: 132 shared_ptr<OmahaRequestAction> update_check_action( 133 new OmahaRequestAction(prefs_, 134 omaha_request_params_, 135 NULL, 136 new LibcurlHttpFetcher)); 137 shared_ptr<OmahaResponseHandlerAction> response_handler_action( 138 new OmahaResponseHandlerAction); 139 shared_ptr<FilesystemCopierAction> filesystem_copier_action( 140 new FilesystemCopierAction(false)); 141 shared_ptr<FilesystemCopierAction> kernel_filesystem_copier_action( 142 new FilesystemCopierAction(true)); 143 shared_ptr<OmahaRequestAction> download_started_action( 144 new OmahaRequestAction(prefs_, 145 omaha_request_params_, 146 new OmahaEvent( 147 OmahaEvent::kTypeUpdateDownloadStarted), 148 new LibcurlHttpFetcher)); 149 shared_ptr<DownloadAction> download_action( 150 new DownloadAction(new LibcurlHttpFetcher)); 151 shared_ptr<OmahaRequestAction> download_finished_action( 152 new OmahaRequestAction(prefs_, 153 omaha_request_params_, 154 new OmahaEvent( 155 OmahaEvent::kTypeUpdateDownloadFinished), 156 new LibcurlHttpFetcher)); 157 shared_ptr<PostinstallRunnerAction> postinstall_runner_action_precommit( 158 new PostinstallRunnerAction(true)); 159 shared_ptr<SetBootableFlagAction> set_bootable_flag_action( 160 new SetBootableFlagAction); 161 shared_ptr<PostinstallRunnerAction> postinstall_runner_action_postcommit( 162 new PostinstallRunnerAction(false)); 163 shared_ptr<OmahaRequestAction> update_complete_action( 164 new OmahaRequestAction(prefs_, 165 omaha_request_params_, 166 new OmahaEvent(OmahaEvent::kTypeUpdateComplete), 167 new LibcurlHttpFetcher)); 168 169 download_action->set_delegate(this); 170 response_handler_action_ = response_handler_action; 171 172 actions_.push_back(shared_ptr<AbstractAction>(update_check_action)); 173 actions_.push_back(shared_ptr<AbstractAction>(response_handler_action)); 174 actions_.push_back(shared_ptr<AbstractAction>(filesystem_copier_action)); 175 actions_.push_back(shared_ptr<AbstractAction>( 176 kernel_filesystem_copier_action)); 177 actions_.push_back(shared_ptr<AbstractAction>(download_started_action)); 178 actions_.push_back(shared_ptr<AbstractAction>(download_action)); 179 actions_.push_back(shared_ptr<AbstractAction>(download_finished_action)); 180 actions_.push_back(shared_ptr<AbstractAction>( 181 postinstall_runner_action_precommit)); 182 actions_.push_back(shared_ptr<AbstractAction>(set_bootable_flag_action)); 183 actions_.push_back(shared_ptr<AbstractAction>( 184 postinstall_runner_action_postcommit)); 185 actions_.push_back(shared_ptr<AbstractAction>(update_complete_action)); 186 187 // Enqueue the actions 188 for (vector<shared_ptr<AbstractAction> >::iterator it = actions_.begin(); 189 it != actions_.end(); ++it) { 190 processor_->EnqueueAction(it->get()); 191 } 192 193 // Bond them together. We have to use the leaf-types when calling 194 // BondActions(). 195 BondActions(update_check_action.get(), 196 response_handler_action.get()); 197 BondActions(response_handler_action.get(), 198 filesystem_copier_action.get()); 199 BondActions(filesystem_copier_action.get(), 200 kernel_filesystem_copier_action.get()); 201 BondActions(kernel_filesystem_copier_action.get(), 202 download_action.get()); 203 BondActions(download_action.get(), 204 postinstall_runner_action_precommit.get()); 205 BondActions(postinstall_runner_action_precommit.get(), 206 set_bootable_flag_action.get()); 207 BondActions(set_bootable_flag_action.get(), 208 postinstall_runner_action_postcommit.get()); 209 210 SetStatusAndNotify(UPDATE_STATUS_CHECKING_FOR_UPDATE); 211 processor_->StartProcessing(); 212} 213 214void UpdateAttempter::CheckForUpdate(const std::string& app_version, 215 const std::string& omaha_url) { 216 if (status_ != UPDATE_STATUS_IDLE) { 217 LOG(INFO) << "Check for update requested, but status is " 218 << UpdateStatusToString(status_) << ", so not checking."; 219 return; 220 } 221 Update(app_version, omaha_url); 222} 223 224bool UpdateAttempter::RebootIfNeeded() { 225 if (status_ != UPDATE_STATUS_UPDATED_NEED_REBOOT) { 226 LOG(INFO) << "Reboot requested, but status is " 227 << UpdateStatusToString(status_) << ", so not rebooting."; 228 return false; 229 } 230 TEST_AND_RETURN_FALSE(utils::Reboot()); 231 return true; 232} 233 234// Delegate methods: 235void UpdateAttempter::ProcessingDone(const ActionProcessor* processor, 236 ActionExitCode code) { 237 CHECK(response_handler_action_); 238 LOG(INFO) << "Processing Done."; 239 actions_.clear(); 240 241 // Reset process priority back to normal. 242 CleanupPriorityManagement(); 243 244 if (status_ == UPDATE_STATUS_REPORTING_ERROR_EVENT) { 245 LOG(INFO) << "Error event sent."; 246 SetStatusAndNotify(UPDATE_STATUS_IDLE); 247 return; 248 } 249 250 if (code == kActionCodeSuccess) { 251 SetStatusAndNotify(UPDATE_STATUS_UPDATED_NEED_REBOOT); 252 utils::WriteFile(kUpdateCompletedMarker, "", 0); 253 254 // Report the time it took to update the system. 255 int64_t update_time = time(NULL) - last_checked_time_; 256 metrics_lib_->SendToUMA("Installer.UpdateTime", 257 static_cast<int>(update_time), // sample 258 1, // min = 1 second 259 20 * 60, // max = 20 minutes 260 50); // buckets 261 return; 262 } 263 264 if (ScheduleErrorEventAction()) { 265 return; 266 } 267 LOG(INFO) << "No update."; 268 SetStatusAndNotify(UPDATE_STATUS_IDLE); 269} 270 271void UpdateAttempter::ProcessingStopped(const ActionProcessor* processor) { 272 // Reset process priority back to normal. 273 CleanupPriorityManagement(); 274 download_progress_ = 0.0; 275 SetStatusAndNotify(UPDATE_STATUS_IDLE); 276 actions_.clear(); 277 error_event_.reset(NULL); 278} 279 280// Called whenever an action has finished processing, either successfully 281// or otherwise. 282void UpdateAttempter::ActionCompleted(ActionProcessor* processor, 283 AbstractAction* action, 284 ActionExitCode code) { 285 // Reset download progress regardless of whether or not the download 286 // action succeeded. Also, get the response code from HTTP request 287 // actions (update download as well as the initial update check 288 // actions). 289 const string type = action->Type(); 290 if (type == DownloadAction::StaticType()) { 291 download_progress_ = 0.0; 292 DownloadAction* download_action = dynamic_cast<DownloadAction*>(action); 293 http_response_code_ = download_action->GetHTTPResponseCode(); 294 } else if (type == OmahaRequestAction::StaticType()) { 295 OmahaRequestAction* omaha_request_action = 296 dynamic_cast<OmahaRequestAction*>(action); 297 // If the request is not an event, then it's the update-check. 298 if (!omaha_request_action->IsEvent()) { 299 http_response_code_ = omaha_request_action->GetHTTPResponseCode(); 300 } 301 } 302 if (code != kActionCodeSuccess) { 303 // On failure, schedule an error event to be sent to Omaha. 304 CreatePendingErrorEvent(action, code); 305 return; 306 } 307 // Find out which action completed. 308 if (type == OmahaResponseHandlerAction::StaticType()) { 309 // Note that the status will be updated to DOWNLOADING when some 310 // bytes get actually downloaded from the server and the 311 // BytesReceived callback is invoked. This avoids notifying the 312 // user that a download has started in cases when the server and 313 // the client are unable to initiate the download. 314 OmahaResponseHandlerAction* omaha_response_handler_action = 315 dynamic_cast<OmahaResponseHandlerAction*>(action); 316 CHECK(omaha_response_handler_action); 317 const InstallPlan& plan = omaha_response_handler_action->install_plan(); 318 last_checked_time_ = time(NULL); 319 // TODO(adlr): put version in InstallPlan 320 new_version_ = "0.0.0.0"; 321 new_size_ = plan.size; 322 SetupPriorityManagement(); 323 } else if (type == DownloadAction::StaticType()) { 324 SetStatusAndNotify(UPDATE_STATUS_FINALIZING); 325 } 326} 327 328// Stop updating. An attempt will be made to record status to the disk 329// so that updates can be resumed later. 330void UpdateAttempter::Terminate() { 331 // TODO(adlr): implement this method. 332 NOTIMPLEMENTED(); 333} 334 335// Try to resume from a previously Terminate()d update. 336void UpdateAttempter::ResumeUpdating() { 337 // TODO(adlr): implement this method. 338 NOTIMPLEMENTED(); 339} 340 341void UpdateAttempter::SetDownloadStatus(bool active) { 342 download_active_ = active; 343 LOG(INFO) << "Download status: " << (active ? "active" : "inactive"); 344} 345 346void UpdateAttempter::BytesReceived(uint64_t bytes_received, uint64_t total) { 347 if (!download_active_) { 348 LOG(ERROR) << "BytesReceived called while not downloading."; 349 return; 350 } 351 double progress = static_cast<double>(bytes_received) / 352 static_cast<double>(total); 353 // Self throttle based on progress. Also send notifications if 354 // progress is too slow. 355 const double kDeltaPercent = 0.01; // 1% 356 if (status_ != UPDATE_STATUS_DOWNLOADING || 357 bytes_received == total || 358 progress - download_progress_ >= kDeltaPercent || 359 TimeTicks::Now() - last_notify_time_ >= TimeDelta::FromSeconds(10)) { 360 download_progress_ = progress; 361 SetStatusAndNotify(UPDATE_STATUS_DOWNLOADING); 362 } 363} 364 365bool UpdateAttempter::GetStatus(int64_t* last_checked_time, 366 double* progress, 367 std::string* current_operation, 368 std::string* new_version, 369 int64_t* new_size) { 370 *last_checked_time = last_checked_time_; 371 *progress = download_progress_; 372 *current_operation = UpdateStatusToString(status_); 373 *new_version = new_version_; 374 *new_size = new_size_; 375 return true; 376} 377 378void UpdateAttempter::SetStatusAndNotify(UpdateStatus status) { 379 status_ = status; 380 if (update_check_scheduler_) { 381 update_check_scheduler_->SetUpdateStatus(status_); 382 } 383 if (!dbus_service_) 384 return; 385 last_notify_time_ = TimeTicks::Now(); 386 update_engine_service_emit_status_update( 387 dbus_service_, 388 last_checked_time_, 389 download_progress_, 390 UpdateStatusToString(status_), 391 new_version_.c_str(), 392 new_size_); 393} 394 395void UpdateAttempter::CreatePendingErrorEvent(AbstractAction* action, 396 ActionExitCode code) { 397 if (error_event_.get()) { 398 // This shouldn't really happen. 399 LOG(WARNING) << "There's already an existing pending error event."; 400 return; 401 } 402 403 // For now assume that Omaha response action failure means that 404 // there's no update so don't send an event. Also, double check that 405 // the failure has not occurred while sending an error event -- in 406 // which case don't schedule another. This shouldn't really happen 407 // but just in case... 408 if (action->Type() == OmahaResponseHandlerAction::StaticType() || 409 status_ == UPDATE_STATUS_REPORTING_ERROR_EVENT) { 410 return; 411 } 412 413 code = GetErrorCodeForAction(action, code); 414 error_event_.reset(new OmahaEvent(OmahaEvent::kTypeUpdateComplete, 415 OmahaEvent::kResultError, 416 code)); 417} 418 419bool UpdateAttempter::ScheduleErrorEventAction() { 420 if (error_event_.get() == NULL) 421 return false; 422 423 LOG(INFO) << "Update failed -- reporting the error event."; 424 shared_ptr<OmahaRequestAction> error_event_action( 425 new OmahaRequestAction(prefs_, 426 omaha_request_params_, 427 error_event_.release(), // Pass ownership. 428 new LibcurlHttpFetcher)); 429 actions_.push_back(shared_ptr<AbstractAction>(error_event_action)); 430 processor_->EnqueueAction(error_event_action.get()); 431 SetStatusAndNotify(UPDATE_STATUS_REPORTING_ERROR_EVENT); 432 processor_->StartProcessing(); 433 return true; 434} 435 436void UpdateAttempter::SetPriority(utils::ProcessPriority priority) { 437 if (priority_ == priority) { 438 return; 439 } 440 if (utils::SetProcessPriority(priority)) { 441 priority_ = priority; 442 LOG(INFO) << "Process priority = " << priority_; 443 } 444} 445 446void UpdateAttempter::SetupPriorityManagement() { 447 if (manage_priority_source_) { 448 LOG(ERROR) << "Process priority timeout source hasn't been destroyed."; 449 CleanupPriorityManagement(); 450 } 451 const int kPriorityTimeout = 10 * 60; // 10 minutes 452 manage_priority_source_ = g_timeout_source_new_seconds(kPriorityTimeout); 453 g_source_set_callback(manage_priority_source_, 454 StaticManagePriorityCallback, 455 this, 456 NULL); 457 g_source_attach(manage_priority_source_, NULL); 458 SetPriority(utils::kProcessPriorityLow); 459} 460 461void UpdateAttempter::CleanupPriorityManagement() { 462 if (manage_priority_source_) { 463 g_source_destroy(manage_priority_source_); 464 manage_priority_source_ = NULL; 465 } 466 SetPriority(utils::kProcessPriorityNormal); 467} 468 469gboolean UpdateAttempter::StaticManagePriorityCallback(gpointer data) { 470 return reinterpret_cast<UpdateAttempter*>(data)->ManagePriorityCallback(); 471} 472 473bool UpdateAttempter::ManagePriorityCallback() { 474 // If the current process priority is below normal, set it to normal 475 // and let GLib invoke this callback again. 476 if (utils::ComparePriorities(priority_, utils::kProcessPriorityNormal) < 0) { 477 SetPriority(utils::kProcessPriorityNormal); 478 return true; 479 } 480 // Set the priority to high and let GLib destroy the timeout source. 481 SetPriority(utils::kProcessPriorityHigh); 482 manage_priority_source_ = NULL; 483 return false; 484} 485 486} // namespace chromeos_update_engine 487