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