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. machine-info is generated only for OOBE and enterprise enrollment 45 // and may not be present. See login-manager/init/machine-info.conf. 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 except 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 } 271 272 if (!base::SysInfo::IsRunningOnChromeOS() && 273 machine_info_.find(kSerialNumber) == machine_info_.end()) { 274 // Set stub value for testing. A time value is appended to avoid clashes of 275 // the same serial for the same domain, which would invalidate earlier 276 // enrollments. A fake /tmp/machine-info file should be used instead if 277 // a stable serial is needed, e.g. to test re-enrollment. 278 base::TimeDelta time = base::Time::Now() - base::Time::UnixEpoch(); 279 machine_info_[kSerialNumber] = 280 "stub_serial_number_" + base::Int64ToString(time.InSeconds()); 281 } 282 283 // Finished loading the statistics. 284 on_statistics_loaded_.Signal(); 285 VLOG(1) << "Finished loading statistics."; 286 } 287 288 void StatisticsProviderImpl::LoadOemManifestFromFile( 289 const base::FilePath& file) { 290 // Called from LoadMachineStatistics. Check cancellation_flag_ again here. 291 if (cancellation_flag_.IsSet()) 292 return; 293 294 KioskOemManifestParser::Manifest oem_manifest; 295 if (!KioskOemManifestParser::Load(file, &oem_manifest)) { 296 LOG(WARNING) << "Unable to load OEM Manifest file: " << file.value(); 297 return; 298 } 299 machine_info_[kOemDeviceRequisitionKey] = 300 oem_manifest.device_requisition; 301 machine_flags_[kOemIsEnterpriseManagedKey] = 302 oem_manifest.enterprise_managed; 303 machine_flags_[kOemCanExitEnterpriseEnrollmentKey] = 304 oem_manifest.can_exit_enrollment; 305 machine_flags_[kOemKeyboardDrivenOobeKey] = 306 oem_manifest.keyboard_driven_oobe; 307 308 oem_manifest_loaded_ = true; 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