|  | // 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/macros.h" | 
|  | #include "base/metrics/histogram_macros.h" | 
|  | #include "base/sequenced_task_runner.h" | 
|  | #include "base/strings/string_piece.h" | 
|  | #include "base/strings/sys_string_conversions.h" | 
|  | #include "base/task/post_task.h" | 
|  | #include "base/task/task_traits.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 thread, | 
|  | // while the BITS specific code runs on a separate thread passed in by the | 
|  | // client. 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 base::char16 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) { | 
|  | return ::CoCreateInstance(CLSID_StdGlobalInterfaceTable, nullptr, | 
|  | CLSCTX_INPROC_SERVER, | 
|  | IID_PPV_ARGS(git->GetAddressOf())); | 
|  | } | 
|  |  | 
|  | // 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.GetAddressOf()); | 
|  | 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.GetAddressOf(), 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, | 
|  | base::string16* local_name, | 
|  | base::string16* remote_name, | 
|  | BG_FILE_PROGRESS* progress) { | 
|  | if (!file) | 
|  | return E_FAIL; | 
|  |  | 
|  | HRESULT hr = S_OK; | 
|  |  | 
|  | if (local_name) { | 
|  | ScopedCoMem<base::char16> name; | 
|  | hr = file->GetLocalName(&name); | 
|  | if (FAILED(hr)) | 
|  | return hr; | 
|  | local_name->assign(name); | 
|  | } | 
|  |  | 
|  | if (remote_name) { | 
|  | ScopedCoMem<base::char16> 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, | 
|  | base::string16* name) { | 
|  | ScopedCoMem<base::char16> 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(copy_error.GetAddressOf()); | 
|  | 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.GetAddressOf()); | 
|  | 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, current_job.GetAddressOf(), nullptr) == S_OK && | 
|  | pred(current_job)) { | 
|  | base::string16 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::TimeDelta::FromDays(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<base::char16> remote_name; | 
|  | if (SUCCEEDED(files[i]->GetRemoteName(&remote_name)) && | 
|  | url == GURL(base::StringPiece16(remote_name))) | 
|  | 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) { | 
|  | base::string16 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( | 
|  | std::unique_ptr<CrxDownloader> successor) | 
|  | : CrxDownloader(std::move(successor)), | 
|  | com_task_runner_(base::CreateCOMSTATaskRunnerWithTraits( | 
|  | kTaskTraitsBackgroundDownloader)), | 
|  | git_cookie_bits_manager_(0), | 
|  | git_cookie_job_(0) {} | 
|  |  | 
|  | BackgroundDownloader::~BackgroundDownloader() { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | timer_.reset(); | 
|  | } | 
|  |  | 
|  | void BackgroundDownloader::StartTimer() { | 
|  | timer_.reset(new base::OneShotTimer); | 
|  | timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(kJobPollingIntervalSec), | 
|  | this, &BackgroundDownloader::OnTimer); | 
|  | } | 
|  |  | 
|  | void BackgroundDownloader::OnTimer() { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | com_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloading, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void BackgroundDownloader::DoStartDownload(const GURL& url) { | 
|  | DCHECK(thread_checker_.CalledOnValidThread()); | 
|  | com_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(&BackgroundDownloader::BeginDownload, | 
|  | base::Unretained(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; | 
|  | } | 
|  |  | 
|  | ResetInterfacePointers(); | 
|  | main_task_runner()->PostTask(FROM_HERE, | 
|  | base::BindOnce(&BackgroundDownloader::StartTimer, | 
|  | base::Unretained(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, | 
|  | base::Unretained(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, | 
|  | base::Unretained(this), is_handled, result, | 
|  | download_metrics)); | 
|  |  | 
|  | // Once the task is posted to the the main thread, this object may be deleted | 
|  | // by its owner. It is not safe to access members of this object on this task | 
|  | // runner from now on. | 
|  | } | 
|  |  | 
|  | // 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(); | 
|  |  | 
|  | main_task_runner()->PostTask( | 
|  | FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloadProgress, | 
|  | base::Unretained(this))); | 
|  | 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.GetAddressOf()); | 
|  | 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 base::string16 filename(base::SysUTF8ToWide(url.ExtractFileName())); | 
|  | HRESULT hr = job->AddFile(base::SysUTF8ToWide(url.spec()).c_str(), | 
|  | tempdir.Append(filename).AsUTF16Unsafe().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::TimeDelta::FromMinutes(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; | 
|  |  | 
|  | base::string16 local_name; | 
|  | BG_FILE_PROGRESS progress = {0}; | 
|  | hr = GetJobFileProperties(files.front(), &local_name, nullptr, &progress); | 
|  | if (FAILED(hr)) | 
|  | return hr; | 
|  |  | 
|  | // Sanity 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_.GetAddressOf())); | 
|  | if (FAILED(hr)) | 
|  | return hr; | 
|  |  | 
|  | hr = GetInterfaceFromGit(git, git_cookie_job_, | 
|  | IID_PPV_ARGS(job_.GetAddressOf())); | 
|  | 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::TimeDelta::FromDays(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 |