pnacl_component_installer.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1// Copyright (c) 2012 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 "chrome/browser/component_updater/pnacl/pnacl_component_installer.h"
6
7#include "base/base_paths.h"
8#include "base/bind.h"
9#include "base/command_line.h"
10#include "base/compiler_specific.h"
11#include "base/file_util.h"
12#include "base/files/file_path.h"
13#include "base/json/json_file_value_serializer.h"
14#include "base/logging.h"
15#include "base/path_service.h"
16#include "base/string_util.h"
17#include "base/values.h"
18#include "base/version.h"
19#include "base/win/windows_version.h"
20#include "build/build_config.h"
21#include "chrome/browser/component_updater/component_updater_service.h"
22#include "chrome/common/chrome_paths.h"
23#include "chrome/common/chrome_switches.h"
24#include "content/public/browser/browser_thread.h"
25
26using content::BrowserThread;
27
28namespace {
29
30// If PNaCl isn't installed yet, but a user is running chrome with
31// --enable-pnacl, this is the amount of time to wait before starting
32// a background install.
33const int kInitialDelaySeconds = 10;
34
35// One of the Pnacl component files, for checking that expected files exist.
36// TODO(jvoung): perhaps replace this with a list of the expected files in the
37// manifest.json. Use that to check that everything is unpacked.
38// However, that would make startup detection even slower (need to check for
39// more than one file!).
40const char kPnaclCompilerFileName[] = "llc_nexe";
41
42// Name of the Pnacl component specified in the manifest.
43const char kPnaclManifestNamePrefix[] = "PNaCl";
44
45// Returns the name of the Pnacl architecture supported by an install.
46// NOTE: this is independent of the Omaha "arch" query parameter.
47const char* PnaclArch() {
48#if defined(ARCH_CPU_X86_FAMILY)
49#if defined(ARCH_CPU_X86_64)
50  return "x86-64";
51#elif defined(OS_WIN)
52  bool x86_64 = (base::win::OSInfo::GetInstance()->wow64_status() ==
53                 base::win::OSInfo::WOW64_ENABLED);
54  return x86_64 ? "x86-64" : "x86-32";
55#else
56  return "x86-32";
57#endif
58#elif defined(ARCH_CPU_ARMEL)
59  return "arm";
60#elif defined(ARCH_CPU_MIPSEL)
61  return "mips32";
62#else
63#error "Add support for your architecture to Pnacl Component Installer."
64#endif
65}
66
67// Sanitize characters given by PnaclArch so that they can be used
68// in path names.  This should only be characters in the set: [a-z0-9_].
69// Keep in sync with chrome/browser/nacl_host/pnacl_file_host.
70std::string SanitizeForPath(const std::string& input) {
71  std::string result;
72  ReplaceChars(input, "-", "_", &result);
73  return result;
74}
75
76// Set the component's hash to the arch-specific PNaCl package.
77void SetPnaclHash(CrxComponent* component) {
78#if defined(ARCH_CPU_X86_FAMILY)
79  // Define both x86_32 and x86_64, and choose below.
80  static const uint8 x86_sha256_hash[][32] = {
81    { // This corresponds to AppID (x86-32): aealhdcgieaiikaifafholmmeooeeioj
82      0x04, 0x0b, 0x73, 0x26, 0x84, 0x08, 0x8a, 0x08, 0x50, 0x57,
83      0xeb, 0xcc, 0x4e, 0xe4, 0x48, 0xe9, 0x44, 0x2c, 0xc8, 0xa6, 0xd6,
84      0x96, 0x11, 0xd4, 0x2a, 0xc5, 0x26, 0x64, 0x34, 0x76, 0x3d, 0x14},
85    { // This corresponds to AppID (x86-64): knlfebnofcjjnkpkapbgfphaagefndik
86      0xad, 0xb5, 0x41, 0xde, 0x52, 0x99, 0xda, 0xfa, 0x0f, 0x16,
87      0x5f, 0x70, 0x06, 0x45, 0xd3, 0x8a, 0x32, 0x20, 0x84, 0x57, 0x5c,
88      0x1f, 0xef, 0xb4, 0x42, 0x32, 0xce, 0x4a, 0x3c, 0x2d, 0x7e, 0x3a}
89  };
90
91#if defined(ARCH_CPU_X86_64)
92  component->pk_hash.assign(
93      x86_sha256_hash[1],
94      &x86_sha256_hash[1][sizeof(x86_sha256_hash[1])]);
95#elif defined(OS_WIN)
96  bool x86_64 = (base::win::OSInfo::GetInstance()->wow64_status() ==
97                 base::win::OSInfo::WOW64_ENABLED);
98  if (x86_64) {
99    component->pk_hash.assign(
100        x86_sha256_hash[1],
101        &x86_sha256_hash[1][sizeof(x86_sha256_hash[1])]);
102  } else {
103    component->pk_hash.assign(
104        x86_sha256_hash[0],
105        &x86_sha256_hash[0][sizeof(x86_sha256_hash[0])]);
106  }
107#else
108  component->pk_hash.assign(
109      x86_sha256_hash[0],
110      &x86_sha256_hash[0][sizeof(x86_sha256_hash[0])]);
111#endif
112#elif defined(ARCH_CPU_ARMEL)
113  // This corresponds to AppID: jgobdlakdbanalhiagkdgcnofkbebejj
114  static const uint8 arm_sha256_hash[] = {
115    0x96, 0xe1, 0x3b, 0x0a, 0x31, 0x0d, 0x0b, 0x78, 0x06, 0xa3,
116    0x62, 0xde, 0x5a, 0x14, 0x14, 0x99, 0xd4, 0xd9, 0x01, 0x85, 0xc6,
117    0x9a, 0xd2, 0x51, 0x90, 0xa4, 0xb4, 0x94, 0xbd, 0xb8, 0x8b, 0xe8};
118
119  component->pk_hash.assign(arm_sha256_hash,
120                            &arm_sha256_hash[sizeof(arm_sha256_hash)]);
121#elif defined(ARCH_CPU_MIPSEL)
122  // This is a dummy CRX hash for MIPS, so that it will at least compile.
123  static const uint8 mips32_sha256_hash[] = {
124    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
125    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
126    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
127
128  component->pk_hash.assign(mips32_sha256_hash,
129                            &mips32_sha256_hash[sizeof(mips32_sha256_hash)]);
130#else
131#error "Add support for your architecture to Pnacl Component Installer."
132#endif
133}
134
135
136// If we don't have Pnacl installed, this is the version we claim.
137const char kNullVersion[] = "0.0.0.0";
138
139// Pnacl components have the version encoded in the path itself:
140// <profile>\AppData\Local\Google\Chrome\User Data\Pnacl\0.1.2.3\.
141// and the base directory will be:
142// <profile>\AppData\Local\Google\Chrome\User Data\Pnacl\.
143base::FilePath GetPnaclBaseDirectory() {
144  base::FilePath result;
145  CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
146  return result;
147}
148
149bool GetLatestPnaclDirectory(base::FilePath* latest_dir,
150                             Version* latest_version,
151                             std::vector<base::FilePath>* older_dirs) {
152  // Enumerate all versions starting from the base directory.
153  base::FilePath base_dir = GetPnaclBaseDirectory();
154  bool found = false;
155  file_util::FileEnumerator
156      file_enumerator(base_dir, false, file_util::FileEnumerator::DIRECTORIES);
157  for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
158       path = file_enumerator.Next()) {
159    Version version(path.BaseName().MaybeAsASCII());
160    if (!version.IsValid())
161      continue;
162    if (found) {
163      if (version.CompareTo(*latest_version) > 0) {
164        older_dirs->push_back(*latest_dir);
165        *latest_dir = path;
166        *latest_version = version;
167      } else {
168        older_dirs->push_back(path);
169      }
170    } else {
171      *latest_version = version;
172      *latest_dir = path;
173      found = true;
174    }
175  }
176  return found;
177}
178
179// Read the PNaCl specific manifest.
180base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) {
181  base::FilePath manifest = unpack_path.Append(
182      FILE_PATH_LITERAL("pnacl_public_pnacl_json"));
183  if (!file_util::PathExists(manifest))
184    return NULL;
185  JSONFileValueSerializer serializer(manifest);
186  std::string error;
187  scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
188  if (!root.get())
189    return NULL;
190  if (!root->IsType(base::Value::TYPE_DICTIONARY))
191    return NULL;
192  return static_cast<base::DictionaryValue*>(root.release());
193}
194
195}  // namespace
196
197bool CheckPnaclComponentManifest(base::DictionaryValue* manifest,
198                                 base::DictionaryValue* pnacl_manifest,
199                                 Version* version_out) {
200  // Make sure we have the right manifest file.
201  std::string name;
202  manifest->GetStringASCII("name", &name);
203  // For the webstore, we've given different names to each of the
204  // architecture specific packages, so only the prefix is the same.
205  if (StartsWithASCII(kPnaclManifestNamePrefix, name, false)) {
206    LOG(WARNING) << "'name' field in manifest is invalid ("
207                 << name << ") -- missing prefix ("
208                 << kPnaclManifestNamePrefix << ")";
209    return false;
210  }
211
212  std::string proposed_version;
213  manifest->GetStringASCII("version", &proposed_version);
214  Version version(proposed_version.c_str());
215  if (!version.IsValid()) {
216    LOG(WARNING) << "'version' field in manifest is invalid "
217                 << version.GetString();
218    return false;
219  }
220
221  std::string arch;
222  pnacl_manifest->GetStringASCII("pnacl-arch", &arch);
223  if (arch.compare(PnaclArch()) != 0) {
224    LOG(WARNING) << "'pnacl-arch' field in manifest is invalid ("
225                 << arch << " vs " << PnaclArch() << ")";
226    return false;
227  }
228
229  *version_out = version;
230  return true;
231}
232
233class PnaclComponentInstaller : public ComponentInstaller {
234 public:
235  explicit PnaclComponentInstaller(const Version& version);
236
237  virtual ~PnaclComponentInstaller() {}
238
239  virtual void OnUpdateError(int error) OVERRIDE;
240
241  virtual bool Install(base::DictionaryValue* manifest,
242                       const base::FilePath& unpack_path) OVERRIDE;
243
244 private:
245  Version current_version_;
246};
247
248PnaclComponentInstaller::PnaclComponentInstaller(
249    const Version& version) : current_version_(version) {
250  DCHECK(version.IsValid());
251}
252
253void PnaclComponentInstaller::OnUpdateError(int error) {
254  NOTREACHED() << "Pnacl update error: " << error;
255}
256
257namespace {
258
259bool PathContainsPnacl(const base::FilePath& base_path) {
260  // Check that at least one of the compiler files exists, for the current ISA.
261  std::string expected_filename("pnacl_public_");
262  std::string arch = PnaclArch();
263  expected_filename = expected_filename + SanitizeForPath(arch) +
264      "_" + kPnaclCompilerFileName;
265  return file_util::PathExists(base_path.AppendASCII(expected_filename));
266}
267
268}  // namespace
269
270bool PnaclComponentInstaller::Install(base::DictionaryValue* manifest,
271                                      const base::FilePath& unpack_path) {
272  scoped_ptr<base::DictionaryValue> pnacl_manifest(
273      ReadPnaclManifest(unpack_path));
274  if (pnacl_manifest == NULL) {
275    LOG(WARNING) << "Failed to read pnacl manifest.";
276    return false;
277  }
278
279  Version version;
280  if (!CheckPnaclComponentManifest(manifest,
281                                   pnacl_manifest.get(),
282                                   &version)) {
283    LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
284    return false;
285  }
286
287  // Don't install if the current version is actually newer.
288  if (current_version_.CompareTo(version) > 0)
289    return false;
290
291  if (!PathContainsPnacl(unpack_path)) {
292    LOG(WARNING) << "PathContainsPnacl check failed, not installing.";
293    return false;
294  }
295
296  // Passed the basic tests. Time to install it.
297  base::FilePath path =
298      GetPnaclBaseDirectory().AppendASCII(version.GetString());
299  if (file_util::PathExists(path)) {
300    LOG(WARNING) << "Target path already exists, not installing.";
301    return false;
302  }
303  if (!file_util::Move(unpack_path, path)) {
304    LOG(WARNING) << "Move failed, not installing.";
305    return false;
306  }
307
308  // Installation is done. Now tell the rest of chrome (just the path service
309  // for now). TODO(jvoung): we need notifications if someone surfed to a
310  // Pnacl webpage and Pnacl was just installed at this time. They should
311  // then be able to reload the page and retry (or something).
312  // See: http://code.google.com/p/chromium/issues/detail?id=107438
313  current_version_ = version;
314
315  PathService::Override(chrome::DIR_PNACL_COMPONENT, path);
316  return true;
317}
318
319namespace {
320
321void DoCheckForUpdate(ComponentUpdateService* cus,
322                   const CrxComponent& pnacl) {
323  if (cus->CheckForUpdateSoon(pnacl) != ComponentUpdateService::kOk) {
324    LOG(WARNING) << "Pnacl check for update failed.";
325  }
326}
327
328// Finally, do the registration with the right version number.
329void FinishPnaclUpdateRegistration(ComponentUpdateService* cus,
330                                   const Version& current_version) {
331  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
332  // Note: the source is the default of BANDAID, even though the
333  // crxes are hosted from CWS.
334  CrxComponent pnacl;
335  pnacl.name = "pnacl";
336  pnacl.installer = new PnaclComponentInstaller(current_version);
337  pnacl.version = current_version;
338  SetPnaclHash(&pnacl);
339  if (cus->RegisterComponent(pnacl) != ComponentUpdateService::kOk) {
340    NOTREACHED() << "Pnacl component registration failed.";
341  }
342
343  // If PNaCl is not yet installed but it is requested by --enable-pnacl,
344  // we want it to be available "soon", so kick off an update check
345  // earlier than usual.
346  Version null_version(kNullVersion);
347  if (current_version.Equals(null_version)) {
348    BrowserThread::PostDelayedTask(
349        BrowserThread::UI, FROM_HERE,
350        base::Bind(DoCheckForUpdate, cus, pnacl),
351        base::TimeDelta::FromSeconds(kInitialDelaySeconds));
352  }
353}
354
355// Check if there is an existing version on disk first to know when
356// a hosted version is actually newer.
357void StartPnaclUpdateRegistration(ComponentUpdateService* cus) {
358  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
359  base::FilePath path = GetPnaclBaseDirectory();
360  if (!file_util::PathExists(path)) {
361    if (!file_util::CreateDirectory(path)) {
362      NOTREACHED() << "Could not create base Pnacl directory.";
363      return;
364    }
365  }
366
367  Version version(kNullVersion);
368  std::vector<base::FilePath> older_dirs;
369  if (GetLatestPnaclDirectory(&path, &version, &older_dirs)) {
370    if (!PathContainsPnacl(path)) {
371      version = Version(kNullVersion);
372    } else {
373      PathService::Override(chrome::DIR_PNACL_COMPONENT, path);
374    }
375  }
376
377  BrowserThread::PostTask(
378      BrowserThread::UI, FROM_HERE,
379      base::Bind(&FinishPnaclUpdateRegistration, cus, version));
380
381  // Remove older versions of PNaCl.
382  for (std::vector<base::FilePath>::iterator iter = older_dirs.begin();
383       iter != older_dirs.end(); ++iter) {
384    file_util::Delete(*iter, true);
385  }
386}
387
388}  // namespace
389
390void RegisterPnaclComponent(ComponentUpdateService* cus,
391                            const CommandLine& command_line) {
392  // Only register when given the right flag.  This is important since
393  // we do an early component updater check above (in DoCheckForUpdate).
394  if (command_line.HasSwitch(switches::kEnablePnacl)) {
395    BrowserThread::PostTask(
396        BrowserThread::FILE, FROM_HERE,
397        base::Bind(&StartPnaclUpdateRegistration, cus));
398  }
399}
400