1 // Copyright (c) 2011 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.
5 #include "chrome_frame/ready_mode/internal/registry_ready_mode_state.h"
9 #include "base/logging.h"
10 #include "base/process/launch.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/time/time.h"
14 #include "base/win/registry.h"
15 #include "base/win/scoped_comptr.h"
16 #include "base/win/scoped_handle.h"
17 #include "base/win/windows_version.h"
18 #include "chrome/installer/util/browser_distribution.h"
19 #include "chrome/installer/util/google_update_constants.h"
20 #include "chrome/installer/util/master_preferences.h"
21 #include "chrome/installer/util/util_constants.h"
22 #include "chrome_frame/chrome_launcher_utils.h"
23 #include "chrome_frame/ready_mode/ready_mode.h"
27 // Looks up a command entry in the registry and attempts to execute it directly.
28 // Returns the new process handle, which the caller is responsible for closing,
29 // or NULL upon failure.
30 HANDLE LaunchCommandDirectly(const std::wstring& command_field) {
31 BrowserDistribution* dist = BrowserDistribution::GetDistribution();
32 std::wstring version_key_name(dist->GetVersionKey());
34 HKEY roots[] = {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
36 for (int i = 0; i < arraysize(roots); i++) {
37 base::win::RegKey version_key;
38 if (version_key.Open(roots[i], version_key_name.c_str(),
39 KEY_QUERY_VALUE) == ERROR_SUCCESS) {
40 std::wstring command_line;
41 if (version_key.ReadValue(command_field.c_str(),
42 &command_line) == ERROR_SUCCESS) {
43 HANDLE launched_process = NULL;
44 base::LaunchOptions options;
45 options.start_hidden = true;
46 if (base::LaunchProcess(command_line, options, &launched_process)) {
47 return launched_process;
55 // Attempts to launch a command using the ProcessLauncher. Returns a handle to
56 // the launched process, which the caller is responsible for closing, or NULL
58 HANDLE LaunchCommandViaProcessLauncher(const std::wstring& command_field) {
59 HANDLE launched_process = NULL;
61 scoped_ptr<CommandLine> command_line;
62 if (chrome_launcher::CreateUpdateCommandLine(command_field, &command_line)) {
63 DCHECK(command_line != NULL);
64 base::LaunchOptions options;
65 options.start_hidden = true;
66 base::LaunchProcess(*command_line, options, &launched_process);
69 return launched_process;
72 // Waits for the provided process to exit, and verifies that its exit code
73 // corresponds to one of the known "success" codes for the installer. If the
74 // exit code cannot be retrieved, or if it signals failure, returns false.
75 bool CheckProcessExitCode(HANDLE handle) {
76 // TODO(erikwright): Use RegisterWaitForSingleObject to wait
78 DWORD wait_result = WaitForSingleObject(handle, 5000); // (ms)
80 if (wait_result == WAIT_OBJECT_0) {
82 if (!::GetExitCodeProcess(handle, &exit_code)) {
83 DPLOG(ERROR) << "GetExitCodeProcess failed.";
87 // These are the only two success codes returned by the installer.
88 // All other codes are errors.
89 if (exit_code != 0 && exit_code != installer::UNINSTALL_REQUIRES_REBOOT) {
90 DLOG(ERROR) << "Process failed with exit code " << exit_code << ".";
97 if (wait_result == WAIT_FAILED)
98 DPLOG(ERROR) << "Error while waiting for elevated child process.";
100 if (wait_result == WAIT_ABANDONED)
101 DLOG(ERROR) << "Unexpeced WAIT_ABANDONED while waiting on child process.";
103 if (wait_result == WAIT_TIMEOUT)
104 DLOG(ERROR) << "Timeout while waiting on child process.";
109 // If we are running on XP (no protected mode) or in a high-integrity process,
110 // we can invoke the installer directly. If not, we will have to go via the
112 bool CanLaunchDirectly() {
113 if (base::win::GetVersion() < base::win::VERSION_VISTA)
116 base::IntegrityLevel integrity_level = base::INTEGRITY_UNKNOWN;
117 if (!base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(),
119 DLOG(ERROR) << "Failed to determine process integrity level.";
123 return integrity_level == base::HIGH_INTEGRITY;
126 // Attempts to launch the specified command either directly or via the
127 // ProcessLauncher. Returns true if the command is launched and returns a
129 bool LaunchAndCheckCommand(const std::wstring& command_field) {
130 base::win::ScopedHandle handle;
132 if (CanLaunchDirectly())
133 handle.Set(LaunchCommandDirectly(command_field));
135 handle.Set(LaunchCommandViaProcessLauncher(command_field));
137 if (handle.IsValid() && CheckProcessExitCode(handle))
140 DLOG(ERROR) << "Command " << command_field << " could not be launched.";
146 RegistryReadyModeState::RegistryReadyModeState(
147 const std::wstring& key_name, base::TimeDelta temporary_decline_duration,
149 : key_name_(key_name),
150 temporary_decline_duration_(temporary_decline_duration),
151 observer_(observer) {
154 RegistryReadyModeState::~RegistryReadyModeState() {
157 base::Time RegistryReadyModeState::GetNow() {
158 return base::Time::Now();
161 ReadyModeStatus RegistryReadyModeState::GetStatus() {
165 if (!GetStateFromRegistry(&value, &exists))
166 return READY_MODE_ACTIVE;
169 return READY_MODE_ACCEPTED;
172 return READY_MODE_PERMANENTLY_DECLINED;
175 return READY_MODE_ACTIVE;
177 base::Time when_declined(base::Time::FromInternalValue(value));
178 base::Time now(GetNow());
180 // If the decline duration has passed, or is further in the future than
181 // the total timeout, consider it expired.
182 bool expired = (now - when_declined) > temporary_decline_duration_ ||
183 (when_declined - now) > temporary_decline_duration_;
186 return READY_MODE_TEMPORARY_DECLINE_EXPIRED;
188 return READY_MODE_TEMPORARILY_DECLINED;
191 void RegistryReadyModeState::NotifyObserver() {
192 if (observer_ != NULL)
193 observer_->OnStateChange(GetStatus());
196 bool RegistryReadyModeState::GetStateFromRegistry(int64* value, bool* exists) {
200 HKEY roots[] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE };
201 LONG result = ERROR_SUCCESS;
202 for (int i = 0; i < arraysize(roots); i++) {
203 base::win::RegKey config_key;
204 result = config_key.Open(roots[i], key_name_.c_str(), KEY_QUERY_VALUE);
205 if (result == ERROR_SUCCESS) {
206 result = config_key.ReadInt64(installer::kChromeFrameReadyModeField,
208 if (result == ERROR_SUCCESS) {
212 if (result != ERROR_FILE_NOT_FOUND) {
213 DLOG(ERROR) << "Failed to read from registry key " << key_name_
214 << " and value " << installer::kChromeFrameReadyModeField
215 << " error: " << result;
224 void RegistryReadyModeState::RefreshStateAndNotify() {
225 HRESULT hr = UrlMkSetSessionOption(URLMON_OPTION_USERAGENT_REFRESH,
228 DLOG(ERROR) << "Failed to refresh user agent string from registry. "
229 << "UrlMkSetSessionOption returned "
230 << base::StringPrintf("0x%08x", hr);
236 void RegistryReadyModeState::ExpireTemporaryDecline() {
237 if (LaunchAndCheckCommand(google_update::kRegCFEndTempOptOutCmdField))
238 RefreshStateAndNotify();
241 void RegistryReadyModeState::TemporarilyDeclineChromeFrame() {
242 if (LaunchAndCheckCommand(google_update::kRegCFTempOptOutCmdField))
243 RefreshStateAndNotify();
246 void RegistryReadyModeState::PermanentlyDeclineChromeFrame() {
247 if (LaunchAndCheckCommand(google_update::kRegCFOptOutCmdField))
248 RefreshStateAndNotify();
251 void RegistryReadyModeState::AcceptChromeFrame() {
252 if (LaunchAndCheckCommand(google_update::kRegCFOptInCmdField))