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