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