• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
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";
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 
HasOemPrefix(const std::string & name)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 
WaitForStatisticsLoaded()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 excpet 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 
GetMachineStatistic(const std::string & name,std::string * result)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 
GetMachineFlag(const std::string & name,bool * result)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 
Shutdown()194 void StatisticsProviderImpl::Shutdown() {
195   cancellation_flag_.Set();  // Cancel any pending loads
196 }
197 
StatisticsProviderImpl()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 
~StatisticsProviderImpl()205 StatisticsProviderImpl::~StatisticsProviderImpl() {
206 }
207 
StartLoadingMachineStatistics(const scoped_refptr<base::TaskRunner> & file_task_runner,bool load_oem_manifest)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 
LoadMachineStatistics(bool load_oem_manifest)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     oem_manifest_loaded_ = true;
271   }
272 
273   if (!base::SysInfo::IsRunningOnChromeOS() &&
274       machine_info_.find(kSerialNumber) == machine_info_.end()) {
275     // Set stub value for testing. A time value is appended to avoid clashes of
276     // the same serial for the same domain, which would invalidate earlier
277     // enrollments. A fake /tmp/machine-info file should be used instead if
278     // a stable serial is needed, e.g. to test re-enrollment.
279     base::TimeDelta time = base::Time::Now() - base::Time::UnixEpoch();
280     machine_info_[kSerialNumber] =
281         "stub_serial_number_" + base::Int64ToString(time.InSeconds());
282   }
283 
284   // Finished loading the statistics.
285   on_statistics_loaded_.Signal();
286   VLOG(1) << "Finished loading statistics.";
287 }
288 
LoadOemManifestFromFile(const base::FilePath & file)289 void StatisticsProviderImpl::LoadOemManifestFromFile(
290     const base::FilePath& file) {
291   // Called from LoadMachineStatistics. Check cancellation_flag_ again here.
292   if (cancellation_flag_.IsSet())
293     return;
294 
295   KioskOemManifestParser::Manifest oem_manifest;
296   if (!KioskOemManifestParser::Load(file, &oem_manifest)) {
297     LOG(WARNING) << "Unable to load OEM Manifest file: " << file.value();
298     return;
299   }
300   machine_info_[kOemDeviceRequisitionKey] =
301       oem_manifest.device_requisition;
302   machine_flags_[kOemIsEnterpriseManagedKey] =
303       oem_manifest.enterprise_managed;
304   machine_flags_[kOemCanExitEnterpriseEnrollmentKey] =
305       oem_manifest.can_exit_enrollment;
306   machine_flags_[kOemKeyboardDrivenOobeKey] =
307       oem_manifest.keyboard_driven_oobe;
308 
309   VLOG(1) << "Loaded OEM Manifest statistics from " << file.value();
310 }
311 
GetInstance()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
GetInstance()320 StatisticsProvider* StatisticsProvider::GetInstance() {
321   if (g_test_statistics_provider)
322     return g_test_statistics_provider;
323   return StatisticsProviderImpl::GetInstance();
324 }
325 
326 // static
SetTestProvider(StatisticsProvider * test_provider)327 void StatisticsProvider::SetTestProvider(StatisticsProvider* test_provider) {
328   g_test_statistics_provider = test_provider;
329 }
330 
331 }  // namespace system
332 }  // namespace chromeos
333