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