| 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 |