pnacl_component_installer.cc revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
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> 8#include <vector> 9 10#include "base/atomicops.h" 11#include "base/base_paths.h" 12#include "base/bind.h" 13#include "base/callback.h" 14#include "base/compiler_specific.h" 15#include "base/file_util.h" 16#include "base/files/file_enumerator.h" 17#include "base/files/file_path.h" 18#include "base/json/json_file_value_serializer.h" 19#include "base/logging.h" 20#include "base/path_service.h" 21#include "base/strings/string_util.h" 22#include "base/values.h" 23#include "base/version.h" 24#include "base/win/windows_version.h" 25#include "build/build_config.h" 26#include "chrome/browser/browser_process.h" 27#include "chrome/common/chrome_paths.h" 28#include "components/component_updater/component_updater_service.h" 29#include "components/nacl/common/nacl_switches.h" 30#include "components/omaha_query_params/omaha_query_params.h" 31#include "content/public/browser/browser_thread.h" 32 33using content::BrowserThread; 34using omaha_query_params::OmahaQueryParams; 35 36namespace component_updater { 37 38namespace { 39 40// Name of the Pnacl component specified in the manifest. 41const char kPnaclManifestName[] = "PNaCl Translator"; 42 43// Sanitize characters from Pnacl Arch value so that they can be used 44// in path names. This should only be characters in the set: [a-z0-9_]. 45// Keep in sync with chrome/browser/nacl_host/nacl_file_host. 46std::string SanitizeForPath(const std::string& input) { 47 std::string result; 48 base::ReplaceChars(input, "-", "_", &result); 49 return result; 50} 51 52// Set the component's hash to the multi-CRX PNaCl package. 53void SetPnaclHash(CrxComponent* component) { 54 static const uint8 sha256_hash[32] = { 55 // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc 56 0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36, 57 0x73, 0x44, 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32, 58 0x4a, 0x06, 0x06, 0xf1, 0x51, 0x3c, 0x51, 0xba, 59 0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c 60 }; 61 62 component->pk_hash.assign(sha256_hash, &sha256_hash[arraysize(sha256_hash)]); 63} 64 65// If we don't have Pnacl installed, this is the version we claim. 66const char kNullVersion[] = "0.0.0.0"; 67const char kMinPnaclVersion[] = "0.1.0.13367"; 68 69// Initially say that we do not need OnDemand updates. This should be 70// updated by CheckVersionCompatiblity(), before doing any URLRequests 71// that depend on PNaCl. 72volatile base::subtle::Atomic32 needs_on_demand_update = 0; 73 74void CheckVersionCompatiblity(const base::Version& current_version) { 75 // Using NoBarrier, since needs_on_demand_update is standalone and does 76 // not have other associated data. 77 base::subtle::NoBarrier_Store(&needs_on_demand_update, 78 current_version.IsOlderThan(kMinPnaclVersion)); 79} 80 81// PNaCl is packaged as a multi-CRX. This returns the platform-specific 82// subdirectory that is part of that multi-CRX. 83base::FilePath GetPlatformDir(const base::FilePath& base_path) { 84 std::string arch = SanitizeForPath(OmahaQueryParams::GetNaclArch()); 85 return base_path.AppendASCII("_platform_specific").AppendASCII(arch); 86} 87 88// Tell the rest of the world where to find the platform-specific PNaCl files. 89void OverrideDirPnaclComponent(const base::FilePath& base_path) { 90 PathService::Override(chrome::DIR_PNACL_COMPONENT, GetPlatformDir(base_path)); 91} 92 93bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci, 94 base::FilePath* latest_dir, 95 Version* latest_version, 96 std::vector<base::FilePath>* older_dirs) { 97 // Enumerate all versions starting from the base directory. 98 base::FilePath base_dir = pci->GetPnaclBaseDirectory(); 99 bool found = false; 100 base::FileEnumerator file_enumerator( 101 base_dir, false, base::FileEnumerator::DIRECTORIES); 102 for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); 103 path = file_enumerator.Next()) { 104 Version version(path.BaseName().MaybeAsASCII()); 105 if (!version.IsValid()) 106 continue; 107 if (found) { 108 if (version.CompareTo(*latest_version) > 0) { 109 older_dirs->push_back(*latest_dir); 110 *latest_dir = path; 111 *latest_version = version; 112 } else { 113 older_dirs->push_back(path); 114 } 115 } else { 116 *latest_version = version; 117 *latest_dir = path; 118 found = true; 119 } 120 } 121 return found; 122} 123 124// Read a manifest file in. 125base::DictionaryValue* ReadJSONManifest(const base::FilePath& manifest_path) { 126 JSONFileValueSerializer serializer(manifest_path); 127 std::string error; 128 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error)); 129 if (!root.get()) 130 return NULL; 131 if (!root->IsType(base::Value::TYPE_DICTIONARY)) 132 return NULL; 133 return static_cast<base::DictionaryValue*>(root.release()); 134} 135 136// Read the PNaCl specific manifest. 137base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) { 138 base::FilePath manifest_path = 139 GetPlatformDir(unpack_path).AppendASCII("pnacl_public_pnacl_json"); 140 if (!base::PathExists(manifest_path)) 141 return NULL; 142 return ReadJSONManifest(manifest_path); 143} 144 145// Read the component's manifest.json. 146base::DictionaryValue* ReadComponentManifest( 147 const base::FilePath& unpack_path) { 148 base::FilePath manifest_path = 149 unpack_path.Append(FILE_PATH_LITERAL("manifest.json")); 150 if (!base::PathExists(manifest_path)) 151 return NULL; 152 return ReadJSONManifest(manifest_path); 153} 154 155// Check that the component's manifest is for PNaCl, and check the 156// PNaCl manifest indicates this is the correct arch-specific package. 157bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest, 158 const base::DictionaryValue& pnacl_manifest, 159 Version* version_out) { 160 // Make sure we have the right |manifest| file. 161 std::string name; 162 if (!manifest.GetStringASCII("name", &name)) { 163 LOG(WARNING) << "'name' field is missing from manifest!"; 164 return false; 165 } 166 // For the webstore, we've given different names to each of the 167 // architecture specific packages (and test/QA vs not test/QA) 168 // so only part of it is the same. 169 if (name.find(kPnaclManifestName) == std::string::npos) { 170 LOG(WARNING) << "'name' field in manifest is invalid (" << name 171 << ") -- missing (" << kPnaclManifestName << ")"; 172 return false; 173 } 174 175 std::string proposed_version; 176 if (!manifest.GetStringASCII("version", &proposed_version)) { 177 LOG(WARNING) << "'version' field is missing from manifest!"; 178 return false; 179 } 180 Version version(proposed_version.c_str()); 181 if (!version.IsValid()) { 182 LOG(WARNING) << "'version' field in manifest is invalid " 183 << version.GetString(); 184 return false; 185 } 186 187 // Now check the |pnacl_manifest|. 188 std::string arch; 189 if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) { 190 LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!"; 191 return false; 192 } 193 if (arch.compare(OmahaQueryParams::GetNaclArch()) != 0) { 194 LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" << arch 195 << " vs " << OmahaQueryParams::GetNaclArch() << ")"; 196 return false; 197 } 198 199 *version_out = version; 200 return true; 201} 202 203} // namespace 204 205PnaclComponentInstaller::PnaclComponentInstaller() : cus_(NULL) { 206} 207 208PnaclComponentInstaller::~PnaclComponentInstaller() { 209} 210 211void PnaclComponentInstaller::OnUpdateError(int error) { 212 NOTREACHED() << "Pnacl update error: " << error; 213} 214 215// Pnacl components have the version encoded in the path itself: 216// <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\. 217// and the base directory will be: 218// <profile>\AppData\Local\Google\Chrome\User Data\pnacl\. 219base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() { 220 base::FilePath result; 221 CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result)); 222 return result; 223} 224 225bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest, 226 const base::FilePath& unpack_path) { 227 scoped_ptr<base::DictionaryValue> pnacl_manifest( 228 ReadPnaclManifest(unpack_path)); 229 if (pnacl_manifest == NULL) { 230 LOG(WARNING) << "Failed to read pnacl manifest."; 231 return false; 232 } 233 234 Version version; 235 if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) { 236 LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing."; 237 return false; 238 } 239 240 // Don't install if the current version is actually newer. 241 if (current_version().CompareTo(version) > 0) { 242 return false; 243 } 244 245 // Passed the basic tests. Time to install it. 246 base::FilePath path = 247 GetPnaclBaseDirectory().AppendASCII(version.GetString()); 248 if (base::PathExists(path)) { 249 if (!base::DeleteFile(path, true)) 250 return false; 251 } 252 if (!base::Move(unpack_path, path)) { 253 LOG(WARNING) << "Move failed, not installing."; 254 return false; 255 } 256 257 // Installation is done. Now tell the rest of chrome. 258 // - The path service. 259 // - Callbacks that requested an update. 260 set_current_version(version); 261 CheckVersionCompatiblity(version); 262 OverrideDirPnaclComponent(path); 263 return true; 264} 265 266// Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo", 267// returns the assumed install path. The path separator in |file| is '/' 268// for all platforms. Caller is responsible for checking that the 269// |installed_file| actually exists. 270bool PnaclComponentInstaller::GetInstalledFile(const std::string& file, 271 base::FilePath* installed_file) { 272 if (current_version().Equals(Version(kNullVersion))) 273 return false; 274 275 *installed_file = GetPnaclBaseDirectory() 276 .AppendASCII(current_version().GetString()) 277 .AppendASCII(file); 278 return true; 279} 280 281CrxComponent PnaclComponentInstaller::GetCrxComponent() { 282 CrxComponent pnacl_component; 283 pnacl_component.version = current_version(); 284 pnacl_component.name = "pnacl"; 285 pnacl_component.installer = this; 286 pnacl_component.fingerprint = current_fingerprint(); 287 SetPnaclHash(&pnacl_component); 288 289 return pnacl_component; 290} 291 292namespace { 293 294void FinishPnaclUpdateRegistration(const Version& current_version, 295 const std::string& current_fingerprint, 296 PnaclComponentInstaller* pci) { 297 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 298 pci->set_current_version(current_version); 299 CheckVersionCompatiblity(current_version); 300 pci->set_current_fingerprint(current_fingerprint); 301 CrxComponent pnacl_component = pci->GetCrxComponent(); 302 303 ComponentUpdateService::Status status = 304 pci->cus()->RegisterComponent(pnacl_component); 305 if (status != ComponentUpdateService::kOk && 306 status != ComponentUpdateService::kReplaced) { 307 NOTREACHED() << "Pnacl component registration failed."; 308 } 309} 310 311// Check if there is an existing version on disk first to know when 312// a hosted version is actually newer. 313void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) { 314 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 315 base::FilePath path = pci->GetPnaclBaseDirectory(); 316 if (!base::PathExists(path)) { 317 if (!base::CreateDirectory(path)) { 318 NOTREACHED() << "Could not create base Pnacl directory."; 319 return; 320 } 321 } 322 323 Version current_version(kNullVersion); 324 std::string current_fingerprint; 325 std::vector<base::FilePath> older_dirs; 326 if (GetLatestPnaclDirectory(pci, &path, ¤t_version, &older_dirs)) { 327 scoped_ptr<base::DictionaryValue> manifest(ReadComponentManifest(path)); 328 scoped_ptr<base::DictionaryValue> pnacl_manifest(ReadPnaclManifest(path)); 329 Version manifest_version; 330 // Check that the component manifest and PNaCl manifest files 331 // are legit, and that the indicated version matches the one 332 // encoded within the path name. 333 if (manifest == NULL || pnacl_manifest == NULL || 334 !CheckPnaclComponentManifest(*manifest, 335 *pnacl_manifest, 336 &manifest_version) || 337 !current_version.Equals(manifest_version)) { 338 current_version = Version(kNullVersion); 339 } else { 340 OverrideDirPnaclComponent(path); 341 base::ReadFileToString(path.AppendASCII("manifest.fingerprint"), 342 ¤t_fingerprint); 343 } 344 } 345 346 BrowserThread::PostTask(BrowserThread::UI, 347 FROM_HERE, 348 base::Bind(&FinishPnaclUpdateRegistration, 349 current_version, 350 current_fingerprint, 351 pci)); 352 353 // Remove older versions of PNaCl. 354 for (std::vector<base::FilePath>::iterator iter = older_dirs.begin(); 355 iter != older_dirs.end(); 356 ++iter) { 357 base::DeleteFile(*iter, true); 358 } 359} 360 361} // namespace 362 363void PnaclComponentInstaller::RegisterPnaclComponent( 364 ComponentUpdateService* cus) { 365 cus_ = cus; 366 BrowserThread::PostTask(BrowserThread::FILE, 367 FROM_HERE, 368 base::Bind(&StartPnaclUpdateRegistration, this)); 369} 370 371} // namespace component_updater 372 373namespace pnacl { 374 375bool NeedsOnDemandUpdate() { 376 return base::subtle::NoBarrier_Load( 377 &component_updater::needs_on_demand_update) != 0; 378} 379 380} // namespace pnacl 381