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.
5 #include "chromeos/system/statistics_provider.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"
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)";
39 const char kHardwareClassCrosSystemKey[] = "hwid";
40 const char kUnknownHardwareClass[] = "unknown";
41 const char kSerialNumber[] = "sn";
43 // File to get machine hardware info from, and key/value delimiters of
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";
50 // File to get ECHO coupon info from, and key/value delimiters of
52 const char kEchoCouponFile[] = "/var/cache/echo/vpd_echo.txt";
53 const char kEchoCouponEq[] = "=";
54 const char kEchoCouponDelim[] = "\n";
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";
61 // Timeout that we should wait for statistics to get loaded
62 const int kTimeoutSecs = 3;
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");
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 const char kDiskSerialNumber[] = "root_disk_serial_number";
79 // OEM specific statistics. Must be prefixed with "oem_".
80 const char kOemCanExitEnterpriseEnrollmentKey[] = "oem_can_exit_enrollment";
81 const char kOemDeviceRequisitionKey[] = "oem_device_requisition";
82 const char kOemIsEnterpriseManagedKey[] = "oem_enterprise_managed";
83 const char kOemKeyboardDrivenOobeKey[] = "oem_keyboard_driven_oobe";
85 bool HasOemPrefix(const std::string& name) {
86 return name.substr(0, 4) == "oem_";
89 // The StatisticsProvider implementation used in production.
90 class StatisticsProviderImpl : public StatisticsProvider {
92 // StatisticsProvider implementation:
93 virtual void StartLoadingMachineStatistics(
94 const scoped_refptr<base::TaskRunner>& file_task_runner,
95 bool load_oem_manifest) OVERRIDE;
96 virtual bool GetMachineStatistic(const std::string& name,
97 std::string* result) OVERRIDE;
98 virtual bool GetMachineFlag(const std::string& name, bool* result) OVERRIDE;
99 virtual void Shutdown() OVERRIDE;
101 static StatisticsProviderImpl* GetInstance();
104 typedef std::map<std::string, bool> MachineFlags;
105 friend struct DefaultSingletonTraits<StatisticsProviderImpl>;
107 StatisticsProviderImpl();
108 virtual ~StatisticsProviderImpl();
110 // Waits up to |kTimeoutSecs| for statistics to be loaded. Returns true if
111 // they were loaded successfully.
112 bool WaitForStatisticsLoaded();
114 // Loads the machine statistics off of disk. Runs on the file thread.
115 void LoadMachineStatistics(bool load_oem_manifest);
117 // Loads the OEM statistics off of disk. Runs on the file thread.
118 void LoadOemManifestFromFile(const base::FilePath& file);
120 bool load_statistics_started_;
121 NameValuePairsParser::NameValueMap machine_info_;
122 MachineFlags machine_flags_;
123 base::CancellationFlag cancellation_flag_;
124 // |on_statistics_loaded_| protects |machine_info_| and |machine_flags_|.
125 base::WaitableEvent on_statistics_loaded_;
126 bool oem_manifest_loaded_;
129 DISALLOW_COPY_AND_ASSIGN(StatisticsProviderImpl);
132 bool StatisticsProviderImpl::WaitForStatisticsLoaded() {
133 CHECK(load_statistics_started_);
134 if (on_statistics_loaded_.IsSignaled())
137 // Block if the statistics are not loaded yet. Normally this shouldn't
138 // happen excpet during OOBE.
139 base::Time start_time = base::Time::Now();
140 base::ThreadRestrictions::ScopedAllowWait allow_wait;
141 on_statistics_loaded_.TimedWait(base::TimeDelta::FromSeconds(kTimeoutSecs));
143 base::TimeDelta dtime = base::Time::Now() - start_time;
144 if (on_statistics_loaded_.IsSignaled()) {
145 LOG(ERROR) << "Statistics loaded after waiting "
146 << dtime.InMilliseconds() << "ms. ";
150 LOG(ERROR) << "Statistics not loaded after waiting "
151 << dtime.InMilliseconds() << "ms. ";
155 bool StatisticsProviderImpl::GetMachineStatistic(const std::string& name,
156 std::string* result) {
157 VLOG(1) << "Machine Statistic requested: " << name;
158 if (!WaitForStatisticsLoaded()) {
159 LOG(ERROR) << "GetMachineStatistic called before load started: " << name;
163 NameValuePairsParser::NameValueMap::iterator iter = machine_info_.find(name);
164 if (iter == machine_info_.end()) {
165 if (base::SysInfo::IsRunningOnChromeOS() &&
166 (oem_manifest_loaded_ || !HasOemPrefix(name))) {
167 LOG(WARNING) << "Requested statistic not found: " << name;
171 *result = iter->second;
175 bool StatisticsProviderImpl::GetMachineFlag(const std::string& name,
177 VLOG(1) << "Machine Flag requested: " << name;
178 if (!WaitForStatisticsLoaded()) {
179 LOG(ERROR) << "GetMachineFlag called before load started: " << name;
183 MachineFlags::const_iterator iter = machine_flags_.find(name);
184 if (iter == machine_flags_.end()) {
185 if (base::SysInfo::IsRunningOnChromeOS() &&
186 (oem_manifest_loaded_ || !HasOemPrefix(name))) {
187 LOG(WARNING) << "Requested machine flag not found: " << name;
191 *result = iter->second;
195 void StatisticsProviderImpl::Shutdown() {
196 cancellation_flag_.Set(); // Cancel any pending loads
199 StatisticsProviderImpl::StatisticsProviderImpl()
200 : load_statistics_started_(false),
201 on_statistics_loaded_(true /* manual_reset */,
202 false /* initially_signaled */),
203 oem_manifest_loaded_(false) {
206 StatisticsProviderImpl::~StatisticsProviderImpl() {
209 void StatisticsProviderImpl::StartLoadingMachineStatistics(
210 const scoped_refptr<base::TaskRunner>& file_task_runner,
211 bool load_oem_manifest) {
212 CHECK(!load_statistics_started_);
213 load_statistics_started_ = true;
215 VLOG(1) << "Started loading statistics. Load OEM Manifest: "
216 << load_oem_manifest;
218 file_task_runner->PostTask(
220 base::Bind(&StatisticsProviderImpl::LoadMachineStatistics,
221 base::Unretained(this),
225 void StatisticsProviderImpl::LoadMachineStatistics(bool load_oem_manifest) {
226 // Run from the file task runner. StatisticsProviderImpl is a Singleton<> and
227 // will not be destroyed until after threads have been stopped, so this test
229 if (cancellation_flag_.IsSet())
232 NameValuePairsParser parser(&machine_info_);
233 if (base::SysInfo::IsRunningOnChromeOS()) {
234 // Parse all of the key/value pairs from the crossystem tool.
235 if (!parser.ParseNameValuePairsFromTool(arraysize(kCrosSystemTool),
239 kCrosSystemCommentDelim)) {
240 LOG(ERROR) << "Errors parsing output from: " << kCrosSystemTool;
244 parser.GetNameValuePairsFromFile(base::FilePath(kMachineHardwareInfoFile),
245 kMachineHardwareInfoEq,
246 kMachineHardwareInfoDelim);
247 parser.GetNameValuePairsFromFile(base::FilePath(kEchoCouponFile),
250 parser.GetNameValuePairsFromFile(base::FilePath(kVpdFile),
254 // Ensure that the hardware class key is present with the expected
255 // key name, and if it couldn't be retrieved, that the value is "unknown".
256 std::string hardware_class = machine_info_[kHardwareClassCrosSystemKey];
257 if (hardware_class.empty() || hardware_class == kCrosSystemUnknownValue)
258 machine_info_[kHardwareClassKey] = kUnknownHardwareClass;
260 machine_info_[kHardwareClassKey] = hardware_class;
262 if (load_oem_manifest) {
263 // If kAppOemManifestFile switch is specified, load OEM Manifest file.
264 CommandLine* command_line = CommandLine::ForCurrentProcess();
265 if (command_line->HasSwitch(switches::kAppOemManifestFile)) {
266 LoadOemManifestFromFile(
267 command_line->GetSwitchValuePath(switches::kAppOemManifestFile));
268 } else if (base::SysInfo::IsRunningOnChromeOS()) {
269 LoadOemManifestFromFile(base::FilePath(kOemManifestFilePath));
271 oem_manifest_loaded_ = true;
274 if (!base::SysInfo::IsRunningOnChromeOS() &&
275 machine_info_.find(kSerialNumber) == machine_info_.end()) {
276 // Set stub value for testing. A time value is appended to avoid clashes of
277 // the same serial for the same domain, which would invalidate earlier
278 // enrollments. A fake /tmp/machine-info file should be used instead if
279 // a stable serial is needed, e.g. to test re-enrollment.
280 base::TimeDelta time = base::Time::Now() - base::Time::UnixEpoch();
281 machine_info_[kSerialNumber] =
282 "stub_serial_number_" + base::Int64ToString(time.InSeconds());
285 // Finished loading the statistics.
286 on_statistics_loaded_.Signal();
287 VLOG(1) << "Finished loading statistics.";
290 void StatisticsProviderImpl::LoadOemManifestFromFile(
291 const base::FilePath& file) {
292 // Called from LoadMachineStatistics. Check cancellation_flag_ again here.
293 if (cancellation_flag_.IsSet())
296 KioskOemManifestParser::Manifest oem_manifest;
297 if (!KioskOemManifestParser::Load(file, &oem_manifest)) {
298 LOG(WARNING) << "Unable to load OEM Manifest file: " << file.value();
301 machine_info_[kOemDeviceRequisitionKey] =
302 oem_manifest.device_requisition;
303 machine_flags_[kOemIsEnterpriseManagedKey] =
304 oem_manifest.enterprise_managed;
305 machine_flags_[kOemCanExitEnterpriseEnrollmentKey] =
306 oem_manifest.can_exit_enrollment;
307 machine_flags_[kOemKeyboardDrivenOobeKey] =
308 oem_manifest.keyboard_driven_oobe;
310 VLOG(1) << "Loaded OEM Manifest statistics from " << file.value();
313 StatisticsProviderImpl* StatisticsProviderImpl::GetInstance() {
314 return Singleton<StatisticsProviderImpl,
315 DefaultSingletonTraits<StatisticsProviderImpl> >::get();
318 static StatisticsProvider* g_test_statistics_provider = NULL;
321 StatisticsProvider* StatisticsProvider::GetInstance() {
322 if (g_test_statistics_provider)
323 return g_test_statistics_provider;
324 return StatisticsProviderImpl::GetInstance();
328 void StatisticsProvider::SetTestProvider(StatisticsProvider* test_provider) {
329 g_test_statistics_provider = test_provider;
332 } // namespace system
333 } // namespace chromeos