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
HasOemPrefix(const std::string & name)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
WaitForStatisticsLoaded()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
GetMachineStatistic(const std::string & name,std::string * result)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
GetMachineFlag(const std::string & name,bool * result)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
Shutdown()190 void StatisticsProviderImpl::Shutdown() {
191 cancellation_flag_.Set(); // Cancel any pending loads
192 }
193
StatisticsProviderImpl()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
~StatisticsProviderImpl()201 StatisticsProviderImpl::~StatisticsProviderImpl() {
202 }
203
StartLoadingMachineStatistics(const scoped_refptr<base::TaskRunner> & file_task_runner,bool load_oem_manifest)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
LoadMachineStatistics(bool load_oem_manifest)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
LoadOemManifestFromFile(const base::FilePath & file)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
GetInstance()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
GetInstance()305 StatisticsProvider* StatisticsProvider::GetInstance() {
306 if (g_test_statistics_provider)
307 return g_test_statistics_provider;
308 return StatisticsProviderImpl::GetInstance();
309 }
310
311 // static
SetTestProvider(StatisticsProvider * test_provider)312 void StatisticsProvider::SetTestProvider(StatisticsProvider* test_provider) {
313 g_test_statistics_provider = test_provider;
314 }
315
316 } // namespace system
317 } // namespace chromeos
318