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