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