• 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/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