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