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