Home | History | Annotate | Download | only in system
      1 // Copyright 2013 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 "chromeos/system/statistics_provider.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/files/file_path.h"
     10 #include "base/location.h"
     11 #include "base/logging.h"
     12 #include "base/memory/singleton.h"
     13 #include "base/path_service.h"
     14 #include "base/strings/string_number_conversions.h"
     15 #include "base/synchronization/cancellation_flag.h"
     16 #include "base/synchronization/waitable_event.h"
     17 #include "base/sys_info.h"
     18 #include "base/task_runner.h"
     19 #include "base/threading/thread_restrictions.h"
     20 #include "base/time/time.h"
     21 #include "chromeos/app_mode/kiosk_oem_manifest_parser.h"
     22 #include "chromeos/chromeos_constants.h"
     23 #include "chromeos/chromeos_switches.h"
     24 #include "chromeos/system/name_value_pairs_parser.h"
     25 
     26 namespace chromeos {
     27 namespace system {
     28 
     29 namespace {
     30 
     31 // Path to the tool used to get system info, and delimiters for the output
     32 // format of the tool.
     33 const char* kCrosSystemTool[] = { "/usr/bin/crossystem" };
     34 const char kCrosSystemEq[] = "=";
     35 const char kCrosSystemDelim[] = "\n";
     36 const char kCrosSystemCommentDelim[] = "#";
     37 const char kCrosSystemUnknownValue[] = "(error)";
     38 
     39 const char kHardwareClassCrosSystemKey[] = "hwid";
     40 const char kUnknownHardwareClass[] = "unknown";
     41 const char kSerialNumber[] = "sn";
     42 
     43 // File to get machine hardware info from, and key/value delimiters of
     44 // the file.
     45 // /tmp/machine-info is generated by platform/init/chromeos_startup.
     46 const char kMachineHardwareInfoFile[] = "/tmp/machine-info";
     47 const char kMachineHardwareInfoEq[] = "=";
     48 const char kMachineHardwareInfoDelim[] = " \n";
     49 
     50 // File to get ECHO coupon info from, and key/value delimiters of
     51 // the file.
     52 const char kEchoCouponFile[] = "/var/cache/echo/vpd_echo.txt";
     53 const char kEchoCouponEq[] = "=";
     54 const char kEchoCouponDelim[] = "\n";
     55 
     56 // File to get VPD info from, and key/value delimiters of the file.
     57 const char kVpdFile[] = "/var/log/vpd_2.0.txt";
     58 const char kVpdEq[] = "=";
     59 const char kVpdDelim[] = "\n";
     60 
     61 // Timeout that we should wait for statistics to get loaded
     62 const int kTimeoutSecs = 3;
     63 
     64 // The location of OEM manifest file used to trigger OOBE flow for kiosk mode.
     65 const CommandLine::CharType kOemManifestFilePath[] =
     66     FILE_PATH_LITERAL("/usr/share/oem/oobe/manifest.json");
     67 
     68 }  // namespace
     69 
     70 // Key values for GetMachineStatistic()/GetMachineFlag() calls.
     71 const char kDevSwitchBootMode[] = "devsw_boot";
     72 const char kCustomizationIdKey[] = "customization_id";
     73 const char kHardwareClassKey[] = "hardware_class";
     74 const char kOffersCouponCodeKey[] = "ubind_attribute";
     75 const char kOffersGroupCodeKey[] = "gbind_attribute";
     76 const char kRlzBrandCodeKey[] = "rlz_brand_code";
     77 
     78 // OEM specific statistics. Must be prefixed with "oem_".
     79 const char kOemCanExitEnterpriseEnrollmentKey[] = "oem_can_exit_enrollment";
     80 const char kOemDeviceRequisitionKey[] = "oem_device_requisition";
     81 const char kOemIsEnterpriseManagedKey[] = "oem_enterprise_managed";
     82 const char kOemKeyboardDrivenOobeKey[] = "oem_keyboard_driven_oobe";
     83 
     84 bool HasOemPrefix(const std::string& name) {
     85   return name.substr(0, 4) == "oem_";
     86 }
     87 
     88 // The StatisticsProvider implementation used in production.
     89 class StatisticsProviderImpl : public StatisticsProvider {
     90  public:
     91   // StatisticsProvider implementation:
     92   virtual void StartLoadingMachineStatistics(
     93       const scoped_refptr<base::TaskRunner>& file_task_runner,
     94       bool load_oem_manifest) OVERRIDE;
     95   virtual bool GetMachineStatistic(const std::string& name,
     96                                    std::string* result) OVERRIDE;
     97   virtual bool GetMachineFlag(const std::string& name, bool* result) OVERRIDE;
     98   virtual void Shutdown() OVERRIDE;
     99 
    100   static StatisticsProviderImpl* GetInstance();
    101 
    102  protected:
    103   typedef std::map<std::string, bool> MachineFlags;
    104   friend struct DefaultSingletonTraits<StatisticsProviderImpl>;
    105 
    106   StatisticsProviderImpl();
    107   virtual ~StatisticsProviderImpl();
    108 
    109   // Waits up to |kTimeoutSecs| for statistics to be loaded. Returns true if
    110   // they were loaded successfully.
    111   bool WaitForStatisticsLoaded();
    112 
    113   // Loads the machine statistics off of disk. Runs on the file thread.
    114   void LoadMachineStatistics(bool load_oem_manifest);
    115 
    116   // Loads the OEM statistics off of disk. Runs on the file thread.
    117   void LoadOemManifestFromFile(const base::FilePath& file);
    118 
    119   bool load_statistics_started_;
    120   NameValuePairsParser::NameValueMap machine_info_;
    121   MachineFlags machine_flags_;
    122   base::CancellationFlag cancellation_flag_;
    123   // |on_statistics_loaded_| protects |machine_info_| and |machine_flags_|.
    124   base::WaitableEvent on_statistics_loaded_;
    125   bool oem_manifest_loaded_;
    126 
    127  private:
    128   DISALLOW_COPY_AND_ASSIGN(StatisticsProviderImpl);
    129 };
    130 
    131 bool StatisticsProviderImpl::WaitForStatisticsLoaded() {
    132   CHECK(load_statistics_started_);
    133   if (on_statistics_loaded_.IsSignaled())
    134     return true;
    135 
    136   // Block if the statistics are not loaded yet. Normally this shouldn't
    137   // happen excpet during OOBE.
    138   base::Time start_time = base::Time::Now();
    139   base::ThreadRestrictions::ScopedAllowWait allow_wait;
    140   on_statistics_loaded_.TimedWait(base::TimeDelta::FromSeconds(kTimeoutSecs));
    141 
    142   base::TimeDelta dtime = base::Time::Now() - start_time;
    143   if (on_statistics_loaded_.IsSignaled()) {
    144     LOG(ERROR) << "Statistics loaded after waiting "
    145                << dtime.InMilliseconds() << "ms. ";
    146     return true;
    147   }
    148 
    149   LOG(ERROR) << "Statistics not loaded after waiting "
    150              << dtime.InMilliseconds() << "ms. ";
    151   return false;
    152 }
    153 
    154 bool StatisticsProviderImpl::GetMachineStatistic(const std::string& name,
    155                                                  std::string* result) {
    156   VLOG(1) << "Machine Statistic requested: " << name;
    157   if (!WaitForStatisticsLoaded()) {
    158     LOG(ERROR) << "GetMachineStatistic called before load started: " << name;
    159     return false;
    160   }
    161 
    162   NameValuePairsParser::NameValueMap::iterator iter = machine_info_.find(name);
    163   if (iter == machine_info_.end()) {
    164     if (base::SysInfo::IsRunningOnChromeOS() &&
    165         (oem_manifest_loaded_ || !HasOemPrefix(name))) {
    166       LOG(WARNING) << "Requested statistic not found: " << name;
    167     }
    168     return false;
    169   }
    170   *result = iter->second;
    171   return true;
    172 }
    173 
    174 bool StatisticsProviderImpl::GetMachineFlag(const std::string& name,
    175                                             bool* result) {
    176   VLOG(1) << "Machine Flag requested: " << name;
    177   if (!WaitForStatisticsLoaded()) {
    178     LOG(ERROR) << "GetMachineFlag called before load started: " << name;
    179     return false;
    180   }
    181 
    182   MachineFlags::const_iterator iter = machine_flags_.find(name);
    183   if (iter == machine_flags_.end()) {
    184     if (base::SysInfo::IsRunningOnChromeOS() &&
    185         (oem_manifest_loaded_ || !HasOemPrefix(name))) {
    186       LOG(WARNING) << "Requested machine flag not found: " << name;
    187     }
    188     return false;
    189   }
    190   *result = iter->second;
    191   return true;
    192 }
    193 
    194 void StatisticsProviderImpl::Shutdown() {
    195   cancellation_flag_.Set();  // Cancel any pending loads
    196 }
    197 
    198 StatisticsProviderImpl::StatisticsProviderImpl()
    199     : load_statistics_started_(false),
    200       on_statistics_loaded_(true  /* manual_reset */,
    201                             false /* initially_signaled */),
    202       oem_manifest_loaded_(false) {
    203 }
    204 
    205 StatisticsProviderImpl::~StatisticsProviderImpl() {
    206 }
    207 
    208 void StatisticsProviderImpl::StartLoadingMachineStatistics(
    209     const scoped_refptr<base::TaskRunner>& file_task_runner,
    210     bool load_oem_manifest) {
    211   CHECK(!load_statistics_started_);
    212   load_statistics_started_ = true;
    213 
    214   VLOG(1) << "Started loading statistics. Load OEM Manifest: "
    215           << load_oem_manifest;
    216 
    217   file_task_runner->PostTask(
    218       FROM_HERE,
    219       base::Bind(&StatisticsProviderImpl::LoadMachineStatistics,
    220                  base::Unretained(this),
    221                  load_oem_manifest));
    222 }
    223 
    224 void StatisticsProviderImpl::LoadMachineStatistics(bool load_oem_manifest) {
    225   // Run from the file task runner. StatisticsProviderImpl is a Singleton<> and
    226   // will not be destroyed until after threads have been stopped, so this test
    227   // is always safe.
    228   if (cancellation_flag_.IsSet())
    229     return;
    230 
    231   NameValuePairsParser parser(&machine_info_);
    232   if (base::SysInfo::IsRunningOnChromeOS()) {
    233     // Parse all of the key/value pairs from the crossystem tool.
    234     if (!parser.ParseNameValuePairsFromTool(arraysize(kCrosSystemTool),
    235                                             kCrosSystemTool,
    236                                             kCrosSystemEq,
    237                                             kCrosSystemDelim,
    238                                             kCrosSystemCommentDelim)) {
    239       LOG(ERROR) << "Errors parsing output from: " << kCrosSystemTool;
    240     }
    241   }
    242 
    243   parser.GetNameValuePairsFromFile(base::FilePath(kMachineHardwareInfoFile),
    244                                    kMachineHardwareInfoEq,
    245                                    kMachineHardwareInfoDelim);
    246   parser.GetNameValuePairsFromFile(base::FilePath(kEchoCouponFile),
    247                                    kEchoCouponEq,
    248                                    kEchoCouponDelim);
    249   parser.GetNameValuePairsFromFile(base::FilePath(kVpdFile),
    250                                    kVpdEq,
    251                                    kVpdDelim);
    252 
    253   // Ensure that the hardware class key is present with the expected
    254   // key name, and if it couldn't be retrieved, that the value is "unknown".
    255   std::string hardware_class = machine_info_[kHardwareClassCrosSystemKey];
    256   if (hardware_class.empty() || hardware_class == kCrosSystemUnknownValue)
    257     machine_info_[kHardwareClassKey] = kUnknownHardwareClass;
    258   else
    259     machine_info_[kHardwareClassKey] = hardware_class;
    260 
    261   if (load_oem_manifest) {
    262     // If kAppOemManifestFile switch is specified, load OEM Manifest file.
    263     CommandLine* command_line = CommandLine::ForCurrentProcess();
    264     if (command_line->HasSwitch(switches::kAppOemManifestFile)) {
    265       LoadOemManifestFromFile(
    266           command_line->GetSwitchValuePath(switches::kAppOemManifestFile));
    267     } else if (base::SysInfo::IsRunningOnChromeOS()) {
    268       LoadOemManifestFromFile(base::FilePath(kOemManifestFilePath));
    269     }
    270     oem_manifest_loaded_ = true;
    271   }
    272 
    273   if (!base::SysInfo::IsRunningOnChromeOS() &&
    274       machine_info_.find(kSerialNumber) == machine_info_.end()) {
    275     // Set stub value for testing. A time value is appended to avoid clashes of
    276     // the same serial for the same domain, which would invalidate earlier
    277     // enrollments. A fake /tmp/machine-info file should be used instead if
    278     // a stable serial is needed, e.g. to test re-enrollment.
    279     base::TimeDelta time = base::Time::Now() - base::Time::UnixEpoch();
    280     machine_info_[kSerialNumber] =
    281         "stub_serial_number_" + base::Int64ToString(time.InSeconds());
    282   }
    283 
    284   // Finished loading the statistics.
    285   on_statistics_loaded_.Signal();
    286   VLOG(1) << "Finished loading statistics.";
    287 }
    288 
    289 void StatisticsProviderImpl::LoadOemManifestFromFile(
    290     const base::FilePath& file) {
    291   // Called from LoadMachineStatistics. Check cancellation_flag_ again here.
    292   if (cancellation_flag_.IsSet())
    293     return;
    294 
    295   KioskOemManifestParser::Manifest oem_manifest;
    296   if (!KioskOemManifestParser::Load(file, &oem_manifest)) {
    297     LOG(WARNING) << "Unable to load OEM Manifest file: " << file.value();
    298     return;
    299   }
    300   machine_info_[kOemDeviceRequisitionKey] =
    301       oem_manifest.device_requisition;
    302   machine_flags_[kOemIsEnterpriseManagedKey] =
    303       oem_manifest.enterprise_managed;
    304   machine_flags_[kOemCanExitEnterpriseEnrollmentKey] =
    305       oem_manifest.can_exit_enrollment;
    306   machine_flags_[kOemKeyboardDrivenOobeKey] =
    307       oem_manifest.keyboard_driven_oobe;
    308 
    309   VLOG(1) << "Loaded OEM Manifest statistics from " << file.value();
    310 }
    311 
    312 StatisticsProviderImpl* StatisticsProviderImpl::GetInstance() {
    313   return Singleton<StatisticsProviderImpl,
    314                    DefaultSingletonTraits<StatisticsProviderImpl> >::get();
    315 }
    316 
    317 static StatisticsProvider* g_test_statistics_provider = NULL;
    318 
    319 // static
    320 StatisticsProvider* StatisticsProvider::GetInstance() {
    321   if (g_test_statistics_provider)
    322     return g_test_statistics_provider;
    323   return StatisticsProviderImpl::GetInstance();
    324 }
    325 
    326 // static
    327 void StatisticsProvider::SetTestProvider(StatisticsProvider* test_provider) {
    328   g_test_statistics_provider = test_provider;
    329 }
    330 
    331 }  // namespace system
    332 }  // namespace chromeos
    333