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