pnacl_component_installer.cc revision 5821806d5e7f356e8fa4b058a389a808ea183019
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 <string.h>
8
9#include "base/base_paths.h"
10#include "base/bind.h"
11#include "base/compiler_specific.h"
12#include "base/file_path.h"
13#include "base/file_util.h"
14#include "base/json/json_file_value_serializer.h"
15#include "base/logging.h"
16#include "base/path_service.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 "content/public/browser/browser_thread.h"
24
25using content::BrowserThread;
26
27namespace {
28
29// CRX hash. This corresponds to AppId: emkhcgigkicgidendmffimilfehocheg
30const uint8 sha256_hash[] = {
31  0x4c, 0xa7, 0x26, 0x86, 0xa8, 0x26, 0x83, 0x4d, 0x3c, 0x55,
32  0x8c, 0x8b, 0x54, 0x7e, 0x27, 0x46, 0xa0, 0xf8, 0xd5, 0x0e, 0xea,
33  0x33, 0x46, 0x6e, 0xab, 0x6c, 0xde, 0xba, 0xc0, 0x91, 0xd4, 0x5e };
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 FilePath::CharType kPnaclCompilerFileName[] =
41    FILE_PATH_LITERAL("llc");
42
43// Name of the Pnacl component specified in the manifest.
44const char kPnaclManifestName[] = "PNaCl";
45
46// Name of the Pnacl architecture in the component manifest.
47// NOTE: this is independent of the Omaha query parameter.
48const char* PnaclArch() {
49#if defined(ARCH_CPU_X86_FAMILY)
50#if defined(ARCH_CPU_X86_64)
51  return "x86-64";
52#elif defined(OS_WIN)
53  bool x86_64 = (base::win::OSInfo::GetInstance()->wow64_status() ==
54                 base::win::OSInfo::WOW64_ENABLED);
55  return x86_64 ? "x86-64" : "x86-32";
56#else
57  return "x86-32";
58#endif
59#elif defined(ARCH_CPU_ARMEL)
60  // Eventually we'll need to distinguish arm32 vs thumb2.
61  // That may need to be based on the actual nexe rather than a static
62  // choice, which would require substantial refactoring.
63  return "arm";
64#elif defined(ARCH_CPU_MIPSEL)
65  return "mips32";
66#else
67#error "Add support for your architecture to Pnacl Component Installer."
68#endif
69}
70
71// If we don't have Pnacl installed, this is the version we claim.
72// TODO(jvoung): Is there a way to trick the configurator to ping the server
73// earlier if there are components that are not yet installed (version 0.0.0.0),
74// So that they will be available ASAP? Be careful not to hurt startup speed.
75// Make kNullVersion part of ComponentUpdater in that case, to avoid skew?
76const char kNullVersion[] = "0.0.0.0";
77
78// Pnacl components have the version encoded in the path itself:
79// <profile>\AppData\Local\Google\Chrome\User Data\Pnacl\0.1.2.3\.
80// and the base directory will be:
81// <profile>\AppData\Local\Google\Chrome\User Data\Pnacl\.
82FilePath GetPnaclBaseDirectory() {
83  FilePath result;
84  CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result));
85  return result;
86}
87
88bool GetLatestPnaclDirectory(FilePath* latest_dir, Version* latest_version,
89                             std::vector<FilePath>* older_dirs) {
90  // Enumerate all versions starting from the base directory.
91  FilePath base_dir = GetPnaclBaseDirectory();
92  bool found = false;
93  file_util::FileEnumerator
94      file_enumerator(base_dir, false, file_util::FileEnumerator::DIRECTORIES);
95  for (FilePath path = file_enumerator.Next(); !path.value().empty();
96       path = file_enumerator.Next()) {
97    Version version(path.BaseName().MaybeAsASCII());
98    if (!version.IsValid())
99      continue;
100    if (found) {
101      if (version.CompareTo(*latest_version) > 0) {
102        older_dirs->push_back(*latest_dir);
103        *latest_dir = path;
104        *latest_version = version;
105      } else {
106        older_dirs->push_back(path);
107      }
108    } else {
109      *latest_version = version;
110      *latest_dir = path;
111      found = true;
112    }
113  }
114  return found;
115}
116
117// Read the PNaCl specific manifest.
118base::DictionaryValue* ReadPnaclManifest(const FilePath& unpack_path) {
119  FilePath manifest = unpack_path.Append(FILE_PATH_LITERAL("pnacl.json"));
120  if (!file_util::PathExists(manifest))
121    return NULL;
122  JSONFileValueSerializer serializer(manifest);
123  std::string error;
124  scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error));
125  if (!root.get())
126    return NULL;
127  if (!root->IsType(base::Value::TYPE_DICTIONARY))
128    return NULL;
129  return static_cast<base::DictionaryValue*>(root.release());
130}
131
132}  // namespace
133
134bool CheckPnaclComponentManifest(base::DictionaryValue* manifest,
135                                 base::DictionaryValue* pnacl_manifest,
136                                 Version* version_out) {
137  // Make sure we have the right manifest file.
138  std::string name;
139  manifest->GetStringASCII("name", &name);
140  if (name != kPnaclManifestName) {
141    LOG(WARNING) << "'name' field in manifest is invalid ("
142                 << name << " vs " << kPnaclManifestName << ")";
143    return false;
144  }
145
146  std::string proposed_version;
147  manifest->GetStringASCII("version", &proposed_version);
148  Version version(proposed_version.c_str());
149  if (!version.IsValid()) {
150    LOG(WARNING) << "'version' field in manifest is invalid "
151                 << version.GetString();
152    return false;
153  }
154
155  std::string arch;
156  pnacl_manifest->GetStringASCII("pnacl-arch", &arch);
157  if (arch.compare(PnaclArch()) != 0) {
158    LOG(WARNING) << "'pnacl-arch' field in manifest is invalid ("
159                 << arch << " vs " << PnaclArch() << ")";
160    return false;
161  }
162
163  *version_out = version;
164  return true;
165}
166
167class PnaclComponentInstaller : public ComponentInstaller {
168 public:
169  explicit PnaclComponentInstaller(const Version& version);
170
171  virtual ~PnaclComponentInstaller() {}
172
173  virtual void OnUpdateError(int error) OVERRIDE;
174
175  virtual bool Install(base::DictionaryValue* manifest,
176                       const FilePath& unpack_path) OVERRIDE;
177
178 private:
179  Version current_version_;
180};
181
182PnaclComponentInstaller::PnaclComponentInstaller(
183    const Version& version) : current_version_(version) {
184  DCHECK(version.IsValid());
185}
186
187void PnaclComponentInstaller::OnUpdateError(int error) {
188  NOTREACHED() << "Pnacl update error: " << error;
189}
190
191namespace {
192
193bool PathContainsPnacl(const FilePath& base_path) {
194  // Check that at least one of the compiler files exists, for the current ISA.
195  return file_util::PathExists(
196      base_path.AppendASCII(PnaclArch()).Append(kPnaclCompilerFileName));
197}
198
199}  // namespace
200
201bool PnaclComponentInstaller::Install(base::DictionaryValue* manifest,
202                                      const FilePath& unpack_path) {
203  scoped_ptr<base::DictionaryValue> pnacl_manifest(
204      ReadPnaclManifest(unpack_path));
205  if (pnacl_manifest == NULL) {
206    LOG(WARNING) << "Failed to read pnacl manifest.";
207    return false;
208  }
209
210  Version version;
211  if (!CheckPnaclComponentManifest(manifest,
212                                   pnacl_manifest.get(),
213                                   &version)) {
214    LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing.";
215    return false;
216  }
217
218  // Don't install if the current version is actually newer.
219  if (current_version_.CompareTo(version) > 0)
220    return false;
221
222  if (!PathContainsPnacl(unpack_path)) {
223    LOG(WARNING) << "PathContainsPnacl check failed, not installing.";
224    return false;
225  }
226
227  // Passed the basic tests. Time to install it.
228  FilePath path =
229      GetPnaclBaseDirectory().AppendASCII(version.GetString());
230  if (file_util::PathExists(path)) {
231    LOG(WARNING) << "Target path already exists, not installing.";
232    return false;
233  }
234  if (!file_util::Move(unpack_path, path)) {
235    LOG(WARNING) << "Move failed, not installing.";
236    return false;
237  }
238
239  // Installation is done. Now tell the rest of chrome (just the path service
240  // for now). TODO(jvoung): we need notifications if someone surfed to a
241  // Pnacl webpage and Pnacl was just installed at this time. They should
242  // then be able to reload the page and retry (or something).
243  // See: http://code.google.com/p/chromium/issues/detail?id=107438
244  current_version_ = version;
245
246  PathService::Override(chrome::DIR_PNACL_COMPONENT, path);
247  return true;
248}
249
250namespace {
251
252// Finally, do the registration with the right version number.
253void FinishPnaclUpdateRegistration(ComponentUpdateService* cus,
254                                   const Version& version) {
255  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
256  CrxComponent pnacl;
257  pnacl.name = "pnacl";
258  pnacl.installer = new PnaclComponentInstaller(version);
259  pnacl.version = version;
260  pnacl.pk_hash.assign(sha256_hash, &sha256_hash[sizeof(sha256_hash)]);
261  if (cus->RegisterComponent(pnacl) != ComponentUpdateService::kOk) {
262    NOTREACHED() << "Pnacl component registration failed.";
263  }
264}
265
266// Check if there is an existing version on disk first to know when
267// a hosted version is actually newer.
268void StartPnaclUpdateRegistration(ComponentUpdateService* cus) {
269  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
270  FilePath path = GetPnaclBaseDirectory();
271  if (!file_util::PathExists(path)) {
272    if (!file_util::CreateDirectory(path)) {
273      NOTREACHED() << "Could not create base Pnacl directory.";
274      return;
275    }
276  }
277
278  Version version(kNullVersion);
279  std::vector<FilePath> older_dirs;
280  if (GetLatestPnaclDirectory(&path, &version, &older_dirs)) {
281    if (!PathContainsPnacl(path)) {
282      version = Version(kNullVersion);
283    } else {
284      PathService::Override(chrome::DIR_PNACL_COMPONENT, path);
285    }
286  }
287
288  BrowserThread::PostTask(
289      BrowserThread::UI, FROM_HERE,
290      base::Bind(&FinishPnaclUpdateRegistration, cus, version));
291
292  // Remove older versions of PNaCl.
293  for (std::vector<FilePath>::iterator iter = older_dirs.begin();
294       iter != older_dirs.end(); ++iter) {
295    file_util::Delete(*iter, true);
296  }
297}
298
299}  // namespace
300
301void RegisterPnaclComponent(ComponentUpdateService* cus) {
302  BrowserThread::PostTask(
303      BrowserThread::FILE, FROM_HERE,
304      base::Bind(&StartPnaclUpdateRegistration, cus));
305}
306