blob: 3b9337d0d82dd02f6e69bcc99de31405ae9de810 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/upgrade_detector/installed_version_poller.h"
#include <stdint.h>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/upgrade_detector/build_state.h"
#include "chrome/browser/upgrade_detector/get_installed_version.h"
#include "chrome/browser/upgrade_detector/installed_version_monitor.h"
#include "chrome/common/chrome_switches.h"
#include "components/version_info/version_info.h"
namespace {
bool g_disabled_for_testing = false;
// Bits in a testing options bitfield.
constexpr uint32_t kSimulateUpgrade = 0x01;
constexpr uint32_t kSimulateCriticalUpdate = 0x02;
// Returns a bitfield of the testing options that are selected via command line
// switches, or 0 if no options are selected.
uint32_t GetTestingOptions() {
uint32_t testing_options = 0;
const base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
if (cmd_line.HasSwitch(switches::kSimulateUpgrade))
testing_options |= kSimulateUpgrade;
// Simulating a critical update is a superset of simulating an upgrade.
if (cmd_line.HasSwitch(switches::kSimulateCriticalUpdate))
testing_options |= (kSimulateUpgrade | kSimulateCriticalUpdate);
return testing_options;
}
// A GetInstalledVersionCallback implementation used when a regular or a
// critical update is simulated via --simulate-upgrade or
// --simulate-critical-update.
void SimulateGetInstalledVersion(uint32_t testing_options,
InstalledVersionCallback callback) {
DCHECK_NE(0U, testing_options);
std::vector<uint32_t> components = version_info::GetVersion().components();
components[3] += 2;
InstalledAndCriticalVersion result((base::Version(components)));
if ((testing_options & kSimulateCriticalUpdate) != 0) {
--components[3];
result.critical_version.emplace(std::move(components));
}
std::move(callback).Run(std::move(result));
}
// Returns the callback to get the installed version. Use of any testing option
// on the process command line results in use of the simulation function.
InstalledVersionPoller::GetInstalledVersionCallback
GetGetInstalledVersionCallback() {
const uint32_t testing_options = GetTestingOptions();
return testing_options ? base::BindRepeating(&SimulateGetInstalledVersion,
testing_options)
: base::BindRepeating(&GetInstalledVersion);
}
// Returns the polling interval specified by --check-for-update-interval, or
// the default interval.
base::TimeDelta GetPollingInterval() {
const base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
const std::string seconds_str =
cmd_line.GetSwitchValueASCII(switches::kCheckForUpdateIntervalSec);
int seconds;
if (!seconds_str.empty() && base::StringToInt(seconds_str, &seconds))
return base::Seconds(seconds);
return InstalledVersionPoller::kDefaultPollingInterval;
}
} // namespace
// InstalledVersionPoller::ScopedDisableForTesting ----------------------------
InstalledVersionPoller::ScopedDisableForTesting::ScopedDisableForTesting() {
g_disabled_for_testing = true;
}
InstalledVersionPoller::ScopedDisableForTesting::~ScopedDisableForTesting() {
g_disabled_for_testing = false;
}
// InstalledVersionPoller ------------------------------------------------------
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class InstalledVersionPoller::PollType {
kStartup = 0, // The initial poll created at startup.
kMonitor = 1, // A poll in response to notification from the monitor.
kPeriodic = 2, // The periodic poll.
kMaxValue = kPeriodic
};
// static
const base::TimeDelta InstalledVersionPoller::kDefaultPollingInterval =
base::Hours(2);
InstalledVersionPoller::InstalledVersionPoller(BuildState* build_state)
: InstalledVersionPoller(build_state,
GetGetInstalledVersionCallback(),
InstalledVersionMonitor::Create(),
nullptr) {}
InstalledVersionPoller::InstalledVersionPoller(
BuildState* build_state,
GetInstalledVersionCallback get_installed_version,
std::unique_ptr<InstalledVersionMonitor> monitor,
const base::TickClock* tick_clock)
: build_state_(build_state),
get_installed_version_(std::move(get_installed_version)),
timer_(tick_clock) {
// Make the first check in the background without delay. Suppress this if
// polling is disabled for testing. This prevents all polling from taking
// place since the result of poll N kicks off poll N+1.
if (!g_disabled_for_testing) {
StartMonitor(std::move(monitor));
Poll(PollType::kStartup);
}
}
InstalledVersionPoller::~InstalledVersionPoller() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void InstalledVersionPoller::StartMonitor(
std::unique_ptr<InstalledVersionMonitor> monitor) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!monitor_);
monitor_ = std::move(monitor);
monitor_->Start(base::BindRepeating(&InstalledVersionPoller::OnMonitorResult,
weak_ptr_factory_.GetWeakPtr()));
}
void InstalledVersionPoller::OnMonitorResult(bool error) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!error) {
// Wait ten seconds before polling for the new version in case the monitor
// provides multiple notifications during a normal update. Repeat
// notifications will push back the poll.
timer_.Start(FROM_HERE, base::Seconds(10),
base::BindOnce(&InstalledVersionPoller::Poll,
base::Unretained(this), PollType::kMonitor));
} else {
// An error occurred while monitoring; disable the monitor.
monitor_.reset();
}
}
void InstalledVersionPoller::Poll(PollType poll_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Get the result back via a weak pointer so that the result is dropped on the
// floor should this instance be destroyed while polling.
get_installed_version_.Run(
base::BindOnce(&InstalledVersionPoller::OnInstalledVersion,
weak_ptr_factory_.GetWeakPtr(), poll_type));
}
void InstalledVersionPoller::OnInstalledVersion(
PollType poll_type,
InstalledAndCriticalVersion versions) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Consider either an invalid version or a higher version a normal update.
BuildState::UpdateType update_type = BuildState::UpdateType::kNormalUpdate;
if (versions.installed_version.IsValid()) {
switch (versions.installed_version.CompareTo(version_info::GetVersion())) {
case -1:
update_type = BuildState::UpdateType::kEnterpriseRollback;
break;
case 0:
update_type = BuildState::UpdateType::kNone;
break;
}
}
if (update_type == BuildState::UpdateType::kNone) {
// The discovered version matches the current version, so report that no
// update is available.
build_state_->SetUpdate(update_type, base::Version(), std::nullopt);
} else {
// Either the installed version could not be discovered (invalid installed
// version) or differs from the running version. Report it accordingly.
build_state_->SetUpdate(update_type, versions.installed_version,
versions.critical_version);
}
// Gather statistics on population that could update but hasn't.
UMA_HISTOGRAM_ENUMERATION("Chrome.BuildState.BuildStateUpdateType",
update_type);
// Poll again after the polling interval passes.
timer_.Start(FROM_HERE, GetPollingInterval(),
base::BindOnce(&InstalledVersionPoller::Poll,
base::Unretained(this), PollType::kPeriodic));
}