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/strings/string_number_conversions.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"
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)";
38 const char kHardwareClassCrosSystemKey
[] = "hwid";
39 const char kUnknownHardwareClass
[] = "unknown";
40 const char kSerialNumber
[] = "sn";
42 // File to get machine hardware info from, and key/value delimiters of
43 // the file. machine-info is generated only for OOBE and enterprise enrollment
44 // and may not be present. See login-manager/init/machine-info.conf.
45 const char kMachineHardwareInfoFile
[] = "/tmp/machine-info";
46 const char kMachineHardwareInfoEq
[] = "=";
47 const char kMachineHardwareInfoDelim
[] = " \n";
49 // File to get ECHO coupon info from, and key/value delimiters of
51 const char kEchoCouponFile
[] = "/var/cache/echo/vpd_echo.txt";
52 const char kEchoCouponEq
[] = "=";
53 const char kEchoCouponDelim
[] = "\n";
55 // File to get VPD info from, and key/value delimiters of the file.
56 const char kVpdFile
[] = "/var/log/vpd_2.0.txt";
57 const char kVpdEq
[] = "=";
58 const char kVpdDelim
[] = "\n";
60 // Timeout that we should wait for statistics to get loaded
61 const int kTimeoutSecs
= 3;
63 // The location of OEM manifest file used to trigger OOBE flow for kiosk mode.
64 const CommandLine::CharType kOemManifestFilePath
[] =
65 FILE_PATH_LITERAL("/usr/share/oem/oobe/manifest.json");
69 // Key values for GetMachineStatistic()/GetMachineFlag() calls.
70 const char kDevSwitchBootMode
[] = "devsw_boot";
71 const char kCustomizationIdKey
[] = "customization_id";
72 const char kHardwareClassKey
[] = "hardware_class";
73 const char kOffersCouponCodeKey
[] = "ubind_attribute";
74 const char kOffersGroupCodeKey
[] = "gbind_attribute";
75 const char kRlzBrandCodeKey
[] = "rlz_brand_code";
76 const char kActivateDateKey
[] = "ActivateDate";
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";
84 bool HasOemPrefix(const std::string
& name
) {
85 return name
.substr(0, 4) == "oem_";
88 // The StatisticsProvider implementation used in production.
89 class StatisticsProviderImpl
: public StatisticsProvider
{
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 HasMachineStatistic(const std::string
& name
) override
;
98 virtual bool GetMachineFlag(const std::string
& name
, bool* result
) override
;
99 virtual bool HasMachineFlag(const std::string
& name
) override
;
100 virtual void Shutdown() override
;
102 static StatisticsProviderImpl
* GetInstance();
105 typedef std::map
<std::string
, bool> MachineFlags
;
106 friend struct DefaultSingletonTraits
<StatisticsProviderImpl
>;
108 StatisticsProviderImpl();
109 virtual ~StatisticsProviderImpl();
111 // Waits up to |kTimeoutSecs| for statistics to be loaded. Returns true if
112 // they were loaded successfully.
113 bool WaitForStatisticsLoaded();
115 // Loads the machine statistics off of disk. Runs on the file thread.
116 void LoadMachineStatistics(bool load_oem_manifest
);
118 // Loads the OEM statistics off of disk. Runs on the file thread.
119 void LoadOemManifestFromFile(const base::FilePath
& file
);
121 bool load_statistics_started_
;
122 NameValuePairsParser::NameValueMap machine_info_
;
123 MachineFlags machine_flags_
;
124 base::CancellationFlag cancellation_flag_
;
125 // |on_statistics_loaded_| protects |machine_info_| and |machine_flags_|.
126 base::WaitableEvent on_statistics_loaded_
;
127 bool oem_manifest_loaded_
;
130 DISALLOW_COPY_AND_ASSIGN(StatisticsProviderImpl
);
133 bool StatisticsProviderImpl::WaitForStatisticsLoaded() {
134 CHECK(load_statistics_started_
);
135 if (on_statistics_loaded_
.IsSignaled())
138 // Block if the statistics are not loaded yet. Normally this shouldn't
139 // happen except during OOBE.
140 base::Time start_time
= base::Time::Now();
141 base::ThreadRestrictions::ScopedAllowWait allow_wait
;
142 on_statistics_loaded_
.TimedWait(base::TimeDelta::FromSeconds(kTimeoutSecs
));
144 base::TimeDelta dtime
= base::Time::Now() - start_time
;
145 if (on_statistics_loaded_
.IsSignaled()) {
146 LOG(ERROR
) << "Statistics loaded after waiting "
147 << dtime
.InMilliseconds() << "ms. ";
151 LOG(ERROR
) << "Statistics not loaded after waiting "
152 << dtime
.InMilliseconds() << "ms. ";
156 bool StatisticsProviderImpl::GetMachineStatistic(const std::string
& name
,
157 std::string
* result
) {
158 VLOG(1) << "Machine Statistic requested: " << name
;
159 if (!WaitForStatisticsLoaded()) {
160 LOG(ERROR
) << "GetMachineStatistic called before load started: " << name
;
164 NameValuePairsParser::NameValueMap::iterator iter
= machine_info_
.find(name
);
165 if (iter
== machine_info_
.end()) {
166 if (base::SysInfo::IsRunningOnChromeOS() &&
167 (oem_manifest_loaded_
|| !HasOemPrefix(name
))) {
168 LOG(WARNING
) << "Requested statistic not found: " << name
;
172 *result
= iter
->second
;
176 bool StatisticsProviderImpl::HasMachineStatistic(const std::string
& name
) {
178 return GetMachineStatistic(name
, &result
);
181 bool StatisticsProviderImpl::GetMachineFlag(const std::string
& name
,
183 VLOG(1) << "Machine Flag requested: " << name
;
184 if (!WaitForStatisticsLoaded()) {
185 LOG(ERROR
) << "GetMachineFlag called before load started: " << name
;
189 MachineFlags::const_iterator iter
= machine_flags_
.find(name
);
190 if (iter
== machine_flags_
.end()) {
191 if (base::SysInfo::IsRunningOnChromeOS() &&
192 (oem_manifest_loaded_
|| !HasOemPrefix(name
))) {
193 LOG(WARNING
) << "Requested machine flag not found: " << name
;
197 *result
= iter
->second
;
201 bool StatisticsProviderImpl::HasMachineFlag(const std::string
& name
) {
203 return GetMachineFlag(name
, &result
);
206 void StatisticsProviderImpl::Shutdown() {
207 cancellation_flag_
.Set(); // Cancel any pending loads
210 StatisticsProviderImpl::StatisticsProviderImpl()
211 : load_statistics_started_(false),
212 on_statistics_loaded_(true /* manual_reset */,
213 false /* initially_signaled */),
214 oem_manifest_loaded_(false) {
217 StatisticsProviderImpl::~StatisticsProviderImpl() {
220 void StatisticsProviderImpl::StartLoadingMachineStatistics(
221 const scoped_refptr
<base::TaskRunner
>& file_task_runner
,
222 bool load_oem_manifest
) {
223 CHECK(!load_statistics_started_
);
224 load_statistics_started_
= true;
226 VLOG(1) << "Started loading statistics. Load OEM Manifest: "
227 << load_oem_manifest
;
229 file_task_runner
->PostTask(
231 base::Bind(&StatisticsProviderImpl::LoadMachineStatistics
,
232 base::Unretained(this),
236 void StatisticsProviderImpl::LoadMachineStatistics(bool load_oem_manifest
) {
237 // Run from the file task runner. StatisticsProviderImpl is a Singleton<> and
238 // will not be destroyed until after threads have been stopped, so this test
240 if (cancellation_flag_
.IsSet())
243 NameValuePairsParser
parser(&machine_info_
);
244 if (base::SysInfo::IsRunningOnChromeOS()) {
245 // Parse all of the key/value pairs from the crossystem tool.
246 if (!parser
.ParseNameValuePairsFromTool(arraysize(kCrosSystemTool
),
250 kCrosSystemCommentDelim
)) {
251 LOG(ERROR
) << "Errors parsing output from: " << kCrosSystemTool
;
255 parser
.GetNameValuePairsFromFile(base::FilePath(kMachineHardwareInfoFile
),
256 kMachineHardwareInfoEq
,
257 kMachineHardwareInfoDelim
);
258 parser
.GetNameValuePairsFromFile(base::FilePath(kEchoCouponFile
),
261 parser
.GetNameValuePairsFromFile(base::FilePath(kVpdFile
),
265 // Ensure that the hardware class key is present with the expected
266 // key name, and if it couldn't be retrieved, that the value is "unknown".
267 std::string hardware_class
= machine_info_
[kHardwareClassCrosSystemKey
];
268 if (hardware_class
.empty() || hardware_class
== kCrosSystemUnknownValue
)
269 machine_info_
[kHardwareClassKey
] = kUnknownHardwareClass
;
271 machine_info_
[kHardwareClassKey
] = hardware_class
;
273 if (load_oem_manifest
) {
274 // If kAppOemManifestFile switch is specified, load OEM Manifest file.
275 CommandLine
* command_line
= CommandLine::ForCurrentProcess();
276 if (command_line
->HasSwitch(switches::kAppOemManifestFile
)) {
277 LoadOemManifestFromFile(
278 command_line
->GetSwitchValuePath(switches::kAppOemManifestFile
));
279 } else if (base::SysInfo::IsRunningOnChromeOS()) {
280 LoadOemManifestFromFile(base::FilePath(kOemManifestFilePath
));
284 if (!base::SysInfo::IsRunningOnChromeOS() &&
285 machine_info_
.find(kSerialNumber
) == machine_info_
.end()) {
286 // Set stub value for testing. A time value is appended to avoid clashes of
287 // the same serial for the same domain, which would invalidate earlier
288 // enrollments. A fake /tmp/machine-info file should be used instead if
289 // a stable serial is needed, e.g. to test re-enrollment.
290 base::TimeDelta time
= base::Time::Now() - base::Time::UnixEpoch();
291 machine_info_
[kSerialNumber
] =
292 "stub_serial_number_" + base::Int64ToString(time
.InSeconds());
295 // Finished loading the statistics.
296 on_statistics_loaded_
.Signal();
297 VLOG(1) << "Finished loading statistics.";
300 void StatisticsProviderImpl::LoadOemManifestFromFile(
301 const base::FilePath
& file
) {
302 // Called from LoadMachineStatistics. Check cancellation_flag_ again here.
303 if (cancellation_flag_
.IsSet())
306 KioskOemManifestParser::Manifest oem_manifest
;
307 if (!KioskOemManifestParser::Load(file
, &oem_manifest
)) {
308 LOG(WARNING
) << "Unable to load OEM Manifest file: " << file
.value();
311 machine_info_
[kOemDeviceRequisitionKey
] =
312 oem_manifest
.device_requisition
;
313 machine_flags_
[kOemIsEnterpriseManagedKey
] =
314 oem_manifest
.enterprise_managed
;
315 machine_flags_
[kOemCanExitEnterpriseEnrollmentKey
] =
316 oem_manifest
.can_exit_enrollment
;
317 machine_flags_
[kOemKeyboardDrivenOobeKey
] =
318 oem_manifest
.keyboard_driven_oobe
;
320 oem_manifest_loaded_
= true;
321 VLOG(1) << "Loaded OEM Manifest statistics from " << file
.value();
324 StatisticsProviderImpl
* StatisticsProviderImpl::GetInstance() {
325 return Singleton
<StatisticsProviderImpl
,
326 DefaultSingletonTraits
<StatisticsProviderImpl
> >::get();
329 static StatisticsProvider
* g_test_statistics_provider
= NULL
;
332 StatisticsProvider
* StatisticsProvider::GetInstance() {
333 if (g_test_statistics_provider
)
334 return g_test_statistics_provider
;
335 return StatisticsProviderImpl::GetInstance();
339 void StatisticsProvider::SetTestProvider(StatisticsProvider
* test_provider
) {
340 g_test_statistics_provider
= test_provider
;
343 } // namespace system
344 } // namespace chromeos