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