pnacl_component_installer.cc revision 7d4cd473f85ac64c3747c96c277f9e506a0d2246
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_enumerator.h" 13#include "base/files/file_path.h" 14#include "base/json/json_file_value_serializer.h" 15#include "base/logging.h" 16#include "base/path_service.h" 17#include "base/strings/string_util.h" 18#include "base/values.h" 19#include "base/version.h" 20#include "base/win/windows_version.h" 21#include "build/build_config.h" 22#include "chrome/browser/browser_process.h" 23#include "chrome/browser/component_updater/component_updater_service.h" 24#include "chrome/browser/profiles/profile.h" 25#include "chrome/browser/profiles/profile_manager.h" 26#include "chrome/common/chrome_paths.h" 27#include "chrome/common/chrome_switches.h" 28#include "chrome/common/omaha_query_params/omaha_query_params.h" 29#include "content/public/browser/browser_thread.h" 30 31using chrome::OmahaQueryParams; 32using content::BrowserThread; 33 34namespace { 35 36// If PNaCl isn't installed yet, but a user is running chrome with 37// --enable-pnacl, this is the amount of time to wait before starting 38// a background install. 39const int kInitialDelaySeconds = 10; 40 41// Name of the Pnacl component specified in the manifest. 42const char kPnaclManifestName[] = "PNaCl Translator"; 43 44// Sanitize characters from Pnacl Arch value so that they can be used 45// in path names. This should only be characters in the set: [a-z0-9_]. 46// Keep in sync with chrome/browser/nacl_host/nacl_file_host. 47std::string SanitizeForPath(const std::string& input) { 48 std::string result; 49 ReplaceChars(input, "-", "_", &result); 50 return result; 51} 52 53// Set the component's hash to the multi-CRX PNaCl package. 54void SetPnaclHash(CrxComponent* component) { 55 static const uint8 sha256_hash[32] = 56 { // This corresponds to AppID: hnimpnehoodheedghdeeijklkeaacbdc 57 0x7d, 0x8c, 0xfd, 0x47, 0xee, 0x37, 0x44, 0x36, 0x73, 0x44, 58 0x89, 0xab, 0xa4, 0x00, 0x21, 0x32, 0x4a, 0x06, 0x06, 0xf1, 0x51, 59 0x3c, 0x51, 0xba, 0x31, 0x2f, 0xbc, 0xb3, 0x99, 0x07, 0xdc, 0x9c}; 60 61 component->pk_hash.assign(sha256_hash, 62 &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"; 67 68// PNaCl is packaged as a multi-CRX. This returns the platform-specific 69// subdirectory that is part of that multi-CRX. 70base::FilePath GetPlatformDir(const base::FilePath& base_path) { 71 std::string arch = SanitizeForPath(OmahaQueryParams::getNaclArch()); 72 return base_path.AppendASCII("_platform_specific").AppendASCII(arch); 73} 74 75// Tell the rest of the world where to find the platform-specific PNaCl files. 76void OverrideDirPnaclComponent(const base::FilePath& base_path) { 77 PathService::Override(chrome::DIR_PNACL_COMPONENT, 78 GetPlatformDir(base_path)); 79} 80 81bool GetLatestPnaclDirectory(PnaclComponentInstaller* pci, 82 base::FilePath* latest_dir, 83 Version* latest_version, 84 std::vector<base::FilePath>* older_dirs) { 85 // Enumerate all versions starting from the base directory. 86 base::FilePath base_dir = pci->GetPnaclBaseDirectory(); 87 bool found = false; 88 base::FileEnumerator 89 file_enumerator(base_dir, false, base::FileEnumerator::DIRECTORIES); 90 for (base::FilePath path = file_enumerator.Next(); !path.value().empty(); 91 path = file_enumerator.Next()) { 92 Version version(path.BaseName().MaybeAsASCII()); 93 if (!version.IsValid()) 94 continue; 95 if (found) { 96 if (version.CompareTo(*latest_version) > 0) { 97 older_dirs->push_back(*latest_dir); 98 *latest_dir = path; 99 *latest_version = version; 100 } else { 101 older_dirs->push_back(path); 102 } 103 } else { 104 *latest_version = version; 105 *latest_dir = path; 106 found = true; 107 } 108 } 109 return found; 110} 111 112// Read a manifest file in. 113base::DictionaryValue* ReadJSONManifest( 114 const base::FilePath& manifest_path) { 115 JSONFileValueSerializer serializer(manifest_path); 116 std::string error; 117 scoped_ptr<base::Value> root(serializer.Deserialize(NULL, &error)); 118 if (!root.get()) 119 return NULL; 120 if (!root->IsType(base::Value::TYPE_DICTIONARY)) 121 return NULL; 122 return static_cast<base::DictionaryValue*>(root.release()); 123} 124 125// Read the PNaCl specific manifest. 126base::DictionaryValue* ReadPnaclManifest(const base::FilePath& unpack_path) { 127 base::FilePath manifest_path = GetPlatformDir(unpack_path).AppendASCII( 128 "pnacl_public_pnacl_json"); 129 if (!file_util::PathExists(manifest_path)) 130 return NULL; 131 return ReadJSONManifest(manifest_path); 132} 133 134// Read the component's manifest.json. 135base::DictionaryValue* ReadComponentManifest( 136 const base::FilePath& unpack_path) { 137 base::FilePath manifest_path = unpack_path.Append( 138 FILE_PATH_LITERAL("manifest.json")); 139 if (!file_util::PathExists(manifest_path)) 140 return NULL; 141 return ReadJSONManifest(manifest_path); 142} 143 144} // namespace 145 146// Check that the component's manifest is for PNaCl, and check the 147// PNaCl manifest indicates this is the correct arch-specific package. 148bool CheckPnaclComponentManifest(const base::DictionaryValue& manifest, 149 const base::DictionaryValue& pnacl_manifest, 150 Version* version_out) { 151 // Make sure we have the right |manifest| file. 152 std::string name; 153 manifest.GetStringASCII("name", &name); 154 // For the webstore, we've given different names to each of the 155 // architecture specific packages (and test/QA vs not test/QA) 156 // so only part of it is the same. 157 if (name.find(kPnaclManifestName) == std::string::npos) { 158 LOG(WARNING) << "'name' field in manifest is invalid (" 159 << name << ") -- missing (" 160 << kPnaclManifestName << ")"; 161 return false; 162 } 163 164 std::string proposed_version; 165 manifest.GetStringASCII("version", &proposed_version); 166 Version version(proposed_version.c_str()); 167 if (!version.IsValid()) { 168 LOG(WARNING) << "'version' field in manifest is invalid " 169 << version.GetString(); 170 return false; 171 } 172 173 // Now check the |pnacl_manifest|. 174 std::string arch; 175 pnacl_manifest.GetStringASCII("pnacl-arch", &arch); 176 if (arch.compare(OmahaQueryParams::getNaclArch()) != 0) { 177 LOG(WARNING) << "'pnacl-arch' field in manifest is invalid (" 178 << arch << " vs " << OmahaQueryParams::getNaclArch() << ")"; 179 return false; 180 } 181 182 *version_out = version; 183 return true; 184} 185 186PnaclComponentInstaller::PnaclComponentInstaller() 187 : per_user_(false), 188 cus_(NULL) { 189#if defined(OS_CHROMEOS) 190 per_user_ = true; 191#endif 192} 193 194PnaclComponentInstaller::~PnaclComponentInstaller() { 195} 196 197void PnaclComponentInstaller::OnUpdateError(int error) { 198 NOTREACHED() << "Pnacl update error: " << error; 199} 200 201// Pnacl components have the version encoded in the path itself: 202// <profile>\AppData\Local\Google\Chrome\User Data\pnacl\0.1.2.3\. 203// and the base directory will be: 204// <profile>\AppData\Local\Google\Chrome\User Data\pnacl\. 205base::FilePath PnaclComponentInstaller::GetPnaclBaseDirectory() { 206 // For ChromeOS, temporarily make this user-dependent (for integrity) until 207 // we find a better solution. 208 // This is not ideal because of the following: 209 // (a) We end up with per-user copies instead of a single copy 210 // (b) The profile can change as users log in to different accounts 211 // so we need to watch for user-login-events (see pnacl_profile_observer.h). 212 if (per_user_) { 213 DCHECK(!current_profile_path_.empty()); 214 base::FilePath path = current_profile_path_.Append( 215 FILE_PATH_LITERAL("pnacl")); 216 return path; 217 } else { 218 base::FilePath result; 219 CHECK(PathService::Get(chrome::DIR_PNACL_BASE, &result)); 220 return result; 221 } 222} 223 224void PnaclComponentInstaller::OnProfileChange() { 225 // On chromeos, we want to find the --login-profile=<foo> dir. 226 // Even though the path does vary between users, the content 227 // changes when logging out and logging in. 228 ProfileManager* pm = g_browser_process->profile_manager(); 229 current_profile_path_ = pm->user_data_dir().Append( 230 pm->GetInitialProfileDir()); 231} 232 233bool PnaclComponentInstaller::Install(const base::DictionaryValue& manifest, 234 const base::FilePath& unpack_path) { 235 scoped_ptr<base::DictionaryValue> pnacl_manifest( 236 ReadPnaclManifest(unpack_path)); 237 if (pnacl_manifest == NULL) { 238 LOG(WARNING) << "Failed to read pnacl manifest."; 239 return false; 240 } 241 242 Version version; 243 if (!CheckPnaclComponentManifest(manifest, *pnacl_manifest, &version)) { 244 LOG(WARNING) << "CheckPnaclComponentManifest failed, not installing."; 245 return false; 246 } 247 248 // Don't install if the current version is actually newer. 249 if (current_version().CompareTo(version) > 0) 250 return false; 251 252 // Passed the basic tests. Time to install it. 253 base::FilePath path = GetPnaclBaseDirectory().AppendASCII( 254 version.GetString()); 255 if (file_util::PathExists(path)) { 256 LOG(WARNING) << "Target path already exists, not installing."; 257 return false; 258 } 259 if (!file_util::Move(unpack_path, path)) { 260 LOG(WARNING) << "Move failed, not installing."; 261 return false; 262 } 263 264 // Installation is done. Now tell the rest of chrome (just the path service 265 // for now). TODO(jvoung): we need notifications if someone surfed to a 266 // Pnacl webpage and Pnacl was just installed at this time. They should 267 // then be able to reload the page and retry (or something). 268 // See: http://code.google.com/p/chromium/issues/detail?id=107438 269 set_current_version(version); 270 271 OverrideDirPnaclComponent(path); 272 return true; 273} 274 275namespace { 276 277void DoCheckForUpdate(ComponentUpdateService* cus, 278 const CrxComponent& pnacl) { 279 if (cus->CheckForUpdateSoon(pnacl) != ComponentUpdateService::kOk) { 280 LOG(WARNING) << "Pnacl check for update failed."; 281 } 282} 283 284// Finally, do the registration with the right version number. 285void FinishPnaclUpdateRegistration(const Version& current_version, 286 PnaclComponentInstaller* pci) { 287 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 288 CrxComponent pnacl_component; 289 pnacl_component.version = current_version; 290 pnacl_component.name = "pnacl"; 291 pnacl_component.installer = pci; 292 pci->set_current_version(current_version); 293 SetPnaclHash(&pnacl_component); 294 295 ComponentUpdateService::Status status = 296 pci->cus()->RegisterComponent(pnacl_component); 297 if (status != ComponentUpdateService::kOk 298 && status != ComponentUpdateService::kReplaced) { 299 NOTREACHED() << "Pnacl component registration failed."; 300 } 301 302 // If PNaCl is not yet installed but it is requested by --enable-pnacl, 303 // we want it to be available "soon", so kick off an update check 304 // earlier than usual. 305 Version null_version(kNullVersion); 306 if (pci->current_version().Equals(null_version)) { 307 BrowserThread::PostDelayedTask( 308 BrowserThread::UI, FROM_HERE, 309 base::Bind(DoCheckForUpdate, pci->cus(), pnacl_component), 310 base::TimeDelta::FromSeconds(kInitialDelaySeconds)); 311 } 312} 313 314// Check if there is an existing version on disk first to know when 315// a hosted version is actually newer. 316void StartPnaclUpdateRegistration(PnaclComponentInstaller* pci) { 317 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE)); 318 base::FilePath path = pci->GetPnaclBaseDirectory(); 319 if (!file_util::PathExists(path)) { 320 if (!file_util::CreateDirectory(path)) { 321 NOTREACHED() << "Could not create base Pnacl directory."; 322 return; 323 } 324 } 325 326 Version version(kNullVersion); 327 std::vector<base::FilePath> older_dirs; 328 if (GetLatestPnaclDirectory(pci, &path, &version, &older_dirs)) { 329 scoped_ptr<base::DictionaryValue> manifest( 330 ReadComponentManifest(path)); 331 scoped_ptr<base::DictionaryValue> pnacl_manifest( 332 ReadPnaclManifest(path)); 333 Version manifest_version; 334 // Check that the component manifest and PNaCl manifest files 335 // are legit, and that the indicated version matches the one 336 // encoded within the path name. 337 if (!CheckPnaclComponentManifest(*manifest, *pnacl_manifest, 338 &manifest_version) 339 || !version.Equals(manifest_version)) { 340 version = Version(kNullVersion); 341 } else { 342 OverrideDirPnaclComponent(path); 343 } 344 } 345 346 BrowserThread::PostTask( 347 BrowserThread::UI, FROM_HERE, 348 base::Bind(&FinishPnaclUpdateRegistration, version, pci)); 349 350 // Remove older versions of PNaCl. 351 for (std::vector<base::FilePath>::iterator iter = older_dirs.begin(); 352 iter != older_dirs.end(); ++iter) { 353 file_util::Delete(*iter, true); 354 } 355} 356 357void GetProfileInformation(PnaclComponentInstaller* pci) { 358 // Bail if not logged in yet. 359 if (!g_browser_process->profile_manager()->IsLoggedIn()) { 360 return; 361 } 362 363 pci->OnProfileChange(); 364 365 BrowserThread::PostTask( 366 BrowserThread::FILE, FROM_HERE, 367 base::Bind(&StartPnaclUpdateRegistration, pci)); 368} 369 370 371} // namespace 372 373void PnaclComponentInstaller::RegisterPnaclComponent( 374 ComponentUpdateService* cus, 375 const CommandLine& command_line) { 376 // Only register when given the right flag. This is important since 377 // we do an early component updater check above (in DoCheckForUpdate). 378 if (command_line.HasSwitch(switches::kEnablePnacl)) { 379 cus_ = cus; 380 // If per_user, create a profile observer to watch for logins. 381 // Only do so after cus_ is set to something non-null. 382 if (per_user_ && !profile_observer_) { 383 profile_observer_.reset(new PnaclProfileObserver(this)); 384 } 385 if (per_user_) { 386 // Figure out profile information, before proceeding to look for files. 387 BrowserThread::PostTask( 388 BrowserThread::UI, FROM_HERE, 389 base::Bind(&GetProfileInformation, this)); 390 } else { 391 BrowserThread::PostTask( 392 BrowserThread::FILE, FROM_HERE, 393 base::Bind(&StartPnaclUpdateRegistration, this)); 394 } 395 } 396} 397 398void PnaclComponentInstaller::ReRegisterPnacl() { 399 // No need to check the commandline flags again here. 400 // We could only have gotten here after RegisterPnaclComponent 401 // found --enable-pnacl, since that is where we create the profile_observer_, 402 // which in turn calls ReRegisterPnacl. 403 DCHECK(per_user_); 404 // Figure out profile information, before proceeding to look for files. 405 BrowserThread::PostTask( 406 BrowserThread::UI, FROM_HERE, 407 base::Bind(&GetProfileInformation, this)); 408} 409