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
26namespace chromeos {
27namespace system {
28
29namespace {
30
31// Path to the tool used to get system info, and delimiters for the output
32// format of the tool.
33const char* kCrosSystemTool[] = { "/usr/bin/crossystem" };
34const char kCrosSystemEq[] = "=";
35const char kCrosSystemDelim[] = "\n";
36const char kCrosSystemCommentDelim[] = "#";
37const char kCrosSystemUnknownValue[] = "(error)";
38
39const char kHardwareClassCrosSystemKey[] = "hwid";
40const char kUnknownHardwareClass[] = "unknown";
41const 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.
46const char kMachineHardwareInfoFile[] = "/tmp/machine-info";
47const char kMachineHardwareInfoEq[] = "=";
48const char kMachineHardwareInfoDelim[] = " \n";
49
50// File to get ECHO coupon info from, and key/value delimiters of
51// the file.
52const char kEchoCouponFile[] = "/var/cache/echo/vpd_echo.txt";
53const char kEchoCouponEq[] = "=";
54const char kEchoCouponDelim[] = "\n";
55
56// File to get VPD info from, and key/value delimiters of the file.
57const char kVpdFile[] = "/var/log/vpd_2.0.txt";
58const char kVpdEq[] = "=";
59const char kVpdDelim[] = "\n";
60
61// Timeout that we should wait for statistics to get loaded
62const int kTimeoutSecs = 3;
63
64// The location of OEM manifest file used to trigger OOBE flow for kiosk mode.
65const CommandLine::CharType kOemManifestFilePath[] =
66    FILE_PATH_LITERAL("/usr/share/oem/oobe/manifest.json");
67
68}  // namespace
69
70// Key values for GetMachineStatistic()/GetMachineFlag() calls.
71const char kDevSwitchBootMode[] = "devsw_boot";
72const char kCustomizationIdKey[] = "customization_id";
73const char kHardwareClassKey[] = "hardware_class";
74const char kOffersCouponCodeKey[] = "ubind_attribute";
75const char kOffersGroupCodeKey[] = "gbind_attribute";
76const char kRlzBrandCodeKey[] = "rlz_brand_code";
77
78// OEM specific statistics. Must be prefixed with "oem_".
79const char kOemCanExitEnterpriseEnrollmentKey[] = "oem_can_exit_enrollment";
80const char kOemDeviceRequisitionKey[] = "oem_device_requisition";
81const char kOemIsEnterpriseManagedKey[] = "oem_enterprise_managed";
82const char kOemKeyboardDrivenOobeKey[] = "oem_keyboard_driven_oobe";
83
84bool HasOemPrefix(const std::string& name) {
85  return name.substr(0, 4) == "oem_";
86}
87
88// The StatisticsProvider implementation used in production.
89class 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
131bool 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
154bool 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
174bool 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
194void StatisticsProviderImpl::Shutdown() {
195  cancellation_flag_.Set();  // Cancel any pending loads
196}
197
198StatisticsProviderImpl::StatisticsProviderImpl()
199    : load_statistics_started_(false),
200      on_statistics_loaded_(true  /* manual_reset */,
201                            false /* initially_signaled */),
202      oem_manifest_loaded_(false) {
203}
204
205StatisticsProviderImpl::~StatisticsProviderImpl() {
206}
207
208void 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
224void 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
288void 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
312StatisticsProviderImpl* StatisticsProviderImpl::GetInstance() {
313  return Singleton<StatisticsProviderImpl,
314                   DefaultSingletonTraits<StatisticsProviderImpl> >::get();
315}
316
317static StatisticsProvider* g_test_statistics_provider = NULL;
318
319// static
320StatisticsProvider* StatisticsProvider::GetInstance() {
321  if (g_test_statistics_provider)
322    return g_test_statistics_provider;
323  return StatisticsProviderImpl::GetInstance();
324}
325
326// static
327void StatisticsProvider::SetTestProvider(StatisticsProvider* test_provider) {
328  g_test_statistics_provider = test_provider;
329}
330
331}  // namespace system
332}  // namespace chromeos
333