blob: 8680f947bc825b1460a009c0f382e646b27d649b [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/browser/chromeos/system/statistics_provider.h"
#include "base/bind.h"
#include "base/chromeos/chromeos_version.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/path_service.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "chromeos/app_mode/kiosk_oem_manifest_parser.h"
#include "chromeos/chromeos_constants.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/system/name_value_pairs_parser.h"
#include "content/public/browser/browser_thread.h"
#if defined(GOOGLE_CHROME_BUILD)
// TODO(phajdan.jr): Drop that dependency, http://crbug.com/180711 .
#include "chrome/common/chrome_version_info.h"
#endif
using content::BrowserThread;
namespace chromeos {
namespace system {
namespace {
// Path to the tool used to get system info, and delimiters for the output
// format of the tool.
const char* kCrosSystemTool[] = { "/usr/bin/crossystem" };
const char kCrosSystemEq[] = "=";
const char kCrosSystemDelim[] = "\n";
const char kCrosSystemCommentDelim[] = "#";
const char kCrosSystemUnknownValue[] = "(error)";
const char kHardwareClassCrosSystemKey[] = "hwid";
const char kHardwareClassKey[] = "hardware_class";
const char kUnknownHardwareClass[] = "unknown";
// File to get machine hardware info from, and key/value delimiters of
// the file.
// /tmp/machine-info is generated by platform/init/chromeos_startup.
const char kMachineHardwareInfoFile[] = "/tmp/machine-info";
const char kMachineHardwareInfoEq[] = "=";
const char kMachineHardwareInfoDelim[] = " \n";
// File to get ECHO coupon info from, and key/value delimiters of
// the file.
const char kEchoCouponFile[] = "/var/cache/echo/vpd_echo.txt";
const char kEchoCouponEq[] = "=";
const char kEchoCouponDelim[] = "\n";
// File to get machine OS info from, and key/value delimiters of the file.
const char kMachineOSInfoFile[] = "/etc/lsb-release";
const char kMachineOSInfoEq[] = "=";
const char kMachineOSInfoDelim[] = "\n";
// File to get VPD info from, and key/value delimiters of the file.
const char kVpdFile[] = "/var/log/vpd_2.0.txt";
const char kVpdEq[] = "=";
const char kVpdDelim[] = "\n";
// Timeout that we should wait for statistics to get loaded
const int kTimeoutSecs = 3;
// The location of OEM manifest file used to trigger OOBE flow for kiosk mode.
const CommandLine::CharType kOemManifestFilePath[] =
FILE_PATH_LITERAL("/usr/share/oem/oobe/manifest.json");
} // namespace
// Key values for GetMachineStatistic()/GetMachineFlag() calls.
const char kDevSwitchBootMode[] = "devsw_boot";
const char kHardwareClass[] = "hardware_class";
const char kMachineInfoBoard[] =
"CHROMEOS_RELEASE_BOARD";
const char kOffersCouponCodeKey[] = "ubind_attribute";
const char kOffersGroupCodeKey[] = "gbind_attribute";
const char kOemCanExitEnterpriseEnrollmentKey[] =
"oem_can_exit_enrollment";
const char kOemDeviceRequisitionKey[] =
"oem_device_requisition";
const char kOemIsEnterpriseManagedKey[] =
"oem_enterprise_managed";
const char kOemKeyboardDrivenOobeKey[] =
"oem_keyboard_driven_oobe";
// The StatisticsProvider implementation used in production.
class StatisticsProviderImpl : public StatisticsProvider {
public:
// StatisticsProvider implementation:
virtual void Init() OVERRIDE;
virtual void StartLoadingMachineStatistics() OVERRIDE;
virtual bool GetMachineStatistic(const std::string& name,
std::string* result) OVERRIDE;
virtual bool GetMachineFlag(const std::string& name,
bool* result) OVERRIDE;
virtual void LoadOemManifest() OVERRIDE;
static StatisticsProviderImpl* GetInstance();
protected:
StatisticsProviderImpl();
void LoadOemManifestFromFile(const base::FilePath& file);
private:
typedef std::map<std::string, bool> MachineFlags;
friend struct DefaultSingletonTraits<StatisticsProviderImpl>;
// Loads the machine info file, which is necessary to get the Chrome channel.
// Treat MachineOSInfoFile specially, as distribution channel information
// (stable, beta, dev, canary) is required at earlier stage than everything
// else. Rather than posting a delayed task, read and parse the machine OS
// info file immediately.
void LoadMachineOSInfoFile();
// Loads the machine statistcs by examining the system.
void LoadMachineStatistics();
bool initialized_;
bool load_statistics_started_;
NameValuePairsParser::NameValueMap machine_info_;
MachineFlags machine_flags_;
base::WaitableEvent on_statistics_loaded_;
DISALLOW_COPY_AND_ASSIGN(StatisticsProviderImpl);
};
void StatisticsProviderImpl::Init() {
DCHECK(!initialized_);
initialized_ = true;
// Load the machine info file immediately to get the channel info.
LoadMachineOSInfoFile();
}
bool StatisticsProviderImpl::GetMachineStatistic(
const std::string& name, std::string* result) {
DCHECK(initialized_);
DCHECK(load_statistics_started_);
VLOG(1) << "Statistic is requested for " << name;
// Block if the statistics are not loaded yet. Per LOG(WARNING) below,
// the statistics are loaded before requested as of now. For regular
// sessions (i.e. not OOBE), statistics are first requested when the
// user is logging in so we have plenty of time to load the data
// beforehand.
//
// If you see the warning appeared for regular sessions, it probably
// means that there is new client code that uses the statistics in the
// very early stage of the browser startup. The statistic name should be
// helpful to identify the caller.
if (!on_statistics_loaded_.IsSignaled()) {
LOG(WARNING) << "Waiting to load statistics. Requested statistic: "
<< name;
// http://crbug.com/125385
base::ThreadRestrictions::ScopedAllowWait allow_wait;
on_statistics_loaded_.TimedWait(base::TimeDelta::FromSeconds(kTimeoutSecs));
if (!on_statistics_loaded_.IsSignaled()) {
LOG(ERROR) << "Statistics weren't loaded after waiting! "
<< "Requested statistic: " << name;
return false;
}
}
NameValuePairsParser::NameValueMap::iterator iter = machine_info_.find(name);
if (iter != machine_info_.end()) {
*result = iter->second;
return true;
}
return false;
}
bool StatisticsProviderImpl::GetMachineFlag(
const std::string& name, bool* result) {
MachineFlags::const_iterator iter = machine_flags_.find(name);
if (iter != machine_flags_.end()) {
*result = iter->second;
return true;
}
return false;
}
void StatisticsProviderImpl::LoadOemManifest() {
LoadOemManifestFromFile(base::FilePath(kOemManifestFilePath));
}
// manual_reset needs to be true, as we want to keep the signaled state.
StatisticsProviderImpl::StatisticsProviderImpl()
: initialized_(false),
load_statistics_started_(false),
on_statistics_loaded_(true /* manual_reset */,
false /* initially_signaled */) {
}
void StatisticsProviderImpl::LoadMachineOSInfoFile() {
NameValuePairsParser parser(&machine_info_);
if (parser.GetNameValuePairsFromFile(base::FilePath(kMachineOSInfoFile),
kMachineOSInfoEq,
kMachineOSInfoDelim)) {
#if defined(GOOGLE_CHROME_BUILD)
const char kChromeOSReleaseTrack[] = "CHROMEOS_RELEASE_TRACK";
NameValuePairsParser::NameValueMap::iterator iter =
machine_info_.find(kChromeOSReleaseTrack);
if (iter != machine_info_.end())
chrome::VersionInfo::SetChannel(iter->second);
#endif
}
}
void StatisticsProviderImpl::StartLoadingMachineStatistics() {
DCHECK(initialized_);
DCHECK(!load_statistics_started_);
load_statistics_started_ = true;
VLOG(1) << "Started loading statistics";
BrowserThread::PostBlockingPoolTask(
FROM_HERE,
base::Bind(&StatisticsProviderImpl::LoadMachineStatistics,
base::Unretained(this)));
}
void StatisticsProviderImpl::LoadMachineStatistics() {
NameValuePairsParser parser(&machine_info_);
// Parse all of the key/value pairs from the crossystem tool.
if (!parser.ParseNameValuePairsFromTool(
arraysize(kCrosSystemTool), kCrosSystemTool, kCrosSystemEq,
kCrosSystemDelim, kCrosSystemCommentDelim)) {
LOG(WARNING) << "There were errors parsing the output of "
<< kCrosSystemTool << ".";
}
// Ensure that the hardware class key is present with the expected
// key name, and if it couldn't be retrieved, that the value is "unknown".
std::string hardware_class = machine_info_[kHardwareClassCrosSystemKey];
if (hardware_class.empty() || hardware_class == kCrosSystemUnknownValue)
machine_info_[kHardwareClassKey] = kUnknownHardwareClass;
else
machine_info_[kHardwareClassKey] = hardware_class;
parser.GetNameValuePairsFromFile(base::FilePath(kMachineHardwareInfoFile),
kMachineHardwareInfoEq,
kMachineHardwareInfoDelim);
parser.GetNameValuePairsFromFile(base::FilePath(kEchoCouponFile),
kEchoCouponEq,
kEchoCouponDelim);
parser.GetNameValuePairsFromFile(base::FilePath(kVpdFile), kVpdEq, kVpdDelim);
// Finished loading the statistics.
on_statistics_loaded_.Signal();
VLOG(1) << "Finished loading statistics";
}
void StatisticsProviderImpl::LoadOemManifestFromFile(
const base::FilePath& file) {
KioskOemManifestParser::Manifest oem_manifest;
if (!KioskOemManifestParser::Load(file, &oem_manifest))
return;
machine_info_[kOemDeviceRequisitionKey] =
oem_manifest.device_requisition;
machine_flags_[kOemIsEnterpriseManagedKey] =
oem_manifest.enterprise_managed;
machine_flags_[kOemCanExitEnterpriseEnrollmentKey] =
oem_manifest.can_exit_enrollment;
machine_flags_[kOemKeyboardDrivenOobeKey] =
oem_manifest.keyboard_driven_oobe;
}
StatisticsProviderImpl* StatisticsProviderImpl::GetInstance() {
return Singleton<StatisticsProviderImpl,
DefaultSingletonTraits<StatisticsProviderImpl> >::get();
}
// The stub StatisticsProvider implementation used on Linux desktop.
class StatisticsProviderStubImpl : public StatisticsProviderImpl {
public:
// StatisticsProvider implementation:
virtual void Init() OVERRIDE {}
virtual void StartLoadingMachineStatistics() OVERRIDE {}
virtual bool GetMachineStatistic(const std::string& name,
std::string* result) OVERRIDE {
if (name == "CHROMEOS_RELEASE_BOARD") {
// Note: syncer::GetSessionNameSynchronously() also uses the mechanism
// below to determine the CrOs release board. However, it cannot include
// statistics_provider.h and use this method because of the mutual
// dependency that creates between sync.gyp:sync and chrome.gyp:browser.
// TODO(rsimha): Update syncer::GetSessionNameSynchronously() if this code
// is ever moved into base/. See http://crbug.com/126732.
const CommandLine* command_line = CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(chromeos::switches::kChromeOSReleaseBoard)) {
*result = command_line->
GetSwitchValueASCII(chromeos::switches::kChromeOSReleaseBoard);
return true;
}
}
return false;
}
virtual void LoadOemManifest() OVERRIDE {
CommandLine* command_line = CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(switches::kAppOemManifestFile))
return;
LoadOemManifestFromFile(
command_line->GetSwitchValuePath(switches::kAppOemManifestFile));
}
static StatisticsProviderStubImpl* GetInstance() {
return Singleton<StatisticsProviderStubImpl,
DefaultSingletonTraits<StatisticsProviderStubImpl> >::get();
}
private:
friend struct DefaultSingletonTraits<StatisticsProviderStubImpl>;
StatisticsProviderStubImpl() {
}
DISALLOW_COPY_AND_ASSIGN(StatisticsProviderStubImpl);
};
StatisticsProvider* StatisticsProvider::GetInstance() {
if (base::chromeos::IsRunningOnChromeOS()) {
return StatisticsProviderImpl::GetInstance();
} else {
return StatisticsProviderStubImpl::GetInstance();
}
}
} // namespace system
} // namespace chromeos