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.
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"
37 // The entry point signature of chrome.dll.
38 typedef int (*DLL_MAIN
)(HINSTANCE
, sandbox::SandboxInterfaceInfo
*);
40 typedef void (*RelaunchChromeBrowserWithNewCommandLineIfNeededFunc
)();
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__
;
50 // Get the timestamp of the build.
51 base::Time build_time
;
52 bool result
= base::Time::FromString(kBuildTimeStr
, &build_time
);
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
);
62 // Return true if the build time predates the expiration time..
63 return build_time
< expiration_time
;
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
71 std::string metrics_id
;
72 GoogleUpdateSettings::GetMetricsId(&metrics_id
);
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
)
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
];
86 reinterpret_cast<const unsigned char*>(random_value
.c_str()),
87 random_value
.size() * sizeof(random_value
[0]),
89 COMPILE_ASSERT(2 * sizeof(uint64
) < sizeof(sha1_hash
), need_more_data
);
90 const uint64
* random_bits
= reinterpret_cast<uint64
*>(&sha1_hash
[0]);
92 // Convert the bits into unit-intervals and return.
93 *population
= base::BitsToOpenEndedUnitInterval(random_bits
[0]);
94 *group
= base::BitsToOpenEndedUnitInterval(random_bits
[1]);
97 // Gets the amount of pre-read to use as well as the experiment group in which
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";
106 COMPILE_ASSERT(kDefaultPercentage
<= 100, default_percentage_too_large
);
107 COMPILE_ASSERT(kDefaultPercentage
% 5 == 0, default_percentage_not_mult_5
);
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;
113 GetPreReadPopulationAndGroup(&population
, &group
);
115 // We limit experiment populations to 1% of the Stable and 10% of each of
116 // the other channels.
117 const base::string16
channel(GoogleUpdateSettings::GetChromeChannel(
118 GoogleUpdateSettings::IsSystemInstall()));
119 double threshold
= (channel
== installer::kChromeChannelStable
) ? 0.01 : 0.10;
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);
132 (value
== kDefaultPercentage
) ? kControlFormatStr
: kGroupFormatStr
;
135 // Generate the group name corresponding to this percentage value.
136 std::string group_name
;
137 base::SStringPrintf(&group_name
, format_str
, value
);
139 // Persist the group name to the environment so that it can be used for
141 scoped_ptr
<base::Environment
> env(base::Environment::Create());
142 env
->SetVar(chrome::kPreReadEnvironmentVariable
, group_name
);
144 // Return the percentage value to be used.
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(base::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
;
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
;
163 dir
->append(dll_name
);
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
);
175 return ::LoadLibraryExW(dir
->c_str(), NULL
,
176 LOAD_WITH_ALTERED_SEARCH_PATH
);
179 void RecordDidRun(const base::string16
& dll_path
) {
180 bool system_level
= !InstallUtil::IsPerUserInstall(dll_path
.c_str());
181 GoogleUpdateSettings::UpdateDidRunState(true, system_level
);
184 void ClearDidRun(const base::string16
& dll_path
) {
185 bool system_level
= !InstallUtil::IsPerUserInstall(dll_path
.c_str());
186 GoogleUpdateSettings::UpdateDidRunState(false, system_level
);
191 base::string16
GetExecutablePath() {
192 wchar_t path
[MAX_PATH
];
193 ::GetModuleFileNameW(NULL
, path
, MAX_PATH
);
194 if (!::PathRemoveFileSpecW(path
))
195 return base::string16();
196 base::string16
exe_path(path
);
197 return exe_path
.append(1, L
'\\');
200 base::string16
GetCurrentModuleVersion() {
201 scoped_ptr
<FileVersionInfo
> file_version_info(
202 FileVersionInfo::CreateFileVersionInfoForCurrentModule());
203 if (file_version_info
.get()) {
204 base::string16
version_string(file_version_info
->file_version());
205 if (Version(WideToASCII(version_string
)).IsValid())
206 return version_string
;
208 return base::string16();
211 //=============================================================================
213 MainDllLoader::MainDllLoader() : dll_(NULL
) {
216 MainDllLoader::~MainDllLoader() {
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
222 // If that fails then we look at the version resource in the current
223 // module. This is the expected path for chrome.exe browser instances in an
225 HMODULE
MainDllLoader::Load(base::string16
* out_version
,
226 base::string16
* out_file
) {
227 const CommandLine
& cmd_line
= *CommandLine::ForCurrentProcess();
228 const base::string16
dir(GetExecutablePath());
230 HMODULE dll
= LoadChromeWithDirectory(out_file
);
232 // Loading from same directory (for developers) failed. Look at the version
233 // resource in the current module and try loading that.
234 base::string16
version_string(GetCurrentModuleVersion());
235 if (version_string
.empty()) {
236 LOG(ERROR
) << "No valid Chrome version found";
241 *out_version
= version_string
;
242 out_file
->append(*out_version
).append(1, L
'\\');
243 dll
= LoadChromeWithDirectory(out_file
);
245 PLOG(ERROR
) << "Failed to load Chrome DLL from " << *out_file
;
255 // Launching is a matter of loading the right dll, setting the CHROME_VERSION
256 // environment variable and just calling the entry point. Derived classes can
257 // add custom code in the OnBeforeLaunch callback.
258 int MainDllLoader::Launch(HINSTANCE instance
,
259 sandbox::SandboxInterfaceInfo
* sbox_info
) {
260 base::string16 version
;
262 dll_
= Load(&version
, &file
);
264 return chrome::RESULT_CODE_MISSING_DATA
;
266 scoped_ptr
<base::Environment
> env(base::Environment::Create());
267 env
->SetVar(chrome::kChromeVersionEnvVar
, base::WideToUTF8(version
));
268 // TODO(erikwright): Remove this when http://crbug.com/174953 is fixed and
270 env
->UnSetVar(env_vars::kGoogleUpdateIsMachineEnvVar
);
272 const CommandLine
& cmd_line
= *CommandLine::ForCurrentProcess();
273 std::string process_type
=
274 cmd_line
.GetSwitchValueASCII(switches::kProcessType
);
275 breakpad::InitCrashReporter(process_type
);
276 OnBeforeLaunch(file
);
278 DLL_MAIN entry_point
=
279 reinterpret_cast<DLL_MAIN
>(::GetProcAddress(dll_
, "ChromeMain"));
281 return chrome::RESULT_CODE_BAD_PROCESS_TYPE
;
283 int rc
= entry_point(instance
, sbox_info
);
284 return OnBeforeExit(rc
, file
);
287 void MainDllLoader::RelaunchChromeBrowserWithNewCommandLineIfNeeded() {
288 RelaunchChromeBrowserWithNewCommandLineIfNeededFunc relaunch_function
=
289 reinterpret_cast<RelaunchChromeBrowserWithNewCommandLineIfNeededFunc
>(
290 ::GetProcAddress(dll_
,
291 "RelaunchChromeBrowserWithNewCommandLineIfNeeded"));
292 if (!relaunch_function
) {
293 LOG(ERROR
) << "Could not find exported function "
294 << "RelaunchChromeBrowserWithNewCommandLineIfNeeded";
300 //=============================================================================
302 class ChromeDllLoader
: public MainDllLoader
{
304 virtual base::string16
GetRegistryPath() {
305 base::string16
key(google_update::kRegPathClients
);
306 BrowserDistribution
* dist
= BrowserDistribution::GetDistribution();
307 key
.append(L
"\\").append(dist
->GetAppGuid());
311 virtual void OnBeforeLaunch(const base::string16
& dll_path
) {
312 RecordDidRun(dll_path
);
315 virtual int OnBeforeExit(int return_code
, const base::string16
& dll_path
) {
316 // NORMAL_EXIT_CANCEL is used for experiments when the user cancels
317 // so we need to reset the did_run signal so omaha does not count
318 // this run as active usage.
319 if (chrome::RESULT_CODE_NORMAL_EXIT_CANCEL
== return_code
) {
320 ClearDidRun(dll_path
);
326 //=============================================================================
328 class ChromiumDllLoader
: public MainDllLoader
{
330 virtual base::string16
GetRegistryPath() {
331 BrowserDistribution
* dist
= BrowserDistribution::GetDistribution();
332 return dist
->GetVersionKey();
336 MainDllLoader
* MakeMainDllLoader() {
337 #if defined(GOOGLE_CHROME_BUILD)
338 return new ChromeDllLoader();
340 return new ChromiumDllLoader();