OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "chrome_frame/ready_mode/internal/registry_ready_mode_state.h" | |
6 | |
7 #include <windows.h> | |
8 | |
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" | |
24 | |
25 namespace { | |
26 | |
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()); | |
33 | |
34 HKEY roots[] = {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE}; | |
35 | |
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 base::win::ScopedHandle launched_process; | |
44 base::LaunchOptions options; | |
45 options.start_hidden = true; | |
46 if (base::LaunchProcess(command_line, options, &launched_process)) { | |
47 return launched_process.Take(); | |
48 } | |
49 } | |
50 } | |
51 } | |
52 return NULL; | |
53 } | |
54 | |
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 | |
57 // upon failure. | |
58 HANDLE LaunchCommandViaProcessLauncher(const std::wstring& command_field) { | |
59 HANDLE launched_process = NULL; | |
60 | |
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); | |
67 } | |
68 | |
69 return launched_process; | |
70 } | |
71 | |
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 | |
77 // asynchronously. | |
78 DWORD wait_result = WaitForSingleObject(handle, 5000); // (ms) | |
79 | |
80 if (wait_result == WAIT_OBJECT_0) { | |
81 DWORD exit_code = 0; | |
82 if (!::GetExitCodeProcess(handle, &exit_code)) { | |
83 DPLOG(ERROR) << "GetExitCodeProcess failed."; | |
84 return false; | |
85 } | |
86 | |
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 << "."; | |
91 return false; | |
92 } | |
93 | |
94 return true; | |
95 } | |
96 | |
97 if (wait_result == WAIT_FAILED) | |
98 DPLOG(ERROR) << "Error while waiting for elevated child process."; | |
99 | |
100 if (wait_result == WAIT_ABANDONED) | |
101 DLOG(ERROR) << "Unexpeced WAIT_ABANDONED while waiting on child process."; | |
102 | |
103 if (wait_result == WAIT_TIMEOUT) | |
104 DLOG(ERROR) << "Timeout while waiting on child process."; | |
105 | |
106 return false; | |
107 } | |
108 | |
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 | |
111 // ProcessLauncher. | |
112 bool CanLaunchDirectly() { | |
113 if (base::win::GetVersion() < base::win::VERSION_VISTA) | |
114 return true; | |
115 | |
116 base::IntegrityLevel integrity_level = base::INTEGRITY_UNKNOWN; | |
117 if (!base::GetProcessIntegrityLevel(base::GetCurrentProcessHandle(), | |
118 &integrity_level)) { | |
119 DLOG(ERROR) << "Failed to determine process integrity level."; | |
120 return false; | |
121 } | |
122 | |
123 return integrity_level == base::HIGH_INTEGRITY; | |
124 } | |
125 | |
126 // Attempts to launch the specified command either directly or via the | |
127 // ProcessLauncher. Returns true if the command is launched and returns a | |
128 // success code. | |
129 bool LaunchAndCheckCommand(const std::wstring& command_field) { | |
130 base::win::ScopedHandle handle; | |
131 | |
132 if (CanLaunchDirectly()) | |
133 handle.Set(LaunchCommandDirectly(command_field)); | |
134 else | |
135 handle.Set(LaunchCommandViaProcessLauncher(command_field)); | |
136 | |
137 if (handle.IsValid() && CheckProcessExitCode(handle)) | |
138 return true; | |
139 | |
140 DLOG(ERROR) << "Command " << command_field << " could not be launched."; | |
141 return false; | |
142 } | |
143 | |
144 } // namespace | |
145 | |
146 RegistryReadyModeState::RegistryReadyModeState( | |
147 const std::wstring& key_name, base::TimeDelta temporary_decline_duration, | |
148 Observer* observer) | |
149 : key_name_(key_name), | |
150 temporary_decline_duration_(temporary_decline_duration), | |
151 observer_(observer) { | |
152 } | |
153 | |
154 RegistryReadyModeState::~RegistryReadyModeState() { | |
155 } | |
156 | |
157 base::Time RegistryReadyModeState::GetNow() { | |
158 return base::Time::Now(); | |
159 } | |
160 | |
161 ReadyModeStatus RegistryReadyModeState::GetStatus() { | |
162 bool exists = false; | |
163 int64 value = 0; | |
164 | |
165 if (!GetStateFromRegistry(&value, &exists)) | |
166 return READY_MODE_ACTIVE; | |
167 | |
168 if (!exists) | |
169 return READY_MODE_ACCEPTED; | |
170 | |
171 if (value == 0) | |
172 return READY_MODE_PERMANENTLY_DECLINED; | |
173 | |
174 if (value == 1) | |
175 return READY_MODE_ACTIVE; | |
176 | |
177 base::Time when_declined(base::Time::FromInternalValue(value)); | |
178 base::Time now(GetNow()); | |
179 | |
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_; | |
184 | |
185 if (expired) | |
186 return READY_MODE_TEMPORARY_DECLINE_EXPIRED; | |
187 else | |
188 return READY_MODE_TEMPORARILY_DECLINED; | |
189 } | |
190 | |
191 void RegistryReadyModeState::NotifyObserver() { | |
192 if (observer_ != NULL) | |
193 observer_->OnStateChange(GetStatus()); | |
194 } | |
195 | |
196 bool RegistryReadyModeState::GetStateFromRegistry(int64* value, bool* exists) { | |
197 *exists = false; | |
198 *value = 0; | |
199 | |
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, | |
207 value); | |
208 if (result == ERROR_SUCCESS) { | |
209 *exists = true; | |
210 return true; | |
211 } | |
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; | |
216 return false; | |
217 } | |
218 } | |
219 } | |
220 | |
221 return true; | |
222 } | |
223 | |
224 void RegistryReadyModeState::RefreshStateAndNotify() { | |
225 HRESULT hr = UrlMkSetSessionOption(URLMON_OPTION_USERAGENT_REFRESH, | |
226 NULL, 0, 0); | |
227 if (FAILED(hr)) { | |
228 DLOG(ERROR) << "Failed to refresh user agent string from registry. " | |
229 << "UrlMkSetSessionOption returned " | |
230 << base::StringPrintf("0x%08x", hr); | |
231 } else { | |
232 NotifyObserver(); | |
233 } | |
234 } | |
235 | |
236 void RegistryReadyModeState::ExpireTemporaryDecline() { | |
237 if (LaunchAndCheckCommand(google_update::kRegCFEndTempOptOutCmdField)) | |
238 RefreshStateAndNotify(); | |
239 } | |
240 | |
241 void RegistryReadyModeState::TemporarilyDeclineChromeFrame() { | |
242 if (LaunchAndCheckCommand(google_update::kRegCFTempOptOutCmdField)) | |
243 RefreshStateAndNotify(); | |
244 } | |
245 | |
246 void RegistryReadyModeState::PermanentlyDeclineChromeFrame() { | |
247 if (LaunchAndCheckCommand(google_update::kRegCFOptOutCmdField)) | |
248 RefreshStateAndNotify(); | |
249 } | |
250 | |
251 void RegistryReadyModeState::AcceptChromeFrame() { | |
252 if (LaunchAndCheckCommand(google_update::kRegCFOptInCmdField)) | |
253 NotifyObserver(); | |
254 } | |
OLD | NEW |