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 // This file declares util functions for setup project. 6 7 #include "chrome/installer/setup/setup_util.h" 8 9 #include <windows.h> 10 11 #include "base/command_line.h" 12 #include "base/cpu.h" 13 #include "base/files/file_enumerator.h" 14 #include "base/files/file_path.h" 15 #include "base/files/file_util.h" 16 #include "base/logging.h" 17 #include "base/process/kill.h" 18 #include "base/process/launch.h" 19 #include "base/process/process_handle.h" 20 #include "base/strings/string_util.h" 21 #include "base/strings/utf_string_conversions.h" 22 #include "base/version.h" 23 #include "base/win/registry.h" 24 #include "base/win/windows_version.h" 25 #include "chrome/installer/setup/setup_constants.h" 26 #include "chrome/installer/util/copy_tree_work_item.h" 27 #include "chrome/installer/util/google_update_constants.h" 28 #include "chrome/installer/util/installation_state.h" 29 #include "chrome/installer/util/installer_state.h" 30 #include "chrome/installer/util/master_preferences.h" 31 #include "chrome/installer/util/util_constants.h" 32 #include "chrome/installer/util/work_item.h" 33 #include "courgette/courgette.h" 34 #include "courgette/third_party/bsdiff.h" 35 #include "third_party/bspatch/mbspatch.h" 36 37 namespace installer { 38 39 namespace { 40 41 // Launches |setup_exe| with |command_line|, save --install-archive and its 42 // value if present. Returns false if the process failed to launch. Otherwise, 43 // waits indefinitely for it to exit and populates |exit_code| as expected. On 44 // the off chance that waiting itself fails, |exit_code| is set to 45 // WAIT_FOR_EXISTING_FAILED. 46 bool LaunchAndWaitForExistingInstall(const base::FilePath& setup_exe, 47 const CommandLine& command_line, 48 int* exit_code) { 49 DCHECK(exit_code); 50 CommandLine new_cl(setup_exe); 51 52 // Copy over all switches but --install-archive. 53 CommandLine::SwitchMap switches(command_line.GetSwitches()); 54 switches.erase(switches::kInstallArchive); 55 for (CommandLine::SwitchMap::const_iterator i = switches.begin(); 56 i != switches.end(); ++i) { 57 if (i->second.empty()) 58 new_cl.AppendSwitch(i->first); 59 else 60 new_cl.AppendSwitchNative(i->first, i->second); 61 } 62 63 // Copy over all arguments. 64 CommandLine::StringVector args(command_line.GetArgs()); 65 for (CommandLine::StringVector::const_iterator i = args.begin(); 66 i != args.end(); ++i) { 67 new_cl.AppendArgNative(*i); 68 } 69 70 // Launch the process and wait for it to exit. 71 VLOG(1) << "Launching existing installer with command: " 72 << new_cl.GetCommandLineString(); 73 base::ProcessHandle handle = INVALID_HANDLE_VALUE; 74 if (!base::LaunchProcess(new_cl, base::LaunchOptions(), &handle)) { 75 PLOG(ERROR) << "Failed to launch existing installer with command: " 76 << new_cl.GetCommandLineString(); 77 return false; 78 } 79 if (!base::WaitForExitCode(handle, exit_code)) { 80 PLOG(DFATAL) << "Failed to get exit code from existing installer"; 81 *exit_code = WAIT_FOR_EXISTING_FAILED; 82 } else { 83 VLOG(1) << "Existing installer returned exit code " << *exit_code; 84 } 85 return true; 86 } 87 88 // Returns true if product |type| cam be meaningfully installed without the 89 // --multi-install flag. 90 bool SupportsSingleInstall(BrowserDistribution::Type type) { 91 return (type == BrowserDistribution::CHROME_BROWSER || 92 type == BrowserDistribution::CHROME_FRAME); 93 } 94 95 } // namespace 96 97 int CourgettePatchFiles(const base::FilePath& src, 98 const base::FilePath& patch, 99 const base::FilePath& dest) { 100 VLOG(1) << "Applying Courgette patch " << patch.value() 101 << " to file " << src.value() 102 << " and generating file " << dest.value(); 103 104 if (src.empty() || patch.empty() || dest.empty()) 105 return installer::PATCH_INVALID_ARGUMENTS; 106 107 const courgette::Status patch_status = 108 courgette::ApplyEnsemblePatch(src.value().c_str(), 109 patch.value().c_str(), 110 dest.value().c_str()); 111 const int exit_code = (patch_status != courgette::C_OK) ? 112 static_cast<int>(patch_status) + kCourgetteErrorOffset : 0; 113 114 LOG_IF(ERROR, exit_code) 115 << "Failed to apply Courgette patch " << patch.value() 116 << " to file " << src.value() << " and generating file " << dest.value() 117 << ". err=" << exit_code; 118 119 return exit_code; 120 } 121 122 int BsdiffPatchFiles(const base::FilePath& src, 123 const base::FilePath& patch, 124 const base::FilePath& dest) { 125 VLOG(1) << "Applying bsdiff patch " << patch.value() 126 << " to file " << src.value() 127 << " and generating file " << dest.value(); 128 129 if (src.empty() || patch.empty() || dest.empty()) 130 return installer::PATCH_INVALID_ARGUMENTS; 131 132 const int patch_status = courgette::ApplyBinaryPatch(src, patch, dest); 133 const int exit_code = patch_status != OK ? 134 patch_status + kBsdiffErrorOffset : 0; 135 136 LOG_IF(ERROR, exit_code) 137 << "Failed to apply bsdiff patch " << patch.value() 138 << " to file " << src.value() << " and generating file " << dest.value() 139 << ". err=" << exit_code; 140 141 return exit_code; 142 } 143 144 Version* GetMaxVersionFromArchiveDir(const base::FilePath& chrome_path) { 145 VLOG(1) << "Looking for Chrome version folder under " << chrome_path.value(); 146 base::FileEnumerator version_enum(chrome_path, false, 147 base::FileEnumerator::DIRECTORIES); 148 // TODO(tommi): The version directory really should match the version of 149 // setup.exe. To begin with, we should at least DCHECK that that's true. 150 151 scoped_ptr<Version> max_version(new Version("0.0.0.0")); 152 bool version_found = false; 153 154 while (!version_enum.Next().empty()) { 155 base::FileEnumerator::FileInfo find_data = version_enum.GetInfo(); 156 VLOG(1) << "directory found: " << find_data.GetName().value(); 157 158 scoped_ptr<Version> found_version( 159 new Version(base::UTF16ToASCII(find_data.GetName().value()))); 160 if (found_version->IsValid() && 161 found_version->CompareTo(*max_version.get()) > 0) { 162 max_version.reset(found_version.release()); 163 version_found = true; 164 } 165 } 166 167 return (version_found ? max_version.release() : NULL); 168 } 169 170 base::FilePath FindArchiveToPatch(const InstallationState& original_state, 171 const InstallerState& installer_state) { 172 // Check based on the version number advertised to Google Update, since that 173 // is the value used to select a specific differential update. If an archive 174 // can't be found using that, fallback to using the newest version present. 175 base::FilePath patch_source; 176 const ProductState* product = 177 original_state.GetProductState(installer_state.system_install(), 178 installer_state.state_type()); 179 if (product) { 180 patch_source = installer_state.GetInstallerDirectory(product->version()) 181 .Append(installer::kChromeArchive); 182 if (base::PathExists(patch_source)) 183 return patch_source; 184 } 185 scoped_ptr<Version> version( 186 installer::GetMaxVersionFromArchiveDir(installer_state.target_path())); 187 if (version) { 188 patch_source = installer_state.GetInstallerDirectory(*version) 189 .Append(installer::kChromeArchive); 190 if (base::PathExists(patch_source)) 191 return patch_source; 192 } 193 return base::FilePath(); 194 } 195 196 bool DeleteFileFromTempProcess(const base::FilePath& path, 197 uint32 delay_before_delete_ms) { 198 static const wchar_t kRunDll32Path[] = 199 L"%SystemRoot%\\System32\\rundll32.exe"; 200 wchar_t rundll32[MAX_PATH]; 201 DWORD size = 202 ExpandEnvironmentStrings(kRunDll32Path, rundll32, arraysize(rundll32)); 203 if (!size || size >= MAX_PATH) 204 return false; 205 206 STARTUPINFO startup = { sizeof(STARTUPINFO) }; 207 PROCESS_INFORMATION pi = {0}; 208 BOOL ok = ::CreateProcess(NULL, rundll32, NULL, NULL, FALSE, CREATE_SUSPENDED, 209 NULL, NULL, &startup, &pi); 210 if (ok) { 211 // We use the main thread of the new process to run: 212 // Sleep(delay_before_delete_ms); 213 // DeleteFile(path); 214 // ExitProcess(0); 215 // This runs before the main routine of the process runs, so it doesn't 216 // matter much which executable we choose except that we don't want to 217 // use e.g. a console app that causes a window to be created. 218 size = (path.value().length() + 1) * sizeof(path.value()[0]); 219 void* mem = ::VirtualAllocEx(pi.hProcess, NULL, size, MEM_COMMIT, 220 PAGE_READWRITE); 221 if (mem) { 222 SIZE_T written = 0; 223 ::WriteProcessMemory( 224 pi.hProcess, mem, path.value().c_str(), 225 (path.value().size() + 1) * sizeof(path.value()[0]), &written); 226 HMODULE kernel32 = ::GetModuleHandle(L"kernel32.dll"); 227 PAPCFUNC sleep = reinterpret_cast<PAPCFUNC>( 228 ::GetProcAddress(kernel32, "Sleep")); 229 PAPCFUNC delete_file = reinterpret_cast<PAPCFUNC>( 230 ::GetProcAddress(kernel32, "DeleteFileW")); 231 PAPCFUNC exit_process = reinterpret_cast<PAPCFUNC>( 232 ::GetProcAddress(kernel32, "ExitProcess")); 233 if (!sleep || !delete_file || !exit_process) { 234 NOTREACHED(); 235 ok = FALSE; 236 } else { 237 ::QueueUserAPC(sleep, pi.hThread, delay_before_delete_ms); 238 ::QueueUserAPC(delete_file, pi.hThread, 239 reinterpret_cast<ULONG_PTR>(mem)); 240 ::QueueUserAPC(exit_process, pi.hThread, 0); 241 ::ResumeThread(pi.hThread); 242 } 243 } else { 244 PLOG(ERROR) << "VirtualAllocEx"; 245 ::TerminateProcess(pi.hProcess, ~static_cast<UINT>(0)); 246 } 247 ::CloseHandle(pi.hThread); 248 ::CloseHandle(pi.hProcess); 249 } 250 251 return ok != FALSE; 252 } 253 254 bool GetExistingHigherInstaller( 255 const InstallationState& original_state, 256 bool system_install, 257 const Version& installer_version, 258 base::FilePath* setup_exe) { 259 DCHECK(setup_exe); 260 bool trying_single_browser = false; 261 const ProductState* existing_state = 262 original_state.GetProductState(system_install, 263 BrowserDistribution::CHROME_BINARIES); 264 if (!existing_state) { 265 // The binaries aren't installed, but perhaps a single-install Chrome is. 266 trying_single_browser = true; 267 existing_state = 268 original_state.GetProductState(system_install, 269 BrowserDistribution::CHROME_BROWSER); 270 } 271 272 if (!existing_state || 273 existing_state->version().CompareTo(installer_version) <= 0) { 274 return false; 275 } 276 277 *setup_exe = existing_state->GetSetupPath(); 278 279 VLOG_IF(1, !setup_exe->empty()) << "Found a higher version of " 280 << (trying_single_browser ? "single-install Chrome." 281 : "multi-install Chrome binaries."); 282 283 return !setup_exe->empty(); 284 } 285 286 bool DeferToExistingInstall(const base::FilePath& setup_exe, 287 const CommandLine& command_line, 288 const InstallerState& installer_state, 289 const base::FilePath& temp_path, 290 InstallStatus* install_status) { 291 // Copy a master_preferences file if there is one. 292 base::FilePath prefs_source_path(command_line.GetSwitchValueNative( 293 switches::kInstallerData)); 294 base::FilePath prefs_dest_path(installer_state.target_path().AppendASCII( 295 kDefaultMasterPrefs)); 296 scoped_ptr<WorkItem> copy_prefs(WorkItem::CreateCopyTreeWorkItem( 297 prefs_source_path, prefs_dest_path, temp_path, WorkItem::ALWAYS, 298 base::FilePath())); 299 // There's nothing to rollback if the copy fails, so punt if so. 300 if (!copy_prefs->Do()) 301 copy_prefs.reset(); 302 303 int exit_code = 0; 304 if (!LaunchAndWaitForExistingInstall(setup_exe, command_line, &exit_code)) { 305 if (copy_prefs) 306 copy_prefs->Rollback(); 307 return false; 308 } 309 *install_status = static_cast<InstallStatus>(exit_code); 310 return true; 311 } 312 313 // There are 4 disjoint cases => return values {false,true}: 314 // (1) Product is being uninstalled => false. 315 // (2) Product is being installed => true. 316 // (3) Current operation ignores product, product is absent => false. 317 // (4) Current operation ignores product, product is present => true. 318 bool WillProductBePresentAfterSetup( 319 const installer::InstallerState& installer_state, 320 const installer::InstallationState& machine_state, 321 BrowserDistribution::Type type) { 322 DCHECK(SupportsSingleInstall(type) || installer_state.is_multi_install()); 323 324 const ProductState* product_state = 325 machine_state.GetProductState(installer_state.system_install(), type); 326 327 // Determine if the product is present prior to the current operation. 328 bool is_present = (product_state != NULL); 329 bool is_uninstall = installer_state.operation() == InstallerState::UNINSTALL; 330 331 // Determine if current operation affects the product. 332 const Product* product = installer_state.FindProduct(type); 333 bool is_affected = (product != NULL); 334 335 // Decide among {(1),(2),(3),(4)}. 336 return is_affected ? !is_uninstall : is_present; 337 } 338 339 bool AdjustProcessPriority() { 340 if (base::win::GetVersion() >= base::win::VERSION_VISTA) { 341 DWORD priority_class = ::GetPriorityClass(::GetCurrentProcess()); 342 if (priority_class == 0) { 343 PLOG(WARNING) << "Failed to get the process's priority class."; 344 } else if (priority_class == BELOW_NORMAL_PRIORITY_CLASS || 345 priority_class == IDLE_PRIORITY_CLASS) { 346 BOOL result = ::SetPriorityClass(::GetCurrentProcess(), 347 PROCESS_MODE_BACKGROUND_BEGIN); 348 PLOG_IF(WARNING, !result) << "Failed to enter background mode."; 349 return !!result; 350 } 351 } 352 return false; 353 } 354 355 void MigrateGoogleUpdateStateMultiToSingle( 356 bool system_level, 357 BrowserDistribution::Type to_migrate, 358 const installer::InstallationState& machine_state) { 359 const HKEY root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; 360 const ProductState* product = NULL; 361 BrowserDistribution* dist = NULL; 362 LONG result = ERROR_SUCCESS; 363 base::win::RegKey state_key; 364 365 Product product_to_migrate( 366 BrowserDistribution::GetSpecificDistribution(to_migrate)); 367 368 // Copy usagestats from the binaries to the product's ClientState key. 369 product = machine_state.GetProductState(system_level, 370 BrowserDistribution::CHROME_BINARIES); 371 DWORD usagestats = 0; 372 if (product && product->GetUsageStats(&usagestats)) { 373 dist = product_to_migrate.distribution(); 374 result = state_key.Open(root, dist->GetStateKey().c_str(), 375 KEY_SET_VALUE); 376 if (result != ERROR_SUCCESS) { 377 LOG(ERROR) << "Failed opening ClientState key for " 378 << dist->GetDisplayName() << " to migrate usagestats."; 379 } else { 380 state_key.WriteValue(google_update::kRegUsageStatsField, usagestats); 381 } 382 } 383 384 // Remove the migrating product from the "ap" value of other multi-install 385 // products. 386 for (int i = 0; i < BrowserDistribution::NUM_TYPES; ++i) { 387 BrowserDistribution::Type type = 388 static_cast<BrowserDistribution::Type>(i); 389 if (type == to_migrate) 390 continue; 391 product = machine_state.GetProductState(system_level, type); 392 if (product && product->is_multi_install()) { 393 installer::ChannelInfo channel_info; 394 dist = BrowserDistribution::GetSpecificDistribution(type); 395 result = state_key.Open(root, dist->GetStateKey().c_str(), 396 KEY_QUERY_VALUE | KEY_SET_VALUE); 397 if (result == ERROR_SUCCESS && 398 channel_info.Initialize(state_key) && 399 product_to_migrate.SetChannelFlags(false, &channel_info)) { 400 VLOG(1) << "Moving " << dist->GetDisplayName() 401 << " to channel: " << channel_info.value(); 402 channel_info.Write(&state_key); 403 } 404 } 405 } 406 407 // Remove -multi, all product modifiers, and everything else but the channel 408 // name from the "ap" value of the product to migrate. 409 dist = product_to_migrate.distribution(); 410 result = state_key.Open(root, dist->GetStateKey().c_str(), 411 KEY_QUERY_VALUE | KEY_SET_VALUE); 412 if (result == ERROR_SUCCESS) { 413 installer::ChannelInfo channel_info; 414 if (!channel_info.Initialize(state_key)) { 415 LOG(ERROR) << "Failed reading " << dist->GetDisplayName() 416 << " channel info."; 417 } else if (channel_info.RemoveAllModifiersAndSuffixes()) { 418 VLOG(1) << "Moving " << dist->GetDisplayName() 419 << " to channel: " << channel_info.value(); 420 channel_info.Write(&state_key); 421 } 422 } 423 } 424 425 bool IsUninstallSuccess(InstallStatus install_status) { 426 // The following status values represent failed uninstalls: 427 // 15: CHROME_NOT_INSTALLED 428 // 20: UNINSTALL_FAILED 429 // 21: UNINSTALL_CANCELLED 430 return (install_status == UNINSTALL_SUCCESSFUL || 431 install_status == UNINSTALL_REQUIRES_REBOOT); 432 } 433 434 bool ContainsUnsupportedSwitch(const CommandLine& cmd_line) { 435 static const char* const kLegacySwitches[] = { 436 // Chrome Frame ready-mode. 437 "ready-mode", 438 "ready-mode-opt-in", 439 "ready-mode-temp-opt-out", 440 "ready-mode-end-temp-opt-out", 441 // Chrome Frame quick-enable. 442 "quick-enable-cf", 443 // Installation of Chrome Frame. 444 "chrome-frame", 445 "migrate-chrome-frame", 446 }; 447 for (size_t i = 0; i < arraysize(kLegacySwitches); ++i) { 448 if (cmd_line.HasSwitch(kLegacySwitches[i])) 449 return true; 450 } 451 return false; 452 } 453 454 bool IsProcessorSupported() { 455 return base::CPU().has_sse2(); 456 } 457 458 ScopedTokenPrivilege::ScopedTokenPrivilege(const wchar_t* privilege_name) 459 : is_enabled_(false) { 460 HANDLE temp_handle; 461 if (!::OpenProcessToken(::GetCurrentProcess(), 462 TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, 463 &temp_handle)) { 464 return; 465 } 466 token_.Set(temp_handle); 467 468 LUID privilege_luid; 469 if (!::LookupPrivilegeValue(NULL, privilege_name, &privilege_luid)) { 470 token_.Close(); 471 return; 472 } 473 474 // Adjust the token's privileges to enable |privilege_name|. If this privilege 475 // was already enabled, |previous_privileges_|.PrivilegeCount will be set to 0 476 // and we then know not to disable this privilege upon destruction. 477 TOKEN_PRIVILEGES tp; 478 tp.PrivilegeCount = 1; 479 tp.Privileges[0].Luid = privilege_luid; 480 tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 481 DWORD return_length; 482 if (!::AdjustTokenPrivileges(token_.Get(), FALSE, &tp, 483 sizeof(TOKEN_PRIVILEGES), 484 &previous_privileges_, &return_length)) { 485 token_.Close(); 486 return; 487 } 488 489 is_enabled_ = true; 490 } 491 492 ScopedTokenPrivilege::~ScopedTokenPrivilege() { 493 if (is_enabled_ && previous_privileges_.PrivilegeCount != 0) { 494 ::AdjustTokenPrivileges(token_.Get(), FALSE, &previous_privileges_, 495 sizeof(TOKEN_PRIVILEGES), NULL, NULL); 496 } 497 } 498 499 } // namespace installer 500