OLD | NEW |
| (Empty) |
1 // Copyright 2011 Google Inc. | |
2 // | |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | |
4 // you may not use this file except in compliance with the License. | |
5 // You may obtain a copy of the License at | |
6 // | |
7 // http://www.apache.org/licenses/LICENSE-2.0 | |
8 // | |
9 // Unless required by applicable law or agreed to in writing, software | |
10 // distributed under the License is distributed on an "AS IS" BASIS, | |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 // See the License for the specific language governing permissions and | |
13 // limitations under the License. | |
14 // ======================================================================== | |
15 | |
16 #include "omaha/goopdate/app_command.h" | |
17 | |
18 #include "base/scoped_ptr.h" | |
19 #include "omaha/base/constants.h" | |
20 #include "omaha/base/debug.h" | |
21 #include "omaha/base/error.h" | |
22 #include "omaha/base/exception_barrier.h" | |
23 #include "omaha/base/reg_key.h" | |
24 #include "omaha/base/scoped_ptr_address.h" | |
25 #include "omaha/base/system.h" | |
26 #include "omaha/base/utils.h" | |
27 #include "omaha/common/config_manager.h" | |
28 #include "omaha/common/const_cmd_line.h" | |
29 #include "omaha/common/const_goopdate.h" | |
30 #include "omaha/common/ping.h" | |
31 #include "omaha/common/ping_event.h" | |
32 | |
33 namespace omaha { | |
34 | |
35 namespace { | |
36 | |
37 // Sends a single ping event to the Omaha server, synchronously. | |
38 void SendPing(const CString& app_guid, | |
39 bool is_machine, | |
40 const CString& session_id, | |
41 PingEvent::Types type, | |
42 PingEvent::Results result, | |
43 int error_code, | |
44 int extra_code) { | |
45 PingEventPtr ping_event(new PingEvent(type, result, error_code, extra_code)); | |
46 | |
47 Ping ping(is_machine, session_id, kCmdLineInstallSource_OneClick); | |
48 std::vector<CString> apps; | |
49 apps.push_back(app_guid); | |
50 ping.LoadAppDataFromRegistry(apps); | |
51 ping.BuildAppsPing(ping_event); | |
52 ping.Send(true); // true == is_fire_and_forget | |
53 } | |
54 | |
55 // Waits on a process to exit, sends a ping based on the outcome. | |
56 // This is a COM object so that, during its lifetime, the process will not exit. | |
57 // The instance is AddRef'd in the instantiating thread and Release'd by the | |
58 // thread procedure when all work is completed. | |
59 class ATL_NO_VTABLE CompletePingSender | |
60 : public CComObjectRootEx<CComMultiThreadModel>, | |
61 public IUnknown { | |
62 public: | |
63 // Starts a wait on a process, belonging to the specified app and having the | |
64 // given reporting ID. Will send a ping when the process exits. | |
65 static void Start(const CString& app_guid, | |
66 bool is_machine, | |
67 const CString& session_id, | |
68 int reporting_id, | |
69 HANDLE process); | |
70 | |
71 BEGIN_COM_MAP(CompletePingSender) | |
72 END_COM_MAP() | |
73 | |
74 protected: | |
75 CompletePingSender(); | |
76 virtual ~CompletePingSender(); | |
77 | |
78 private: | |
79 static HRESULT Create(const CString& app_guid, | |
80 bool is_machine, | |
81 const CString& session_id, | |
82 int reporting_id, | |
83 HANDLE process, | |
84 CompletePingSender** sender); | |
85 | |
86 // Sends an EVENT_APP_COMMAND_COMPLETE ping with data from member | |
87 // variables and parameters. | |
88 void SendCompletePing(PingEvent::Results result, int error_code); | |
89 | |
90 // Waits for the process to exit, returning S_OK and the exit_code or the | |
91 // underlying error code if the wait fails. | |
92 HRESULT WaitForProcessExit(DWORD* exit_code); | |
93 | |
94 // Waits until the process exits or timeout occurs, then sends a ping with | |
95 // the result. parameter is the CompletePingSender instance. | |
96 static DWORD WINAPI WaitFunction(void* parameter); | |
97 | |
98 CString app_guid_; | |
99 bool is_machine_; | |
100 CString session_id_; | |
101 int reporting_id_; | |
102 scoped_process process_; | |
103 | |
104 DISALLOW_COPY_AND_ASSIGN(CompletePingSender); | |
105 }; // class CompletePingSender | |
106 | |
107 CompletePingSender::CompletePingSender() { | |
108 } | |
109 | |
110 HRESULT CompletePingSender::Create(const CString& app_guid, | |
111 bool is_machine, | |
112 const CString& session_id, | |
113 int reporting_id, | |
114 HANDLE process, | |
115 CompletePingSender** sender) { | |
116 ASSERT1(process && sender); | |
117 | |
118 scoped_process process_handle(process); | |
119 process = NULL; | |
120 | |
121 typedef CComObject<CompletePingSender> ComObjectCompletePingSender; | |
122 | |
123 scoped_ptr<ComObjectCompletePingSender> new_object; | |
124 HRESULT hr = ComObjectCompletePingSender::CreateInstance(address(new_object)); | |
125 if (FAILED(hr)) { | |
126 return hr; | |
127 } | |
128 | |
129 new_object->app_guid_ = app_guid; | |
130 new_object->is_machine_ = is_machine; | |
131 new_object->session_id_ = session_id; | |
132 new_object->reporting_id_ = reporting_id; | |
133 reset(new_object->process_, release(process_handle)); | |
134 | |
135 new_object->AddRef(); | |
136 *sender = new_object.release(); | |
137 return S_OK; | |
138 } | |
139 | |
140 CompletePingSender::~CompletePingSender() { | |
141 } | |
142 | |
143 void CompletePingSender::Start(const CString& app_guid, | |
144 bool is_machine, | |
145 const CString& session_id, | |
146 int reporting_id, | |
147 HANDLE process) { | |
148 ASSERT1(process); | |
149 | |
150 scoped_process process_handle(process); | |
151 process = NULL; | |
152 | |
153 CComPtr<CompletePingSender> sender; | |
154 HRESULT hr = CompletePingSender::Create(app_guid, | |
155 is_machine, | |
156 session_id, | |
157 reporting_id, | |
158 release(process_handle), | |
159 &sender); | |
160 if (FAILED(hr)) { | |
161 CORE_LOG(LE, (_T("[failed to create CompletePingSender]"), | |
162 _T("[0x%08x]"), hr)); | |
163 return; | |
164 } | |
165 | |
166 void* context = | |
167 reinterpret_cast<void *>(static_cast<CompletePingSender*>(sender)); | |
168 | |
169 scoped_handle thread(::CreateThread(NULL, 0, WaitFunction, context, 0, NULL)); | |
170 | |
171 if (thread) { | |
172 // In case of success, the thread is responsible for calling Release. | |
173 sender.Detach(); | |
174 } else { | |
175 hr = HRESULTFromLastError(); | |
176 CORE_LOG(LE, (_T("[failed to start wait thread for app command ") | |
177 _T("process exit]") _T("[0x%08x]"), hr)); | |
178 sender->SendCompletePing(PingEvent::EVENT_RESULT_ERROR, hr); | |
179 } | |
180 } | |
181 | |
182 void CompletePingSender::SendCompletePing(PingEvent::Results result, | |
183 int error_code) { | |
184 SendPing(app_guid_, | |
185 is_machine_, | |
186 session_id_, | |
187 PingEvent::EVENT_APP_COMMAND_COMPLETE, | |
188 result, | |
189 error_code, | |
190 reporting_id_); | |
191 } | |
192 | |
193 HRESULT CompletePingSender::WaitForProcessExit(DWORD* exit_code) { | |
194 ASSERT1(exit_code); | |
195 if (!exit_code) { | |
196 return E_INVALIDARG; | |
197 } | |
198 | |
199 DWORD wait_result = ::WaitForSingleObject(get(process_), INFINITE); | |
200 | |
201 if (wait_result == WAIT_TIMEOUT) { | |
202 return GOOPDATEINSTALL_E_INSTALLER_TIMED_OUT; | |
203 } else if (wait_result == WAIT_FAILED) { | |
204 return HRESULTFromLastError(); | |
205 } | |
206 | |
207 ASSERT1(wait_result == WAIT_OBJECT_0); | |
208 | |
209 if (wait_result != WAIT_OBJECT_0) { | |
210 return E_UNEXPECTED; | |
211 } | |
212 | |
213 if (!::GetExitCodeProcess(get(process_), exit_code)) { | |
214 return HRESULTFromLastError(); | |
215 } | |
216 | |
217 return S_OK; | |
218 } | |
219 | |
220 DWORD WINAPI CompletePingSender::WaitFunction(void* parameter) { | |
221 scoped_co_init init_com_apt(COINIT_MULTITHREADED); | |
222 HRESULT hr = init_com_apt.hresult(); | |
223 if (FAILED(hr)) { | |
224 CORE_LOG(LE, (_T("[init_com_apt failed][0x%x]"), hr)); | |
225 return 0; | |
226 } | |
227 | |
228 CComPtr<CompletePingSender> instance( | |
229 reinterpret_cast<CompletePingSender*>(parameter)); | |
230 DWORD exit_code = 0; | |
231 hr = instance->WaitForProcessExit(&exit_code); | |
232 | |
233 PingEvent::Results result = PingEvent::EVENT_RESULT_SUCCESS; | |
234 int error_code = 0; | |
235 | |
236 if (FAILED(hr)) { | |
237 result = PingEvent::EVENT_RESULT_ERROR; | |
238 error_code = hr; | |
239 } else { | |
240 switch (exit_code) { | |
241 case ERROR_SUCCESS_REBOOT_REQUIRED: | |
242 result = PingEvent::EVENT_RESULT_SUCCESS_REBOOT; | |
243 break; | |
244 case ERROR_SUCCESS: | |
245 result = PingEvent::EVENT_RESULT_SUCCESS; | |
246 break; | |
247 default: | |
248 result = PingEvent::EVENT_RESULT_INSTALLER_ERROR_OTHER; | |
249 error_code = exit_code; | |
250 break; | |
251 } | |
252 } | |
253 | |
254 instance->SendCompletePing(result, error_code); | |
255 | |
256 return 0; | |
257 } | |
258 | |
259 // Attempts to read the command line from the given registry key and value. | |
260 // Logs a message in case of failure. | |
261 HRESULT ReadCommandLine(const CString& key_name, | |
262 const CString& value_name, | |
263 CString* command_line) { | |
264 HRESULT hr = RegKey::GetValue(key_name, value_name, command_line); | |
265 if (FAILED(hr)) { | |
266 CORE_LOG(LE, (_T("[failed to read command line]") | |
267 _T("[key %s][value %s][0x%08x]"), key_name, value_name, hr)); | |
268 } | |
269 | |
270 return hr; | |
271 } | |
272 | |
273 // Checks if the specified value exists in the registry under the specified key. | |
274 // If so, attempts to read the value's DWORD contents into 'paramter'. Succeeds | |
275 // iff the value is absent or a DWORD value is successfully read. | |
276 HRESULT ReadCommandParameter(const CString& key_name, | |
277 const CString& value_name, | |
278 DWORD* parameter) { | |
279 if (!RegKey::HasValue(key_name, value_name)) { | |
280 return S_OK; | |
281 } | |
282 | |
283 HRESULT hr = RegKey::GetValue(key_name, value_name, parameter); | |
284 if (FAILED(hr)) { | |
285 CORE_LOG(LE, (_T("[failed to read command parameter]") | |
286 _T("[key %s][value %s][0x%08x]"), key_name, value_name, hr)); | |
287 } | |
288 | |
289 return hr; | |
290 } | |
291 | |
292 } // namespace | |
293 | |
294 AppCommand::AppCommand(const CString& app_guid, | |
295 bool is_machine, | |
296 const CString& cmd_id, | |
297 const CString& cmd_line, | |
298 bool sends_pings, | |
299 const CString& session_id, | |
300 bool is_web_accessible, | |
301 DWORD reporting_id) | |
302 : app_guid_(app_guid), | |
303 is_machine_(is_machine), | |
304 cmd_id_(cmd_id), | |
305 session_id_(session_id), | |
306 cmd_line_(cmd_line), | |
307 sends_pings_(sends_pings), | |
308 reporting_id_(reporting_id), | |
309 is_web_accessible_(is_web_accessible) { | |
310 } | |
311 | |
312 HRESULT AppCommand::Load(const CString& app_guid, | |
313 bool is_machine, | |
314 const CString& cmd_id, | |
315 const CString& session_id, | |
316 AppCommand** app_command) { | |
317 ASSERT1(app_command); | |
318 | |
319 CString cmd_line; | |
320 DWORD sends_pings = 0; | |
321 DWORD is_web_accessible = 0; | |
322 DWORD reporting_id = 0; | |
323 | |
324 ConfigManager* config_manager = ConfigManager::Instance(); | |
325 CString clients_key_name = config_manager->registry_clients(is_machine); | |
326 | |
327 CString app_key_name(AppendRegKeyPath(clients_key_name, app_guid)); | |
328 CString command_key_name( | |
329 AppendRegKeyPath(app_key_name, kCommandsRegKeyName, cmd_id)); | |
330 | |
331 // Prefer the new layout, otherwise look for the legacy layout. See comments | |
332 // in app_command.h for description of each. | |
333 if (!RegKey::HasKey(command_key_name)) { | |
334 if (!RegKey::HasValue(app_key_name, cmd_id)) { | |
335 return GOOPDATE_E_CORE_MISSING_CMD; | |
336 } | |
337 | |
338 // Legacy command layout. | |
339 HRESULT hr = ReadCommandLine(app_key_name, cmd_id, &cmd_line); | |
340 if (FAILED(hr)) { | |
341 return hr; | |
342 } | |
343 } else { | |
344 // New command layout. | |
345 HRESULT hr = ReadCommandLine(command_key_name, kRegValueCommandLine, | |
346 &cmd_line); | |
347 if (FAILED(hr)) { | |
348 return hr; | |
349 } | |
350 | |
351 hr = ReadCommandParameter(command_key_name, kRegValueSendsPings, | |
352 &sends_pings); | |
353 if (FAILED(hr)) { | |
354 return hr; | |
355 } | |
356 | |
357 hr = ReadCommandParameter(command_key_name, kRegValueWebAccessible, | |
358 &is_web_accessible); | |
359 if (FAILED(hr)) { | |
360 return hr; | |
361 } | |
362 | |
363 hr = ReadCommandParameter(command_key_name, kRegValueReportingId, | |
364 &reporting_id); | |
365 if (FAILED(hr)) { | |
366 return hr; | |
367 } | |
368 } | |
369 | |
370 *app_command = new AppCommand(app_guid, | |
371 is_machine, | |
372 cmd_id, | |
373 cmd_line, | |
374 sends_pings != 0, | |
375 session_id, | |
376 is_web_accessible != 0, | |
377 reporting_id); | |
378 return S_OK; | |
379 } | |
380 | |
381 HRESULT AppCommand::Execute(HANDLE* process) const { | |
382 ASSERT1(process); | |
383 if (!process) { | |
384 return E_INVALIDARG; | |
385 } | |
386 | |
387 *process = NULL; | |
388 | |
389 CString cmd_line(cmd_line_); | |
390 | |
391 PROCESS_INFORMATION pi = {0}; | |
392 HRESULT hr = System::StartProcess(NULL, cmd_line.GetBuffer(), &pi); | |
393 | |
394 if (sends_pings_) { | |
395 PingEvent::Results result = SUCCEEDED(hr) ? | |
396 PingEvent::EVENT_RESULT_SUCCESS : PingEvent::EVENT_RESULT_ERROR; | |
397 | |
398 SendPing(app_guid_, | |
399 is_machine_, | |
400 session_id_, | |
401 PingEvent::EVENT_APP_COMMAND_BEGIN, | |
402 result, | |
403 hr, | |
404 reporting_id_); | |
405 } | |
406 | |
407 if (FAILED(hr)) { | |
408 CORE_LOG(LE, (_T("[failed to launch cmd][%s][0x%08x]"), cmd_line_, hr)); | |
409 return hr; | |
410 } | |
411 | |
412 ASSERT1(pi.hProcess); | |
413 VERIFY1(::CloseHandle(pi.hThread)); | |
414 | |
415 *process = pi.hProcess; | |
416 | |
417 if (sends_pings_) { | |
418 StartBackgroundThread(pi.hProcess); | |
419 } | |
420 | |
421 return S_OK; | |
422 } | |
423 | |
424 // Starts a background thread with a duplicate of the process handle. | |
425 // We need to duplicate the handle because the original handle will be returned | |
426 // to the client. | |
427 void AppCommand::StartBackgroundThread(HANDLE command_process) const { | |
428 HANDLE duplicate_process = NULL; | |
429 | |
430 // This is a pseudo handle that need not be closed. | |
431 HANDLE this_process_handle = ::GetCurrentProcess(); | |
432 | |
433 if (::DuplicateHandle(this_process_handle, command_process, | |
434 this_process_handle, &duplicate_process, | |
435 NULL, false, DUPLICATE_SAME_ACCESS)) { | |
436 CompletePingSender::Start(app_guid_, | |
437 is_machine_, | |
438 session_id_, | |
439 reporting_id_, | |
440 duplicate_process); | |
441 } else { | |
442 CORE_LOG(LE, (_T("[failed call to DuplicateHandle][0x%08x]"), | |
443 HRESULTFromLastError())); | |
444 SendPing(app_guid_, | |
445 is_machine_, | |
446 session_id_, | |
447 PingEvent::EVENT_APP_COMMAND_COMPLETE, | |
448 PingEvent::EVENT_RESULT_ERROR, | |
449 HRESULTFromLastError(), | |
450 reporting_id_); | |
451 } | |
452 } | |
453 | |
454 } // namespace omaha | |
OLD | NEW |