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