setup_util.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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_util.h"
17#include "base/strings/string_util.h"
18#include "base/version.h"
19#include "base/win/windows_version.h"
20#include "chrome/installer/util/copy_tree_work_item.h"
21#include "chrome/installer/util/installation_state.h"
22#include "chrome/installer/util/installer_state.h"
23#include "chrome/installer/util/master_preferences.h"
24#include "chrome/installer/util/util_constants.h"
25#include "chrome/installer/util/work_item.h"
26#include "courgette/courgette.h"
27#include "courgette/third_party/bsdiff.h"
28#include "third_party/bspatch/mbspatch.h"
29
30namespace installer {
31
32namespace {
33
34// Launches |setup_exe| with |command_line|, save --install-archive and its
35// value if present. Returns false if the process failed to launch. Otherwise,
36// waits indefinitely for it to exit and populates |exit_code| as expected. On
37// the off chance that waiting itself fails, |exit_code| is set to
38// WAIT_FOR_EXISTING_FAILED.
39bool LaunchAndWaitForExistingInstall(const base::FilePath& setup_exe,
40                                     const CommandLine& command_line,
41                                     int* exit_code) {
42  DCHECK(exit_code);
43  CommandLine new_cl(setup_exe);
44
45  // Copy over all switches but --install-archive.
46  CommandLine::SwitchMap switches(command_line.GetSwitches());
47  switches.erase(switches::kInstallArchive);
48  for (CommandLine::SwitchMap::const_iterator i = switches.begin();
49       i != switches.end(); ++i) {
50    if (i->second.empty())
51      new_cl.AppendSwitch(i->first);
52    else
53      new_cl.AppendSwitchNative(i->first, i->second);
54  }
55
56  // Copy over all arguments.
57  CommandLine::StringVector args(command_line.GetArgs());
58  for (CommandLine::StringVector::const_iterator i = args.begin();
59       i != args.end(); ++i) {
60    new_cl.AppendArgNative(*i);
61  }
62
63  // Launch the process and wait for it to exit.
64  VLOG(1) << "Launching existing installer with command: "
65          << new_cl.GetCommandLineString();
66  base::ProcessHandle handle = INVALID_HANDLE_VALUE;
67  if (!base::LaunchProcess(new_cl, base::LaunchOptions(), &handle)) {
68    PLOG(ERROR) << "Failed to launch existing installer with command: "
69                << new_cl.GetCommandLineString();
70    return false;
71  }
72  if (!base::WaitForExitCode(handle, exit_code)) {
73    PLOG(DFATAL) << "Failed to get exit code from existing installer";
74    *exit_code = WAIT_FOR_EXISTING_FAILED;
75  } else {
76    VLOG(1) << "Existing installer returned exit code " << *exit_code;
77  }
78  return true;
79}
80
81// Returns true if product |type| cam be meaningfully installed without the
82// --multi-install flag.
83bool SupportsSingleInstall(BrowserDistribution::Type type) {
84  return (type == BrowserDistribution::CHROME_BROWSER ||
85          type == BrowserDistribution::CHROME_FRAME);
86}
87
88}  // namespace
89
90int CourgettePatchFiles(const base::FilePath& src,
91                        const base::FilePath& patch,
92                        const base::FilePath& dest) {
93  VLOG(1) << "Applying Courgette patch " << patch.value()
94          << " to file " << src.value()
95          << " and generating file " << dest.value();
96
97  if (src.empty() || patch.empty() || dest.empty())
98    return installer::PATCH_INVALID_ARGUMENTS;
99
100  const courgette::Status patch_status =
101      courgette::ApplyEnsemblePatch(src.value().c_str(),
102                                    patch.value().c_str(),
103                                    dest.value().c_str());
104  const int exit_code = (patch_status != courgette::C_OK) ?
105      static_cast<int>(patch_status) + kCourgetteErrorOffset : 0;
106
107  LOG_IF(ERROR, exit_code)
108      << "Failed to apply Courgette patch " << patch.value()
109      << " to file " << src.value() << " and generating file " << dest.value()
110      << ". err=" << exit_code;
111
112  return exit_code;
113}
114
115int BsdiffPatchFiles(const base::FilePath& src,
116                     const base::FilePath& patch,
117                     const base::FilePath& dest) {
118  VLOG(1) << "Applying bsdiff patch " << patch.value()
119          << " to file " << src.value()
120          << " and generating file " << dest.value();
121
122  if (src.empty() || patch.empty() || dest.empty())
123    return installer::PATCH_INVALID_ARGUMENTS;
124
125  const int patch_status = courgette::ApplyBinaryPatch(src, patch, dest);
126  const int exit_code = patch_status != OK ?
127                        patch_status + kBsdiffErrorOffset : 0;
128
129  LOG_IF(ERROR, exit_code)
130      << "Failed to apply bsdiff patch " << patch.value()
131      << " to file " << src.value() << " and generating file " << dest.value()
132      << ". err=" << exit_code;
133
134  return exit_code;
135}
136
137int ApplyDiffPatch(const base::FilePath& src,
138                   const base::FilePath& patch,
139                   const base::FilePath& dest,
140                   const InstallerState* installer_state) {
141  VLOG(1) << "Applying patch " << patch.value() << " to file "
142          << src.value() << " and generating file " << dest.value();
143
144  if (installer_state != NULL)
145    installer_state->UpdateStage(installer::ENSEMBLE_PATCHING);
146
147  // Try Courgette first.  Courgette checks the patch file first and fails
148  // quickly if the patch file does not have a valid Courgette header.
149  courgette::Status patch_status =
150      courgette::ApplyEnsemblePatch(src.value().c_str(),
151                                    patch.value().c_str(),
152                                    dest.value().c_str());
153  if (patch_status == courgette::C_OK)
154    return 0;
155
156  LOG(ERROR)
157        << "Failed to apply patch " << patch.value()
158        << " to file " << src.value() << " and generating file " << dest.value()
159        << " using courgette. err=" << patch_status;
160
161  // If we ran out of memory or disk space, then these are likely the errors
162  // we will see.  If we run into them, return an error and stay on the
163  // 'ENSEMBLE_PATCHING' update stage.
164  if (patch_status == courgette::C_DISASSEMBLY_FAILED ||
165      patch_status == courgette::C_STREAM_ERROR) {
166    return MEM_ERROR;
167  }
168
169  if (installer_state != NULL)
170    installer_state->UpdateStage(installer::BINARY_PATCHING);
171
172  int binary_patch_status = ApplyBinaryPatch(src.value().c_str(),
173                                             patch.value().c_str(),
174                                             dest.value().c_str());
175
176  LOG_IF(ERROR, binary_patch_status != OK)
177      << "Failed to apply patch " << patch.value()
178      << " to file " << src.value() << " and generating file " << dest.value()
179      << " using bsdiff. err=" << binary_patch_status;
180
181  return binary_patch_status;
182}
183
184Version* GetMaxVersionFromArchiveDir(const base::FilePath& chrome_path) {
185  VLOG(1) << "Looking for Chrome version folder under " << chrome_path.value();
186  Version* version = NULL;
187  base::FileEnumerator version_enum(chrome_path, false,
188      base::FileEnumerator::DIRECTORIES);
189  // TODO(tommi): The version directory really should match the version of
190  // setup.exe.  To begin with, we should at least DCHECK that that's true.
191
192  scoped_ptr<Version> max_version(new Version("0.0.0.0"));
193  bool version_found = false;
194
195  while (!version_enum.Next().empty()) {
196    base::FileEnumerator::FileInfo find_data = version_enum.GetInfo();
197    VLOG(1) << "directory found: " << find_data.GetName().value();
198
199    scoped_ptr<Version> found_version(
200        new Version(WideToASCII(find_data.GetName().value())));
201    if (found_version->IsValid() &&
202        found_version->CompareTo(*max_version.get()) > 0) {
203      max_version.reset(found_version.release());
204      version_found = true;
205    }
206  }
207
208  return (version_found ? max_version.release() : NULL);
209}
210
211bool DeleteFileFromTempProcess(const base::FilePath& path,
212                               uint32 delay_before_delete_ms) {
213  static const wchar_t kRunDll32Path[] =
214      L"%SystemRoot%\\System32\\rundll32.exe";
215  wchar_t rundll32[MAX_PATH];
216  DWORD size =
217      ExpandEnvironmentStrings(kRunDll32Path, rundll32, arraysize(rundll32));
218  if (!size || size >= MAX_PATH)
219    return false;
220
221  STARTUPINFO startup = { sizeof(STARTUPINFO) };
222  PROCESS_INFORMATION pi = {0};
223  BOOL ok = ::CreateProcess(NULL, rundll32, NULL, NULL, FALSE, CREATE_SUSPENDED,
224                            NULL, NULL, &startup, &pi);
225  if (ok) {
226    // We use the main thread of the new process to run:
227    //   Sleep(delay_before_delete_ms);
228    //   DeleteFile(path);
229    //   ExitProcess(0);
230    // This runs before the main routine of the process runs, so it doesn't
231    // matter much which executable we choose except that we don't want to
232    // use e.g. a console app that causes a window to be created.
233    size = (path.value().length() + 1) * sizeof(path.value()[0]);
234    void* mem = ::VirtualAllocEx(pi.hProcess, NULL, size, MEM_COMMIT,
235                                 PAGE_READWRITE);
236    if (mem) {
237      SIZE_T written = 0;
238      ::WriteProcessMemory(
239          pi.hProcess, mem, path.value().c_str(),
240          (path.value().size() + 1) * sizeof(path.value()[0]), &written);
241      HMODULE kernel32 = ::GetModuleHandle(L"kernel32.dll");
242      PAPCFUNC sleep = reinterpret_cast<PAPCFUNC>(
243          ::GetProcAddress(kernel32, "Sleep"));
244      PAPCFUNC delete_file = reinterpret_cast<PAPCFUNC>(
245          ::GetProcAddress(kernel32, "DeleteFileW"));
246      PAPCFUNC exit_process = reinterpret_cast<PAPCFUNC>(
247          ::GetProcAddress(kernel32, "ExitProcess"));
248      if (!sleep || !delete_file || !exit_process) {
249        NOTREACHED();
250        ok = FALSE;
251      } else {
252        ::QueueUserAPC(sleep, pi.hThread, delay_before_delete_ms);
253        ::QueueUserAPC(delete_file, pi.hThread,
254                       reinterpret_cast<ULONG_PTR>(mem));
255        ::QueueUserAPC(exit_process, pi.hThread, 0);
256        ::ResumeThread(pi.hThread);
257      }
258    } else {
259      PLOG(ERROR) << "VirtualAllocEx";
260      ::TerminateProcess(pi.hProcess, ~0);
261    }
262    ::CloseHandle(pi.hThread);
263    ::CloseHandle(pi.hProcess);
264  }
265
266  return ok != FALSE;
267}
268
269bool GetExistingHigherInstaller(
270    const InstallationState& original_state,
271    bool system_install,
272    const Version& installer_version,
273    base::FilePath* setup_exe) {
274  DCHECK(setup_exe);
275  bool trying_single_browser = false;
276  const ProductState* existing_state =
277      original_state.GetProductState(system_install,
278                                     BrowserDistribution::CHROME_BINARIES);
279  if (!existing_state) {
280    // The binaries aren't installed, but perhaps a single-install Chrome is.
281    trying_single_browser = true;
282    existing_state =
283        original_state.GetProductState(system_install,
284                                       BrowserDistribution::CHROME_BROWSER);
285  }
286
287  if (!existing_state ||
288      existing_state->version().CompareTo(installer_version) <= 0) {
289    return false;
290  }
291
292  *setup_exe = existing_state->GetSetupPath();
293
294  VLOG_IF(1, !setup_exe->empty()) << "Found a higher version of "
295      << (trying_single_browser ? "single-install Chrome."
296          : "multi-install Chrome binaries.");
297
298  return !setup_exe->empty();
299}
300
301bool DeferToExistingInstall(const base::FilePath& setup_exe,
302                            const CommandLine& command_line,
303                            const InstallerState& installer_state,
304                            const base::FilePath& temp_path,
305                            InstallStatus* install_status) {
306  // Copy a master_preferences file if there is one.
307  base::FilePath prefs_source_path(command_line.GetSwitchValueNative(
308      switches::kInstallerData));
309  base::FilePath prefs_dest_path(installer_state.target_path().AppendASCII(
310      kDefaultMasterPrefs));
311  scoped_ptr<WorkItem> copy_prefs(WorkItem::CreateCopyTreeWorkItem(
312      prefs_source_path, prefs_dest_path, temp_path, WorkItem::ALWAYS,
313      base::FilePath()));
314  // There's nothing to rollback if the copy fails, so punt if so.
315  if (!copy_prefs->Do())
316    copy_prefs.reset();
317
318  int exit_code = 0;
319  if (!LaunchAndWaitForExistingInstall(setup_exe, command_line, &exit_code)) {
320    if (copy_prefs)
321      copy_prefs->Rollback();
322    return false;
323  }
324  *install_status = static_cast<InstallStatus>(exit_code);
325  return true;
326}
327
328// There are 4 disjoint cases => return values {false,true}:
329// (1) Product is being uninstalled => false.
330// (2) Product is being installed => true.
331// (3) Current operation ignores product, product is absent => false.
332// (4) Current operation ignores product, product is present => true.
333bool WillProductBePresentAfterSetup(
334    const installer::InstallerState& installer_state,
335    const installer::InstallationState& machine_state,
336    BrowserDistribution::Type type) {
337  DCHECK(SupportsSingleInstall(type) || installer_state.is_multi_install());
338
339  const ProductState* product_state =
340      machine_state.GetProductState(installer_state.system_install(), type);
341
342  // Determine if the product is present prior to the current operation.
343  bool is_present = false;
344  if (product_state != NULL) {
345    if (type == BrowserDistribution::CHROME_FRAME) {
346      is_present = !product_state->uninstall_command().HasSwitch(
347                        switches::kChromeFrameReadyMode);
348    } else {
349      is_present = true;
350    }
351  }
352
353  bool is_uninstall = installer_state.operation() == InstallerState::UNINSTALL;
354
355  // Determine if current operation affects the product.
356  bool is_affected = false;
357  const Product* product = installer_state.FindProduct(type);
358  if (product != NULL) {
359    if (type == BrowserDistribution::CHROME_FRAME) {
360      // If Chrome Frame is being uninstalled, we don't bother to check
361      // !HasOption(kOptionReadyMode) since CF would not have been installed
362      // in the first place. If for some odd reason it weren't, we would be
363      // conservative, and cause false to be retruned since CF should not be
364      // installed then (so is_uninstall = true and is_affected = true).
365      is_affected = is_uninstall || !product->HasOption(kOptionReadyMode);
366    } else {
367      is_affected = true;
368    }
369  }
370
371  // Decide among {(1),(2),(3),(4)}.
372  return is_affected ? !is_uninstall : is_present;
373}
374
375bool AdjustProcessPriority() {
376  if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
377    DWORD priority_class = ::GetPriorityClass(::GetCurrentProcess());
378    if (priority_class == 0) {
379      PLOG(WARNING) << "Failed to get the process's priority class.";
380    } else if (priority_class == BELOW_NORMAL_PRIORITY_CLASS ||
381               priority_class == IDLE_PRIORITY_CLASS) {
382      BOOL result = ::SetPriorityClass(::GetCurrentProcess(),
383                                       PROCESS_MODE_BACKGROUND_BEGIN);
384      PLOG_IF(WARNING, !result) << "Failed to enter background mode.";
385      return !!result;
386    }
387  }
388  return false;
389}
390
391ScopedTokenPrivilege::ScopedTokenPrivilege(const wchar_t* privilege_name)
392    : is_enabled_(false) {
393  if (!::OpenProcessToken(::GetCurrentProcess(),
394                          TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
395                          token_.Receive())) {
396    return;
397  }
398
399  LUID privilege_luid;
400  if (!::LookupPrivilegeValue(NULL, privilege_name, &privilege_luid)) {
401    token_.Close();
402    return;
403  }
404
405  // Adjust the token's privileges to enable |privilege_name|. If this privilege
406  // was already enabled, |previous_privileges_|.PrivilegeCount will be set to 0
407  // and we then know not to disable this privilege upon destruction.
408  TOKEN_PRIVILEGES tp;
409  tp.PrivilegeCount = 1;
410  tp.Privileges[0].Luid = privilege_luid;
411  tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
412  DWORD return_length;
413  if (!::AdjustTokenPrivileges(token_, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),
414                               &previous_privileges_, &return_length)) {
415    token_.Close();
416    return;
417  }
418
419  is_enabled_ = true;
420}
421
422ScopedTokenPrivilege::~ScopedTokenPrivilege() {
423  if (is_enabled_ && previous_privileges_.PrivilegeCount != 0) {
424    ::AdjustTokenPrivileges(token_, FALSE, &previous_privileges_,
425                            sizeof(TOKEN_PRIVILEGES), NULL, NULL);
426  }
427}
428
429}  // namespace installer
430