Home | History | Annotate | Download | only in first_run
      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