setup_util.cc revision f2477e01787aa58f445919b809d89e252beef54f
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
35namespace installer {
36
37namespace {
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.
44bool 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.
88bool SupportsSingleInstall(BrowserDistribution::Type type) {
89  return (type == BrowserDistribution::CHROME_BROWSER ||
90          type == BrowserDistribution::CHROME_FRAME);
91}
92
93}  // namespace
94
95int 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
120int 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
142Version* 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
168base::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
194bool 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
252bool 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
284bool 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.
316bool 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 = false;
327  if (product_state != NULL) {
328    if (type == BrowserDistribution::CHROME_FRAME) {
329      is_present = !product_state->uninstall_command().HasSwitch(
330                        switches::kChromeFrameReadyMode);
331    } else {
332      is_present = true;
333    }
334  }
335
336  bool is_uninstall = installer_state.operation() == InstallerState::UNINSTALL;
337
338  // Determine if current operation affects the product.
339  bool is_affected = false;
340  const Product* product = installer_state.FindProduct(type);
341  if (product != NULL) {
342    if (type == BrowserDistribution::CHROME_FRAME) {
343      // If Chrome Frame is being uninstalled, we don't bother to check
344      // !HasOption(kOptionReadyMode) since CF would not have been installed
345      // in the first place. If for some odd reason it weren't, we would be
346      // conservative, and cause false to be retruned since CF should not be
347      // installed then (so is_uninstall = true and is_affected = true).
348      is_affected = is_uninstall || !product->HasOption(kOptionReadyMode);
349    } else {
350      is_affected = true;
351    }
352  }
353
354  // Decide among {(1),(2),(3),(4)}.
355  return is_affected ? !is_uninstall : is_present;
356}
357
358bool AdjustProcessPriority() {
359  if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
360    DWORD priority_class = ::GetPriorityClass(::GetCurrentProcess());
361    if (priority_class == 0) {
362      PLOG(WARNING) << "Failed to get the process's priority class.";
363    } else if (priority_class == BELOW_NORMAL_PRIORITY_CLASS ||
364               priority_class == IDLE_PRIORITY_CLASS) {
365      BOOL result = ::SetPriorityClass(::GetCurrentProcess(),
366                                       PROCESS_MODE_BACKGROUND_BEGIN);
367      PLOG_IF(WARNING, !result) << "Failed to enter background mode.";
368      return !!result;
369    }
370  }
371  return false;
372}
373
374void MigrateGoogleUpdateStateMultiToSingle(
375    bool system_level,
376    BrowserDistribution::Type to_migrate,
377    const installer::InstallationState& machine_state) {
378  const HKEY root = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
379  const ProductState* product = NULL;
380  BrowserDistribution* dist = NULL;
381  LONG result = ERROR_SUCCESS;
382  base::win::RegKey state_key;
383
384  Product product_to_migrate(
385      BrowserDistribution::GetSpecificDistribution(to_migrate));
386
387  // Copy usagestats from the binaries to the product's ClientState key.
388  product = machine_state.GetProductState(system_level,
389                                          BrowserDistribution::CHROME_BINARIES);
390  DWORD usagestats = 0;
391  if (product && product->GetUsageStats(&usagestats)) {
392    dist = product_to_migrate.distribution();
393    result = state_key.Open(root, dist->GetStateKey().c_str(),
394                            KEY_SET_VALUE);
395    if (result != ERROR_SUCCESS) {
396      LOG(ERROR) << "Failed opening ClientState key for "
397                 << dist->GetDisplayName() << " to migrate usagestats.";
398    } else {
399      state_key.WriteValue(google_update::kRegUsageStatsField, usagestats);
400    }
401  }
402
403  // Remove the migrating product from the "ap" value of other multi-install
404  // products.
405  for (int i = 0; i < BrowserDistribution::NUM_TYPES; ++i) {
406    BrowserDistribution::Type type =
407        static_cast<BrowserDistribution::Type>(i);
408    if (type == to_migrate)
409      continue;
410    product = machine_state.GetProductState(system_level, type);
411    if (product && product->is_multi_install()) {
412      installer::ChannelInfo channel_info;
413      dist = BrowserDistribution::GetSpecificDistribution(type);
414      result = state_key.Open(root, dist->GetStateKey().c_str(),
415                              KEY_QUERY_VALUE | KEY_SET_VALUE);
416      if (result == ERROR_SUCCESS &&
417          channel_info.Initialize(state_key) &&
418          product_to_migrate.SetChannelFlags(false, &channel_info)) {
419        VLOG(1) << "Moving " << dist->GetDisplayName()
420                << " to channel: " << channel_info.value();
421        channel_info.Write(&state_key);
422      }
423    }
424  }
425
426  // Remove -multi, all product modifiers, and everything else but the channel
427  // name from the "ap" value of the product to migrate.
428  dist = product_to_migrate.distribution();
429  result = state_key.Open(root, dist->GetStateKey().c_str(),
430                          KEY_QUERY_VALUE | KEY_SET_VALUE);
431  if (result == ERROR_SUCCESS) {
432    installer::ChannelInfo channel_info;
433    if (!channel_info.Initialize(state_key)) {
434      LOG(ERROR) << "Failed reading " << dist->GetDisplayName()
435                 << " channel info.";
436    } else if (channel_info.RemoveAllModifiersAndSuffixes()) {
437      VLOG(1) << "Moving " << dist->GetDisplayName()
438              << " to channel: " << channel_info.value();
439      channel_info.Write(&state_key);
440    }
441  }
442}
443
444bool IsUninstallSuccess(InstallStatus install_status) {
445  // The following status values represent failed uninstalls:
446  // 15: CHROME_NOT_INSTALLED
447  // 20: UNINSTALL_FAILED
448  // 21: UNINSTALL_CANCELLED
449  return (install_status == UNINSTALL_SUCCESSFUL ||
450          install_status == UNINSTALL_REQUIRES_REBOOT);
451}
452
453ScopedTokenPrivilege::ScopedTokenPrivilege(const wchar_t* privilege_name)
454    : is_enabled_(false) {
455  HANDLE temp_handle;
456  if (!::OpenProcessToken(::GetCurrentProcess(),
457                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
458                          &temp_handle)) {
459    return;
460  }
461  token_.Set(temp_handle);
462
463  LUID privilege_luid;
464  if (!::LookupPrivilegeValue(NULL, privilege_name, &privilege_luid)) {
465    token_.Close();
466    return;
467  }
468
469  // Adjust the token's privileges to enable |privilege_name|. If this privilege
470  // was already enabled, |previous_privileges_|.PrivilegeCount will be set to 0
471  // and we then know not to disable this privilege upon destruction.
472  TOKEN_PRIVILEGES tp;
473  tp.PrivilegeCount = 1;
474  tp.Privileges[0].Luid = privilege_luid;
475  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
476  DWORD return_length;
477  if (!::AdjustTokenPrivileges(token_, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),
478                               &previous_privileges_, &return_length)) {
479    token_.Close();
480    return;
481  }
482
483  is_enabled_ = true;
484}
485
486ScopedTokenPrivilege::~ScopedTokenPrivilege() {
487  if (is_enabled_ && previous_privileges_.PrivilegeCount != 0) {
488    ::AdjustTokenPrivileges(token_, FALSE, &previous_privileges_,
489                            sizeof(TOKEN_PRIVILEGES), NULL, NULL);
490  }
491}
492
493}  // namespace installer
494