Home | History | Annotate | Download | only in system
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/system/statistics_provider.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/chromeos/chromeos_version.h"
      9 #include "base/command_line.h"
     10 #include "base/files/file_path.h"
     11 #include "base/logging.h"
     12 #include "base/memory/singleton.h"
     13 #include "base/path_service.h"
     14 #include "base/synchronization/waitable_event.h"
     15 #include "base/threading/thread_restrictions.h"
     16 #include "base/time/time.h"
     17 #include "chromeos/app_mode/kiosk_oem_manifest_parser.h"
     18 #include "chromeos/chromeos_constants.h"
     19 #include "chromeos/chromeos_switches.h"
     20 #include "chromeos/system/name_value_pairs_parser.h"
     21 #include "content/public/browser/browser_thread.h"
     22 
     23 #if defined(GOOGLE_CHROME_BUILD)
     24 // TODO(phajdan.jr): Drop that dependency, http://crbug.com/180711 .
     25 #include "chrome/common/chrome_version_info.h"
     26 #endif
     27 
     28 using content::BrowserThread;
     29 
     30 namespace chromeos {
     31 namespace system {
     32 namespace {
     33 
     34 // Path to the tool used to get system info, and delimiters for the output
     35 // format of the tool.
     36 const char* kCrosSystemTool[] = { "/usr/bin/crossystem" };
     37 const char kCrosSystemEq[] = "=";
     38 const char kCrosSystemDelim[] = "\n";
     39 const char kCrosSystemCommentDelim[] = "#";
     40 const char kCrosSystemUnknownValue[] = "(error)";
     41 
     42 const char kHardwareClassCrosSystemKey[] = "hwid";
     43 const char kHardwareClassKey[] = "hardware_class";
     44 const char kUnknownHardwareClass[] = "unknown";
     45 
     46 // File to get machine hardware info from, and key/value delimiters of
     47 // the file.
     48 // /tmp/machine-info is generated by platform/init/chromeos_startup.
     49 const char kMachineHardwareInfoFile[] = "/tmp/machine-info";
     50 const char kMachineHardwareInfoEq[] = "=";
     51 const char kMachineHardwareInfoDelim[] = " \n";
     52 
     53 // File to get ECHO coupon info from, and key/value delimiters of
     54 // the file.
     55 const char kEchoCouponFile[] = "/var/cache/echo/vpd_echo.txt";
     56 const char kEchoCouponEq[] = "=";
     57 const char kEchoCouponDelim[] = "\n";
     58 
     59 // File to get machine OS info from, and key/value delimiters of the file.
     60 const char kMachineOSInfoFile[] = "/etc/lsb-release";
     61 const char kMachineOSInfoEq[] = "=";
     62 const char kMachineOSInfoDelim[] = "\n";
     63 
     64 // File to get VPD info from, and key/value delimiters of the file.
     65 const char kVpdFile[] = "/var/log/vpd_2.0.txt";
     66 const char kVpdEq[] = "=";
     67 const char kVpdDelim[] = "\n";
     68 
     69 // Timeout that we should wait for statistics to get loaded
     70 const int kTimeoutSecs = 3;
     71 
     72 // The location of OEM manifest file used to trigger OOBE flow for kiosk mode.
     73 const CommandLine::CharType kOemManifestFilePath[] =
     74     FILE_PATH_LITERAL("/usr/share/oem/oobe/manifest.json");
     75 
     76 }  // namespace
     77 
     78 // Key values for GetMachineStatistic()/GetMachineFlag() calls.
     79 const char kDevSwitchBootMode[] = "devsw_boot";
     80 const char kHardwareClass[] = "hardware_class";
     81 const char kMachineInfoBoard[] =
     82     "CHROMEOS_RELEASE_BOARD";
     83 const char kOffersCouponCodeKey[] = "ubind_attribute";
     84 const char kOffersGroupCodeKey[] = "gbind_attribute";
     85 const char kOemCanExitEnterpriseEnrollmentKey[] =
     86     "oem_can_exit_enrollment";
     87 const char kOemDeviceRequisitionKey[] =
     88     "oem_device_requisition";
     89 const char kOemIsEnterpriseManagedKey[] =
     90     "oem_enterprise_managed";
     91 const char kOemKeyboardDrivenOobeKey[] =
     92     "oem_keyboard_driven_oobe";
     93 
     94 // The StatisticsProvider implementation used in production.
     95 class StatisticsProviderImpl : public StatisticsProvider {
     96  public:
     97   // StatisticsProvider implementation:
     98   virtual void Init() OVERRIDE;
     99   virtual void StartLoadingMachineStatistics() OVERRIDE;
    100   virtual bool GetMachineStatistic(const std::string& name,
    101                                    std::string* result) OVERRIDE;
    102   virtual bool GetMachineFlag(const std::string& name,
    103                               bool* result) OVERRIDE;
    104   virtual void LoadOemManifest() OVERRIDE;
    105 
    106   static StatisticsProviderImpl* GetInstance();
    107 
    108  protected:
    109   StatisticsProviderImpl();
    110   void LoadOemManifestFromFile(const base::FilePath& file);
    111 
    112  private:
    113   typedef std::map<std::string, bool> MachineFlags;
    114   friend struct DefaultSingletonTraits<StatisticsProviderImpl>;
    115 
    116   // Loads the machine info file, which is necessary to get the Chrome channel.
    117   // Treat MachineOSInfoFile specially, as distribution channel information
    118   // (stable, beta, dev, canary) is required at earlier stage than everything
    119   // else. Rather than posting a delayed task, read and parse the machine OS
    120   // info file immediately.
    121   void LoadMachineOSInfoFile();
    122 
    123   // Loads the machine statistcs by examining the system.
    124   void LoadMachineStatistics();
    125 
    126   bool initialized_;
    127   bool load_statistics_started_;
    128   NameValuePairsParser::NameValueMap machine_info_;
    129   MachineFlags machine_flags_;
    130   base::WaitableEvent on_statistics_loaded_;
    131 
    132   DISALLOW_COPY_AND_ASSIGN(StatisticsProviderImpl);
    133 };
    134 
    135 void StatisticsProviderImpl::Init() {
    136   DCHECK(!initialized_);
    137   initialized_ = true;
    138 
    139   // Load the machine info file immediately to get the channel info.
    140   LoadMachineOSInfoFile();
    141 }
    142 
    143 bool StatisticsProviderImpl::GetMachineStatistic(
    144     const std::string& name, std::string* result) {
    145   DCHECK(initialized_);
    146   DCHECK(load_statistics_started_);
    147 
    148   VLOG(1) << "Statistic is requested for " << name;
    149   // Block if the statistics are not loaded yet. Per LOG(WARNING) below,
    150   // the statistics are loaded before requested as of now. For regular
    151   // sessions (i.e. not OOBE), statistics are first requested when the
    152   // user is logging in so we have plenty of time to load the data
    153   // beforehand.
    154   //
    155   // If you see the warning appeared for regular sessions, it probably
    156   // means that there is new client code that uses the statistics in the
    157   // very early stage of the browser startup. The statistic name should be
    158   // helpful to identify the caller.
    159   if (!on_statistics_loaded_.IsSignaled()) {
    160     LOG(WARNING) << "Waiting to load statistics. Requested statistic: "
    161                  << name;
    162     // http://crbug.com/125385
    163     base::ThreadRestrictions::ScopedAllowWait allow_wait;
    164     on_statistics_loaded_.TimedWait(base::TimeDelta::FromSeconds(kTimeoutSecs));
    165 
    166     if (!on_statistics_loaded_.IsSignaled()) {
    167       LOG(ERROR) << "Statistics weren't loaded after waiting! "
    168                  << "Requested statistic: " << name;
    169       return false;
    170     }
    171   }
    172 
    173   NameValuePairsParser::NameValueMap::iterator iter = machine_info_.find(name);
    174   if (iter != machine_info_.end()) {
    175     *result = iter->second;
    176     return true;
    177   }
    178   return false;
    179 }
    180 
    181 bool StatisticsProviderImpl::GetMachineFlag(
    182     const std::string& name, bool* result) {
    183   MachineFlags::const_iterator iter = machine_flags_.find(name);
    184   if (iter != machine_flags_.end()) {
    185     *result = iter->second;
    186     return true;
    187   }
    188 
    189   return false;
    190 }
    191 
    192 void StatisticsProviderImpl::LoadOemManifest() {
    193   LoadOemManifestFromFile(base::FilePath(kOemManifestFilePath));
    194 }
    195 
    196 // manual_reset needs to be true, as we want to keep the signaled state.
    197 StatisticsProviderImpl::StatisticsProviderImpl()
    198     : initialized_(false),
    199       load_statistics_started_(false),
    200       on_statistics_loaded_(true  /* manual_reset */,
    201                             false /* initially_signaled */) {
    202 }
    203 
    204 void StatisticsProviderImpl::LoadMachineOSInfoFile() {
    205   NameValuePairsParser parser(&machine_info_);
    206   if (parser.GetNameValuePairsFromFile(base::FilePath(kMachineOSInfoFile),
    207                                        kMachineOSInfoEq,
    208                                        kMachineOSInfoDelim)) {
    209 #if defined(GOOGLE_CHROME_BUILD)
    210     const char kChromeOSReleaseTrack[] = "CHROMEOS_RELEASE_TRACK";
    211     NameValuePairsParser::NameValueMap::iterator iter =
    212         machine_info_.find(kChromeOSReleaseTrack);
    213     if (iter != machine_info_.end())
    214       chrome::VersionInfo::SetChannel(iter->second);
    215 #endif
    216   }
    217 }
    218 
    219 void StatisticsProviderImpl::StartLoadingMachineStatistics() {
    220   DCHECK(initialized_);
    221   DCHECK(!load_statistics_started_);
    222   load_statistics_started_ = true;
    223 
    224   VLOG(1) << "Started loading statistics";
    225   BrowserThread::PostBlockingPoolTask(
    226       FROM_HERE,
    227       base::Bind(&StatisticsProviderImpl::LoadMachineStatistics,
    228                  base::Unretained(this)));
    229 }
    230 
    231 void StatisticsProviderImpl::LoadMachineStatistics() {
    232   NameValuePairsParser parser(&machine_info_);
    233 
    234   // Parse all of the key/value pairs from the crossystem tool.
    235   if (!parser.ParseNameValuePairsFromTool(
    236           arraysize(kCrosSystemTool), kCrosSystemTool, kCrosSystemEq,
    237           kCrosSystemDelim, kCrosSystemCommentDelim)) {
    238     LOG(WARNING) << "There were errors parsing the output of "
    239                  << kCrosSystemTool << ".";
    240   }
    241 
    242   // Ensure that the hardware class key is present with the expected
    243   // key name, and if it couldn't be retrieved, that the value is "unknown".
    244   std::string hardware_class = machine_info_[kHardwareClassCrosSystemKey];
    245   if (hardware_class.empty() || hardware_class == kCrosSystemUnknownValue)
    246     machine_info_[kHardwareClassKey] = kUnknownHardwareClass;
    247   else
    248     machine_info_[kHardwareClassKey] = hardware_class;
    249 
    250   parser.GetNameValuePairsFromFile(base::FilePath(kMachineHardwareInfoFile),
    251                                    kMachineHardwareInfoEq,
    252                                    kMachineHardwareInfoDelim);
    253   parser.GetNameValuePairsFromFile(base::FilePath(kEchoCouponFile),
    254                                    kEchoCouponEq,
    255                                    kEchoCouponDelim);
    256   parser.GetNameValuePairsFromFile(base::FilePath(kVpdFile), kVpdEq, kVpdDelim);
    257 
    258   // Finished loading the statistics.
    259   on_statistics_loaded_.Signal();
    260   VLOG(1) << "Finished loading statistics";
    261 }
    262 
    263 void StatisticsProviderImpl::LoadOemManifestFromFile(
    264     const base::FilePath& file) {
    265   KioskOemManifestParser::Manifest oem_manifest;
    266   if (!KioskOemManifestParser::Load(file, &oem_manifest))
    267     return;
    268 
    269   machine_info_[kOemDeviceRequisitionKey] =
    270       oem_manifest.device_requisition;
    271   machine_flags_[kOemIsEnterpriseManagedKey] =
    272       oem_manifest.enterprise_managed;
    273   machine_flags_[kOemCanExitEnterpriseEnrollmentKey] =
    274       oem_manifest.can_exit_enrollment;
    275   machine_flags_[kOemKeyboardDrivenOobeKey] =
    276       oem_manifest.keyboard_driven_oobe;
    277 }
    278 
    279 StatisticsProviderImpl* StatisticsProviderImpl::GetInstance() {
    280   return Singleton<StatisticsProviderImpl,
    281                    DefaultSingletonTraits<StatisticsProviderImpl> >::get();
    282 }
    283 
    284 // The stub StatisticsProvider implementation used on Linux desktop.
    285 class StatisticsProviderStubImpl : public StatisticsProviderImpl {
    286  public:
    287   // StatisticsProvider implementation:
    288   virtual void Init() OVERRIDE {}
    289 
    290   virtual void StartLoadingMachineStatistics() OVERRIDE {}
    291 
    292   virtual bool GetMachineStatistic(const std::string& name,
    293                                    std::string* result) OVERRIDE {
    294     if (name == "CHROMEOS_RELEASE_BOARD") {
    295       // Note: syncer::GetSessionNameSynchronously() also uses the mechanism
    296       // below to determine the CrOs release board. However, it cannot include
    297       // statistics_provider.h and use this method because of the mutual
    298       // dependency that creates between sync.gyp:sync and chrome.gyp:browser.
    299       // TODO(rsimha): Update syncer::GetSessionNameSynchronously() if this code
    300       // is ever moved into base/. See http://crbug.com/126732.
    301       const CommandLine* command_line = CommandLine::ForCurrentProcess();
    302       if (command_line->HasSwitch(chromeos::switches::kChromeOSReleaseBoard)) {
    303         *result = command_line->
    304             GetSwitchValueASCII(chromeos::switches::kChromeOSReleaseBoard);
    305         return true;
    306       }
    307     }
    308     return false;
    309   }
    310 
    311   virtual void LoadOemManifest() OVERRIDE {
    312     CommandLine* command_line = CommandLine::ForCurrentProcess();
    313     if (!command_line->HasSwitch(switches::kAppOemManifestFile))
    314       return;
    315 
    316     LoadOemManifestFromFile(
    317         command_line->GetSwitchValuePath(switches::kAppOemManifestFile));
    318   }
    319 
    320   static StatisticsProviderStubImpl* GetInstance() {
    321     return Singleton<StatisticsProviderStubImpl,
    322         DefaultSingletonTraits<StatisticsProviderStubImpl> >::get();
    323   }
    324 
    325  private:
    326   friend struct DefaultSingletonTraits<StatisticsProviderStubImpl>;
    327 
    328   StatisticsProviderStubImpl() {
    329   }
    330 
    331   DISALLOW_COPY_AND_ASSIGN(StatisticsProviderStubImpl);
    332 };
    333 
    334 StatisticsProvider* StatisticsProvider::GetInstance() {
    335   if (base::chromeos::IsRunningOnChromeOS()) {
    336     return StatisticsProviderImpl::GetInstance();
    337   } else {
    338     return StatisticsProviderStubImpl::GetInstance();
    339   }
    340 }
    341 
    342 }  // namespace system
    343 }  // namespace chromeos
    344