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