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 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 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 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. 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 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 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 223 bool RelaunchChromeBrowser(const CommandLine& command_line) { 224 return RelaunchChromeHelper(command_line, RELAUNCH_MODE_DEFAULT); 225 } 226 227 bool RelaunchChromeWithMode(const CommandLine& command_line, 228 const RelaunchMode& relaunch_mode) { 229 return RelaunchChromeHelper(command_line, relaunch_mode); 230 } 231 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 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 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 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