1 // Copyright 2013 The Chromium 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.
5 #include "chrome/browser/component_updater/background_downloader_win.h"
14 #include "base/file_util.h"
15 #include "base/strings/sys_string_conversions.h"
16 #include "base/win/scoped_co_mem.h"
17 #include "chrome/browser/component_updater/component_updater_utils.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "ui/base/win/atl_module.h"
22 using base::win::ScopedCoMem
;
23 using base::win::ScopedComPtr
;
24 using content::BrowserThread
;
26 // The class BackgroundDownloader in this module is an adapter between
27 // the CrxDownloader interface and the BITS service interfaces.
28 // The interface exposed on the CrxDownloader code runs on the UI thread, while
29 // the BITS specific code runs in a single threaded apartment on the FILE
31 // For every url to download, a BITS job is created, unless there is already
32 // an existing job for that url, in which case, the downloader connects to it.
33 // Once a job is associated with the url, the code looks for changes in the
34 // BITS job state. The checks are triggered by a timer.
35 // The BITS job contains just one file to download. There could only be one
36 // download in progress at a time. If Chrome closes down before the download is
37 // complete, the BITS job remains active and finishes in the background, without
38 // any intervention. The job can be completed next time the code runs, if the
39 // file is still needed, otherwise it will be cleaned up on a periodic basis.
41 // To list the BITS jobs for a user, use the |bitsadmin| tool. The command line
42 // to do that is: "bitsadmin /list /verbose". Another useful command is
43 // "bitsadmin /info" and provide the job id returned by the previous /list
46 // Ignoring the suspend/resume issues since this code is not using them, the
47 // job state machine implemented by BITS is something like this:
49 // Suspended--->Queued--->Connecting---->Transferring--->Transferred
51 // | | V V | (complete)
52 // +----------|---------+-----------------+-----+ V
53 // | | | | Acknowledged
55 // | Transient Error------->Error |
57 // | +-------+---------+--->-+
60 // +------<----------+ +---->Cancelled
62 // The job is created in the "suspended" state. Once |Resume| is called,
63 // BITS queues up the job, then tries to connect, begins transferring the
64 // job bytes, and moves the job to the "transferred state, after the job files
65 // have been transferred. When calling |Complete| for a job, the job files are
66 // made available to the caller, and the job is moved to the "acknowledged"
68 // At any point, the job can be cancelled, in which case, the job is moved
69 // to the "cancelled state" and the job object is removed from the BITS queue.
70 // Along the way, the job can encounter recoverable and non-recoverable errors.
71 // BITS moves the job to "transient error" or "error", depending on which kind
72 // of error has occured.
73 // If the job has reached the "transient error" state, BITS retries the
74 // job after a certain programmable delay. If the job can't be completed in a
75 // certain time interval, BITS stops retrying and errors the job out. This time
76 // interval is also programmable.
77 // If the job is in either of the error states, the job parameters can be
78 // adjusted to handle the error, after which the job can be resumed, and the
79 // whole cycle starts again.
80 // Jobs that are not touched in 90 days (or a value set by group policy) are
81 // automatically disposed off by BITS. This concludes the brief description of
82 // a job lifetime, according to BITS.
84 // In addition to how BITS is managing the life time of the job, there are a
85 // couple of special cases defined by the BackgroundDownloader.
86 // First, if the job encounters any of the 5xx HTTP responses, the job is
87 // not retried, in order to avoid DDOS-ing the servers.
88 // Second, there is a simple mechanism to detect stuck jobs, and allow the rest
89 // of the code to move on to trying other urls or trying other components.
90 // Last, after completing a job, irrespective of the outcome, the jobs older
91 // than a week are proactively cleaned up.
93 namespace component_updater
{
97 // All jobs created by this module have a specific description so they can
98 // be found at run-time or by using system administration tools.
99 const base::char16 kJobDescription
[] = L
"Chrome Component Updater";
101 // How often the code looks for changes in the BITS job state.
102 const int kJobPollingIntervalSec
= 10;
104 // How long BITS waits before retrying a job after the job encountered
105 // a transient error. If this value is not set, the BITS default is 10 minutes.
106 const int kMinimumRetryDelayMin
= 1;
108 // How long to wait for stuck jobs. Stuck jobs could be queued for too long,
109 // have trouble connecting, could be suspended for any reason, or they have
110 // encountered some transient error.
111 const int kJobStuckTimeoutMin
= 15;
113 // How long BITS waits before giving up on a job that could not be completed
114 // since the job has encountered its first transient error. If this value is
115 // not set, the BITS default is 14 days.
116 const int kSetNoProgressTimeoutDays
= 1;
118 // How often the jobs which were started but not completed for any reason
119 // are cleaned up. Reasons for jobs to be left behind include browser restarts,
120 // system restarts, etc. Also, the check to purge stale jobs only happens
121 // at most once a day. If the job clean up code is not running, the BITS
122 // default policy is to cancel jobs after 90 days of inactivity.
123 const int kPurgeStaleJobsAfterDays
= 7;
124 const int kPurgeStaleJobsIntervalBetweenChecksDays
= 1;
126 // Returns the status code from a given BITS error.
127 int GetHttpStatusFromBitsError(HRESULT error
) {
128 // BITS errors are defined in bitsmsg.h. Although not documented, it is
129 // clear that all errors corresponding to http status code have the high
130 // word equal to 0x8019 and the low word equal to the http status code.
131 const int kHttpStatusFirst
= 100; // Continue.
132 const int kHttpStatusLast
= 505; // Version not supported.
133 bool is_valid
= HIWORD(error
) == 0x8019 &&
134 LOWORD(error
) >= kHttpStatusFirst
&&
135 LOWORD(error
) <= kHttpStatusLast
;
136 return is_valid
? LOWORD(error
) : 0;
139 // Returns the files in a BITS job.
140 HRESULT
GetFilesInJob(IBackgroundCopyJob
* job
,
141 std::vector
<ScopedComPtr
<IBackgroundCopyFile
> >* files
) {
142 ScopedComPtr
<IEnumBackgroundCopyFiles
> enum_files
;
143 HRESULT hr
= job
->EnumFiles(enum_files
.Receive());
148 hr
= enum_files
->GetCount(&num_files
);
152 for (ULONG i
= 0; i
!= num_files
; ++i
) {
153 ScopedComPtr
<IBackgroundCopyFile
> file
;
154 if (enum_files
->Next(1, file
.Receive(), NULL
) == S_OK
)
155 files
->push_back(file
);
161 // Returns the file name, the url, and some per-file progress information.
162 // The function out parameters can be NULL if that data is not requested.
163 HRESULT
GetJobFileProperties(IBackgroundCopyFile
* file
,
164 base::string16
* local_name
,
165 base::string16
* remote_name
,
166 BG_FILE_PROGRESS
* progress
) {
170 ScopedCoMem
<base::char16
> name
;
171 hr
= file
->GetLocalName(&name
);
174 local_name
->assign(name
);
178 ScopedCoMem
<base::char16
> name
;
179 hr
= file
->GetRemoteName(&name
);
182 remote_name
->assign(name
);
186 BG_FILE_PROGRESS bg_file_progress
= {};
187 hr
= file
->GetProgress(&bg_file_progress
);
190 *progress
= bg_file_progress
;
196 HRESULT
GetJobDescription(IBackgroundCopyJob
* job
, const base::string16
* name
) {
197 ScopedCoMem
<base::char16
> description
;
198 return job
->GetDescription(&description
);
201 // Returns the job error code in |error_code| if the job is in the transient
202 // or the final error state. Otherwise, the job error is not available and
203 // the function fails.
204 HRESULT
GetJobError(IBackgroundCopyJob
* job
, HRESULT
* error_code_out
) {
205 *error_code_out
= S_OK
;
206 ScopedComPtr
<IBackgroundCopyError
> copy_error
;
207 HRESULT hr
= job
->GetError(copy_error
.Receive());
211 BG_ERROR_CONTEXT error_context
= BG_ERROR_CONTEXT_NONE
;
212 HRESULT error_code
= S_OK
;
213 hr
= copy_error
->GetError(&error_context
, &error_code
);
217 *error_code_out
= FAILED(error_code
) ? error_code
: E_FAIL
;
221 // Finds the component updater jobs matching the given predicate.
222 // Returns S_OK if the function has found at least one job, returns S_FALSE if
223 // no job was found, and it returns an error otherwise.
224 template<class Predicate
>
225 HRESULT
FindBitsJobIf(Predicate pred
,
226 IBackgroundCopyManager
* bits_manager
,
227 std::vector
<ScopedComPtr
<IBackgroundCopyJob
> >* jobs
) {
228 ScopedComPtr
<IEnumBackgroundCopyJobs
> enum_jobs
;
229 HRESULT hr
= bits_manager
->EnumJobs(0, enum_jobs
.Receive());
234 hr
= enum_jobs
->GetCount(&job_count
);
238 // Iterate over jobs, run the predicate, and select the job only if
239 // the job description matches the component updater jobs.
240 for (ULONG i
= 0; i
!= job_count
; ++i
) {
241 ScopedComPtr
<IBackgroundCopyJob
> current_job
;
242 if (enum_jobs
->Next(1, current_job
.Receive(), NULL
) == S_OK
&&
244 base::string16 job_description
;
245 hr
= GetJobDescription(current_job
, &job_description
);
246 if (job_description
.compare(kJobDescription
) == 0)
247 jobs
->push_back(current_job
);
251 return jobs
->empty() ? S_FALSE
: S_OK
;
254 // Compares the job creation time and returns true if the job creation time
255 // is older than |num_days|.
256 struct JobCreationOlderThanDays
257 : public std::binary_function
<IBackgroundCopyJob
*, int, bool> {
258 bool operator()(IBackgroundCopyJob
* job
, int num_days
) const;
261 bool JobCreationOlderThanDays::operator()(IBackgroundCopyJob
* job
,
262 int num_days
) const {
263 BG_JOB_TIMES times
= {0};
264 HRESULT hr
= job
->GetTimes(×
);
268 const base::TimeDelta
time_delta(base::TimeDelta::FromDays(num_days
));
269 const base::Time
creation_time(base::Time::FromFileTime(times
.CreationTime
));
271 return creation_time
+ time_delta
< base::Time::Now();
274 // Compares the url of a file in a job and returns true if the remote name
275 // of any file in a job matches the argument.
276 struct JobFileUrlEqual
277 : public std::binary_function
<IBackgroundCopyJob
*, const base::string16
&,
279 bool operator()(IBackgroundCopyJob
* job
,
280 const base::string16
& remote_name
) const;
283 bool JobFileUrlEqual::operator()(IBackgroundCopyJob
* job
,
284 const base::string16
& remote_name
) const {
285 std::vector
<ScopedComPtr
<IBackgroundCopyFile
> > files
;
286 HRESULT hr
= GetFilesInJob(job
, &files
);
290 for (size_t i
= 0; i
!= files
.size(); ++i
) {
291 ScopedCoMem
<base::char16
> name
;
292 if (SUCCEEDED(files
[i
]->GetRemoteName(&name
)) &&
293 remote_name
.compare(name
) == 0)
300 // Creates an instance of the BITS manager.
301 HRESULT
GetBitsManager(IBackgroundCopyManager
** bits_manager
) {
302 ScopedComPtr
<IBackgroundCopyManager
> object
;
303 HRESULT hr
= object
.CreateInstance(__uuidof(BackgroundCopyManager
));
305 VLOG(1) << "Failed to instantiate BITS." << std::hex
<< hr
;
306 // TODO: add UMA pings.
309 *bits_manager
= object
.Detach();
313 void CleanupJobFiles(IBackgroundCopyJob
* job
) {
314 std::vector
<ScopedComPtr
<IBackgroundCopyFile
> > files
;
315 if (FAILED(GetFilesInJob(job
, &files
)))
317 for (size_t i
= 0; i
!= files
.size(); ++i
) {
318 base::string16 local_name
;
319 HRESULT
hr(GetJobFileProperties(files
[i
], &local_name
, NULL
, NULL
));
321 DeleteFileAndEmptyParentDirectory(base::FilePath(local_name
));
325 // Cleans up incompleted jobs that are too old.
326 HRESULT
CleanupStaleJobs(
327 base::win::ScopedComPtr
<IBackgroundCopyManager
> bits_manager
) {
331 static base::Time last_sweep
;
333 const base::TimeDelta
time_delta(base::TimeDelta::FromDays(
334 kPurgeStaleJobsIntervalBetweenChecksDays
));
335 const base::Time
current_time(base::Time::Now());
336 if (last_sweep
+ time_delta
> current_time
)
339 last_sweep
= current_time
;
341 std::vector
<ScopedComPtr
<IBackgroundCopyJob
> > jobs
;
342 HRESULT hr
= FindBitsJobIf(
343 std::bind2nd(JobCreationOlderThanDays(), kPurgeStaleJobsAfterDays
),
349 for (size_t i
= 0; i
!= jobs
.size(); ++i
) {
351 CleanupJobFiles(jobs
[i
]);
359 BackgroundDownloader::BackgroundDownloader(
360 scoped_ptr
<CrxDownloader
> successor
,
361 net::URLRequestContextGetter
* context_getter
,
362 scoped_refptr
<base::SequencedTaskRunner
> task_runner
,
363 const DownloadCallback
& download_callback
)
364 : CrxDownloader(successor
.Pass(), download_callback
),
365 context_getter_(context_getter
),
366 task_runner_(task_runner
),
367 is_completed_(false) {
368 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
371 BackgroundDownloader::~BackgroundDownloader() {
374 void BackgroundDownloader::DoStartDownload(const GURL
& url
) {
375 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
377 BrowserThread::PostTask(
380 base::Bind(&BackgroundDownloader::BeginDownload
,
381 base::Unretained(this),
385 // Called once when this class is asked to do a download. Creates or opens
386 // an existing bits job, hooks up the notifications, and starts the timer.
387 void BackgroundDownloader::BeginDownload(const GURL
& url
) {
388 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
392 is_completed_
= false;
393 download_start_time_
= base::Time::Now();
394 job_stuck_begin_time_
= download_start_time_
;
396 HRESULT hr
= QueueBitsJob(url
);
402 // A repeating timer retains the user task. This timer can be stopped and
403 // reset multiple times.
404 timer_
.reset(new base::RepeatingTimer
<BackgroundDownloader
>);
405 timer_
->Start(FROM_HERE
,
406 base::TimeDelta::FromSeconds(kJobPollingIntervalSec
),
408 &BackgroundDownloader::OnDownloading
);
411 // Called any time the timer fires.
412 void BackgroundDownloader::OnDownloading() {
413 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
417 DCHECK(!is_completed_
);
421 BG_JOB_STATE job_state
= BG_JOB_STATE_ERROR
;
422 HRESULT hr
= job_
->GetState(&job_state
);
429 case BG_JOB_STATE_TRANSFERRED
:
430 OnStateTransferred();
433 case BG_JOB_STATE_ERROR
:
437 case BG_JOB_STATE_CANCELLED
:
441 case BG_JOB_STATE_ACKNOWLEDGED
:
442 OnStateAcknowledged();
445 case BG_JOB_STATE_QUEUED
:
447 case BG_JOB_STATE_CONNECTING
:
449 case BG_JOB_STATE_SUSPENDED
:
453 case BG_JOB_STATE_TRANSIENT_ERROR
:
454 OnStateTransientError();
457 case BG_JOB_STATE_TRANSFERRING
:
458 OnStateTransferring();
466 // Completes the BITS download, picks up the file path of the response, and
467 // notifies the CrxDownloader. The function should be called only once.
468 void BackgroundDownloader::EndDownload(HRESULT error
) {
469 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
471 DCHECK(!is_completed_
);
472 is_completed_
= true;
476 const base::Time
download_end_time(base::Time::Now());
477 const base::TimeDelta download_time
=
478 download_end_time
>= download_start_time_
?
479 download_end_time
- download_start_time_
: base::TimeDelta();
481 base::FilePath response
;
482 int64 bytes_downloaded
= -1;
483 int64 bytes_total
= -1;
485 if (SUCCEEDED(error
)) {
487 std::vector
<ScopedComPtr
<IBackgroundCopyFile
> > files
;
488 GetFilesInJob(job_
, &files
);
489 DCHECK(files
.size() == 1);
490 base::string16 local_name
;
491 BG_FILE_PROGRESS progress
= {0};
492 HRESULT hr
= GetJobFileProperties(files
[0], &local_name
, NULL
, &progress
);
494 DCHECK(progress
.Completed
);
495 response
= base::FilePath(local_name
);
496 if (progress
.BytesTransferred
<= kint64max
)
497 bytes_downloaded
= progress
.BytesTransferred
;
498 if (progress
.BytesTotal
<= kint64max
)
499 bytes_total
= progress
.BytesTotal
;
505 if (FAILED(error
) && job_
) {
507 CleanupJobFiles(job_
);
512 // Consider the url handled if it has been successfully downloaded or a
513 // 5xx has been received.
514 const bool is_handled
= SUCCEEDED(error
) ||
515 IsHttpServerError(GetHttpStatusFromBitsError(error
));
517 DownloadMetrics download_metrics
;
518 download_metrics
.url
= url();
519 download_metrics
.downloader
= DownloadMetrics::kBits
;
520 download_metrics
.error
= SUCCEEDED(error
) ? 0 : error
;
521 download_metrics
.bytes_downloaded
= bytes_downloaded
;
522 download_metrics
.bytes_total
= bytes_total
;
523 download_metrics
.download_time_ms
= download_time
.InMilliseconds();
525 // Clean up stale jobs before invoking the callback.
526 CleanupStaleJobs(bits_manager_
);
528 bits_manager_
= NULL
;
531 result
.error
= error
;
532 result
.response
= response
;
533 BrowserThread::PostTask(
536 base::Bind(&BackgroundDownloader::OnDownloadComplete
,
537 base::Unretained(this),
542 // Once the task is posted to the the UI thread, this object may be deleted
543 // by its owner. It is not safe to access members of this object on the
544 // FILE thread from this point on. The timer is stopped and all BITS
545 // interface pointers have been released.
548 // Called when the BITS job has been transferred successfully. Completes the
549 // BITS job by removing it from the BITS queue and making the download
550 // available to the caller.
551 void BackgroundDownloader::OnStateTransferred() {
552 HRESULT hr
= job_
->Complete();
553 if (SUCCEEDED(hr
) || hr
== BG_S_UNABLE_TO_DELETE_FILES
)
558 // Called when the job has encountered an error and no further progress can
559 // be made. Cancels this job and removes it from the BITS queue.
560 void BackgroundDownloader::OnStateError() {
561 HRESULT error_code
= S_OK
;
562 HRESULT hr
= GetJobError(job_
, &error_code
);
565 DCHECK(FAILED(error_code
));
566 EndDownload(error_code
);
569 // Called when the job has encountered a transient error, such as a
570 // network disconnect, a server error, or some other recoverable error.
571 void BackgroundDownloader::OnStateTransientError() {
572 // If the job appears to be stuck, handle the transient error as if
573 // it were a final error. This causes the job to be cancelled and a specific
574 // error be returned, if the error was available.
580 // Don't retry at all if the transient error was a 5xx.
581 HRESULT error_code
= S_OK
;
582 HRESULT hr
= GetJobError(job_
, &error_code
);
584 IsHttpServerError(GetHttpStatusFromBitsError(error_code
))) {
590 void BackgroundDownloader::OnStateQueued() {
592 EndDownload(E_ABORT
); // Return a generic error for now.
595 void BackgroundDownloader::OnStateTransferring() {
596 // Resets the baseline for detecting a stuck job since the job is transferring
597 // data and it is making progress.
598 job_stuck_begin_time_
= base::Time::Now();
601 // Called when the download was cancelled. Since the observer should have
602 // been disconnected by now, this notification must not be seen.
603 void BackgroundDownloader::OnStateCancelled() {
604 EndDownload(E_UNEXPECTED
);
607 // Called when the download was completed. Same as above.
608 void BackgroundDownloader::OnStateAcknowledged() {
609 EndDownload(E_UNEXPECTED
);
612 // Creates or opens a job for the given url and queues it up. Tries to
613 // install a job observer but continues on if an observer can't be set up.
614 HRESULT
BackgroundDownloader::QueueBitsJob(const GURL
& url
) {
615 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
618 if (bits_manager_
== NULL
) {
619 hr
= GetBitsManager(bits_manager_
.Receive());
624 hr
= CreateOrOpenJob(url
);
629 hr
= InitializeNewJob(url
);
634 return job_
->Resume();
637 HRESULT
BackgroundDownloader::CreateOrOpenJob(const GURL
& url
) {
638 std::vector
<ScopedComPtr
<IBackgroundCopyJob
> > jobs
;
639 HRESULT hr
= FindBitsJobIf(
640 std::bind2nd(JobFileUrlEqual(), base::SysUTF8ToWide(url
.spec())),
643 if (SUCCEEDED(hr
) && !jobs
.empty()) {
648 // Use kJobDescription as a temporary job display name until the proper
649 // display name is initialized later on.
651 ScopedComPtr
<IBackgroundCopyJob
> job
;
652 hr
= bits_manager_
->CreateJob(kJobDescription
,
653 BG_JOB_TYPE_DOWNLOAD
,
663 HRESULT
BackgroundDownloader::InitializeNewJob(const GURL
& url
) {
664 const base::string16
filename(base::SysUTF8ToWide(url
.ExtractFileName()));
666 base::FilePath tempdir
;
667 if (!base::CreateNewTempDirectory(
668 FILE_PATH_LITERAL("chrome_BITS_"),
672 HRESULT hr
= job_
->AddFile(
673 base::SysUTF8ToWide(url
.spec()).c_str(),
674 tempdir
.Append(filename
).AsUTF16Unsafe().c_str());
678 hr
= job_
->SetDisplayName(filename
.c_str());
682 hr
= job_
->SetDescription(kJobDescription
);
686 hr
= job_
->SetPriority(BG_JOB_PRIORITY_NORMAL
);
690 hr
= job_
->SetMinimumRetryDelay(60 * kMinimumRetryDelayMin
);
694 const int kSecondsDay
= 60 * 60 * 24;
695 hr
= job_
->SetNoProgressTimeout(kSecondsDay
* kSetNoProgressTimeoutDays
);
702 bool BackgroundDownloader::IsStuck() {
703 const base::TimeDelta
job_stuck_timeout(
704 base::TimeDelta::FromMinutes(kJobStuckTimeoutMin
));
705 return job_stuck_begin_time_
+ job_stuck_timeout
< base::Time::Now();
708 } // namespace component_updater