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/rand_util.h" // For PreRead experiment. 15 #include "base/sha1.h" // For PreRead experiment. 16 #include "base/strings/string16.h" 17 #include "base/strings/string_util.h" 18 #include "base/strings/stringprintf.h" 19 #include "base/strings/utf_string_conversions.h" 20 #include "base/version.h" 21 #include "base/win/windows_version.h" 22 #include "chrome/app/client_util.h" 23 #include "chrome/app/image_pre_reader_win.h" 24 #include "chrome/common/chrome_constants.h" 25 #include "chrome/common/chrome_result_codes.h" 26 #include "chrome/common/chrome_switches.h" 27 #include "chrome/common/env_vars.h" 28 #include "chrome/installer/util/browser_distribution.h" 29 #include "chrome/installer/util/channel_info.h" 30 #include "chrome/installer/util/google_update_constants.h" 31 #include "chrome/installer/util/google_update_settings.h" 32 #include "chrome/installer/util/install_util.h" 33 #include "chrome/installer/util/util_constants.h" 34 #include "components/breakpad/app/breakpad_win.h" 35 36 namespace { 37 // The entry point signature of chrome.dll. 38 typedef int (*DLL_MAIN)(HINSTANCE, sandbox::SandboxInterfaceInfo*); 39 40 typedef void (*RelaunchChromeBrowserWithNewCommandLineIfNeededFunc)(); 41 42 // Returns true if the build date for this module precedes the expiry date 43 // for the pre-read experiment. 44 bool PreReadExperimentIsActive() { 45 const int kPreReadExpiryYear = 2014; 46 const int kPreReadExpiryMonth = 7; 47 const int kPreReadExpiryDay = 1; 48 const char kBuildTimeStr[] = __DATE__ " " __TIME__; 49 50 // Get the timestamp of the build. 51 base::Time build_time; 52 bool result = base::Time::FromString(kBuildTimeStr, &build_time); 53 DCHECK(result); 54 55 // Get the timestamp at which the experiment expires. 56 base::Time::Exploded exploded = {0}; 57 exploded.year = kPreReadExpiryYear; 58 exploded.month = kPreReadExpiryMonth; 59 exploded.day_of_month = kPreReadExpiryDay; 60 base::Time expiration_time = base::Time::FromLocalExploded(exploded); 61 62 // Return true if the build time predates the expiration time.. 63 return build_time < expiration_time; 64 } 65 66 // Get random unit values, i.e., in the range (0, 1), denoting a die-toss for 67 // being in an experiment population and experimental group thereof. 68 void GetPreReadPopulationAndGroup(double* population, double* group) { 69 // By default we use the metrics id for the user as stable pseudo-random 70 // input to a hash. 71 std::string metrics_id; 72 GoogleUpdateSettings::GetMetricsId(&metrics_id); 73 74 // If this user has not metrics id, we fall back to a purely random value 75 // per browser session. 76 const size_t kLength = 16; 77 std::string random_value(metrics_id.empty() ? base::RandBytesAsString(kLength) 78 : metrics_id); 79 80 // To interpret the value as a random number we hash it and read the first 8 81 // bytes of the hash as a unit-interval representing a die-toss for being in 82 // the experiment population and the second 8 bytes as a die-toss for being 83 // in various experiment groups. 84 unsigned char sha1_hash[base::kSHA1Length]; 85 base::SHA1HashBytes( 86 reinterpret_cast<const unsigned char*>(random_value.c_str()), 87 random_value.size() * sizeof(random_value[0]), 88 sha1_hash); 89 COMPILE_ASSERT(2 * sizeof(uint64) < sizeof(sha1_hash), need_more_data); 90 const uint64* random_bits = reinterpret_cast<uint64*>(&sha1_hash[0]); 91 92 // Convert the bits into unit-intervals and return. 93 *population = base::BitsToOpenEndedUnitInterval(random_bits[0]); 94 *group = base::BitsToOpenEndedUnitInterval(random_bits[1]); 95 } 96 97 // Gets the amount of pre-read to use as well as the experiment group in which 98 // the user falls. 99 size_t InitPreReadPercentage() { 100 // By default use the old behaviour: read 100%. 101 const int kDefaultPercentage = 100; 102 const char kDefaultFormatStr[] = "%d-pct-default"; 103 const char kControlFormatStr[] = "%d-pct-control"; 104 const char kGroupFormatStr[] = "%d-pct"; 105 106 COMPILE_ASSERT(kDefaultPercentage <= 100, default_percentage_too_large); 107 COMPILE_ASSERT(kDefaultPercentage % 5 == 0, default_percentage_not_mult_5); 108 109 // Roll the dice to determine if this user is in the experiment and if so, 110 // in which experimental group. 111 double population = 0.0; 112 double group = 0.0; 113 GetPreReadPopulationAndGroup(&population, &group); 114 115 // We limit experiment populations to 1% of the Stable and 10% of each of 116 // the other channels. 117 const string16 channel(GoogleUpdateSettings::GetChromeChannel( 118 GoogleUpdateSettings::IsSystemInstall())); 119 double threshold = (channel == installer::kChromeChannelStable) ? 0.01 : 0.10; 120 121 // If the experiment has expired use the default pre-read level. Otherwise, 122 // those not in the experiment population also use the default pre-read level. 123 size_t value = kDefaultPercentage; 124 const char* format_str = kDefaultFormatStr; 125 if (PreReadExperimentIsActive() && (population <= threshold)) { 126 // We divide the experiment population into groups pre-reading at 5 percent 127 // increments in the range [0, 100]. 128 value = static_cast<size_t>(group * 21.0) * 5; 129 DCHECK_LE(value, 100u); 130 DCHECK_EQ(0u, value % 5); 131 format_str = 132 (value == kDefaultPercentage) ? kControlFormatStr : kGroupFormatStr; 133 } 134 135 // Generate the group name corresponding to this percentage value. 136 std::string group_name; 137 base::SStringPrintf(&group_name, format_str, value); 138 139 // Persist the group name to the environment so that it can be used for 140 // reporting. 141 scoped_ptr<base::Environment> env(base::Environment::Create()); 142 env->SetVar(chrome::kPreReadEnvironmentVariable, group_name); 143 144 // Return the percentage value to be used. 145 return value; 146 } 147 148 // Expects that |dir| has a trailing backslash. |dir| is modified so it 149 // contains the full path that was tried. Caller must check for the return 150 // value not being null to determine if this path contains a valid dll. 151 HMODULE LoadChromeWithDirectory(string16* dir) { 152 ::SetCurrentDirectoryW(dir->c_str()); 153 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); 154 #if !defined(CHROME_MULTIPLE_DLL) 155 const wchar_t* dll_name = installer::kChromeDll; 156 #else 157 const wchar_t* dll_name = 158 cmd_line.HasSwitch(switches::kProcessType) && 159 cmd_line.GetSwitchValueASCII(switches::kProcessType) != "service" 160 ? installer::kChromeChildDll 161 : installer::kChromeDll; 162 #endif 163 dir->append(dll_name); 164 165 #if !defined(WIN_DISABLE_PREREAD) 166 // We pre-read the binary to warm the memory caches (fewer hard faults to 167 // page parts of the binary in). 168 if (!cmd_line.HasSwitch(switches::kProcessType)) { 169 const size_t kStepSize = 1024 * 1024; 170 size_t percentage = InitPreReadPercentage(); 171 ImagePreReader::PartialPreReadImage(dir->c_str(), percentage, kStepSize); 172 } 173 #endif 174 175 return ::LoadLibraryExW(dir->c_str(), NULL, 176 LOAD_WITH_ALTERED_SEARCH_PATH); 177 } 178 179 void RecordDidRun(const string16& dll_path) { 180 bool system_level = !InstallUtil::IsPerUserInstall(dll_path.c_str()); 181 GoogleUpdateSettings::UpdateDidRunState(true, system_level); 182 } 183 184 void ClearDidRun(const string16& dll_path) { 185 bool system_level = !InstallUtil::IsPerUserInstall(dll_path.c_str()); 186 GoogleUpdateSettings::UpdateDidRunState(false, system_level); 187 } 188 189 } // namespace 190 191 string16 GetExecutablePath() { 192 wchar_t path[MAX_PATH]; 193 ::GetModuleFileNameW(NULL, path, MAX_PATH); 194 if (!::PathRemoveFileSpecW(path)) 195 return string16(); 196 string16 exe_path(path); 197 return exe_path.append(1, L'\\'); 198 } 199 200 string16 GetCurrentModuleVersion() { 201 scoped_ptr<FileVersionInfo> file_version_info( 202 FileVersionInfo::CreateFileVersionInfoForCurrentModule()); 203 if (file_version_info.get()) { 204 string16 version_string(file_version_info->file_version()); 205 if (Version(WideToASCII(version_string)).IsValid()) 206 return version_string; 207 } 208 return string16(); 209 } 210 211 //============================================================================= 212 213 MainDllLoader::MainDllLoader() : dll_(NULL) { 214 } 215 216 MainDllLoader::~MainDllLoader() { 217 } 218 219 // Loading chrome is an interesting affair. First we try loading from the 220 // current directory to support run-what-you-compile and other development 221 // scenarios. 222 // If that fails then we look at the --chrome-version command line flag to 223 // determine if we should stick with an older dll version even if a new one is 224 // available to support upgrade-in-place scenarios. 225 // If that fails then finally we look at the version resource in the current 226 // module. This is the expected path for chrome.exe browser instances in an 227 // installed build. 228 HMODULE MainDllLoader::Load(string16* out_version, string16* out_file) { 229 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); 230 const string16 dir(GetExecutablePath()); 231 *out_file = dir; 232 HMODULE dll = LoadChromeWithDirectory(out_file); 233 if (!dll) { 234 // Loading from same directory (for developers) failed. 235 string16 version_string; 236 if (cmd_line.HasSwitch(switches::kChromeVersion)) { 237 // This is used to support Chrome Frame, see http://crbug.com/88589. 238 version_string = cmd_line.GetSwitchValueNative(switches::kChromeVersion); 239 240 if (!Version(WideToASCII(version_string)).IsValid()) { 241 // If a bogus command line flag was given, then abort. 242 LOG(ERROR) << "Invalid command line version: " << version_string; 243 return NULL; 244 } 245 } 246 247 // If no version on the command line, then look at the version resource in 248 // the current module and try loading that. 249 if (version_string.empty()) 250 version_string = GetCurrentModuleVersion(); 251 252 if (version_string.empty()) { 253 LOG(ERROR) << "No valid Chrome version found"; 254 return NULL; 255 } 256 257 *out_file = dir; 258 *out_version = version_string; 259 out_file->append(*out_version).append(1, L'\\'); 260 dll = LoadChromeWithDirectory(out_file); 261 if (!dll) { 262 PLOG(ERROR) << "Failed to load Chrome DLL from " << *out_file; 263 return NULL; 264 } 265 } 266 267 DCHECK(dll); 268 269 return dll; 270 } 271 272 // Launching is a matter of loading the right dll, setting the CHROME_VERSION 273 // environment variable and just calling the entry point. Derived classes can 274 // add custom code in the OnBeforeLaunch callback. 275 int MainDllLoader::Launch(HINSTANCE instance, 276 sandbox::SandboxInterfaceInfo* sbox_info) { 277 string16 version; 278 string16 file; 279 dll_ = Load(&version, &file); 280 if (!dll_) 281 return chrome::RESULT_CODE_MISSING_DATA; 282 283 scoped_ptr<base::Environment> env(base::Environment::Create()); 284 env->SetVar(chrome::kChromeVersionEnvVar, WideToUTF8(version)); 285 // TODO(erikwright): Remove this when http://crbug.com/174953 is fixed and 286 // widely deployed. 287 env->UnSetVar(env_vars::kGoogleUpdateIsMachineEnvVar); 288 289 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess(); 290 std::string process_type = 291 cmd_line.GetSwitchValueASCII(switches::kProcessType); 292 breakpad::InitCrashReporter(process_type); 293 OnBeforeLaunch(file); 294 295 DLL_MAIN entry_point = 296 reinterpret_cast<DLL_MAIN>(::GetProcAddress(dll_, "ChromeMain")); 297 if (!entry_point) 298 return chrome::RESULT_CODE_BAD_PROCESS_TYPE; 299 300 int rc = entry_point(instance, sbox_info); 301 return OnBeforeExit(rc, file); 302 } 303 304 void MainDllLoader::RelaunchChromeBrowserWithNewCommandLineIfNeeded() { 305 RelaunchChromeBrowserWithNewCommandLineIfNeededFunc relaunch_function = 306 reinterpret_cast<RelaunchChromeBrowserWithNewCommandLineIfNeededFunc>( 307 ::GetProcAddress(dll_, 308 "RelaunchChromeBrowserWithNewCommandLineIfNeeded")); 309 if (!relaunch_function) { 310 LOG(ERROR) << "Could not find exported function " 311 << "RelaunchChromeBrowserWithNewCommandLineIfNeeded"; 312 } else { 313 relaunch_function(); 314 } 315 } 316 317 //============================================================================= 318 319 class ChromeDllLoader : public MainDllLoader { 320 public: 321 virtual string16 GetRegistryPath() { 322 string16 key(google_update::kRegPathClients); 323 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 324 key.append(L"\\").append(dist->GetAppGuid()); 325 return key; 326 } 327 328 virtual void OnBeforeLaunch(const string16& dll_path) { 329 RecordDidRun(dll_path); 330 } 331 332 virtual int OnBeforeExit(int return_code, const string16& dll_path) { 333 // NORMAL_EXIT_CANCEL is used for experiments when the user cancels 334 // so we need to reset the did_run signal so omaha does not count 335 // this run as active usage. 336 if (chrome::RESULT_CODE_NORMAL_EXIT_CANCEL == return_code) { 337 ClearDidRun(dll_path); 338 } 339 return return_code; 340 } 341 }; 342 343 //============================================================================= 344 345 class ChromiumDllLoader : public MainDllLoader { 346 public: 347 virtual string16 GetRegistryPath() { 348 BrowserDistribution* dist = BrowserDistribution::GetDistribution(); 349 return dist->GetVersionKey(); 350 } 351 }; 352 353 MainDllLoader* MakeMainDllLoader() { 354 #if defined(GOOGLE_CHROME_BUILD) 355 return new ChromeDllLoader(); 356 #else 357 return new ChromiumDllLoader(); 358 #endif 359 } 360