OLD | NEW |
| (Empty) |
1 // Copyright 2007-2010 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 // | |
17 // Implementation of the Google Update recovery mechanism to be included in | |
18 // Google apps. | |
19 | |
20 #include "omaha/recovery/client/google_update_recovery.h" | |
21 #include <shellapi.h> | |
22 #include <wininet.h> | |
23 #include <atlstr.h> | |
24 #include "omaha/base/const_addresses.h" | |
25 #include "omaha/base/signaturevalidator.h" | |
26 #include "omaha/common/const_group_policy.h" | |
27 #include "omaha/third_party/smartany/scoped_any.h" | |
28 | |
29 namespace omaha { | |
30 | |
31 namespace { | |
32 | |
33 const int kRollbackWindowDays = 100; | |
34 | |
35 const TCHAR* const kMachineRepairArgs = _T("/recover /machine"); | |
36 const TCHAR* const kUserRepairArgs = _T("/recover"); | |
37 | |
38 // TODO(omaha): Add a Code Red lib version that is manually updated when | |
39 // we check in the lib. | |
40 const TCHAR* const kQueryStringFormat = | |
41 _T("?appid=%s&appversion=%s&applang=%s&machine=%u") | |
42 _T("&version=%s&osversion=%s&servicepack=%s"); | |
43 | |
44 // Information about where to obtain Omaha info. | |
45 // This must never change in Omaha. | |
46 const TCHAR* const kRegValueProductVersion = _T("pv"); | |
47 const TCHAR* const kRelativeGoopdateRegPath = _T("Software\\Google\\Update\\"); | |
48 const TCHAR* const kRelativeClientsGoopdateRegPath = | |
49 _T("Software\\Google\\Update\\Clients\\") | |
50 _T("{430FD4D0-B729-4F61-AA34-91526481799D}"); | |
51 | |
52 // The UpdateDev registry value to override the Code Red url. | |
53 const TCHAR* const kRegValueNameCodeRedUrl = _T("CodeRedUrl"); | |
54 | |
55 // Starts another process via ::CreateProcess. | |
56 HRESULT StartProcess(const TCHAR* process_name, TCHAR* command_line) { | |
57 if (!process_name && !command_line) { | |
58 return E_INVALIDARG; | |
59 } | |
60 | |
61 PROCESS_INFORMATION pi = {0}; | |
62 STARTUPINFO si = {sizeof(si), 0}; | |
63 | |
64 // Feedback cursor is off while the process is starting. | |
65 si.dwFlags = STARTF_FORCEOFFFEEDBACK; | |
66 | |
67 BOOL success = ::CreateProcess( | |
68 process_name, // Module name | |
69 command_line, // Command line | |
70 NULL, // Process handle not inheritable | |
71 NULL, // Thread handle not inheritable | |
72 FALSE, // Set handle inheritance to FALSE | |
73 0, // No creation flags | |
74 NULL, // Use parent's environment block | |
75 NULL, // Use parent's starting directory | |
76 &si, // Pointer to STARTUPINFO structure | |
77 &pi); // Pointer to PROCESS_INFORMATION structure | |
78 | |
79 if (!success) { | |
80 return HRESULT_FROM_WIN32(::GetLastError()); | |
81 } | |
82 | |
83 ::CloseHandle(pi.hProcess); | |
84 ::CloseHandle(pi.hThread); | |
85 | |
86 return S_OK; | |
87 } | |
88 | |
89 // Check if a string starts with another string. Case-sensitive. | |
90 bool StringStartsWith(const TCHAR *str, const TCHAR *start_str) { | |
91 if (!start_str || !str) { | |
92 return false; | |
93 } | |
94 | |
95 while (0 != *str) { | |
96 // Check for matching characters | |
97 TCHAR c1 = *str; | |
98 TCHAR c2 = *start_str; | |
99 | |
100 // Reached the end of start_str? | |
101 if (0 == c2) | |
102 return true; | |
103 | |
104 if (c1 != c2) | |
105 return false; | |
106 | |
107 ++str; | |
108 ++start_str; | |
109 } | |
110 | |
111 // If str is shorter than start_str, no match. If equal size, match. | |
112 return 0 == *start_str; | |
113 } | |
114 | |
115 // Escape and unescape strings (shlwapi-based implementation). | |
116 // The intended usage for these APIs is escaping strings to make up | |
117 // URLs, for example building query strings. | |
118 // | |
119 // Pass false to the flag segment_only to escape the url. This will not | |
120 // cause the conversion of the # (%23), ? (%3F), and / (%2F) characters. | |
121 | |
122 // Characters that must be encoded include any characters that have no | |
123 // corresponding graphic character in the US-ASCII coded character | |
124 // set (hexadecimal 80-FF, which are not used in the US-ASCII coded character | |
125 // set, and hexadecimal 00-1F and 7F, which are control characters), | |
126 // blank spaces, "%" (which is used to encode other characters), | |
127 // and unsafe characters (<, >, ", #, {, }, |, \, ^, ~, [, ], and '). | |
128 // | |
129 // The input and output strings can't be longer than INTERNET_MAX_URL_LENGTH | |
130 | |
131 HRESULT StringEscape(const CString& str_in, | |
132 bool segment_only, | |
133 CString* escaped_string) { | |
134 if (!escaped_string) { | |
135 return E_INVALIDARG; | |
136 } | |
137 | |
138 DWORD buf_len = INTERNET_MAX_URL_LENGTH + 1; | |
139 HRESULT hr = ::UrlEscape(str_in, | |
140 escaped_string->GetBufferSetLength(buf_len), | |
141 &buf_len, | |
142 segment_only ? | |
143 URL_ESCAPE_PERCENT | URL_ESCAPE_SEGMENT_ONLY : | |
144 URL_ESCAPE_PERCENT); | |
145 if (SUCCEEDED(hr)) { | |
146 escaped_string->ReleaseBuffer(); | |
147 } | |
148 return hr; | |
149 } | |
150 | |
151 // Gets the temporary files directory for the current user. | |
152 // The directory returned may not exist. | |
153 // The returned path ends with a '\'. | |
154 // Fails if the path is longer than MAX_PATH. | |
155 HRESULT GetTempDir(CString* temp_path) { | |
156 if (!temp_path) { | |
157 return E_INVALIDARG; | |
158 } | |
159 | |
160 temp_path->Empty(); | |
161 | |
162 TCHAR buffer[MAX_PATH] = {0}; | |
163 DWORD num_chars = ::GetTempPath(MAX_PATH, buffer); | |
164 if (!num_chars) { | |
165 return HRESULT_FROM_WIN32(::GetLastError()); | |
166 } else if (num_chars >= MAX_PATH) { | |
167 return E_FAIL; | |
168 } | |
169 | |
170 *temp_path = buffer; | |
171 return S_OK; | |
172 } | |
173 | |
174 // Creates the specified directory. | |
175 HRESULT CreateDir(const CString& dir) { | |
176 if (!::CreateDirectory(dir, NULL)) { | |
177 DWORD error = ::GetLastError(); | |
178 if (ERROR_FILE_EXISTS != error && ERROR_ALREADY_EXISTS != error) { | |
179 return HRESULT_FROM_WIN32(error); | |
180 } | |
181 } | |
182 return S_OK; | |
183 } | |
184 | |
185 HRESULT GetAndCreateTempDir(CString* temp_path) { | |
186 if (!temp_path) { | |
187 return E_INVALIDARG; | |
188 } | |
189 | |
190 HRESULT hr = GetTempDir(temp_path); | |
191 if (FAILED(hr)) { | |
192 return hr; | |
193 } | |
194 if (temp_path->IsEmpty()) { | |
195 return E_FAIL; | |
196 } | |
197 | |
198 // Create this dir if it doesn't already exist. | |
199 return CreateDir(*temp_path); | |
200 } | |
201 | |
202 | |
203 // Create a unique temporary file and returns the full path. | |
204 HRESULT CreateUniqueTempFile(const CString& user_temp_dir, | |
205 CString* unique_temp_file_path) { | |
206 if (user_temp_dir.IsEmpty() || !unique_temp_file_path) { | |
207 return E_INVALIDARG; | |
208 } | |
209 | |
210 TCHAR unique_temp_filename[MAX_PATH] = {0}; | |
211 if (!::GetTempFileName(user_temp_dir, | |
212 _T("GUR"), // prefix | |
213 0, // form a unique filename | |
214 unique_temp_filename)) { | |
215 return HRESULT_FROM_WIN32(::GetLastError()); | |
216 } | |
217 | |
218 *unique_temp_file_path = unique_temp_filename; | |
219 if (unique_temp_file_path->IsEmpty()) { | |
220 return E_FAIL; | |
221 } | |
222 | |
223 return S_OK; | |
224 } | |
225 | |
226 // Obtains the OS version and service pack. | |
227 HRESULT GetOSInfo(CString* os_version, CString* service_pack) { | |
228 if (!os_version || !service_pack) { | |
229 return E_INVALIDARG; | |
230 } | |
231 | |
232 OSVERSIONINFO os_version_info = { 0 }; | |
233 os_version_info.dwOSVersionInfoSize = sizeof(os_version_info); | |
234 if (!::GetVersionEx(&os_version_info)) { | |
235 HRESULT hr = HRESULT_FROM_WIN32(::GetLastError()); | |
236 return hr; | |
237 } else { | |
238 os_version->Format(_T("%d.%d"), | |
239 os_version_info.dwMajorVersion, | |
240 os_version_info.dwMinorVersion); | |
241 *service_pack = os_version_info.szCSDVersion; | |
242 } | |
243 return S_OK; | |
244 } | |
245 | |
246 // Reads the specified string value from the specified registry key. | |
247 // Only supports value types REG_SZ and REG_EXPAND_SZ. | |
248 // REG_EXPAND_SZ strings are not expanded. | |
249 HRESULT GetRegStringValue(bool is_machine_key, | |
250 const CString& relative_key_path, | |
251 const CString& value_name, | |
252 CString* value) { | |
253 if (!value) { | |
254 return E_INVALIDARG; | |
255 } | |
256 | |
257 value->Empty(); | |
258 HKEY root_key = is_machine_key ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; | |
259 HKEY key = NULL; | |
260 LONG res = ::RegOpenKeyEx(root_key, relative_key_path, 0, KEY_READ, &key); | |
261 if (res != ERROR_SUCCESS) { | |
262 return HRESULT_FROM_WIN32(res); | |
263 } | |
264 | |
265 // First get the size of the string buffer. | |
266 DWORD type = 0; | |
267 DWORD byte_count = 0; | |
268 res = ::RegQueryValueEx(key, value_name, NULL, &type, NULL, &byte_count); | |
269 if (ERROR_SUCCESS != res) { | |
270 ::RegCloseKey(key); | |
271 return HRESULT_FROM_WIN32(res); | |
272 } | |
273 if ((type != REG_SZ && type != REG_EXPAND_SZ) || (0 == byte_count)) { | |
274 ::RegCloseKey(key); | |
275 return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); | |
276 } | |
277 | |
278 CString local_value; | |
279 // GetBuffer throws when not able to allocate the requested buffer. | |
280 TCHAR* buffer = local_value.GetBuffer(byte_count / sizeof(TCHAR)); | |
281 res = ::RegQueryValueEx(key, | |
282 value_name, | |
283 NULL, | |
284 NULL, | |
285 reinterpret_cast<BYTE*>(buffer), | |
286 &byte_count); | |
287 ::RegCloseKey(key); | |
288 if (ERROR_SUCCESS == res) { | |
289 local_value.ReleaseBufferSetLength(byte_count / sizeof(TCHAR)); | |
290 *value = local_value; | |
291 } | |
292 | |
293 return HRESULT_FROM_WIN32(res); | |
294 } | |
295 | |
296 // Reads the specified DWORD value from the specified registry key. | |
297 // Only supports value types REG_DWORD. | |
298 // Assumes DWORD is sufficient buffer, which must be true for valid value type. | |
299 HRESULT GetRegDwordValue(bool is_machine_key, | |
300 const CString& relative_key_path, | |
301 const CString& value_name, | |
302 DWORD* value) { | |
303 if (!value) { | |
304 return E_INVALIDARG; | |
305 } | |
306 | |
307 HKEY root_key = is_machine_key ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; | |
308 HKEY key = NULL; | |
309 LONG res = ::RegOpenKeyEx(root_key, relative_key_path, 0, KEY_READ, &key); | |
310 if (res != ERROR_SUCCESS) { | |
311 return HRESULT_FROM_WIN32(res); | |
312 } | |
313 | |
314 DWORD type = 0; | |
315 DWORD byte_count = sizeof(*value); | |
316 res = ::RegQueryValueEx(key, | |
317 value_name, | |
318 NULL, | |
319 &type, | |
320 reinterpret_cast<BYTE*>(value), | |
321 &byte_count); | |
322 ::RegCloseKey(key); | |
323 if (ERROR_SUCCESS != res) { | |
324 return HRESULT_FROM_WIN32(res); | |
325 } | |
326 if ((type != REG_DWORD) || (0 == byte_count)) { | |
327 return HRESULT_FROM_WIN32(ERROR_INVALID_DATA); | |
328 } | |
329 | |
330 return S_OK; | |
331 } | |
332 | |
333 // Obtains information about the current Omaha installation. | |
334 // Attempts to obtain as much information as possible even if errors occur. | |
335 // Therefore, return values of GetRegStringValue are ignored. | |
336 HRESULT GetOmahaInformation(bool is_machine_app, | |
337 CString* omaha_version) { | |
338 if (!omaha_version) { | |
339 return E_INVALIDARG; | |
340 } | |
341 | |
342 if (FAILED(GetRegStringValue(is_machine_app, | |
343 kRelativeClientsGoopdateRegPath, | |
344 kRegValueProductVersion, | |
345 omaha_version))) { | |
346 *omaha_version = _T("0.0.0.0"); | |
347 } | |
348 | |
349 return S_OK; | |
350 } | |
351 | |
352 // Builds the query portion of the recovery url. | |
353 // This method obtains values necessary to build the query that are not provided | |
354 // as parameters. | |
355 // Attempts to build with as much information as possible even if errors occur. | |
356 HRESULT BuildUrlQueryPortion(const CString& app_guid, | |
357 const CString& app_version, | |
358 const CString& app_language, | |
359 bool is_machine_app, | |
360 CString* query) { | |
361 if (!query) { | |
362 return E_INVALIDARG; | |
363 } | |
364 | |
365 CString omaha_version; | |
366 GetOmahaInformation(is_machine_app, &omaha_version); | |
367 | |
368 CString os_version; | |
369 CString os_service_pack; | |
370 GetOSInfo(&os_version, &os_service_pack); | |
371 | |
372 // All parameters must be escaped individually before building the query. | |
373 CString app_guid_escaped; | |
374 CString app_version_escaped; | |
375 CString app_language_escaped; | |
376 CString omaha_version_escaped; | |
377 CString os_version_escaped; | |
378 CString os_service_pack_escaped; | |
379 StringEscape(app_guid, true, &app_guid_escaped); | |
380 StringEscape(app_version, true, &app_version_escaped); | |
381 StringEscape(app_language, true, &app_language_escaped); | |
382 StringEscape(omaha_version, true, &omaha_version_escaped); | |
383 StringEscape(os_version, true, &os_version_escaped); | |
384 StringEscape(os_service_pack, true, &os_service_pack_escaped); | |
385 | |
386 query->Format(kQueryStringFormat, | |
387 app_guid_escaped, | |
388 app_version_escaped, | |
389 app_language_escaped, | |
390 is_machine_app ? 1 : 0, | |
391 omaha_version_escaped, | |
392 os_version_escaped, | |
393 os_service_pack_escaped); | |
394 | |
395 return S_OK; | |
396 } | |
397 | |
398 // Returns the full path to save the downloaded file to. | |
399 // The path is based on a unique temporary filename to avoid a conflict | |
400 // between multiple apps downloading to the same location. | |
401 // The path to this file is also returned. The caller is responsible for | |
402 // deleting the temporary file after using the download target path. | |
403 // If it cannot create the unique directory, it attempts to use the user's | |
404 // temporary directory and a constant filename. | |
405 HRESULT GetDownloadTargetPath(CString* download_target_path, | |
406 CString* temp_file_path) { | |
407 if (!download_target_path || !temp_file_path) { | |
408 return E_INVALIDARG; | |
409 } | |
410 | |
411 CString user_temp_dir; | |
412 HRESULT hr = GetAndCreateTempDir(&user_temp_dir); | |
413 if (FAILED(hr)) { | |
414 return hr; | |
415 } | |
416 | |
417 hr = CreateUniqueTempFile(user_temp_dir, temp_file_path); | |
418 if (SUCCEEDED(hr) && !temp_file_path->IsEmpty()) { | |
419 *download_target_path = *temp_file_path; | |
420 // Ignore the return value. A .tmp filename is better than none. | |
421 download_target_path->Replace(_T(".tmp"), _T(".exe")); | |
422 } else { | |
423 // Try a static filename in the temp directory as a fallback. | |
424 *download_target_path = user_temp_dir + _T("GoogleUpdateSetup.exe"); | |
425 *temp_file_path = _T(""); | |
426 } | |
427 | |
428 return S_OK; | |
429 } | |
430 | |
431 HRESULT DownloadRepairFile(const CString& download_target_path, | |
432 const CString& app_guid, | |
433 const CString& app_version, | |
434 const CString& app_language, | |
435 bool is_machine_app, | |
436 DownloadCallback download_callback, | |
437 void* context) { | |
438 CString query; | |
439 BuildUrlQueryPortion(app_guid, | |
440 app_version, | |
441 app_language, | |
442 is_machine_app, | |
443 &query); | |
444 | |
445 CString url; | |
446 HRESULT hr = GetRegStringValue(true, | |
447 _T("SOFTWARE\\Google\\UpdateDev"), | |
448 kRegValueNameCodeRedUrl, | |
449 &url); | |
450 if (FAILED(hr)) { | |
451 url = omaha::kUrlCodeRedCheck; | |
452 } | |
453 | |
454 url += query; | |
455 | |
456 return download_callback(url, download_target_path, context); | |
457 } | |
458 | |
459 // Makes sure the path is enclosed with double quotation marks. | |
460 void EnclosePath(CString* path) { | |
461 if (path) { | |
462 return; | |
463 } | |
464 | |
465 if (!path->IsEmpty() && path->GetAt(0) != _T('"')) { | |
466 path->Insert(0, _T('"')); | |
467 path->AppendChar(_T('"')); | |
468 } | |
469 } | |
470 | |
471 HRESULT RunRepairFile(const CString& file_path, bool is_machine_app) { | |
472 const TCHAR* repair_file_args = is_machine_app ? kMachineRepairArgs : | |
473 kUserRepairArgs; | |
474 | |
475 CString command_line(file_path); | |
476 EnclosePath(&command_line); | |
477 command_line.AppendChar(_T(' ')); | |
478 command_line.Append(repair_file_args); | |
479 | |
480 return StartProcess(NULL, command_line.GetBuffer()); | |
481 } | |
482 | |
483 } // namespace | |
484 | |
485 // Verifies the file's integrity and that it is signed by Google. | |
486 // We cannot prevent rollback attacks by using a version because the client | |
487 // may not be able to determine the current version if the files and/or | |
488 // registry entries have been deleted/corrupted. | |
489 // Therefore, we check that the file was signed recently. | |
490 HRESULT VerifyFileSignature(const CString& filename) { | |
491 // Use Authenticode/WinVerifyTrust to verify the file. | |
492 // Allow the revocation check to use the network. | |
493 HRESULT hr = VerifySignature(filename, true); | |
494 if (FAILED(hr)) { | |
495 return hr; | |
496 } | |
497 | |
498 // Verify that there is a Google certificate. | |
499 if (!VerifySigneeIsGoogle(filename)) { | |
500 return CERT_E_CN_NO_MATCH; | |
501 } | |
502 | |
503 // Check that the file was signed recently to limit the window for | |
504 // rollback attacks. | |
505 return VerifyFileSignedWithinDays(filename, kRollbackWindowDays); | |
506 } | |
507 | |
508 // Verifies the file contains the special markup resource for repair files. | |
509 HRESULT VerifyRepairFileMarkup(const CString& filename) { | |
510 const TCHAR* kMarkupResourceName = MAKEINTRESOURCE(1); | |
511 const TCHAR* kMarkupResourceType = _T("GOOGLEUPDATEREPAIR"); | |
512 const DWORD kMarkupResourceExpectedValue = 1; | |
513 | |
514 scoped_library module(::LoadLibraryEx(filename, 0, LOAD_LIBRARY_AS_DATAFILE)); | |
515 if (!module) { | |
516 return HRESULT_FROM_WIN32(::GetLastError()); | |
517 } | |
518 | |
519 HRSRC resource(::FindResource(get(module), | |
520 kMarkupResourceName, | |
521 kMarkupResourceType)); | |
522 if (!resource) { | |
523 return HRESULT_FROM_WIN32(::GetLastError()); | |
524 } | |
525 | |
526 if (sizeof(kMarkupResourceExpectedValue) != | |
527 ::SizeofResource(get(module), resource)) { | |
528 return E_UNEXPECTED; | |
529 } | |
530 | |
531 HGLOBAL loaded_resource(::LoadResource(get(module), resource)); | |
532 if (!loaded_resource) { | |
533 return HRESULT_FROM_WIN32(::GetLastError()); | |
534 } | |
535 | |
536 const DWORD* value = static_cast<DWORD*>(::LockResource(loaded_resource)); | |
537 if (!value) { | |
538 return E_HANDLE; | |
539 } | |
540 | |
541 if (kMarkupResourceExpectedValue != *value) { | |
542 return E_UNEXPECTED; | |
543 } | |
544 | |
545 return S_OK; | |
546 } | |
547 | |
548 // Verifies the filename is not UNC name, the file exists, has a valid signature | |
549 // chain, is signed by Google, and contains the special markup resource for | |
550 // repair files. | |
551 HRESULT VerifyIsValidRepairFile(const CString& filename) { | |
552 // Make sure file exists. | |
553 if (!::PathFileExists(filename)) { | |
554 return HRESULT_FROM_WIN32(::GetLastError()); | |
555 } | |
556 | |
557 HRESULT hr = VerifyFileSignature(filename); | |
558 if (FAILED(hr)) { | |
559 return hr; | |
560 } | |
561 | |
562 return VerifyRepairFileMarkup(filename); | |
563 } | |
564 | |
565 } // namespace omaha | |
566 | |
567 // If a repair file is run, the file will not be deleted until reboot. Delete | |
568 // after reboot will only succeed when executed by an admin or LocalSystem. | |
569 // Returns HRESULT_FROM_WIN32(ERROR_ACCESS_DISABLED_BY_POLICY) if automatic | |
570 // update checks are disabled. | |
571 HRESULT FixGoogleUpdate(const TCHAR* app_guid, | |
572 const TCHAR* app_version, | |
573 const TCHAR* app_language, | |
574 bool is_machine_app, | |
575 DownloadCallback download_callback, | |
576 void* context) { | |
577 if (!app_guid || !app_version || !app_language || !download_callback) { | |
578 return E_INVALIDARG; | |
579 } | |
580 | |
581 DWORD update_check_period_override_minutes(UINT_MAX); | |
582 HRESULT hr = omaha::GetRegDwordValue( | |
583 true, | |
584 GOOPDATE_POLICIES_RELATIVE, | |
585 omaha::kRegValueAutoUpdateCheckPeriodOverrideMinutes, | |
586 &update_check_period_override_minutes); | |
587 if (SUCCEEDED(hr) && (0 == update_check_period_override_minutes)) { | |
588 return HRESULT_FROM_WIN32(ERROR_ACCESS_DISABLED_BY_POLICY); | |
589 } | |
590 | |
591 CString download_target_path; | |
592 CString temp_file_path; | |
593 hr = omaha::GetDownloadTargetPath(&download_target_path, &temp_file_path); | |
594 if (FAILED(hr)) { | |
595 return hr; | |
596 } | |
597 if (download_target_path.IsEmpty()) { | |
598 return E_FAIL; | |
599 } | |
600 | |
601 // After calling DownloadRepairFile, don't return until the repair file and | |
602 // temp file have been deleted. | |
603 hr = omaha::DownloadRepairFile(download_target_path, | |
604 app_guid, | |
605 app_version, | |
606 app_language, | |
607 is_machine_app, | |
608 download_callback, | |
609 context); | |
610 | |
611 if (SUCCEEDED(hr)) { | |
612 hr = omaha::VerifyIsValidRepairFile(download_target_path); | |
613 } | |
614 | |
615 if (FAILED(hr)) { | |
616 ::DeleteFile(download_target_path); | |
617 ::DeleteFile(temp_file_path); | |
618 return hr; | |
619 } | |
620 | |
621 hr = omaha::RunRepairFile(download_target_path, is_machine_app); | |
622 ::MoveFileEx(download_target_path, NULL, MOVEFILE_DELAY_UNTIL_REBOOT); | |
623 ::DeleteFile(temp_file_path); | |
624 | |
625 return hr; | |
626 } | |
OLD | NEW |