upgrade_util_win.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
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#include "chrome/browser/first_run/upgrade_util.h"
6
7#include <windows.h>
8#include <psapi.h>
9#include <shellapi.h>
10
11#include <algorithm>
12#include <string>
13
14#include "base/base_paths.h"
15#include "base/command_line.h"
16#include "base/environment.h"
17#include "base/file_util.h"
18#include "base/files/file_path.h"
19#include "base/logging.h"
20#include "base/path_service.h"
21#include "base/process/launch.h"
22#include "base/process/process_handle.h"
23#include "base/strings/string_number_conversions.h"
24#include "base/strings/string_util.h"
25#include "base/strings/stringprintf.h"
26#include "base/win/metro.h"
27#include "base/win/registry.h"
28#include "base/win/scoped_comptr.h"
29#include "base/win/windows_version.h"
30#include "chrome/browser/first_run/upgrade_util_win.h"
31#include "chrome/browser/shell_integration.h"
32#include "chrome/common/chrome_constants.h"
33#include "chrome/common/chrome_switches.h"
34#include "chrome/installer/util/browser_distribution.h"
35#include "chrome/installer/util/google_update_constants.h"
36#include "chrome/installer/util/install_util.h"
37#include "chrome/installer/util/shell_util.h"
38#include "chrome/installer/util/util_constants.h"
39#include "google_update/google_update_idl.h"
40
41namespace {
42
43bool GetNewerChromeFile(base::FilePath* path) {
44  if (!PathService::Get(base::DIR_EXE, path))
45    return false;
46  *path = path->Append(installer::kChromeNewExe);
47  return true;
48}
49
50bool InvokeGoogleUpdateForRename() {
51  base::win::ScopedComPtr<IProcessLauncher> ipl;
52  if (!FAILED(ipl.CreateInstance(__uuidof(ProcessLauncherClass)))) {
53    ULONG_PTR phandle = NULL;
54    DWORD id = GetCurrentProcessId();
55    BrowserDistribution* dist = BrowserDistribution::GetDistribution();
56    if (!FAILED(ipl->LaunchCmdElevated(dist->GetAppGuid().c_str(),
57                                       google_update::kRegRenameCmdField,
58                                       id,
59                                       &phandle))) {
60      HANDLE handle = HANDLE(phandle);
61      WaitForSingleObject(handle, INFINITE);
62      DWORD exit_code;
63      ::GetExitCodeProcess(handle, &exit_code);
64      ::CloseHandle(handle);
65      if (exit_code == installer::RENAME_SUCCESSFUL)
66        return true;
67    }
68  }
69  return false;
70}
71
72base::FilePath GetMetroRelauncherPath(const base::FilePath& chrome_exe,
73                                      const std::string& version_str) {
74  base::FilePath path(chrome_exe.DirName());
75
76  // The relauncher is ordinarily in the version directory.  When running in a
77  // build tree however (where CHROME_VERSION is not set in the environment)
78  // look for it in Chrome's directory.
79  if (!version_str.empty())
80    path = path.AppendASCII(version_str);
81
82  return path.Append(installer::kDelegateExecuteExe);
83}
84
85}  // namespace
86
87namespace upgrade_util {
88
89const char kRelaunchModeMetro[] = "relaunch.mode.metro";
90const char kRelaunchModeDesktop[] = "relaunch.mode.desktop";
91const char kRelaunchModeDefault[] = "relaunch.mode.default";
92
93// TODO(shrikant): Have a map/array to quickly map enum to strings.
94std::string RelaunchModeEnumToString(const RelaunchMode relaunch_mode) {
95  if (relaunch_mode == RELAUNCH_MODE_METRO)
96    return kRelaunchModeMetro;
97
98  if (relaunch_mode == RELAUNCH_MODE_DESKTOP)
99    return kRelaunchModeDesktop;
100
101  // For the purpose of code flow, even in case of wrong value we will
102  // return default re-launch mode.
103  return kRelaunchModeDefault;
104}
105
106RelaunchMode RelaunchModeStringToEnum(const std::string& relaunch_mode) {
107  if (relaunch_mode == kRelaunchModeMetro)
108    return RELAUNCH_MODE_METRO;
109
110  if (relaunch_mode == kRelaunchModeDesktop)
111    return RELAUNCH_MODE_DESKTOP;
112
113  return RELAUNCH_MODE_DEFAULT;
114}
115
116bool RelaunchChromeHelper(const CommandLine& command_line,
117                          const RelaunchMode& relaunch_mode) {
118  scoped_ptr<base::Environment> env(base::Environment::Create());
119  std::string version_str;
120
121  // Get the version variable and remove it from the environment.
122  if (env->GetVar(chrome::kChromeVersionEnvVar, &version_str))
123    env->UnSetVar(chrome::kChromeVersionEnvVar);
124  else
125    version_str.clear();
126
127  base::FilePath chrome_exe;
128  if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
129    NOTREACHED();
130    return false;
131  }
132
133  // Explicitly make sure to relaunch chrome.exe rather than old_chrome.exe.
134  // This can happen when old_chrome.exe is launched by a user.
135  CommandLine chrome_exe_command_line = command_line;
136  chrome_exe_command_line.SetProgram(
137      chrome_exe.DirName().Append(installer::kChromeExe));
138
139  if (base::win::GetVersion() < base::win::VERSION_WIN8)
140    return base::LaunchProcess(chrome_exe_command_line,
141                               base::LaunchOptions(), NULL);
142
143  // On Windows 8 we always use the delegate_execute for re-launching chrome.
144  //
145  // Pass this Chrome's Start Menu shortcut path to the relauncher so it can re-
146  // activate chrome via ShellExecute which will wait until we exit. Since
147  // ShellExecute does not support handle passing to the child process we create
148  // a uniquely named mutex that we aquire and never release. So when we exit,
149  // Windows marks our mutex as abandoned and the wait is satisfied. The format
150  // of the named mutex is important. See DelegateExecuteOperation for more
151  // details.
152  base::string16 mutex_name =
153      base::StringPrintf(L"chrome.relaunch.%d", ::GetCurrentProcessId());
154  HANDLE mutex = ::CreateMutexW(NULL, TRUE, mutex_name.c_str());
155  // The |mutex| handle needs to be leaked. See comment above.
156  if (!mutex) {
157    NOTREACHED();
158    return false;
159  }
160  if (::GetLastError() == ERROR_ALREADY_EXISTS) {
161    NOTREACHED() << "Relaunch mutex already exists";
162    return false;
163  }
164
165  CommandLine relaunch_cmd(CommandLine::NO_PROGRAM);
166  relaunch_cmd.AppendSwitchPath(switches::kRelaunchShortcut,
167      ShellIntegration::GetStartMenuShortcut(chrome_exe));
168  relaunch_cmd.AppendSwitchNative(switches::kWaitForMutex, mutex_name);
169
170  if (relaunch_mode != RELAUNCH_MODE_DEFAULT) {
171    relaunch_cmd.AppendSwitch(relaunch_mode == RELAUNCH_MODE_METRO?
172        switches::kForceImmersive : switches::kForceDesktop);
173  }
174
175  base::string16 params(relaunch_cmd.GetCommandLineString());
176  base::string16 path(GetMetroRelauncherPath(chrome_exe, version_str).value());
177
178  SHELLEXECUTEINFO sei = { sizeof(sei) };
179  sei.fMask = SEE_MASK_FLAG_LOG_USAGE | SEE_MASK_NOCLOSEPROCESS;
180  sei.nShow = SW_SHOWNORMAL;
181  sei.lpFile = path.c_str();
182  sei.lpParameters = params.c_str();
183
184  if (!::ShellExecuteExW(&sei)) {
185    NOTREACHED() << "ShellExecute failed with " << GetLastError();
186    return false;
187  }
188  DWORD pid = ::GetProcessId(sei.hProcess);
189  CloseHandle(sei.hProcess);
190  if (!pid)
191    return false;
192  // The next call appears to be needed if we are relaunching from desktop into
193  // metro mode. The observed effect if not done is that chrome starts in metro
194  // mode but it is not given focus and it gets killed by windows after a few
195  // seconds.
196  ::AllowSetForegroundWindow(pid);
197  return true;
198}
199
200bool RelaunchChromeBrowser(const CommandLine& command_line) {
201  return RelaunchChromeHelper(command_line, RELAUNCH_MODE_DEFAULT);
202}
203
204bool RelaunchChromeWithMode(const CommandLine& command_line,
205                            const RelaunchMode& relaunch_mode) {
206  return RelaunchChromeHelper(command_line, relaunch_mode);
207}
208
209bool IsUpdatePendingRestart() {
210  base::FilePath new_chrome_exe;
211  if (!GetNewerChromeFile(&new_chrome_exe))
212    return false;
213  return base::PathExists(new_chrome_exe);
214}
215
216bool SwapNewChromeExeIfPresent() {
217  base::FilePath new_chrome_exe;
218  if (!GetNewerChromeFile(&new_chrome_exe))
219    return false;
220  if (!base::PathExists(new_chrome_exe))
221    return false;
222  base::FilePath cur_chrome_exe;
223  if (!PathService::Get(base::FILE_EXE, &cur_chrome_exe))
224    return false;
225
226  // Open up the registry key containing current version and rename information.
227  bool user_install =
228      InstallUtil::IsPerUserInstall(cur_chrome_exe.value().c_str());
229  HKEY reg_root = user_install ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
230  BrowserDistribution *dist = BrowserDistribution::GetDistribution();
231  base::win::RegKey key;
232  if (key.Open(reg_root, dist->GetVersionKey().c_str(),
233               KEY_QUERY_VALUE) == ERROR_SUCCESS) {
234    // First try to rename exe by launching rename command ourselves.
235    std::wstring rename_cmd;
236    if (key.ReadValue(google_update::kRegRenameCmdField,
237                      &rename_cmd) == ERROR_SUCCESS) {
238      base::win::ScopedHandle handle;
239      base::LaunchOptions options;
240      options.wait = true;
241      options.start_hidden = true;
242      if (base::LaunchProcess(rename_cmd, options, &handle)) {
243        DWORD exit_code;
244        ::GetExitCodeProcess(handle, &exit_code);
245        if (exit_code == installer::RENAME_SUCCESSFUL)
246          return true;
247      }
248    }
249  }
250
251  // Rename didn't work so try to rename by calling Google Update
252  return InvokeGoogleUpdateForRename();
253}
254
255bool IsRunningOldChrome() {
256  // This figures out the actual file name that the section containing the
257  // mapped exe refers to. This is used instead of GetModuleFileName because the
258  // .exe may have been renamed out from under us while we've been running which
259  // GetModuleFileName won't notice.
260  wchar_t mapped_file_name[MAX_PATH * 2] = {};
261
262  if (!::GetMappedFileName(::GetCurrentProcess(),
263                           reinterpret_cast<void*>(::GetModuleHandle(NULL)),
264                           mapped_file_name,
265                           arraysize(mapped_file_name))) {
266    return false;
267  }
268
269  base::FilePath file_name(base::FilePath(mapped_file_name).BaseName());
270  return base::FilePath::CompareEqualIgnoreCase(file_name.value(),
271                                                installer::kChromeOldExe);
272}
273
274bool DoUpgradeTasks(const CommandLine& command_line) {
275  // The DelegateExecute verb handler finalizes pending in-use updates for
276  // metro mode launches, as Chrome cannot be gracefully relaunched when
277  // running in this mode.
278  if (base::win::IsMetroProcess())
279    return false;
280  if (!SwapNewChromeExeIfPresent() && !IsRunningOldChrome())
281    return false;
282  // At this point the chrome.exe has been swapped with the new one.
283  if (!RelaunchChromeBrowser(command_line)) {
284    // The re-launch fails. Feel free to panic now.
285    NOTREACHED();
286  }
287  return true;
288}
289
290}  // namespace upgrade_util
291