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 <windows.h> 6 #include <shlwapi.h> 7 8 #include "base/command_line.h" 9 #include "base/debug/trace_event.h" 10 #include "base/environment.h" 11 #include "base/file_version_info.h" 12 #include "base/logging.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "base/strings/string16.h" 15 #include "base/strings/string_util.h" 16 #include "base/strings/stringprintf.h" 17 #include "base/strings/utf_string_conversions.h" 18 #include "base/version.h" 19 #include "base/win/windows_version.h" 20 #include "chrome/app/breakpad_win.h" 21 #include "chrome/app/client_util.h" 22 #include "chrome/app/image_pre_reader_win.h" 23 #include "chrome/common/chrome_constants.h" 24 #include "chrome/common/chrome_result_codes.h" 25 #include "chrome/common/chrome_switches.h" 26 #include "chrome/common/env_vars.h" 27 #include "chrome/installer/util/browser_distribution.h" 28 #include "chrome/installer/util/channel_info.h" 29 #include "chrome/installer/util/google_update_constants.h" 30 #include "chrome/installer/util/google_update_settings.h" 31 #include "chrome/installer/util/install_util.h" 32 #include "chrome/installer/util/util_constants.h" 33 34 namespace { 35 // The entry point signature of chrome.dll. 36 typedef int (*DLL_MAIN)(HINSTANCE, sandbox::SandboxInterfaceInfo*); 37 38 typedef void (*RelaunchChromeBrowserWithNewCommandLineIfNeededFunc)(); 39 40 // Expects that |dir| has a trailing backslash. |dir| is modified so it 41 // contains the full path that was tried. Caller must check for the return 42 // value not being null to determine if this path contains a valid dll. 43 HMODULE LoadChromeWithDirectory(string16* dir) { 44 ::SetCurrentDirectoryW(dir->c_str()); 45 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); 46 #if !defined(CHROME_MULTIPLE_DLL) 47 const wchar_t* dll_name = installer::kChromeDll; 48 #else 49 const wchar_t* dll_name = cmd_line.HasSwitch(switches::kProcessType) ? 50 installer::kChromeChildDll : installer::kChromeDll; 51 #endif 52 dir->append(dll_name); 53 54 #if !defined(WIN_DISABLE_PREREAD) 55 // On Win7 with Syzygy, pre-read is a win. There've very little difference 56 // between 25% and 100%. For cold starts, with or without prefetch 25% 57 // performs slightly better than 100%. On XP, pre-read is generally a 58 // performance loss. 59 if (!cmd_line.HasSwitch(switches::kProcessType)) { 60 const size_t kStepSize = 1024 * 1024; 61 uint8 percent = base::win::GetVersion() > base::win::VERSION_XP ? 25 : 0; 62 ImagePreReader::PartialPreReadImage(dir->c_str(), percent, kStepSize); 63 } 64 #endif 65 66 return ::LoadLibraryExW(dir->c_str(), NULL, 67 LOAD_WITH_ALTERED_SEARCH_PATH); 68 } 69 70 void RecordDidRun(const string16& dll_path) { 71 bool system_level = !InstallUtil::IsPerUserInstall(dll_path.c_str()); 72 GoogleUpdateSettings::UpdateDidRunState(true, system_level); 73 } 74 75 void ClearDidRun(const string16& dll_path) { 76 bool system_level = !InstallUtil::IsPerUserInstall(dll_path.c_str()); 77 GoogleUpdateSettings::UpdateDidRunState(false, system_level); 78 } 79 80 } // namespace 81 82 string16 GetExecutablePath() { 83 wchar_t path[MAX_PATH]; 84 ::GetModuleFileNameW(NULL, path, MAX_PATH); 85 if (!::PathRemoveFileSpecW(path)) 86 return string16(); 87 string16 exe_path(path); 88 return exe_path.append(1, L'\\'); 89 } 90 91 string16 GetCurrentModuleVersion() { 92 scoped_ptr<FileVersionInfo> file_version_info( 93 FileVersionInfo::CreateFileVersionInfoForCurrentModule()); 94 if (file_version_info.get()) { 95 string16 version_string(file_version_info->file_version()); 96 if (Version(WideToASCII(version_string)).IsValid()) 97 return version_string; 98 } 99 return string16(); 100 } 101 102 //============================================================================= 103 104 MainDllLoader::MainDllLoader() : dll_(NULL) { 105 } 106 107 MainDllLoader::~MainDllLoader() { 108 } 109 110 // Loading chrome is an interesting affair. First we try loading from the 111 // current directory to support run-what-you-compile and other development 112 // scenarios. 113 // If that fails then we look at the --chrome-version command line flag to 114 // determine if we should stick with an older dll version even if a new one is 115 // available to support upgrade-in-place scenarios. 116 // If that fails then finally we look at the version resource in the current 117 // module. This is the expected path for chrome.exe browser instances in an 118 // installed build. 119 HMODULE MainDllLoader::Load(string16* out_version, string16* out_file) { 120 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); 121 const string16 dir(GetExecutablePath()); 122 *out_file = dir; 123 HMODULE dll = LoadChromeWithDirectory(out_file); 124 if (!dll) { 125 // Loading from same directory (for developers) failed. 126 string16 version_string; 127 if (cmd_line.HasSwitch(switches::kChromeVersion)) { 128 // This is used to support Chrome Frame, see http://crbug.com/88589. 129 version_string = cmd_line.GetSwitchValueNative(switches::kChromeVersion); 130 131 if (!Version(WideToASCII(version_string)).IsValid()) { 132 // If a bogus command line flag was given, then abort. 133 LOG(ERROR) << "Invalid command line version: " << version_string; 134 return NULL; 135 } 136 } 137 138 // If no version on the command line, then look at the version resource in 139 // the current module and try loading that. 140 if (version_string.empty()) 141 version_string = GetCurrentModuleVersion(); 142 143 if (version_string.empty()) { 144 LOG(ERROR) << "No valid Chrome version found"; 145 return NULL; 146 } 147 148 *out_file = dir; 149 *out_version = version_string; 150 out_file->append(*out_version).append(1, L'\\'); 151 dll = LoadChromeWithDirectory(out_file); 152 if (!dll) { 153 PLOG(ERROR) << "Failed to load Chrome DLL from " << *out_file; 154 return NULL; 155 } 156 } 157 158 DCHECK(dll); 159 160 return dll; 161 } 162 163 // Launching is a matter of loading the right dll, setting the CHROME_VERSION 164 // environment variable and just calling the entry point. Derived classes can 165 // add custom code in the OnBeforeLaunch callback. 166 int MainDllLoader::Launch(HINSTANCE instance, 167 sandbox::SandboxInterfaceInfo* sbox_info) { 168 string16 version; 169 string16 file; 170 dll_ = Load(&version, &file); 171 if (!dll_) 172 return chrome::RESULT_CODE_MISSING_DATA; 173 174 scoped_ptr<base::Environment> env(base::Environment::Create()); 175 env->SetVar(chrome::kChromeVersionEnvVar, WideToUTF8(version)); 176 // TODO(erikwright): Remove this when http://crbug.com/174953 is fixed and 177 // widely deployed. 178 env->UnSetVar(env_vars::kGoogleUpdateIsMachineEnvVar); 179 180 InitCrashReporter(); 181 OnBeforeLaunch(file); 182 183 DLL_MAIN entry_point = 184 reinterpret_cast<DLL_MAIN>(::GetProcAddress(dll_, "ChromeMain")); 185 if (!entry_point) 186 return chrome::RESULT_CODE_BAD_PROCESS_TYPE; 187 188 int rc = entry_point(instance, sbox_info); 189 return OnBeforeExit(rc, file); 190 } 191 192 void MainDllLoader::RelaunchChromeBrowserWithNewCommandLineIfNeeded() { 193 RelaunchChromeBrowserWithNewCommandLineIfNeededFunc relaunch_function = 194 reinterpret_cast<RelaunchChromeBrowserWithNewCommandLineIfNeededFunc>( 195 ::GetProcAddress(dll_, 196 "RelaunchChromeBrowserWithNewCommandLineIfNeeded")); 197 if (!relaunch_function) { 198 LOG(ERROR) << "Could not find exported function " 199 << "RelaunchChromeBrowserWithNewCommandLineIfNeeded"; 200 } else { 201 relaunch_function(); 202 } 203 } 204 205 //============================================================================= 206 207 class ChromeDllLoader : public MainDllLoader { 208 public: 209 virtual string16 GetRegistryPath() { 210 string16 key(google_update::kRegPathClients); 211 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 212 key.append(L"\\").append(dist->GetAppGuid()); 213 return key; 214 } 215 216 virtual void OnBeforeLaunch(const string16& dll_path) { 217 RecordDidRun(dll_path); 218 } 219 220 virtual int OnBeforeExit(int return_code, const string16& dll_path) { 221 // NORMAL_EXIT_CANCEL is used for experiments when the user cancels 222 // so we need to reset the did_run signal so omaha does not count 223 // this run as active usage. 224 if (chrome::RESULT_CODE_NORMAL_EXIT_CANCEL == return_code) { 225 ClearDidRun(dll_path); 226 } 227 return return_code; 228 } 229 }; 230 231 //============================================================================= 232 233 class ChromiumDllLoader : public MainDllLoader { 234 public: 235 virtual string16 GetRegistryPath() { 236 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 237 return dist->GetVersionKey(); 238 } 239 }; 240 241 MainDllLoader* MakeMainDllLoader() { 242 #if defined(GOOGLE_CHROME_BUILD) 243 return new ChromeDllLoader(); 244 #else 245 return new ChromiumDllLoader(); 246 #endif 247 } 248