// 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/file_path.h" #include "base/file_util.h" #include "base/logging.h" #include "base/memory/singleton.h" #include "base/synchronization/waitable_event.h" #include "base/threading/thread_restrictions.h" #include "base/time.h" #include "chrome/browser/chromeos/system/name_value_pairs_parser.h" #include "chrome/common/child_process_logging.h" #include "chrome/common/chrome_version_info.h" #include "chromeos/chromeos_switches.h" #include "content/public/browser/browser_thread.h" 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; } // namespace // 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; static StatisticsProviderImpl* GetInstance(); private: 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_; 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; } // 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"; } StatisticsProviderImpl* StatisticsProviderImpl::GetInstance() { return Singleton >::get(); } // The stub StatisticsProvider implementation used on Linux desktop. class StatisticsProviderStubImpl : public StatisticsProvider { 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; } static StatisticsProviderStubImpl* GetInstance() { return Singleton >::get(); } private: friend struct DefaultSingletonTraits; StatisticsProviderStubImpl() { } DISALLOW_COPY_AND_ASSIGN(StatisticsProviderStubImpl); }; StatisticsProvider* StatisticsProvider::GetInstance() { if (base::chromeos::IsRunningOnChromeOS()) { return StatisticsProviderImpl::GetInstance(); } else { return StatisticsProviderStubImpl::GetInstance(); } } } // namespace system } // namespace chromeos