// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/component_updater/pnacl/pnacl_component_installer.h" #include #include "base/base_paths.h" #include "base/bind.h" #include "base/compiler_specific.h" #include "base/file_path.h" #include "base/file_util.h" #include "base/json/json_file_value_serializer.h" #include "base/logging.h" #include "base/path_service.h" #include "base/values.h" #include "base/version.h" #include "base/win/windows_version.h" #include "build/build_config.h" #include "chrome/browser/component_updater/component_updater_service.h" #include "chrome/common/chrome_paths.h" #include "content/public/browser/browser_thread.h" using content::BrowserThread; namespace { // CRX hash. This corresponds to AppId: emkhcgigkicgidendmffimilfehocheg const uint8 sha256_hash[] = { 0x4c, 0xa7, 0x26, 0x86, 0xa8, 0x26, 0x83, 0x4d, 0x3c, 0x55, 0x8c, 0x8b, 0x54, 0x7e, 0x27, 0x46, 0xa0, 0xf8, 0xd5, 0x0e, 0xea, 0x33, 0x46, 0x6e, 0xab, 0x6c, 0xde, 0xba, 0xc0, 0x91, 0xd4, 0x5e }; // One of the Pnacl component files, for checking that expected files exist. // TODO(jvoung): perhaps replace this with a list of the expected files in the // manifest.json. Use that to check that everything is unpacked. // However, that would make startup detection even slower (need to check for // more than one file!). const FilePath::CharType kPnaclCompilerFileName[] = FILE_PATH_LITERAL("llc"); // Name of the Pnacl component specified in the manifest. const char kPnaclManifestName[] = "PNaCl"; // Name of the Pnacl architecture in the component manifest. // NOTE: this is independent of the Omaha query parameter. const char* PnaclArch() { #if defined(ARCH_CPU_X86_FAMILY) #if defined(ARCH_CPU_X86_64) return "x86-64"; #elif defined(OS_WIN) bool x86_64 = (base::win::OSInfo::GetInstance()->wow64_status() == base::win::OSInfo::WOW64_ENABLED); return x86_64 ? "x86-64" : "x86-32"; #else return "x86-32"; #endif #elif defined(ARCH_CPU_ARMEL) // Eventually we'll need to distinguish arm32 vs thumb2. // That may need to be based on the actual nexe rather than a static // choice, which would require substantial refactoring. return "arm"; #else #error "Add support for your architecture to Pnacl Component Installer." #endif } // If we don't have Pnacl installed, this is the version we claim. // TODO(jvoung): Is there a way to trick the configurator to ping the server // earlier if there are components that are not yet installed (version 0.0.0.0), // So that they will be available ASAP? Be careful not to hurt startup speed. // Make kNullVersion part of ComponentUpdater in that case, to avoid skew? const char kNullVersion[] = "0.0.0.0"; // Pnacl components have the version encoded in the path itself: // \AppData\Local\Google\Chrome\User Data\Pnacl\0.1.2.3\. // and the base directory will be: // \AppData\Local\Google\Chrome\User Data\Pnacl\. FilePath GetPnaclBaseDirectory() { FilePath result; CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result)); return result; } bool GetLatestPnaclDirectory(FilePath* latest_dir, Version* latest_version, std::vector* older_dirs) { // Enumerate all versions starting from the base directory. FilePath base_dir = GetPnaclBaseDirectory(); bool found = false; file_util::FileEnumerator file_enumerator(base_dir, false, file_util::FileEnumerator::DIRECTORIES); for (FilePath path = file_enumerator.Next(); !path.value().empty(); path = file_enumerator.Next()) { Version version(path.BaseName().MaybeAsASCII()); if (!version.IsValid()) continue; if (found) { if (version.CompareTo(*latest_version) > 0) { older_dirs->push_back(*latest_dir); *latest_dir = path; *latest_version = version; } else { older_dirs->push_back(path); } } else { *latest_version = version; *latest_dir = path; found = true; } } return found; } // Read the PNaCl specific manifest. base::DictionaryValue* ReadPnaclManifest(const FilePath& unpack_path) { FilePath manifest = unpack_path.Append(FILE_PATH_LITERAL("pnacl.json")); if (!file_util::PathExists(manifest)) return NULL; JSONFileValueSerializer serializer(manifest); std::string error; scoped_ptr root(serializer.Deserialize(NULL, &error)); if (!root.get()) return NULL; if (!root->IsType(base::Value::TYPE_DICTIONARY)) return NULL; return static_cast(root.release()); } } // namespace bool CheckPnaclComponentManifest(base::DictionaryValue* manifest, base::DictionaryValue* pnacl_manifest, Version* version_out) { // Make sure we have the right manifest file. std::string name; manifest->GetStringASCII("name", &name); if (name != kPnaclManifestName) { LOG(WARNING) << "'name' field in manifest is invalid (" << name << " vs " << kPnaclManifestName << ")"; return false; } std::string proposed_version; manifest->GetStringASCII("version", &proposed_version); Version version(proposed_version.c_str()); if (!version.IsValid()) { LOG(WARNING) << "'version' field in manifest is invalid " << version.GetString(); return false; } std::string arch; pnacl_manifest->GetStringASCII("pnacl-arch", &arch); if (arch.compare(PnaclArch()) != 0) { LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" << arch << " vs " << PnaclArch() << ")"; return false; } *version_out = version; return true; } class PnaclComponentInstaller : public ComponentInstaller { public: explicit PnaclComponentInstaller(const Version& version); virtual ~PnaclComponentInstaller() {} virtual void OnUpdateError(int error) OVERRIDE; virtual bool Install(base::DictionaryValue* manifest, const FilePath& unpack_path) OVERRIDE; private: Version current_version_; }; PnaclComponentInstaller::PnaclComponentInstaller( const Version& version) : current_version_(version) { DCHECK(version.IsValid()); } void PnaclComponentInstaller::OnUpdateError(int error) { NOTREACHED() << "Pnacl update error: " << error; } namespace { bool PathContainsPnacl(const FilePath& base_path) { // Check that at least one of the compiler files exists, for the current ISA. return file_util::PathExists( base_path.AppendASCII(PnaclArch()).Append(kPnaclCompilerFileName)); } } // namespace bool PnaclComponentInstaller::Install(base::DictionaryValue* manifest, const FilePath& unpack_path) { scoped_ptr pnacl_manifest( ReadPnaclManifest(unpack_path)); if (pnacl_manifest == NULL) { LOG(WARNING) << "Failed to read pnacl manifest."; return false; } Version version; if (!CheckPnaclComponentManifest(manifest, pnacl_manifest.get(), &version)) { LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing."; return false; } // Don't install if the current version is actually newer. if (current_version_.CompareTo(version) > 0) return false; if (!PathContainsPnacl(unpack_path)) { LOG(WARNING) << "PathContainsPnacl check failed, not installing."; return false; } // Passed the basic tests. Time to install it. FilePath path = GetPnaclBaseDirectory().AppendASCII(version.GetString()); if (file_util::PathExists(path)) { LOG(WARNING) << "Target path already exists, not installing."; return false; } if (!file_util::Move(unpack_path, path)) { LOG(WARNING) << "Move failed, not installing."; return false; } // Installation is done. Now tell the rest of chrome (just the path service // for now). TODO(jvoung): we need notifications if someone surfed to a // Pnacl webpage and Pnacl was just installed at this time. They should // then be able to reload the page and retry (or something). // See: http://code.google.com/p/chromium/issues/detail?id=107438 current_version_ = version; PathService::Override(chrome::DIR_PNACL_COMPONENT, path); return true; } namespace { // Finally, do the registration with the right version number. void FinishPnaclUpdateRegistration(ComponentUpdateService* cus, const Version& version) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); CrxComponent pnacl; pnacl.name = "pnacl"; pnacl.installer = new PnaclComponentInstaller(version); pnacl.version = version; pnacl.pk_hash.assign(sha256_hash, &sha256_hash[sizeof(sha256_hash)]); if (cus->RegisterComponent(pnacl) != ComponentUpdateService::kOk) { NOTREACHED() << "Pnacl component registration failed."; } } // Check if there is an existing version on disk first to know when // a hosted version is actually newer. void StartPnaclUpdateRegistration(ComponentUpdateService* cus) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); FilePath path = GetPnaclBaseDirectory(); if (!file_util::PathExists(path)) { if (!file_util::CreateDirectory(path)) { NOTREACHED() << "Could not create base Pnacl directory."; return; } } Version version(kNullVersion); std::vector older_dirs; if (GetLatestPnaclDirectory(&path, &version, &older_dirs)) { if (!PathContainsPnacl(path)) { version = Version(kNullVersion); } else { PathService::Override(chrome::DIR_PNACL_COMPONENT, path); } } BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(&FinishPnaclUpdateRegistration, cus, version)); // Remove older versions of PNaCl. for (std::vector::iterator iter = older_dirs.begin(); iter != older_dirs.end(); ++iter) { file_util::Delete(*iter, true); } } } // namespace void RegisterPnaclComponent(ComponentUpdateService* cus) { BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, base::Bind(&StartPnaclUpdateRegistration, cus)); }