setup_util.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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/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/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"
36
37namespace installer {
38
39namespace {
40
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.
46bool LaunchAndWaitForExistingInstall(const base::FilePath& setup_exe,
47                                     const CommandLine& command_line,
48                                     int* exit_code) {
49  DCHECK(exit_code);
50  CommandLine new_cl(setup_exe);
51
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);
59    else
60      new_cl.AppendSwitchNative(i->first, i->second);
61  }
62
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);
68  }
69
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();
77    return false;
78  }
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;
82  } else {
83    VLOG(1) << "Existing installer returned exit code " << *exit_code;
84  }
85  return true;
86}
87
88// Returns true if product |type| cam be meaningfully installed without the
89// --multi-install flag.
90bool SupportsSingleInstall(BrowserDistribution::Type type) {
91  return (type == BrowserDistribution::CHROME_BROWSER ||
92          type == BrowserDistribution::CHROME_FRAME);
93}
94
95}  // namespace
96
97int 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();
103
104  if (src.empty() || patch.empty() || dest.empty())
105    return installer::PATCH_INVALID_ARGUMENTS;
106
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;
113
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;
118
119  return exit_code;
120}
121
122int 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();
128
129  if (src.empty() || patch.empty() || dest.empty())
130    return installer::PATCH_INVALID_ARGUMENTS;
131
132  const int patch_status = courgette::ApplyBinaryPatch(src, patch, dest);
133  const int exit_code = patch_status != OK ?
134                        patch_status + kBsdiffErrorOffset : 0;
135
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;
140
141  return exit_code;
142}
143
144Version* 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.
150
151  scoped_ptr<Version> max_version(new Version("0.0.0.0"));
152  bool version_found = false;
153
154  while (!version_enum.Next().empty()) {
155    base::FileEnumerator::FileInfo find_data = version_enum.GetInfo();
156    VLOG(1) << "directory found: " << find_data.GetName().value();
157
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;
164    }
165  }
166
167  return (version_found ? max_version.release() : NULL);
168}
169
170base::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());
179  if (product) {
180    patch_source = installer_state.GetInstallerDirectory(product->version())
181        .Append(installer::kChromeArchive);
182    if (base::PathExists(patch_source))
183      return patch_source;
184  }
185  scoped_ptr<Version> version(
186      installer::GetMaxVersionFromArchiveDir(installer_state.target_path()));
187  if (version) {
188    patch_source = installer_state.GetInstallerDirectory(*version)
189        .Append(installer::kChromeArchive);
190    if (base::PathExists(patch_source))
191      return patch_source;
192  }
193  return base::FilePath();
194}
195
196bool 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];
201  DWORD size =
202      ExpandEnvironmentStrings(kRunDll32Path, rundll32, arraysize(rundll32));
203  if (!size || size >= MAX_PATH)
204    return false;
205
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);
210  if (ok) {
211    // We use the main thread of the new process to run:
212    //   Sleep(delay_before_delete_ms);
213    //   DeleteFile(path);
214    //   ExitProcess(0);
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,
220                                 PAGE_READWRITE);
221    if (mem) {
222      SIZE_T written = 0;
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) {
234        NOTREACHED();
235        ok = FALSE;
236      } else {
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);
242      }
243    } else {
244      PLOG(ERROR) << "VirtualAllocEx";
245      ::TerminateProcess(pi.hProcess, ~static_cast<UINT>(0));
246    }
247    ::CloseHandle(pi.hThread);
248    ::CloseHandle(pi.hProcess);
249  }
250
251  return ok != FALSE;
252}
253
254bool GetExistingHigherInstaller(
255    const InstallationState& original_state,
256    bool system_install,
257    const Version& installer_version,
258    base::FilePath* setup_exe) {
259  DCHECK(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;
267    existing_state =
268        original_state.GetProductState(system_install,
269                                       BrowserDistribution::CHROME_BROWSER);
270  }
271
272  if (!existing_state ||
273      existing_state->version().CompareTo(installer_version) <= 0) {
274    return false;
275  }
276
277  *setup_exe = existing_state->GetSetupPath();
278
279  VLOG_IF(1, !setup_exe->empty()) << "Found a higher version of "
280      << (trying_single_browser ? "single-install Chrome."
281          : "multi-install Chrome binaries.");
282
283  return !setup_exe->empty();
284}
285
286bool 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,
298      base::FilePath()));
299  // There's nothing to rollback if the copy fails, so punt if so.
300  if (!copy_prefs->Do())
301    copy_prefs.reset();
302
303  int exit_code = 0;
304  if (!LaunchAndWaitForExistingInstall(setup_exe, command_line, &exit_code)) {
305    if (copy_prefs)
306      copy_prefs->Rollback();
307    return false;
308  }
309  *install_status = static_cast<InstallStatus>(exit_code);
310  return true;
311}
312
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.
318bool 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());
323
324  const ProductState* product_state =
325      machine_state.GetProductState(installer_state.system_install(), type);
326
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;
330
331  // Determine if current operation affects the product.
332  const Product* product = installer_state.FindProduct(type);
333  bool is_affected = (product != NULL);
334
335  // Decide among {(1),(2),(3),(4)}.
336  return is_affected ? !is_uninstall : is_present;
337}
338
339bool 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.";
349      return !!result;
350    }
351  }
352  return false;
353}
354
355void MigrateGoogleUpdateStateMultiToSingle(
356    bool system_level,
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;
364
365  Product product_to_migrate(
366      BrowserDistribution::GetSpecificDistribution(to_migrate));
367
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(),
375                            KEY_SET_VALUE);
376    if (result != ERROR_SUCCESS) {
377      LOG(ERROR) << "Failed opening ClientState key for "
378                 << dist->GetDisplayName() << " to migrate usagestats.";
379    } else {
380      state_key.WriteValue(google_update::kRegUsageStatsField, usagestats);
381    }
382  }
383
384  // Remove the migrating product from the "ap" value of other multi-install
385  // products.
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)
390      continue;
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);
403      }
404    }
405  }
406
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()
416                 << " channel info.";
417    } else if (channel_info.RemoveAllModifiersAndSuffixes()) {
418      VLOG(1) << "Moving " << dist->GetDisplayName()
419              << " to channel: " << channel_info.value();
420      channel_info.Write(&state_key);
421    }
422  }
423}
424
425bool 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);
432}
433
434bool ContainsUnsupportedSwitch(const CommandLine& cmd_line) {
435  static const char* const kLegacySwitches[] = {
436    // Chrome Frame ready-mode.
437    "ready-mode",
438    "ready-mode-opt-in",
439    "ready-mode-temp-opt-out",
440    "ready-mode-end-temp-opt-out",
441    // Chrome Frame quick-enable.
442    "quick-enable-cf",
443    // Installation of Chrome Frame.
444    "chrome-frame",
445    "migrate-chrome-frame",
446  };
447  for (size_t i = 0; i < arraysize(kLegacySwitches); ++i) {
448    if (cmd_line.HasSwitch(kLegacySwitches[i]))
449      return true;
450  }
451  return false;
452}
453
454bool IsProcessorSupported() {
455  return base::CPU().has_sse2();
456}
457
458ScopedTokenPrivilege::ScopedTokenPrivilege(const wchar_t* privilege_name)
459    : is_enabled_(false) {
460  HANDLE temp_handle;
461  if (!::OpenProcessToken(::GetCurrentProcess(),
462                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
463                          &temp_handle)) {
464    return;
465  }
466  token_.Set(temp_handle);
467
468  LUID privilege_luid;
469  if (!::LookupPrivilegeValue(NULL, privilege_name, &privilege_luid)) {
470    token_.Close();
471    return;
472  }
473
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.
477  TOKEN_PRIVILEGES tp;
478  tp.PrivilegeCount = 1;
479  tp.Privileges[0].Luid = privilege_luid;
480  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
481  DWORD return_length;
482  if (!::AdjustTokenPrivileges(token_.Get(), FALSE, &tp,
483                               sizeof(TOKEN_PRIVILEGES),
484                               &previous_privileges_, &return_length)) {
485    token_.Close();
486    return;
487  }
488
489  is_enabled_ = true;
490}
491
492ScopedTokenPrivilege::~ScopedTokenPrivilege() {
493  if (is_enabled_ && previous_privileges_.PrivilegeCount != 0) {
494    ::AdjustTokenPrivileges(token_.Get(), FALSE, &previous_privileges_,
495                            sizeof(TOKEN_PRIVILEGES), NULL, NULL);
496  }
497}
498
499}  // namespace installer
500