123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898 |
- // Copyright 2014 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 "components/update_client/background_downloader_win.h"
- #include <objbase.h>
- #include <winerror.h>
- #include <stddef.h>
- #include <stdint.h>
- #include <functional>
- #include <iomanip>
- #include <limits>
- #include <memory>
- #include <utility>
- #include <vector>
- #include "base/bind.h"
- #include "base/files/file_util.h"
- #include "base/location.h"
- #include "base/logging.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/strings/string_piece.h"
- #include "base/strings/sys_string_conversions.h"
- #include "base/task/sequenced_task_runner.h"
- #include "base/task/thread_pool.h"
- #include "base/threading/scoped_thread_priority.h"
- #include "base/win/atl.h"
- #include "base/win/scoped_co_mem.h"
- #include "components/update_client/task_traits.h"
- #include "components/update_client/update_client_errors.h"
- #include "components/update_client/utils.h"
- #include "url/gurl.h"
- using Microsoft::WRL::ComPtr;
- using base::win::ScopedCoMem;
- // The class BackgroundDownloader in this module is an adapter between
- // the CrxDownloader interface and the BITS service interfaces.
- // The interface exposed on the CrxDownloader code runs on the main sequence,
- // while the BITS specific code runs in a separate sequence bound to a
- // COM apartment. For every url to download, a BITS job is created, unless
- // there is already an existing job for that url, in which case, the downloader
- // connects to it. Once a job is associated with the url, the code looks for
- // changes in the BITS job state. The checks are triggered by a timer.
- // The BITS job contains just one file to download. There could only be one
- // download in progress at a time. If Chrome closes down before the download is
- // complete, the BITS job remains active and finishes in the background, without
- // any intervention. The job can be completed next time the code runs, if the
- // file is still needed, otherwise it will be cleaned up on a periodic basis.
- //
- // To list the BITS jobs for a user, use the |bitsadmin| tool. The command line
- // to do that is: "bitsadmin /list /verbose". Another useful command is
- // "bitsadmin /info" and provide the job id returned by the previous /list
- // command.
- //
- // Ignoring the suspend/resume issues since this code is not using them, the
- // job state machine implemented by BITS is something like this:
- //
- // Suspended--->Queued--->Connecting---->Transferring--->Transferred
- // | ^ | | |
- // | | V V | (complete)
- // +----------|---------+-----------------+-----+ V
- // | | | | Acknowledged
- // | V V |
- // | Transient Error------->Error |
- // | | | |(cancel)
- // | +-------+---------+--->-+
- // | V |
- // | (resume) | |
- // +------<----------+ +---->Cancelled
- //
- // The job is created in the "suspended" state. Once |Resume| is called,
- // BITS queues up the job, then tries to connect, begins transferring the
- // job bytes, and moves the job to the "transferred state, after the job files
- // have been transferred. When calling |Complete| for a job, the job files are
- // made available to the caller, and the job is moved to the "acknowledged"
- // state.
- // At any point, the job can be cancelled, in which case, the job is moved
- // to the "cancelled state" and the job object is removed from the BITS queue.
- // Along the way, the job can encounter recoverable and non-recoverable errors.
- // BITS moves the job to "transient error" or "error", depending on which kind
- // of error has occured.
- // If the job has reached the "transient error" state, BITS retries the
- // job after a certain programmable delay. If the job can't be completed in a
- // certain time interval, BITS stops retrying and errors the job out. This time
- // interval is also programmable.
- // If the job is in either of the error states, the job parameters can be
- // adjusted to handle the error, after which the job can be resumed, and the
- // whole cycle starts again.
- // Jobs that are not touched in 90 days (or a value set by group policy) are
- // automatically disposed off by BITS. This concludes the brief description of
- // a job lifetime, according to BITS.
- //
- // In addition to how BITS is managing the life time of the job, there are a
- // couple of special cases defined by the BackgroundDownloader.
- // First, if the job encounters any of the 5xx HTTP responses, the job is
- // not retried, in order to avoid DDOS-ing the servers.
- // Second, there is a simple mechanism to detect stuck jobs, and allow the rest
- // of the code to move on to trying other urls or trying other components.
- // Last, after completing a job, irrespective of the outcome, the jobs older
- // than a week are proactively cleaned up.
- namespace update_client {
- namespace {
- // All jobs created by this module have a specific description so they can
- // be found at run-time or by using system administration tools.
- const wchar_t kJobName[] = L"Chrome Component Updater";
- // How often the code looks for changes in the BITS job state.
- const int kJobPollingIntervalSec = 4;
- // How long BITS waits before retrying a job after the job encountered
- // a transient error. If this value is not set, the BITS default is 10 minutes.
- const int kMinimumRetryDelayMin = 1;
- // How long to wait for stuck jobs. Stuck jobs could be queued for too long,
- // have trouble connecting, could be suspended for any reason, or they have
- // encountered some transient error.
- const int kJobStuckTimeoutMin = 15;
- // How long BITS waits before giving up on a job that could not be completed
- // since the job has encountered its first transient error. If this value is
- // not set, the BITS default is 14 days.
- const int kSetNoProgressTimeoutDays = 1;
- // How often the jobs which were started but not completed for any reason
- // are cleaned up. Reasons for jobs to be left behind include browser restarts,
- // system restarts, etc. Also, the check to purge stale jobs only happens
- // at most once a day. If the job clean up code is not running, the BITS
- // default policy is to cancel jobs after 90 days of inactivity.
- const int kPurgeStaleJobsAfterDays = 3;
- const int kPurgeStaleJobsIntervalBetweenChecksDays = 1;
- // Number of maximum BITS jobs this downloader can create and queue up.
- const int kMaxQueuedJobs = 10;
- // Retrieves the singleton instance of GIT for this process.
- HRESULT GetGit(ComPtr<IGlobalInterfaceTable>* git) {
- // Mitigate the issues caused by loading DLLs on a background thread
- // (http://crbug/973868).
- SCOPED_MAY_LOAD_LIBRARY_AT_BACKGROUND_PRIORITY();
- return ::CoCreateInstance(CLSID_StdGlobalInterfaceTable, nullptr,
- CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&(*git)));
- }
- // Retrieves an interface pointer from the process GIT for a given |cookie|.
- HRESULT GetInterfaceFromGit(const ComPtr<IGlobalInterfaceTable>& git,
- DWORD cookie,
- REFIID riid,
- void** ppv) {
- return git->GetInterfaceFromGlobal(cookie, riid, ppv);
- }
- // Registers an interface pointer in GIT and returns its corresponding |cookie|.
- template <typename T>
- HRESULT RegisterInterfaceInGit(const ComPtr<IGlobalInterfaceTable>& git,
- const ComPtr<T>& p,
- DWORD* cookie) {
- return git->RegisterInterfaceInGlobal(p.Get(), __uuidof(T), cookie);
- }
- // Returns the status code from a given BITS error.
- int GetHttpStatusFromBitsError(HRESULT error) {
- // BITS errors are defined in bitsmsg.h. Although not documented, it is
- // clear that all errors corresponding to http status code have the high
- // word equal to 0x8019 and the low word equal to the http status code.
- const int kHttpStatusFirst = 100; // Continue.
- const int kHttpStatusLast = 505; // Version not supported.
- bool is_valid = HIWORD(error) == 0x8019 &&
- LOWORD(error) >= kHttpStatusFirst &&
- LOWORD(error) <= kHttpStatusLast;
- return is_valid ? LOWORD(error) : 0;
- }
- // Returns the files in a BITS job.
- HRESULT GetFilesInJob(const ComPtr<IBackgroundCopyJob>& job,
- std::vector<ComPtr<IBackgroundCopyFile>>* files) {
- ComPtr<IEnumBackgroundCopyFiles> enum_files;
- HRESULT hr = job->EnumFiles(&enum_files);
- if (FAILED(hr))
- return hr;
- ULONG num_files = 0;
- hr = enum_files->GetCount(&num_files);
- if (FAILED(hr))
- return hr;
- for (ULONG i = 0; i != num_files; ++i) {
- ComPtr<IBackgroundCopyFile> file;
- if (enum_files->Next(1, &file, nullptr) == S_OK && file.Get())
- files->push_back(file);
- }
- return S_OK;
- }
- // Returns the file name, the url, and some per-file progress information.
- // The function out parameters can be NULL if that data is not requested.
- HRESULT GetJobFileProperties(const ComPtr<IBackgroundCopyFile>& file,
- std::wstring* local_name,
- std::wstring* remote_name,
- BG_FILE_PROGRESS* progress) {
- if (!file)
- return E_FAIL;
- HRESULT hr = S_OK;
- if (local_name) {
- ScopedCoMem<wchar_t> name;
- hr = file->GetLocalName(&name);
- if (FAILED(hr))
- return hr;
- local_name->assign(name);
- }
- if (remote_name) {
- ScopedCoMem<wchar_t> name;
- hr = file->GetRemoteName(&name);
- if (FAILED(hr))
- return hr;
- remote_name->assign(name);
- }
- if (progress) {
- BG_FILE_PROGRESS bg_file_progress = {};
- hr = file->GetProgress(&bg_file_progress);
- if (FAILED(hr))
- return hr;
- *progress = bg_file_progress;
- }
- return hr;
- }
- // Returns the number of bytes downloaded and bytes to download for all files
- // in the job. If the values are not known or if an error has occurred,
- // a value of -1 is reported.
- HRESULT GetJobByteCount(const ComPtr<IBackgroundCopyJob>& job,
- int64_t* downloaded_bytes,
- int64_t* total_bytes) {
- *downloaded_bytes = -1;
- *total_bytes = -1;
- if (!job)
- return E_FAIL;
- BG_JOB_PROGRESS job_progress = {};
- HRESULT hr = job->GetProgress(&job_progress);
- if (FAILED(hr))
- return hr;
- const uint64_t kMaxNumBytes =
- static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
- if (job_progress.BytesTransferred <= kMaxNumBytes)
- *downloaded_bytes = job_progress.BytesTransferred;
- if (job_progress.BytesTotal <= kMaxNumBytes &&
- job_progress.BytesTotal != BG_SIZE_UNKNOWN)
- *total_bytes = job_progress.BytesTotal;
- return S_OK;
- }
- HRESULT GetJobDisplayName(const ComPtr<IBackgroundCopyJob>& job,
- std::wstring* name) {
- ScopedCoMem<wchar_t> local_name;
- const HRESULT hr = job->GetDisplayName(&local_name);
- if (FAILED(hr))
- return hr;
- *name = local_name.get();
- return S_OK;
- }
- // Returns the job error code in |error_code| if the job is in the transient
- // or the final error state. Otherwise, the job error is not available and
- // the function fails.
- HRESULT GetJobError(const ComPtr<IBackgroundCopyJob>& job,
- HRESULT* error_code_out) {
- *error_code_out = S_OK;
- ComPtr<IBackgroundCopyError> copy_error;
- HRESULT hr = job->GetError(©_error);
- if (FAILED(hr))
- return hr;
- BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE;
- HRESULT error_code = S_OK;
- hr = copy_error->GetError(&error_context, &error_code);
- if (FAILED(hr))
- return hr;
- *error_code_out = FAILED(error_code) ? error_code : E_FAIL;
- return S_OK;
- }
- // Finds the component updater jobs matching the given predicate.
- // Returns S_OK if the function has found at least one job, returns S_FALSE if
- // no job was found, and it returns an error otherwise.
- template <class Predicate>
- HRESULT FindBitsJobIf(Predicate pred,
- const ComPtr<IBackgroundCopyManager>& bits_manager,
- std::vector<ComPtr<IBackgroundCopyJob>>* jobs) {
- ComPtr<IEnumBackgroundCopyJobs> enum_jobs;
- HRESULT hr = bits_manager->EnumJobs(0, &enum_jobs);
- if (FAILED(hr))
- return hr;
- ULONG job_count = 0;
- hr = enum_jobs->GetCount(&job_count);
- if (FAILED(hr))
- return hr;
- // Iterate over jobs, run the predicate, and select the job only if
- // the job description matches the component updater jobs.
- for (ULONG i = 0; i != job_count; ++i) {
- ComPtr<IBackgroundCopyJob> current_job;
- if (enum_jobs->Next(1, ¤t_job, nullptr) == S_OK &&
- pred(current_job)) {
- std::wstring job_name;
- hr = GetJobDisplayName(current_job, &job_name);
- if (job_name.compare(kJobName) == 0)
- jobs->push_back(current_job);
- }
- }
- return jobs->empty() ? S_FALSE : S_OK;
- }
- bool JobCreationOlderThanDaysPredicate(ComPtr<IBackgroundCopyJob> job,
- int num_days) {
- BG_JOB_TIMES times = {};
- HRESULT hr = job->GetTimes(×);
- if (FAILED(hr))
- return false;
- const base::TimeDelta time_delta(base::Days(num_days));
- const base::Time creation_time(base::Time::FromFileTime(times.CreationTime));
- return creation_time + time_delta < base::Time::Now();
- }
- bool JobFileUrlEqualPredicate(ComPtr<IBackgroundCopyJob> job, const GURL& url) {
- std::vector<ComPtr<IBackgroundCopyFile>> files;
- HRESULT hr = GetFilesInJob(job, &files);
- if (FAILED(hr))
- return false;
- for (size_t i = 0; i != files.size(); ++i) {
- ScopedCoMem<wchar_t> remote_name;
- if (SUCCEEDED(files[i]->GetRemoteName(&remote_name)) &&
- url == GURL(base::SysWideToUTF8(remote_name.get())))
- return true;
- }
- return false;
- }
- // Creates an instance of the BITS manager.
- HRESULT CreateBitsManager(ComPtr<IBackgroundCopyManager>* bits_manager) {
- ComPtr<IBackgroundCopyManager> local_bits_manager;
- HRESULT hr =
- ::CoCreateInstance(__uuidof(BackgroundCopyManager), nullptr, CLSCTX_ALL,
- IID_PPV_ARGS(&local_bits_manager));
- if (FAILED(hr)) {
- return hr;
- }
- *bits_manager = local_bits_manager;
- return S_OK;
- }
- void CleanupJob(const ComPtr<IBackgroundCopyJob>& job) {
- if (!job)
- return;
- // Get the file paths associated with this job before canceling the job.
- // Canceling the job removes it from the BITS queue right away. It appears
- // that it is still possible to query for the properties of the job after
- // the job has been canceled. It seems safer though to get the paths first.
- std::vector<ComPtr<IBackgroundCopyFile>> files;
- GetFilesInJob(job, &files);
- std::vector<base::FilePath> paths;
- for (const auto& file : files) {
- std::wstring local_name;
- HRESULT hr = GetJobFileProperties(file, &local_name, nullptr, nullptr);
- if (SUCCEEDED(hr))
- paths.push_back(base::FilePath(local_name));
- }
- job->Cancel();
- for (const auto& path : paths)
- DeleteFileAndEmptyParentDirectory(path);
- }
- } // namespace
- BackgroundDownloader::BackgroundDownloader(
- scoped_refptr<CrxDownloader> successor)
- : CrxDownloader(std::move(successor)),
- com_task_runner_(base::ThreadPool::CreateCOMSTATaskRunner(
- kTaskTraitsBackgroundDownloader)),
- git_cookie_bits_manager_(0),
- git_cookie_job_(0) {}
- BackgroundDownloader::~BackgroundDownloader() = default;
- void BackgroundDownloader::StartTimer() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- timer_ = std::make_unique<base::OneShotTimer>();
- timer_->Start(FROM_HERE, base::Seconds(kJobPollingIntervalSec), this,
- &BackgroundDownloader::OnTimer);
- }
- void BackgroundDownloader::OnTimer() {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- timer_ = nullptr;
- com_task_runner_->PostTask(
- FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloading, this));
- }
- void BackgroundDownloader::DoStartDownload(const GURL& url) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- com_task_runner_->PostTask(
- FROM_HERE,
- base::BindOnce(&BackgroundDownloader::BeginDownload, this, url));
- }
- // Called one time when this class is asked to do a download.
- void BackgroundDownloader::BeginDownload(const GURL& url) {
- DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
- download_start_time_ = base::TimeTicks::Now();
- job_stuck_begin_time_ = download_start_time_;
- HRESULT hr = BeginDownloadHelper(url);
- if (FAILED(hr)) {
- EndDownload(hr);
- return;
- }
- VLOG(1) << "Starting BITS download for: " << url.spec();
- ResetInterfacePointers();
- main_task_runner()->PostTask(
- FROM_HERE, base::BindOnce(&BackgroundDownloader::StartTimer, this));
- }
- // Creates or opens an existing BITS job to download the |url|, and handles
- // the marshalling of the interfaces in GIT.
- HRESULT BackgroundDownloader::BeginDownloadHelper(const GURL& url) {
- ComPtr<IGlobalInterfaceTable> git;
- HRESULT hr = GetGit(&git);
- if (FAILED(hr))
- return hr;
- hr = CreateBitsManager(&bits_manager_);
- if (FAILED(hr))
- return hr;
- hr = QueueBitsJob(url, &job_);
- if (FAILED(hr))
- return hr;
- hr = RegisterInterfaceInGit(git, bits_manager_, &git_cookie_bits_manager_);
- if (FAILED(hr))
- return hr;
- hr = RegisterInterfaceInGit(git, job_, &git_cookie_job_);
- if (FAILED(hr))
- return hr;
- return S_OK;
- }
- // Called any time the timer fires.
- void BackgroundDownloader::OnDownloading() {
- DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
- HRESULT hr = UpdateInterfacePointers();
- if (FAILED(hr)) {
- EndDownload(hr);
- return;
- }
- BG_JOB_STATE job_state = BG_JOB_STATE_ERROR;
- hr = job_->GetState(&job_state);
- if (FAILED(hr)) {
- EndDownload(hr);
- return;
- }
- bool is_handled = false;
- switch (job_state) {
- case BG_JOB_STATE_TRANSFERRED:
- is_handled = OnStateTransferred();
- break;
- case BG_JOB_STATE_ERROR:
- is_handled = OnStateError();
- break;
- case BG_JOB_STATE_CANCELLED:
- is_handled = OnStateCancelled();
- break;
- case BG_JOB_STATE_ACKNOWLEDGED:
- is_handled = OnStateAcknowledged();
- break;
- case BG_JOB_STATE_QUEUED:
- // Fall through.
- case BG_JOB_STATE_CONNECTING:
- // Fall through.
- case BG_JOB_STATE_SUSPENDED:
- is_handled = OnStateQueued();
- break;
- case BG_JOB_STATE_TRANSIENT_ERROR:
- is_handled = OnStateTransientError();
- break;
- case BG_JOB_STATE_TRANSFERRING:
- is_handled = OnStateTransferring();
- break;
- default:
- break;
- }
- if (is_handled)
- return;
- ResetInterfacePointers();
- main_task_runner()->PostTask(
- FROM_HERE, base::BindOnce(&BackgroundDownloader::StartTimer, this));
- }
- // Completes the BITS download, picks up the file path of the response, and
- // notifies the CrxDownloader. The function should be called only once.
- void BackgroundDownloader::EndDownload(HRESULT error) {
- DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
- const base::TimeTicks download_end_time(base::TimeTicks::Now());
- const base::TimeDelta download_time =
- download_end_time >= download_start_time_
- ? download_end_time - download_start_time_
- : base::TimeDelta();
- int64_t downloaded_bytes = -1;
- int64_t total_bytes = -1;
- GetJobByteCount(job_, &downloaded_bytes, &total_bytes);
- if (FAILED(error))
- CleanupJob(job_);
- ClearGit();
- // Consider the url handled if it has been successfully downloaded or a
- // 5xx has been received.
- const bool is_handled =
- SUCCEEDED(error) || IsHttpServerError(GetHttpStatusFromBitsError(error));
- const int error_to_report = SUCCEEDED(error) ? 0 : error;
- DCHECK(static_cast<bool>(error_to_report) == !base::PathExists(response_));
- DownloadMetrics download_metrics;
- download_metrics.url = url();
- download_metrics.downloader = DownloadMetrics::kBits;
- download_metrics.error = error_to_report;
- download_metrics.downloaded_bytes = downloaded_bytes;
- download_metrics.total_bytes = total_bytes;
- download_metrics.download_time_ms = download_time.InMilliseconds();
- Result result;
- result.error = error_to_report;
- if (!result.error)
- result.response = response_;
- main_task_runner()->PostTask(
- FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloadComplete, this,
- is_handled, result, download_metrics));
- }
- // Called when the BITS job has been transferred successfully. Completes the
- // BITS job by removing it from the BITS queue and making the download
- // available to the caller.
- bool BackgroundDownloader::OnStateTransferred() {
- EndDownload(CompleteJob());
- return true;
- }
- // Called when the job has encountered an error and no further progress can
- // be made. Cancels this job and removes it from the BITS queue.
- bool BackgroundDownloader::OnStateError() {
- HRESULT error_code = S_OK;
- HRESULT hr = GetJobError(job_, &error_code);
- if (FAILED(hr))
- error_code = hr;
- DCHECK(FAILED(error_code));
- EndDownload(error_code);
- return true;
- }
- // Called when the download was completed. This notification is not seen
- // in the current implementation but provided here as a defensive programming
- // measure.
- bool BackgroundDownloader::OnStateAcknowledged() {
- EndDownload(E_UNEXPECTED);
- return true;
- }
- // Called when the download was cancelled. Same as above.
- bool BackgroundDownloader::OnStateCancelled() {
- EndDownload(E_UNEXPECTED);
- return true;
- }
- // Called when the job has encountered a transient error, such as a
- // network disconnect, a server error, or some other recoverable error.
- bool BackgroundDownloader::OnStateTransientError() {
- // If the job appears to be stuck, handle the transient error as if
- // it were a final error. This causes the job to be cancelled and a specific
- // error be returned, if the error was available.
- if (IsStuck()) {
- return OnStateError();
- }
- // Don't retry at all if the transient error was a 5xx.
- HRESULT error_code = S_OK;
- HRESULT hr = GetJobError(job_, &error_code);
- if (SUCCEEDED(hr) &&
- IsHttpServerError(GetHttpStatusFromBitsError(error_code))) {
- return OnStateError();
- }
- return false;
- }
- bool BackgroundDownloader::OnStateQueued() {
- if (!IsStuck())
- return false;
- // Terminate the download if the job has not made progress in a while.
- EndDownload(E_ABORT);
- return true;
- }
- bool BackgroundDownloader::OnStateTransferring() {
- // Resets the baseline for detecting a stuck job since the job is transferring
- // data and it is making progress.
- job_stuck_begin_time_ = base::TimeTicks::Now();
- int64_t downloaded_bytes = -1;
- int64_t total_bytes = -1;
- GetJobByteCount(job_, &downloaded_bytes, &total_bytes);
- main_task_runner()->PostTask(
- FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloadProgress, this,
- downloaded_bytes, total_bytes));
- return false;
- }
- HRESULT BackgroundDownloader::QueueBitsJob(const GURL& url,
- ComPtr<IBackgroundCopyJob>* job) {
- DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
- size_t num_jobs = std::numeric_limits<size_t>::max();
- HRESULT hr = GetBackgroundDownloaderJobCount(&num_jobs);
- // The metric records a large number if the job count can't be read.
- UMA_HISTOGRAM_COUNTS_100("UpdateClient.BackgroundDownloaderJobs", num_jobs);
- // Remove some old jobs from the BITS queue before creating new jobs.
- CleanupStaleJobs();
- if (FAILED(hr) || num_jobs >= kMaxQueuedJobs)
- return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF,
- CrxDownloaderError::BITS_TOO_MANY_JOBS);
- ComPtr<IBackgroundCopyJob> local_job;
- hr = CreateOrOpenJob(url, &local_job);
- if (FAILED(hr)) {
- CleanupJob(local_job);
- return hr;
- }
- const bool is_new_job = hr == S_OK;
- UMA_HISTOGRAM_BOOLEAN("UpdateClient.BackgroundDownloaderNewJob", is_new_job);
- hr = local_job->Resume();
- if (FAILED(hr)) {
- CleanupJob(local_job);
- return hr;
- }
- *job = local_job;
- return S_OK;
- }
- HRESULT BackgroundDownloader::CreateOrOpenJob(const GURL& url,
- ComPtr<IBackgroundCopyJob>* job) {
- std::vector<ComPtr<IBackgroundCopyJob>> jobs;
- HRESULT hr = FindBitsJobIf(
- [&url](ComPtr<IBackgroundCopyJob> job) {
- return JobFileUrlEqualPredicate(job, url);
- },
- bits_manager_, &jobs);
- if (SUCCEEDED(hr) && !jobs.empty()) {
- *job = jobs.front();
- return S_FALSE;
- }
- ComPtr<IBackgroundCopyJob> local_job;
- GUID guid = {0};
- hr = bits_manager_->CreateJob(kJobName, BG_JOB_TYPE_DOWNLOAD, &guid,
- &local_job);
- if (FAILED(hr)) {
- CleanupJob(local_job);
- return hr;
- }
- hr = InitializeNewJob(local_job, url);
- if (FAILED(hr)) {
- CleanupJob(local_job);
- return hr;
- }
- *job = local_job;
- return S_OK;
- }
- HRESULT BackgroundDownloader::InitializeNewJob(
- const ComPtr<IBackgroundCopyJob>& job,
- const GURL& url) {
- base::FilePath tempdir;
- if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_BITS_"),
- &tempdir))
- return E_FAIL;
- const std::wstring filename(base::SysUTF8ToWide(url.ExtractFileName()));
- HRESULT hr = job->AddFile(base::SysUTF8ToWide(url.spec()).c_str(),
- tempdir.Append(filename).value().c_str());
- if (FAILED(hr))
- return hr;
- hr = job->SetDescription(filename.c_str());
- if (FAILED(hr))
- return hr;
- hr = job->SetPriority(BG_JOB_PRIORITY_NORMAL);
- if (FAILED(hr))
- return hr;
- hr = job->SetMinimumRetryDelay(60 * kMinimumRetryDelayMin);
- if (FAILED(hr))
- return hr;
- const int kSecondsDay = 60 * 60 * 24;
- hr = job->SetNoProgressTimeout(kSecondsDay * kSetNoProgressTimeoutDays);
- if (FAILED(hr))
- return hr;
- return S_OK;
- }
- bool BackgroundDownloader::IsStuck() {
- const base::TimeDelta job_stuck_timeout(base::Minutes(kJobStuckTimeoutMin));
- return job_stuck_begin_time_ + job_stuck_timeout < base::TimeTicks::Now();
- }
- HRESULT BackgroundDownloader::CompleteJob() {
- HRESULT hr = job_->Complete();
- if (FAILED(hr) && hr != BG_S_UNABLE_TO_DELETE_FILES)
- return hr;
- std::vector<ComPtr<IBackgroundCopyFile>> files;
- hr = GetFilesInJob(job_, &files);
- if (FAILED(hr))
- return hr;
- if (files.empty())
- return E_UNEXPECTED;
- std::wstring local_name;
- BG_FILE_PROGRESS progress = {0};
- hr = GetJobFileProperties(files.front(), &local_name, nullptr, &progress);
- if (FAILED(hr))
- return hr;
- // Check the post-conditions of a successful download, including the file and
- // job invariants. The byte counts for a job and its file must match as a job
- // only contains one file.
- DCHECK(progress.Completed);
- DCHECK_EQ(progress.BytesTotal, progress.BytesTransferred);
- response_ = base::FilePath(local_name);
- return S_OK;
- }
- HRESULT BackgroundDownloader::UpdateInterfacePointers() {
- DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
- bits_manager_ = nullptr;
- job_ = nullptr;
- ComPtr<IGlobalInterfaceTable> git;
- HRESULT hr = GetGit(&git);
- if (FAILED(hr))
- return hr;
- hr = GetInterfaceFromGit(git, git_cookie_bits_manager_,
- IID_PPV_ARGS(&bits_manager_));
- if (FAILED(hr))
- return hr;
- hr = GetInterfaceFromGit(git, git_cookie_job_, IID_PPV_ARGS(&job_));
- if (FAILED(hr))
- return hr;
- return S_OK;
- }
- void BackgroundDownloader::ResetInterfacePointers() {
- job_ = nullptr;
- bits_manager_ = nullptr;
- }
- HRESULT BackgroundDownloader::ClearGit() {
- DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
- ResetInterfacePointers();
- ComPtr<IGlobalInterfaceTable> git;
- HRESULT hr = GetGit(&git);
- if (FAILED(hr))
- return hr;
- const DWORD cookies[] = {
- git_cookie_job_, git_cookie_bits_manager_
- };
- for (auto cookie : cookies) {
- // TODO(sorin): check the result of the call, see crbug.com/644857.
- git->RevokeInterfaceFromGlobal(cookie);
- }
- return S_OK;
- }
- HRESULT BackgroundDownloader::GetBackgroundDownloaderJobCount(
- size_t* num_jobs) {
- DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
- DCHECK(bits_manager_);
- std::vector<ComPtr<IBackgroundCopyJob>> jobs;
- const HRESULT hr =
- FindBitsJobIf([](const ComPtr<IBackgroundCopyJob>&) { return true; },
- bits_manager_, &jobs);
- if (FAILED(hr))
- return hr;
- *num_jobs = jobs.size();
- return S_OK;
- }
- void BackgroundDownloader::CleanupStaleJobs() {
- DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
- DCHECK(bits_manager_);
- static base::Time last_sweep;
- const base::TimeDelta time_delta(
- base::Days(kPurgeStaleJobsIntervalBetweenChecksDays));
- const base::Time current_time(base::Time::Now());
- if (last_sweep + time_delta > current_time)
- return;
- last_sweep = current_time;
- std::vector<ComPtr<IBackgroundCopyJob>> jobs;
- FindBitsJobIf(
- [](ComPtr<IBackgroundCopyJob> job) {
- return JobCreationOlderThanDaysPredicate(job, kPurgeStaleJobsAfterDays);
- },
- bits_manager_, &jobs);
- for (const auto& job : jobs)
- CleanupJob(job);
- }
- } // namespace update_client
|