OLD | NEW |
| (Empty) |
1 // Copyright 2006-2009 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 // Implementation of the metainstaller logic. | |
17 // Untars a tarball and executes the extracted executable. | |
18 // If no command line is specified, "/install" is passed to the executable | |
19 // along with a .gup file if one is extracted. | |
20 // If found, the contents of the signature tag are also passed to the | |
21 // executable unmodified. | |
22 | |
23 #include <tchar.h> | |
24 #include <atlsimpcoll.h> | |
25 #include <atlstr.h> | |
26 #include <shellapi.h> | |
27 #include <shlwapi.h> | |
28 #include <windows.h> | |
29 | |
30 #pragma warning(push) | |
31 // C4310: cast truncates constant value | |
32 #pragma warning(disable : 4310) | |
33 #include "base/basictypes.h" | |
34 #pragma warning(pop) | |
35 #include "base/scoped_ptr.h" | |
36 #include "omaha/base/constants.h" | |
37 #include "omaha/base/error.h" | |
38 #include "omaha/base/extractor.h" | |
39 #include "omaha/base/scoped_any.h" | |
40 #include "omaha/base/system_info.h" | |
41 #include "omaha/common/const_cmd_line.h" | |
42 #include "omaha/mi_exe_stub/process.h" | |
43 #include "omaha/mi_exe_stub/mi.grh" | |
44 #include "omaha/mi_exe_stub/tar.h" | |
45 extern "C" { | |
46 #include "third_party/lzma/v4_65/files/C/Bcj2.h" | |
47 #include "third_party/lzma/v4_65/files/C/LzmaDec.h" | |
48 } | |
49 | |
50 namespace omaha { | |
51 | |
52 // Resource ID of the goopdate payload inside the meta-installer. | |
53 #define IDR_PAYLOAD 102 | |
54 | |
55 namespace { | |
56 | |
57 HRESULT HandleError(HRESULT hr); | |
58 | |
59 // The function assumes that the extractor has already been opened. | |
60 // The buffer must be deleted by the caller. | |
61 char* ReadTag(TagExtractor* extractor) { | |
62 const int kMaxTagLength = 0x10000; // 64KB | |
63 | |
64 int tag_buffer_size = 0; | |
65 if (!extractor->ExtractTag(NULL, &tag_buffer_size)) { | |
66 return NULL; | |
67 } | |
68 if (!tag_buffer_size || (tag_buffer_size >= kMaxTagLength)) { | |
69 return NULL; | |
70 } | |
71 | |
72 scoped_array<char> tag_buffer(new char[tag_buffer_size]); | |
73 if (!tag_buffer.get()) { | |
74 return NULL; | |
75 } | |
76 | |
77 if (!extractor->ExtractTag(tag_buffer.get(), &tag_buffer_size)) { | |
78 _ASSERTE(false); | |
79 return NULL; | |
80 } | |
81 | |
82 // Do a sanity check of the tag string. The double quote '"' | |
83 // is a special character that should not be included in the tag string. | |
84 for (const char* tag_char = tag_buffer.get(); *tag_char; ++tag_char) { | |
85 if (*tag_char == '"') { | |
86 _ASSERTE(false); | |
87 return NULL; | |
88 } | |
89 } | |
90 | |
91 return tag_buffer.release(); | |
92 } | |
93 | |
94 // Extract the tag containing the extra information written by the server. | |
95 // The memory returned by the function will have to be freed using delete[] | |
96 // operator. | |
97 char* ExtractTag(const TCHAR* module_file_name) { | |
98 if (!module_file_name) { | |
99 return NULL; | |
100 } | |
101 | |
102 TagExtractor extractor; | |
103 if (!extractor.OpenFile(module_file_name)) { | |
104 return NULL; | |
105 } | |
106 char* ret = ReadTag(&extractor); | |
107 extractor.CloseFile(); | |
108 | |
109 return ret; | |
110 } | |
111 | |
112 class MetaInstaller { | |
113 public: | |
114 MetaInstaller(HINSTANCE instance, LPCSTR cmd_line) | |
115 : instance_(instance), | |
116 cmd_line_(cmd_line), | |
117 exit_code_(0) { | |
118 } | |
119 | |
120 ~MetaInstaller() { | |
121 // When a crash happens while running GoogleUpdate and breakpad gets it | |
122 // GooogleUpdate.exe is started with the /report to report the crash. | |
123 // In a crash, the temp directory and the contained files can't be deleted. | |
124 if (exit_code_ != GOOPDATE_E_CRASH) { | |
125 CleanUpTempDirectory(); | |
126 } | |
127 } | |
128 | |
129 int ExtractAndRun() { | |
130 if (CreateUniqueTempDirectory() != 0) { | |
131 return -1; | |
132 } | |
133 scoped_hfile tarball_file(ExtractTarballToTempLocation()); | |
134 if (!valid(tarball_file)) { | |
135 return -1; | |
136 } | |
137 | |
138 // Extract files from the archive and run the first EXE we find in it. | |
139 Tar tar(temp_dir_, get(tarball_file), true); | |
140 tar.SetCallback(TarFileCallback, this); | |
141 if (!tar.ExtractToDir()) { | |
142 return -1; | |
143 } | |
144 | |
145 exit_code_ = ULONG_MAX; | |
146 if (!exe_path_.IsEmpty()) { | |
147 // Build the command line. There are three scenarios we consider: | |
148 // 1. Run by the user, in which case the MI does not receive any | |
149 // argument on its command line. In this case the command line | |
150 // to run is: "exe_path" /install [["]manifest["]] | |
151 // 2. Run with command line arguments. The tag, if present, will be | |
152 // appended to the command line. | |
153 // The command line is: "exe_path" args <tag> | |
154 // For example, pass "/silent /install" to the metainstaller to | |
155 // initiate a silent install using the extra args in the tag. | |
156 // If a command line does not take a tag or a custom tag is needed, | |
157 // use an untagged file. | |
158 CString command_line(exe_path_); | |
159 ::PathQuoteSpaces(CStrBuf(command_line, MAX_PATH)); | |
160 | |
161 scoped_array<char> tag(GetTag()); | |
162 if (cmd_line_.IsEmpty()) { | |
163 // Run-by-user case. | |
164 if (!tag.get()) { | |
165 _ASSERTE(!_T("Must provide arguments with untagged metainstaller.")); | |
166 HRESULT hr = GOOPDATE_E_UNTAGGED_METAINSTALLER; | |
167 HandleError(hr); | |
168 return hr; | |
169 } | |
170 command_line.AppendFormat(_T(" /%s %s /%s"), | |
171 kCmdLineInstallSource, | |
172 kCmdLineInstallSource_TaggedMetainstaller, | |
173 kCmdLineInstall); | |
174 } else { | |
175 command_line.AppendFormat(_T(" %s"), cmd_line_); | |
176 | |
177 CheckAndHandleRecoveryCase(&command_line); | |
178 } | |
179 | |
180 if (tag.get()) { | |
181 command_line.AppendFormat(_T(" \"%s\""), CString(tag.get())); | |
182 } | |
183 | |
184 RunAndWait(command_line, &exit_code_); | |
185 } | |
186 // Propagate up the exit code of the program we have run. | |
187 return exit_code_; | |
188 } | |
189 | |
190 private: | |
191 void CleanUpTempDirectory() { | |
192 // Delete our temp directory and its contents. | |
193 for (int i = 0; i != files_to_delete_.GetSize(); ++i) { | |
194 DeleteFile(files_to_delete_[i]); | |
195 } | |
196 files_to_delete_.RemoveAll(); | |
197 | |
198 ::RemoveDirectory(temp_dir_); | |
199 temp_dir_.Empty(); | |
200 } | |
201 | |
202 // Determines whether this is a silent install. | |
203 bool IsSilentInstall() { | |
204 CString silent_argument; | |
205 silent_argument.Format(_T("/%s"), kCmdLineSilent); | |
206 | |
207 return silent_argument == cmd_line_; | |
208 } | |
209 | |
210 // Determines whether the MI is being invoked for recovery purposes, and, | |
211 // if so, appends the MI's full path to the command line. | |
212 // cmd_line_ must begin with "/recover" in order for the recovery case to be | |
213 // detected. | |
214 void CheckAndHandleRecoveryCase(CString* command_line) { | |
215 _ASSERTE(command_line); | |
216 | |
217 CString recover_argument; | |
218 recover_argument.Format(_T("/%s"), kCmdLineRecover); | |
219 | |
220 if (cmd_line_.Left(recover_argument.GetLength()) == recover_argument) { | |
221 TCHAR current_path[MAX_PATH] = {}; | |
222 if (::GetModuleFileName(NULL, current_path, arraysize(current_path))) { | |
223 command_line->AppendFormat(_T(" \"%s\""), current_path); | |
224 } | |
225 } | |
226 } | |
227 | |
228 // Create a temp directory to hold the embedded setup files. | |
229 // This is a bit of a hack: we ask the system to create a temporary | |
230 // filename for us, and instead we use that name for a subdirectory name. | |
231 int CreateUniqueTempDirectory() { | |
232 ::GetTempPath(MAX_PATH, CStrBuf(temp_root_dir_, MAX_PATH)); | |
233 if (::CreateDirectory(temp_root_dir_, NULL) != 0 || | |
234 ::GetLastError() == ERROR_ALREADY_EXISTS) { | |
235 if (!::GetTempFileName(temp_root_dir_, | |
236 _T("GUM"), | |
237 0, // form a unique filename | |
238 CStrBuf(temp_dir_, MAX_PATH))) { | |
239 return -1; | |
240 } | |
241 // GetTempFileName() actually creates the temp file, so delete it. | |
242 ::DeleteFile(temp_dir_); | |
243 ::CreateDirectory(temp_dir_, NULL); | |
244 } else { | |
245 return -1; | |
246 } | |
247 return 0; | |
248 } | |
249 | |
250 HANDLE ExtractTarballToTempLocation() { | |
251 HANDLE tarball_file = INVALID_HANDLE_VALUE; | |
252 TCHAR tarball_filename[MAX_PATH] = {0}; | |
253 if (::GetTempFileName(temp_root_dir_, | |
254 _T("GUT"), | |
255 0, // form a unique filename | |
256 tarball_filename)) { | |
257 files_to_delete_.Add(tarball_filename); | |
258 HRSRC res_info = ::FindResource(NULL, | |
259 MAKEINTRESOURCE(IDR_PAYLOAD), | |
260 _T("B")); | |
261 if (NULL != res_info) { | |
262 HGLOBAL resource = ::LoadResource(NULL, res_info); | |
263 if (NULL != resource) { | |
264 LPVOID resource_pointer = ::LockResource(resource); | |
265 if (NULL != resource_pointer) { | |
266 tarball_file = ::CreateFile(tarball_filename, | |
267 GENERIC_READ | GENERIC_WRITE, | |
268 0, | |
269 NULL, | |
270 OPEN_ALWAYS, | |
271 0, | |
272 NULL); | |
273 if (INVALID_HANDLE_VALUE != tarball_file) { | |
274 LARGE_INTEGER file_position = {}; | |
275 if (0 != DecompressBufferToFile( | |
276 static_cast<const uint8*>(resource_pointer), | |
277 ::SizeofResource(NULL, res_info), | |
278 tarball_file) || | |
279 !::SetFilePointerEx(tarball_file, file_position, NULL, | |
280 FILE_BEGIN)) { | |
281 ::CloseHandle(tarball_file); | |
282 tarball_file = INVALID_HANDLE_VALUE; | |
283 } | |
284 } | |
285 } | |
286 } | |
287 } | |
288 } | |
289 return tarball_file; | |
290 } | |
291 | |
292 char* GetTag() const { | |
293 // Get this module file name. | |
294 TCHAR module_file_name[MAX_PATH] = {}; | |
295 if (!::GetModuleFileName(instance_, module_file_name, | |
296 arraysize(module_file_name))) { | |
297 _ASSERTE(false); | |
298 return NULL; | |
299 } | |
300 | |
301 return ExtractTag(module_file_name); | |
302 } | |
303 | |
304 static CString GetFilespec(const CString& path) { | |
305 int pos = path.ReverseFind('\\'); | |
306 if (pos >= 0) { | |
307 return path.Mid(pos + 1); | |
308 } | |
309 return path; | |
310 } | |
311 | |
312 void HandleTarFile(const TCHAR* filename) { | |
313 CString new_filename(filename); | |
314 files_to_delete_.Add(new_filename); | |
315 CString filespec(GetFilespec(new_filename)); | |
316 filespec.MakeLower(); | |
317 | |
318 if (filespec.GetLength() > 4) { | |
319 CString extension(filespec.Mid(filespec.GetLength() - 4)); | |
320 | |
321 if (extension == _T(".exe")) { | |
322 // We're interested in remembering only the first exe in the tarball. | |
323 if (exe_path_.IsEmpty()) { | |
324 exe_path_ = new_filename; | |
325 } | |
326 } | |
327 } | |
328 } | |
329 | |
330 static void TarFileCallback(void* context, const TCHAR* filename) { | |
331 MetaInstaller* mi = reinterpret_cast<MetaInstaller*>(context); | |
332 mi->HandleTarFile(filename); | |
333 } | |
334 | |
335 // TODO(omaha): reimplement the relevant files in the LZMA SDK to optimize | |
336 // for size. We'll have to release the modifications (LZMA SDK is CDDL/CDL), | |
337 // which shouldn't be a problem. | |
338 static void* MyAlloc(void* p, size_t size) { | |
339 UNREFERENCED_PARAMETER(p); | |
340 return new uint8[size]; | |
341 } | |
342 | |
343 static void MyFree(void* p, void* address) { | |
344 UNREFERENCED_PARAMETER(p); | |
345 delete[] address; | |
346 } | |
347 | |
348 // Decompress the content of the memory buffer into the file | |
349 static int DecompressBufferToFile(const uint8* packed_buffer, | |
350 size_t packed_size, | |
351 HANDLE file) { | |
352 // need header and len minimally | |
353 if (packed_size < LZMA_PROPS_SIZE + 8) { | |
354 return -1; | |
355 } | |
356 | |
357 // Note this code won't properly handle decoding large files, since uint32 | |
358 // is used in several places to count size. | |
359 ISzAlloc allocators = { &MyAlloc, &MyFree }; | |
360 CLzmaDec lzma_state; | |
361 LzmaDec_Construct(&lzma_state); | |
362 LzmaDec_Allocate(&lzma_state, packed_buffer, LZMA_PROPS_SIZE, &allocators); | |
363 LzmaDec_Init(&lzma_state); | |
364 packed_buffer += LZMA_PROPS_SIZE; | |
365 packed_size -= LZMA_PROPS_SIZE; | |
366 | |
367 // TODO(omaha): make this independent of endianness. | |
368 uint64 unpacked_size_64 = *reinterpret_cast<const uint64*>(packed_buffer); | |
369 size_t unpacked_size = static_cast<size_t>(unpacked_size_64); | |
370 packed_buffer += sizeof(unpacked_size_64); | |
371 packed_size -= sizeof(unpacked_size_64); | |
372 | |
373 scoped_array<uint8> unpacked_buffer(new uint8[unpacked_size]); | |
374 | |
375 ELzmaStatus status = static_cast<ELzmaStatus>(0); | |
376 SRes result = LzmaDec_DecodeToBuf( | |
377 &lzma_state, | |
378 unpacked_buffer.get(), | |
379 &unpacked_size, | |
380 packed_buffer, | |
381 &packed_size, | |
382 LZMA_FINISH_END, | |
383 &status); | |
384 LzmaDec_Free(&lzma_state, &allocators); | |
385 if (SZ_OK != result) { | |
386 return -1; | |
387 } | |
388 | |
389 #if 0 | |
390 // Reverse BCJ coding. | |
391 uint32 x86_conversion_state; | |
392 x86_Convert_Init(x86_conversion_state); | |
393 x86_Convert(unpacked_buffer.get(), unpacked_size, 0, &x86_conversion_state, | |
394 0 /* decoding */); | |
395 #else | |
396 // Reverse BCJ2 coding. | |
397 const uint8* p = unpacked_buffer.get(); | |
398 uint32 original_size = *reinterpret_cast<const uint32*>(p); | |
399 p += sizeof(uint32); // NOLINT | |
400 uint32 stream0_size = *reinterpret_cast<const uint32*>(p); | |
401 p += sizeof(uint32); // NOLINT | |
402 uint32 stream1_size = *reinterpret_cast<const uint32*>(p); | |
403 p += sizeof(uint32); // NOLINT | |
404 uint32 stream2_size = *reinterpret_cast<const uint32*>(p); | |
405 p += sizeof(uint32); // NOLINT | |
406 uint32 stream3_size = *reinterpret_cast<const uint32*>(p); | |
407 p += sizeof(uint32); // NOLINT | |
408 | |
409 scoped_array<uint8> output_buffer(new uint8[original_size]); | |
410 if (SZ_OK != Bcj2_Decode(p, | |
411 stream0_size, | |
412 p + stream0_size, | |
413 stream1_size, | |
414 p + stream0_size + stream1_size, | |
415 stream2_size, | |
416 p + stream0_size + stream1_size + stream2_size, | |
417 stream3_size, | |
418 output_buffer.get(), original_size)) { | |
419 return 1; | |
420 } | |
421 #endif | |
422 | |
423 DWORD written; | |
424 if (!::WriteFile(file, output_buffer.get(), original_size, &written, | |
425 NULL) || | |
426 written != original_size) { | |
427 return -1; | |
428 } | |
429 | |
430 return 0; | |
431 } | |
432 | |
433 HINSTANCE instance_; | |
434 CString cmd_line_; | |
435 CString exe_path_; | |
436 DWORD exit_code_; | |
437 CSimpleArray<CString> files_to_delete_; | |
438 CString temp_dir_; | |
439 CString temp_root_dir_; | |
440 }; | |
441 | |
442 HRESULT CheckOSRequirements() { | |
443 return SystemInfo::OSWin2KSP4OrLater() ? S_OK : | |
444 GOOPDATE_E_RUNNING_INFERIOR_WINDOWS; | |
445 } | |
446 | |
447 CString GetCompanyDisplayName() { | |
448 CString company_name; | |
449 company_name.LoadString(IDS_FRIENDLY_COMPANY_NAME); | |
450 _ASSERTE(!company_name.IsEmpty()); | |
451 return company_name; | |
452 } | |
453 | |
454 CString GetUiTitle() { | |
455 CString title; | |
456 title.FormatMessage(IDS_INSTALLER_DISPLAY_NAME, GetCompanyDisplayName()); | |
457 return title; | |
458 } | |
459 | |
460 HRESULT HandleError(HRESULT result) { | |
461 _ASSERTE(FAILED(result)); | |
462 CString msg_box_text; | |
463 | |
464 switch (result) { | |
465 case GOOPDATE_E_RUNNING_INFERIOR_WINDOWS: | |
466 msg_box_text.FormatMessage(IDS_RUNNING_INFERIOR_WINDOWS, | |
467 GetCompanyDisplayName()); | |
468 break; | |
469 | |
470 case GOOPDATE_E_UNTAGGED_METAINSTALLER: | |
471 default: | |
472 msg_box_text.LoadString(IDS_GENERIC_ERROR); | |
473 _ASSERTE(!msg_box_text.IsEmpty()); | |
474 break; | |
475 } | |
476 | |
477 ::MessageBox(NULL, msg_box_text, GetUiTitle(), MB_OK); | |
478 return result; | |
479 } | |
480 | |
481 } // namespace | |
482 | |
483 } // namespace omaha | |
484 | |
485 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int) { | |
486 scoped_co_init init_com_apt; | |
487 HRESULT hr(init_com_apt.hresult()); | |
488 if (FAILED(hr)) { | |
489 return omaha::HandleError(hr); | |
490 } | |
491 | |
492 hr = omaha::CheckOSRequirements(); | |
493 if (FAILED(hr)) { | |
494 return omaha::HandleError(hr); | |
495 } | |
496 | |
497 omaha::MetaInstaller mi(hInstance, lpCmdLine); | |
498 int result = mi.ExtractAndRun(); | |
499 return result; | |
500 } | |
501 | |
OLD | NEW |