blob: 4d6e28e9f614ef142aeb13970215b76478a19a1b [file] [log] [blame]
// 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(&times);
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;
}
VLOG(1) << "Starting BITS download for: " << url.spec();
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