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