• 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 #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 
41 namespace {
42 
GetNewerChromeFile(base::FilePath * path)43 bool 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 
InvokeGoogleUpdateForRename()50 bool 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 
GetMetroRelauncherPath(const base::FilePath & chrome_exe,const std::string & version_str)72 base::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 
87 namespace upgrade_util {
88 
89 const char kRelaunchModeMetro[] = "relaunch.mode.metro";
90 const char kRelaunchModeDesktop[] = "relaunch.mode.desktop";
91 const char kRelaunchModeDefault[] = "relaunch.mode.default";
92 
93 // TODO(shrikant): Have a map/array to quickly map enum to strings.
RelaunchModeEnumToString(const RelaunchMode relaunch_mode)94 std::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 
RelaunchModeStringToEnum(const std::string & relaunch_mode)106 RelaunchMode 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 
RelaunchChromeHelper(const CommandLine & command_line,const RelaunchMode & relaunch_mode)116 bool 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 
RelaunchChromeBrowser(const CommandLine & command_line)200 bool RelaunchChromeBrowser(const CommandLine& command_line) {
201   return RelaunchChromeHelper(command_line, RELAUNCH_MODE_DEFAULT);
202 }
203 
RelaunchChromeWithMode(const CommandLine & command_line,const RelaunchMode & relaunch_mode)204 bool RelaunchChromeWithMode(const CommandLine& command_line,
205                             const RelaunchMode& relaunch_mode) {
206   return RelaunchChromeHelper(command_line, relaunch_mode);
207 }
208 
IsUpdatePendingRestart()209 bool 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 
SwapNewChromeExeIfPresent()216 bool 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 
IsRunningOldChrome()255 bool 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 
DoUpgradeTasks(const CommandLine & command_line)274 bool 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