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