pnacl_component_installer.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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/callback.h" 10#include "base/command_line.h" 11#include "base/compiler_specific.h" 12#include "base/file_util.h" 13#include "base/files/file_enumerator.h" 14#include "base/files/file_path.h" 15#include "base/json/json_file_value_serializer.h" 16#include "base/logging.h" 17#include "base/path_service.h" 18#include "base/strings/string_util.h" 19#include "base/values.h" 20#include "base/version.h" 21#include "base/win/windows_version.h" 22#include "build/build_config.h" 23#include "chrome/browser/browser_process.h" 24#include "chrome/browser/component_updater/component_updater_service.h" 25#include "chrome/browser/profiles/profile.h" 26#include "chrome/browser/profiles/profile_manager.h" 27#include "chrome/common/chrome_paths.h" 28#include "chrome/common/chrome_switches.h" 29#include "chrome/common/omaha_query_params/omaha_query_params.h" 30#include "content/public/browser/browser_thread.h" 31 32using chrome::OmahaQueryParams; 33using content::BrowserThread; 34 35namespace { 36 37// Name of the Pnacl component specified in the manifest. 38const char kPnaclManifestName[] = "PNaCl Translator"; 39 40// Sanitize characters from Pnacl Arch value so that they can be used 41// in path names. This should only be characters in the set: [a-z0-9_]. 42// Keep in sync with chrome/browser/nacl_host/nacl_file_host. 43std::string SanitizeForPath(const std::string& input) { 44 std::string result; 45 ReplaceChars(input, "-", "_", &result); 46 return result; 47} 48 49// Set the component's hash to the multi-CRX PNaCl package. 50void SetPnaclHash(CrxComponent* component) { 51 static const uint8 sha256_hash[32] = 52 { // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc 53 0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36, 0x73, 0x44, 54 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32, 0x4a, 0x06, 0x06, 0xf1, 0x51, 55 0x3c, 0x51, 0xba, 0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c}; 56 57 component->pk_hash.assign(sha256_hash, 58 &sha256_hash[arraysize(sha256_hash)]); 59} 60 61// If we don't have Pnacl installed, this is the version we claim. 62const char kNullVersion[] = "0.0.0.0"; 63 64// PNaCl is packaged as a multi-CRX. This returns the platform-specific 65// subdirectory that is part of that multi-CRX. 66base::FilePath GetPlatformDir(const base::FilePath& base_path) { 67 std::string arch = SanitizeForPath(OmahaQueryParams::getNaclArch()); 68 return base_path.AppendASCII("_platform_specific").AppendASCII(arch); 69} 70 71// Tell the rest of the world where to find the platform-specific PNaCl files. 72void OverrideDirPnaclComponent(const base::FilePath& base_path) { 73 PathService::Override(chrome::DIR_PNACL_COMPONENT, 74 GetPlatformDir(base_path)); 75} 76 77bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci, 78 base::FilePath* latest_dir, 79 Version* latest_version, 80 std::vector<base::FilePath>* older_dirs) { 81 // Enumerate all versions starting from the base directory. 82 base::FilePath base_dir = pci->GetPnaclBaseDirectory(); 83 bool found = false; 84 base::FileEnumerator 85 file_enumerator(base_dir, false, base::FileEnumerator::DIRECTORIES); 86 for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); 87 path = file_enumerator.Next()) { 88 Version version(path.BaseName().MaybeAsASCII()); 89 if (!version.IsValid()) 90 continue; 91 if (found) { 92 if (version.CompareTo(*latest_version) > 0) { 93 older_dirs->push_back(*latest_dir); 94 *latest_dir = path; 95 *latest_version = version; 96 } else { 97 older_dirs->push_back(path); 98 } 99 } else { 100 *latest_version = version; 101 *latest_dir = path; 102 found = true; 103 } 104 } 105 return found; 106} 107 108// Read a manifest file in. 109base::DictionaryValue* ReadJSONManifest( 110 const base::FilePath& manifest_path) { 111 JSONFileValueSerializer serializer(manifest_path); 112 std::string error; 113 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error)); 114 if (!root.get()) 115 return NULL; 116 if (!root->IsType(base::Value::TYPE_DICTIONARY)) 117 return NULL; 118 return static_cast<base::DictionaryValue*>(root.release()); 119} 120 121// Read the PNaCl specific manifest. 122base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) { 123 base::FilePath manifest_path = GetPlatformDir(unpack_path).AppendASCII( 124 "pnacl_public_pnacl_json"); 125 if (!base::PathExists(manifest_path)) 126 return NULL; 127 return ReadJSONManifest(manifest_path); 128} 129 130// Read the component's manifest.json. 131base::DictionaryValue* ReadComponentManifest( 132 const base::FilePath& unpack_path) { 133 base::FilePath manifest_path = unpack_path.Append( 134 FILE_PATH_LITERAL("manifest.json")); 135 if (!base::PathExists(manifest_path)) 136 return NULL; 137 return ReadJSONManifest(manifest_path); 138} 139 140} // namespace 141 142// Check that the component's manifest is for PNaCl, and check the 143// PNaCl manifest indicates this is the correct arch-specific package. 144bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest, 145 const base::DictionaryValue& pnacl_manifest, 146 Version* version_out) { 147 // Make sure we have the right |manifest| file. 148 std::string name; 149 if (!manifest.GetStringASCII("name", &name)) { 150 LOG(WARNING) << "'name' field is missing from manifest!"; 151 return false; 152 } 153 // For the webstore, we've given different names to each of the 154 // architecture specific packages (and test/QA vs not test/QA) 155 // so only part of it is the same. 156 if (name.find(kPnaclManifestName) == std::string::npos) { 157 LOG(WARNING) << "'name' field in manifest is invalid (" 158 << name << ") -- missing (" 159 << kPnaclManifestName << ")"; 160 return false; 161 } 162 163 std::string proposed_version; 164 if (!manifest.GetStringASCII("version", &proposed_version)) { 165 LOG(WARNING) << "'version' field is missing from manifest!"; 166 return false; 167 } 168 Version version(proposed_version.c_str()); 169 if (!version.IsValid()) { 170 LOG(WARNING) << "'version' field in manifest is invalid " 171 << version.GetString(); 172 return false; 173 } 174 175 // Now check the |pnacl_manifest|. 176 std::string arch; 177 if (!pnacl_manifest.GetStringASCII("pnacl-arch", &arch)) { 178 LOG(WARNING) << "'pnacl-arch' field is missing from pnacl-manifest!"; 179 return false; 180 } 181 if (arch.compare(OmahaQueryParams::getNaclArch()) != 0) { 182 LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" 183 << arch << " vs " << OmahaQueryParams::getNaclArch() << ")"; 184 return false; 185 } 186 187 *version_out = version; 188 return true; 189} 190 191PnaclComponentInstaller::PnaclComponentInstaller() 192 : per_user_(false), 193 updates_disabled_(false), 194 cus_(NULL), 195 callback_nums_(0) { 196#if defined(OS_CHROMEOS) 197 per_user_ = true; 198#endif 199 updater_observer_.reset(new PnaclUpdaterObserver(this)); 200} 201 202PnaclComponentInstaller::~PnaclComponentInstaller() { 203} 204 205void PnaclComponentInstaller::OnUpdateError(int error) { 206 NOTREACHED() << "Pnacl update error: " << error; 207} 208 209// Pnacl components have the version encoded in the path itself: 210// <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\. 211// and the base directory will be: 212// <profile>\AppData\Local\Google\Chrome\User Data\pnacl\. 213base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() { 214 // For ChromeOS, temporarily make this user-dependent (for integrity) until 215 // we find a better solution. 216 // This is not ideal because of the following: 217 // (a) We end up with per-user copies instead of a single copy 218 // (b) The profile can change as users log in to different accounts 219 // so we need to watch for user-login-events (see pnacl_profile_observer.h). 220 if (per_user_) { 221 DCHECK(!current_profile_path_.empty()); 222 base::FilePath path = current_profile_path_.Append( 223 FILE_PATH_LITERAL("pnacl")); 224 return path; 225 } else { 226 base::FilePath result; 227 CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result)); 228 return result; 229 } 230} 231 232void PnaclComponentInstaller::OnProfileChange() { 233 // On chromeos, we want to find the --login-profile=<foo> dir. 234 // Even though the path does vary between users, the content 235 // changes when logging out and logging in. 236 ProfileManager* pm = g_browser_process->profile_manager(); 237 current_profile_path_ = pm->user_data_dir().Append( 238 pm->GetInitialProfileDir()); 239} 240 241bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest, 242 const base::FilePath& unpack_path) { 243 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 244 scoped_ptr<base::DictionaryValue> pnacl_manifest( 245 ReadPnaclManifest(unpack_path)); 246 if (pnacl_manifest == NULL) { 247 LOG(WARNING) << "Failed to read pnacl manifest."; 248 NotifyInstallError(); 249 return false; 250 } 251 252 Version version; 253 if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) { 254 LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing."; 255 NotifyInstallError(); 256 return false; 257 } 258 259 // Don't install if the current version is actually newer. 260 if (current_version().CompareTo(version) > 0) { 261 NotifyInstallError(); 262 return false; 263 } 264 265 // Passed the basic tests. Time to install it. 266 base::FilePath path = GetPnaclBaseDirectory().AppendASCII( 267 version.GetString()); 268 if (base::PathExists(path)) { 269 LOG(WARNING) << "Target path already exists, not installing."; 270 NotifyInstallError(); 271 return false; 272 } 273 if (!base::Move(unpack_path, path)) { 274 LOG(WARNING) << "Move failed, not installing."; 275 NotifyInstallError(); 276 return false; 277 } 278 279 // Installation is done. Now tell the rest of chrome. 280 // - The path service. 281 // - Callbacks that requested an update. 282 set_current_version(version); 283 NotifyInstallSuccess(); 284 OverrideDirPnaclComponent(path); 285 return true; 286} 287 288// Given |file|, which can be a path like "_platform_specific/arm/pnacl_foo", 289// returns the assumed install path. The path separator in |file| is '/' 290// for all platforms. Caller is responsible for checking that the 291// |installed_file| actually exists. 292bool PnaclComponentInstaller::GetInstalledFile( 293 const std::string& file, base::FilePath* installed_file) { 294 if (current_version().Equals(Version(kNullVersion))) 295 return false; 296 297 *installed_file = GetPnaclBaseDirectory().AppendASCII( 298 current_version().GetString()).AppendASCII(file); 299 return true; 300} 301 302void PnaclComponentInstaller::AddInstallCallback( 303 const InstallCallback& cb) { 304 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 305 int num = ++callback_nums_; 306 install_callbacks_.push_back(std::make_pair(cb, num)); 307} 308 309void PnaclComponentInstaller::CancelCallback(int num) { 310 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 311 for (std::list<std::pair<InstallCallback, int> >::iterator 312 i = install_callbacks_.begin(), 313 e = install_callbacks_.end(); i != e; ++i) { 314 if (i->second == num) { 315 i->first.Run(false); 316 install_callbacks_.erase(i); 317 return; 318 } 319 } 320} 321 322void PnaclComponentInstaller::NotifyAllWithResult(bool status) { 323 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 324 while (!install_callbacks_.empty()) { 325 install_callbacks_.front().first.Run(status); 326 install_callbacks_.pop_front(); 327 } 328} 329 330void PnaclComponentInstaller::NotifyInstallError() { 331 if (!install_callbacks_.empty()) { 332 BrowserThread::PostTask( 333 BrowserThread::UI, FROM_HERE, 334 base::Bind(&PnaclComponentInstaller::NotifyAllWithResult, 335 // Unretained because installer lives until process shutdown. 336 base::Unretained(this), false)); 337 } 338} 339 340void PnaclComponentInstaller::NotifyInstallSuccess() { 341 if (!install_callbacks_.empty()) { 342 BrowserThread::PostTask( 343 BrowserThread::UI, FROM_HERE, 344 base::Bind(&PnaclComponentInstaller::NotifyAllWithResult, 345 // Unretained because installer lives until process shutdown. 346 base::Unretained(this), true)); 347 } 348} 349 350namespace { 351 352void FinishPnaclUpdateRegistration(const Version& current_version, 353 PnaclComponentInstaller* pci) { 354 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 355 CrxComponent pnacl_component; 356 pnacl_component.version = current_version; 357 pnacl_component.name = "pnacl"; 358 pnacl_component.installer = pci; 359 pci->set_current_version(current_version); 360 SetPnaclHash(&pnacl_component); 361 362 ComponentUpdateService::Status status = 363 pci->cus()->RegisterComponent(pnacl_component); 364 if (status != ComponentUpdateService::kOk 365 && status != ComponentUpdateService::kReplaced) { 366 NOTREACHED() << "Pnacl component registration failed."; 367 } 368} 369 370// Check if there is an existing version on disk first to know when 371// a hosted version is actually newer. 372void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) { 373 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 374 base::FilePath path = pci->GetPnaclBaseDirectory(); 375 if (!base::PathExists(path)) { 376 if (!file_util::CreateDirectory(path)) { 377 NOTREACHED() << "Could not create base Pnacl directory."; 378 return; 379 } 380 } 381 382 Version version(kNullVersion); 383 std::vector<base::FilePath> older_dirs; 384 if (GetLatestPnaclDirectory(pci, &path, &version, &older_dirs)) { 385 scoped_ptr<base::DictionaryValue> manifest( 386 ReadComponentManifest(path)); 387 scoped_ptr<base::DictionaryValue> pnacl_manifest( 388 ReadPnaclManifest(path)); 389 Version manifest_version; 390 // Check that the component manifest and PNaCl manifest files 391 // are legit, and that the indicated version matches the one 392 // encoded within the path name. 393 if (manifest == NULL || pnacl_manifest == NULL 394 || !CheckPnaclComponentManifest(*manifest, 395 *pnacl_manifest, 396 &manifest_version) 397 || !version.Equals(manifest_version)) { 398 version = Version(kNullVersion); 399 } else { 400 OverrideDirPnaclComponent(path); 401 } 402 } 403 404 // If updates are disabled, only discover the current version 405 // and OverrideDirPnaclComponent. That way, developers can use 406 // a pinned version. Do not actually finish registration with 407 // the component update service. 408 if (pci->updates_disabled()) 409 return; 410 411 BrowserThread::PostTask( 412 BrowserThread::UI, FROM_HERE, 413 base::Bind(&FinishPnaclUpdateRegistration, version, pci)); 414 415 // Remove older versions of PNaCl. 416 for (std::vector<base::FilePath>::iterator iter = older_dirs.begin(); 417 iter != older_dirs.end(); ++iter) { 418 base::DeleteFile(*iter, true); 419 } 420} 421 422void GetProfileInformation(PnaclComponentInstaller* pci) { 423 // Bail if not logged in yet. 424 if (!g_browser_process->profile_manager()->IsLoggedIn()) { 425 return; 426 } 427 428 pci->OnProfileChange(); 429 430 BrowserThread::PostTask( 431 BrowserThread::FILE, FROM_HERE, 432 base::Bind(&StartPnaclUpdateRegistration, pci)); 433} 434 435 436} // namespace 437 438void PnaclComponentInstaller::RegisterPnaclComponent( 439 ComponentUpdateService* cus, 440 const CommandLine& command_line) { 441 // Register PNaCl by default (can be disabled). 442 updates_disabled_ = command_line.HasSwitch(switches::kDisablePnaclInstall); 443 cus_ = cus; 444 // If per_user, create a profile observer to watch for logins. 445 // Only do so after cus_ is set to something non-null. 446 if (per_user_ && !profile_observer_) { 447 profile_observer_.reset(new PnaclProfileObserver(this)); 448 } 449 if (per_user_) { 450 // Figure out profile information, before proceeding to look for files. 451 BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, 452 base::Bind(&GetProfileInformation, this)); 453 } else { 454 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, 455 base::Bind(&StartPnaclUpdateRegistration, this)); 456 } 457} 458 459void PnaclComponentInstaller::ReRegisterPnacl() { 460 DCHECK(per_user_); 461 // Figure out profile information, before proceeding to look for files. 462 BrowserThread::PostTask( 463 BrowserThread::UI, FROM_HERE, 464 base::Bind(&GetProfileInformation, this)); 465} 466 467void RequestFirstInstall(ComponentUpdateService* cus, 468 PnaclComponentInstaller* pci, 469 const base::Callback<void(bool)>& installed) { 470 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 471 Version null_version(kNullVersion); 472 CrxComponent pnacl_component; 473 pci->set_current_version(null_version); 474 pnacl_component.version = null_version; 475 pnacl_component.name = "pnacl"; 476 pnacl_component.installer = pci; 477 SetPnaclHash(&pnacl_component); 478 ComponentUpdateService::Status status = cus->CheckForUpdateSoon( 479 pnacl_component); 480 if (status != ComponentUpdateService::kOk) { 481 installed.Run(false); 482 return; 483 } 484 pci->AddInstallCallback(installed); 485} 486