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