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.
5 // This file declares util functions for setup project.
7 #include "chrome/installer/setup/setup_util.h"
11 #include "base/command_line.h"
13 #include "base/file_util.h"
14 #include "base/files/file_enumerator.h"
15 #include "base/files/file_path.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/copy_tree_work_item.h"
27 #include "chrome/installer/util/google_update_constants.h"
28 #include "chrome/installer/util/installation_state.h"
29 #include "chrome/installer/util/installer_state.h"
30 #include "chrome/installer/util/master_preferences.h"
31 #include "chrome/installer/util/util_constants.h"
32 #include "chrome/installer/util/work_item.h"
33 #include "courgette/courgette.h"
34 #include "courgette/third_party/bsdiff.h"
35 #include "third_party/bspatch/mbspatch.h"
41 // Launches |setup_exe| with |command_line|, save --install-archive and its
42 // value if present. Returns false if the process failed to launch. Otherwise,
43 // waits indefinitely for it to exit and populates |exit_code| as expected. On
44 // the off chance that waiting itself fails, |exit_code| is set to
45 // WAIT_FOR_EXISTING_FAILED.
46 bool LaunchAndWaitForExistingInstall(const base::FilePath
& setup_exe
,
47 const CommandLine
& command_line
,
50 CommandLine
new_cl(setup_exe
);
52 // Copy over all switches but --install-archive.
53 CommandLine::SwitchMap
switches(command_line
.GetSwitches());
54 switches
.erase(switches::kInstallArchive
);
55 for (CommandLine::SwitchMap::const_iterator i
= switches
.begin();
56 i
!= switches
.end(); ++i
) {
57 if (i
->second
.empty())
58 new_cl
.AppendSwitch(i
->first
);
60 new_cl
.AppendSwitchNative(i
->first
, i
->second
);
63 // Copy over all arguments.
64 CommandLine::StringVector
args(command_line
.GetArgs());
65 for (CommandLine::StringVector::const_iterator i
= args
.begin();
66 i
!= args
.end(); ++i
) {
67 new_cl
.AppendArgNative(*i
);
70 // Launch the process and wait for it to exit.
71 VLOG(1) << "Launching existing installer with command: "
72 << new_cl
.GetCommandLineString();
73 base::ProcessHandle handle
= INVALID_HANDLE_VALUE
;
74 if (!base::LaunchProcess(new_cl
, base::LaunchOptions(), &handle
)) {
75 PLOG(ERROR
) << "Failed to launch existing installer with command: "
76 << new_cl
.GetCommandLineString();
79 if (!base::WaitForExitCode(handle
, exit_code
)) {
80 PLOG(DFATAL
) << "Failed to get exit code from existing installer";
81 *exit_code
= WAIT_FOR_EXISTING_FAILED
;
83 VLOG(1) << "Existing installer returned exit code " << *exit_code
;
88 // Returns true if product |type| cam be meaningfully installed without the
89 // --multi-install flag.
90 bool SupportsSingleInstall(BrowserDistribution::Type type
) {
91 return (type
== BrowserDistribution::CHROME_BROWSER
||
92 type
== BrowserDistribution::CHROME_FRAME
);
97 int CourgettePatchFiles(const base::FilePath
& src
,
98 const base::FilePath
& patch
,
99 const base::FilePath
& dest
) {
100 VLOG(1) << "Applying Courgette patch " << patch
.value()
101 << " to file " << src
.value()
102 << " and generating file " << dest
.value();
104 if (src
.empty() || patch
.empty() || dest
.empty())
105 return installer::PATCH_INVALID_ARGUMENTS
;
107 const courgette::Status patch_status
=
108 courgette::ApplyEnsemblePatch(src
.value().c_str(),
109 patch
.value().c_str(),
110 dest
.value().c_str());
111 const int exit_code
= (patch_status
!= courgette::C_OK
) ?
112 static_cast<int>(patch_status
) + kCourgetteErrorOffset
: 0;
114 LOG_IF(ERROR
, exit_code
)
115 << "Failed to apply Courgette patch " << patch
.value()
116 << " to file " << src
.value() << " and generating file " << dest
.value()
117 << ". err=" << exit_code
;
122 int BsdiffPatchFiles(const base::FilePath
& src
,
123 const base::FilePath
& patch
,
124 const base::FilePath
& dest
) {
125 VLOG(1) << "Applying bsdiff patch " << patch
.value()
126 << " to file " << src
.value()
127 << " and generating file " << dest
.value();
129 if (src
.empty() || patch
.empty() || dest
.empty())
130 return installer::PATCH_INVALID_ARGUMENTS
;
132 const int patch_status
= courgette::ApplyBinaryPatch(src
, patch
, dest
);
133 const int exit_code
= patch_status
!= OK
?
134 patch_status
+ kBsdiffErrorOffset
: 0;
136 LOG_IF(ERROR
, exit_code
)
137 << "Failed to apply bsdiff patch " << patch
.value()
138 << " to file " << src
.value() << " and generating file " << dest
.value()
139 << ". err=" << exit_code
;
144 Version
* GetMaxVersionFromArchiveDir(const base::FilePath
& chrome_path
) {
145 VLOG(1) << "Looking for Chrome version folder under " << chrome_path
.value();
146 base::FileEnumerator
version_enum(chrome_path
, false,
147 base::FileEnumerator::DIRECTORIES
);
148 // TODO(tommi): The version directory really should match the version of
149 // setup.exe. To begin with, we should at least DCHECK that that's true.
151 scoped_ptr
<Version
> max_version(new Version("0.0.0.0"));
152 bool version_found
= false;
154 while (!version_enum
.Next().empty()) {
155 base::FileEnumerator::FileInfo find_data
= version_enum
.GetInfo();
156 VLOG(1) << "directory found: " << find_data
.GetName().value();
158 scoped_ptr
<Version
> found_version(
159 new Version(base::UTF16ToASCII(find_data
.GetName().value())));
160 if (found_version
->IsValid() &&
161 found_version
->CompareTo(*max_version
.get()) > 0) {
162 max_version
.reset(found_version
.release());
163 version_found
= true;
167 return (version_found
? max_version
.release() : NULL
);
170 base::FilePath
FindArchiveToPatch(const InstallationState
& original_state
,
171 const InstallerState
& installer_state
) {
172 // Check based on the version number advertised to Google Update, since that
173 // is the value used to select a specific differential update. If an archive
174 // can't be found using that, fallback to using the newest version present.
175 base::FilePath patch_source
;
176 const ProductState
* product
=
177 original_state
.GetProductState(installer_state
.system_install(),
178 installer_state
.state_type());
180 patch_source
= installer_state
.GetInstallerDirectory(product
->version())
181 .Append(installer::kChromeArchive
);
182 if (base::PathExists(patch_source
))
185 scoped_ptr
<Version
> version(
186 installer::GetMaxVersionFromArchiveDir(installer_state
.target_path()));
188 patch_source
= installer_state
.GetInstallerDirectory(*version
)
189 .Append(installer::kChromeArchive
);
190 if (base::PathExists(patch_source
))
193 return base::FilePath();
196 bool DeleteFileFromTempProcess(const base::FilePath
& path
,
197 uint32 delay_before_delete_ms
) {
198 static const wchar_t kRunDll32Path
[] =
199 L
"%SystemRoot%\\System32\\rundll32.exe";
200 wchar_t rundll32
[MAX_PATH
];
202 ExpandEnvironmentStrings(kRunDll32Path
, rundll32
, arraysize(rundll32
));
203 if (!size
|| size
>= MAX_PATH
)
206 STARTUPINFO startup
= { sizeof(STARTUPINFO
) };
207 PROCESS_INFORMATION pi
= {0};
208 BOOL ok
= ::CreateProcess(NULL
, rundll32
, NULL
, NULL
, FALSE
, CREATE_SUSPENDED
,
209 NULL
, NULL
, &startup
, &pi
);
211 // We use the main thread of the new process to run:
212 // Sleep(delay_before_delete_ms);
215 // This runs before the main routine of the process runs, so it doesn't
216 // matter much which executable we choose except that we don't want to
217 // use e.g. a console app that causes a window to be created.
218 size
= (path
.value().length() + 1) * sizeof(path
.value()[0]);
219 void* mem
= ::VirtualAllocEx(pi
.hProcess
, NULL
, size
, MEM_COMMIT
,
223 ::WriteProcessMemory(
224 pi
.hProcess
, mem
, path
.value().c_str(),
225 (path
.value().size() + 1) * sizeof(path
.value()[0]), &written
);
226 HMODULE kernel32
= ::GetModuleHandle(L
"kernel32.dll");
227 PAPCFUNC sleep
= reinterpret_cast<PAPCFUNC
>(
228 ::GetProcAddress(kernel32
, "Sleep"));
229 PAPCFUNC delete_file
= reinterpret_cast<PAPCFUNC
>(
230 ::GetProcAddress(kernel32
, "DeleteFileW"));
231 PAPCFUNC exit_process
= reinterpret_cast<PAPCFUNC
>(
232 ::GetProcAddress(kernel32
, "ExitProcess"));
233 if (!sleep
|| !delete_file
|| !exit_process
) {
237 ::QueueUserAPC(sleep
, pi
.hThread
, delay_before_delete_ms
);
238 ::QueueUserAPC(delete_file
, pi
.hThread
,
239 reinterpret_cast<ULONG_PTR
>(mem
));
240 ::QueueUserAPC(exit_process
, pi
.hThread
, 0);
241 ::ResumeThread(pi
.hThread
);
244 PLOG(ERROR
) << "VirtualAllocEx";
245 ::TerminateProcess(pi
.hProcess
, ~0);
247 ::CloseHandle(pi
.hThread
);
248 ::CloseHandle(pi
.hProcess
);
254 bool GetExistingHigherInstaller(
255 const InstallationState
& original_state
,
257 const Version
& installer_version
,
258 base::FilePath
* setup_exe
) {
260 bool trying_single_browser
= false;
261 const ProductState
* existing_state
=
262 original_state
.GetProductState(system_install
,
263 BrowserDistribution::CHROME_BINARIES
);
264 if (!existing_state
) {
265 // The binaries aren't installed, but perhaps a single-install Chrome is.
266 trying_single_browser
= true;
268 original_state
.GetProductState(system_install
,
269 BrowserDistribution::CHROME_BROWSER
);
272 if (!existing_state
||
273 existing_state
->version().CompareTo(installer_version
) <= 0) {
277 *setup_exe
= existing_state
->GetSetupPath();
279 VLOG_IF(1, !setup_exe
->empty()) << "Found a higher version of "
280 << (trying_single_browser
? "single-install Chrome."
281 : "multi-install Chrome binaries.");
283 return !setup_exe
->empty();
286 bool DeferToExistingInstall(const base::FilePath
& setup_exe
,
287 const CommandLine
& command_line
,
288 const InstallerState
& installer_state
,
289 const base::FilePath
& temp_path
,
290 InstallStatus
* install_status
) {
291 // Copy a master_preferences file if there is one.
292 base::FilePath
prefs_source_path(command_line
.GetSwitchValueNative(
293 switches::kInstallerData
));
294 base::FilePath
prefs_dest_path(installer_state
.target_path().AppendASCII(
295 kDefaultMasterPrefs
));
296 scoped_ptr
<WorkItem
> copy_prefs(WorkItem::CreateCopyTreeWorkItem(
297 prefs_source_path
, prefs_dest_path
, temp_path
, WorkItem::ALWAYS
,
299 // There's nothing to rollback if the copy fails, so punt if so.
300 if (!copy_prefs
->Do())
304 if (!LaunchAndWaitForExistingInstall(setup_exe
, command_line
, &exit_code
)) {
306 copy_prefs
->Rollback();
309 *install_status
= static_cast<InstallStatus
>(exit_code
);
313 // There are 4 disjoint cases => return values {false,true}:
314 // (1) Product is being uninstalled => false.
315 // (2) Product is being installed => true.
316 // (3) Current operation ignores product, product is absent => false.
317 // (4) Current operation ignores product, product is present => true.
318 bool WillProductBePresentAfterSetup(
319 const installer::InstallerState
& installer_state
,
320 const installer::InstallationState
& machine_state
,
321 BrowserDistribution::Type type
) {
322 DCHECK(SupportsSingleInstall(type
) || installer_state
.is_multi_install());
324 const ProductState
* product_state
=
325 machine_state
.GetProductState(installer_state
.system_install(), type
);
327 // Determine if the product is present prior to the current operation.
328 bool is_present
= (product_state
!= NULL
);
329 bool is_uninstall
= installer_state
.operation() == InstallerState::UNINSTALL
;
331 // Determine if current operation affects the product.
332 const Product
* product
= installer_state
.FindProduct(type
);
333 bool is_affected
= (product
!= NULL
);
335 // Decide among {(1),(2),(3),(4)}.
336 return is_affected
? !is_uninstall
: is_present
;
339 bool AdjustProcessPriority() {
340 if (base::win::GetVersion() >= base::win::VERSION_VISTA
) {
341 DWORD priority_class
= ::GetPriorityClass(::GetCurrentProcess());
342 if (priority_class
== 0) {
343 PLOG(WARNING
) << "Failed to get the process's priority class.";
344 } else if (priority_class
== BELOW_NORMAL_PRIORITY_CLASS
||
345 priority_class
== IDLE_PRIORITY_CLASS
) {
346 BOOL result
= ::SetPriorityClass(::GetCurrentProcess(),
347 PROCESS_MODE_BACKGROUND_BEGIN
);
348 PLOG_IF(WARNING
, !result
) << "Failed to enter background mode.";
355 void MigrateGoogleUpdateStateMultiToSingle(
357 BrowserDistribution::Type to_migrate
,
358 const installer::InstallationState
& machine_state
) {
359 const HKEY root
= system_level
? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER
;
360 const ProductState
* product
= NULL
;
361 BrowserDistribution
* dist
= NULL
;
362 LONG result
= ERROR_SUCCESS
;
363 base::win::RegKey state_key
;
365 Product
product_to_migrate(
366 BrowserDistribution::GetSpecificDistribution(to_migrate
));
368 // Copy usagestats from the binaries to the product's ClientState key.
369 product
= machine_state
.GetProductState(system_level
,
370 BrowserDistribution::CHROME_BINARIES
);
371 DWORD usagestats
= 0;
372 if (product
&& product
->GetUsageStats(&usagestats
)) {
373 dist
= product_to_migrate
.distribution();
374 result
= state_key
.Open(root
, dist
->GetStateKey().c_str(),
376 if (result
!= ERROR_SUCCESS
) {
377 LOG(ERROR
) << "Failed opening ClientState key for "
378 << dist
->GetDisplayName() << " to migrate usagestats.";
380 state_key
.WriteValue(google_update::kRegUsageStatsField
, usagestats
);
384 // Remove the migrating product from the "ap" value of other multi-install
386 for (int i
= 0; i
< BrowserDistribution::NUM_TYPES
; ++i
) {
387 BrowserDistribution::Type type
=
388 static_cast<BrowserDistribution::Type
>(i
);
389 if (type
== to_migrate
)
391 product
= machine_state
.GetProductState(system_level
, type
);
392 if (product
&& product
->is_multi_install()) {
393 installer::ChannelInfo channel_info
;
394 dist
= BrowserDistribution::GetSpecificDistribution(type
);
395 result
= state_key
.Open(root
, dist
->GetStateKey().c_str(),
396 KEY_QUERY_VALUE
| KEY_SET_VALUE
);
397 if (result
== ERROR_SUCCESS
&&
398 channel_info
.Initialize(state_key
) &&
399 product_to_migrate
.SetChannelFlags(false, &channel_info
)) {
400 VLOG(1) << "Moving " << dist
->GetDisplayName()
401 << " to channel: " << channel_info
.value();
402 channel_info
.Write(&state_key
);
407 // Remove -multi, all product modifiers, and everything else but the channel
408 // name from the "ap" value of the product to migrate.
409 dist
= product_to_migrate
.distribution();
410 result
= state_key
.Open(root
, dist
->GetStateKey().c_str(),
411 KEY_QUERY_VALUE
| KEY_SET_VALUE
);
412 if (result
== ERROR_SUCCESS
) {
413 installer::ChannelInfo channel_info
;
414 if (!channel_info
.Initialize(state_key
)) {
415 LOG(ERROR
) << "Failed reading " << dist
->GetDisplayName()
417 } else if (channel_info
.RemoveAllModifiersAndSuffixes()) {
418 VLOG(1) << "Moving " << dist
->GetDisplayName()
419 << " to channel: " << channel_info
.value();
420 channel_info
.Write(&state_key
);
425 bool IsUninstallSuccess(InstallStatus install_status
) {
426 // The following status values represent failed uninstalls:
427 // 15: CHROME_NOT_INSTALLED
428 // 20: UNINSTALL_FAILED
429 // 21: UNINSTALL_CANCELLED
430 return (install_status
== UNINSTALL_SUCCESSFUL
||
431 install_status
== UNINSTALL_REQUIRES_REBOOT
);
434 bool ContainsUnsupportedSwitch(const CommandLine
& cmd_line
) {
435 static const char* const kLegacySwitches
[] = {
436 // Chrome Frame ready-mode.
439 "ready-mode-temp-opt-out",
440 "ready-mode-end-temp-opt-out",
441 // Chrome Frame quick-enable.
443 // Installation of Chrome Frame.
445 "migrate-chrome-frame",
447 for (size_t i
= 0; i
< arraysize(kLegacySwitches
); ++i
) {
448 if (cmd_line
.HasSwitch(kLegacySwitches
[i
]))
454 bool IsProcessorSupported() {
455 return base::CPU().has_sse2();
458 ScopedTokenPrivilege::ScopedTokenPrivilege(const wchar_t* privilege_name
)
459 : is_enabled_(false) {
461 if (!::OpenProcessToken(::GetCurrentProcess(),
462 TOKEN_ADJUST_PRIVILEGES
| TOKEN_QUERY
,
466 token_
.Set(temp_handle
);
469 if (!::LookupPrivilegeValue(NULL
, privilege_name
, &privilege_luid
)) {
474 // Adjust the token's privileges to enable |privilege_name|. If this privilege
475 // was already enabled, |previous_privileges_|.PrivilegeCount will be set to 0
476 // and we then know not to disable this privilege upon destruction.
478 tp
.PrivilegeCount
= 1;
479 tp
.Privileges
[0].Luid
= privilege_luid
;
480 tp
.Privileges
[0].Attributes
= SE_PRIVILEGE_ENABLED
;
482 if (!::AdjustTokenPrivileges(token_
, FALSE
, &tp
, sizeof(TOKEN_PRIVILEGES
),
483 &previous_privileges_
, &return_length
)) {
491 ScopedTokenPrivilege::~ScopedTokenPrivilege() {
492 if (is_enabled_
&& previous_privileges_
.PrivilegeCount
!= 0) {
493 ::AdjustTokenPrivileges(token_
, FALSE
, &previous_privileges_
,
494 sizeof(TOKEN_PRIVILEGES
), NULL
, NULL
);
498 } // namespace installer