| // Copyright 2006-2009 Google Inc. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // ======================================================================== |
| // |
| // Implementation of the metainstaller logic. |
| // Untars a tarball and executes the extracted executable. |
| // If no command line is specified, "/install" is passed to the executable |
| // along with a .gup file if one is extracted. |
| // If found, the contents of the signature tag are also passed to the |
| // executable unmodified. |
| |
| #pragma warning(push) |
| // C4548: expression before comma has no effect |
| #pragma warning(disable : 4548) |
| #include <windows.h> |
| #include <atlstr.h> |
| #include <atlsimpcoll.h> |
| #pragma warning(pop) |
| #include <msxml2.h> |
| #include <shellapi.h> |
| #include <algorithm> |
| |
| #include "base/scoped_ptr.h" |
| #include "omaha/common/constants.h" |
| #include "omaha/common/const_cmd_line.h" |
| #include "omaha/common/error.h" |
| #include "omaha/common/extractor.h" |
| #include "omaha/common/scoped_any.h" |
| #include "omaha/common/system_info.h" |
| #include "omaha/mi_exe_stub/process.h" |
| #include "omaha/mi_exe_stub/mi.grh" |
| #include "omaha/mi_exe_stub/tar.h" |
| extern "C" { |
| #include "third_party/lzma/LzmaStateDecode.h" |
| } |
| |
| namespace omaha { |
| |
| // Resource ID of the goopdate payload inside the meta-installer. |
| #define IDR_PAYLOAD 102 |
| |
| namespace { |
| |
| HRESULT HandleError(HRESULT hr); |
| |
| // The function assumes that the extractor has already been opened. |
| // The buffer must be deleted by the caller. |
| char* ReadTag(TagExtractor* extractor) { |
| const int kMaxTagLength = 0x10000; // 64KB |
| |
| int tag_buffer_size = 0; |
| if (!extractor->ExtractTag(NULL, &tag_buffer_size)) { |
| return NULL; |
| } |
| if (!tag_buffer_size || (tag_buffer_size >= kMaxTagLength)) { |
| return NULL; |
| } |
| |
| scoped_array<char> tag_buffer(new char[tag_buffer_size]); |
| if (!tag_buffer.get()) { |
| return NULL; |
| } |
| |
| if (!extractor->ExtractTag(tag_buffer.get(), &tag_buffer_size)) { |
| _ASSERTE(false); |
| return NULL; |
| } |
| |
| // Do a sanity check of the tag string. The double quote '"' |
| // is a special character that should not be included in the tag string. |
| for (const char* tag_char = tag_buffer.get(); *tag_char; ++tag_char) { |
| if (*tag_char == '"') { |
| _ASSERTE(false); |
| return NULL; |
| } |
| } |
| |
| return tag_buffer.release(); |
| } |
| |
| // Extract the tag containing the extra information written by the server. |
| // The memory returned by the function will have to be freed using delete[] |
| // operator. |
| char* ExtractTag(const TCHAR* module_file_name) { |
| if (!module_file_name) { |
| return NULL; |
| } |
| |
| TagExtractor extractor; |
| if (!extractor.OpenFile(module_file_name)) { |
| return NULL; |
| } |
| char* ret = ReadTag(&extractor); |
| extractor.CloseFile(); |
| |
| return ret; |
| } |
| |
| class MetaInstaller { |
| public: |
| MetaInstaller(HINSTANCE instance, LPCSTR cmd_line) |
| : instance_(instance), |
| cmd_line_(cmd_line), |
| exit_code_(0) { |
| } |
| |
| ~MetaInstaller() { |
| // When a crash happens while running GoogleUpdate and breakpad gets it |
| // GooogleUpdate.exe is started with the /report to report the crash. |
| // In a crash, the temp directory and the contained files can't be deleted. |
| if (exit_code_ != GOOPDATE_E_CRASH) { |
| CleanUpTempDirectory(); |
| } |
| } |
| |
| CString ConvertByteArrayToWideCharArray(const char* input, size_t len) { |
| _ASSERTE(input != NULL); |
| CString out_str; |
| TCHAR* out = out_str.GetBufferSetLength(len); |
| for (size_t i = 0; i < len; ++i) { |
| out[i] = static_cast<TCHAR>(input[i]); |
| } |
| return out_str; |
| } |
| |
| int ExtractAndRun() { |
| if (CreateUniqueTempDirectory() != 0) { |
| return -1; |
| } |
| CString tarball_filename(ExtractTarballToTempLocation()); |
| if (tarball_filename.IsEmpty()) { |
| return -1; |
| } |
| scoped_hfile tarball_file(::CreateFile(tarball_filename, |
| GENERIC_READ, |
| FILE_SHARE_READ, |
| NULL, |
| OPEN_EXISTING, |
| 0, |
| NULL)); |
| if (!tarball_file) return -1; |
| |
| // Extract files from the archive and run the first EXE we find in it. |
| Tar tar(temp_dir_, get(tarball_file), true); |
| tar.SetCallback(TarFileCallback, this); |
| if (!tar.ExtractToDir()) { |
| return -1; |
| } |
| |
| exit_code_ = ULONG_MAX; |
| if (!exe_path_.IsEmpty()) { |
| // Build the command line. There are three scenarios we consider: |
| // 1. Run by the user, in which case the MI does not receive any |
| // argument on its command line. In this case the command line |
| // to run is: "exe_path" /install [["]manifest["]] |
| // 2. Run with command line arguments. The tag, if present, will be |
| // appended to the command line. |
| // The command line is: "exe_path" args <tag> |
| // For example, pass "/silent /install" to the metainstaller to |
| // initiate a silent install using the extra args in the tag. |
| // If a command line does not take a tag or a custom tag is needed, |
| // use an untagged file. |
| CString command_line(exe_path_); |
| ::PathQuoteSpaces(CStrBuf(command_line, MAX_PATH)); |
| |
| scoped_array<char> tag(GetTag()); |
| CString wide_tag; |
| if (tag.get()) { |
| wide_tag = ConvertByteArrayToWideCharArray(tag.get(), |
| strlen(tag.get())); |
| } |
| |
| if (cmd_line_.IsEmpty()) { |
| // Run-by-user case. |
| if (wide_tag.IsEmpty()) { |
| _ASSERTE(!"Must provide arguments with an untagged metainstaller."); |
| HRESULT hr = GOOPDATE_E_UNTAGGED_METAINSTALLER; |
| HandleError(hr); |
| return hr; |
| } |
| command_line.AppendFormat(" /%s", kCmdLineInstall); |
| } else { |
| command_line.AppendFormat(" %s", cmd_line_); |
| |
| CheckAndHandleRecoveryCase(&command_line); |
| } |
| |
| if (!wide_tag.IsEmpty()) { |
| command_line.AppendFormat(" \"%s\"", wide_tag); |
| } |
| |
| RunAndWait(command_line, &exit_code_); |
| } |
| // Propagate up the exit code of the program we have run. |
| return exit_code_; |
| } |
| |
| private: |
| void CleanUpTempDirectory() { |
| // Delete our temp directory and its contents. |
| for (int i = 0; i != files_to_delete_.GetSize(); ++i) { |
| DeleteFile(files_to_delete_[i]); |
| } |
| files_to_delete_.RemoveAll(); |
| |
| ::RemoveDirectory(temp_dir_); |
| temp_dir_.Empty(); |
| } |
| |
| // Determines whether this is a silent install. |
| bool IsSilentInstall() { |
| CString silent_argument; |
| silent_argument.Format("/%s", kCmdLineSilent); |
| |
| return silent_argument == cmd_line_; |
| } |
| |
| // Determines whether the MI is being invoked for recovery purposes, and, |
| // if so, appends the MI's full path to the command line. |
| // cmd_line_ must begin with "/recover" in order for the recovery case to be |
| // detected. |
| void CheckAndHandleRecoveryCase(CString* command_line) { |
| _ASSERTE(command_line); |
| |
| CString recover_argument; |
| recover_argument.Format("/%s", kCmdLineRecover); |
| |
| if (cmd_line_.Left(recover_argument.GetLength()) == recover_argument) { |
| TCHAR current_path[MAX_PATH] = {0}; |
| if (::GetModuleFileName(NULL, current_path, MAX_PATH - 1)) { |
| command_line->AppendFormat(" \"%s\"", current_path); |
| } |
| } |
| } |
| |
| // Create a temp directory to hold the embedded setup files. |
| // This is a bit of a hack: we ask the system to create a temporary |
| // filename for us, and instead we use that name for a subdirectory name. |
| int CreateUniqueTempDirectory() { |
| ::GetTempPath(MAX_PATH, CStrBuf(temp_root_dir_, MAX_PATH)); |
| if (::CreateDirectory(temp_root_dir_, NULL) != 0 || |
| ::GetLastError() == ERROR_ALREADY_EXISTS) { |
| if (!::GetTempFileName(temp_root_dir_, |
| _T("GUM"), |
| 0, // form a unique filename |
| CStrBuf(temp_dir_, MAX_PATH))) { |
| return -1; |
| } |
| // GetTempFileName() actually creates the temp file, so delete it. |
| ::DeleteFile(temp_dir_); |
| ::CreateDirectory(temp_dir_, NULL); |
| } else { |
| return -1; |
| } |
| return 0; |
| } |
| |
| CString ExtractTarballToTempLocation() { |
| CString tarball_filename; |
| if (::GetTempFileName(temp_root_dir_, |
| _T("GUT"), |
| 0, // form a unique filename |
| CStrBuf(tarball_filename, MAX_PATH))) { |
| files_to_delete_.Add(tarball_filename); |
| HRSRC res_info = ::FindResource(NULL, |
| MAKEINTRESOURCE(IDR_PAYLOAD), |
| _T("B")); |
| if (NULL != res_info) { |
| HGLOBAL resource = ::LoadResource(NULL, res_info); |
| if (NULL != resource) { |
| LPVOID resource_pointer = ::LockResource(resource); |
| if (NULL != resource_pointer) { |
| scoped_hfile tarball_file(::CreateFile(tarball_filename, |
| GENERIC_READ | GENERIC_WRITE, |
| 0, |
| NULL, |
| OPEN_ALWAYS, |
| 0, |
| NULL)); |
| if (valid(tarball_file)) { |
| int error = DecompressBufferToFile( |
| resource_pointer, |
| ::SizeofResource(NULL, res_info), |
| get(tarball_file)); |
| if (error == 0) { |
| return tarball_filename; |
| } |
| } |
| } |
| } |
| } |
| } |
| return CString(); |
| } |
| |
| char* GetTag() const { |
| // Get this module file name. |
| TCHAR module_file_name[MAX_PATH] = {0}; |
| if (!::GetModuleFileName(instance_, module_file_name, MAX_PATH)) { |
| _ASSERTE(false); |
| return NULL; |
| } |
| |
| return ExtractTag(module_file_name); |
| } |
| |
| static CString GetFilespec(const CString &path) { |
| int pos = path.ReverseFind('\\'); |
| if (pos != -1) { |
| return path.Mid(pos + 1); |
| } |
| return path; |
| } |
| |
| void HandleTarFile(const char *filename) { |
| CString new_filename(filename); |
| files_to_delete_.Add(new_filename); |
| CString filespec(GetFilespec(new_filename)); |
| filespec.MakeLower(); |
| |
| if (filespec.GetLength() > 4) { |
| CString extension(filespec.Mid(filespec.GetLength() - 4)); |
| |
| if (extension == ".exe") { |
| // We're interested in remembering only the first exe in the tarball. |
| if (exe_path_.IsEmpty()) { |
| exe_path_ = new_filename; |
| } |
| } |
| } |
| } |
| |
| static void TarFileCallback(void *context, const char *filename) { |
| MetaInstaller *mi = reinterpret_cast<MetaInstaller *>(context); |
| mi->HandleTarFile(filename); |
| } |
| |
| // Decompress the content of the memory buffer into the file |
| // This code stolen from jeanluc in //googleclient/bar |
| static int DecompressBufferToFile(const void *buf, |
| size_t buf_len, |
| HANDLE file) { |
| // need header and len minimally |
| if (buf_len < LZMA_PROPERTIES_SIZE + 8) { |
| return -1; |
| } |
| |
| CLzmaDecoderState decoder = {}; |
| const unsigned char *pos = reinterpret_cast<const unsigned char*>(buf); |
| |
| // get properties |
| int res_info = LzmaDecodeProperties(&decoder.Properties, pos, |
| LZMA_PROPERTIES_SIZE); |
| if (LZMA_RESULT_OK != res_info) { |
| return -1; |
| } |
| |
| // advance buffer past header |
| pos += LZMA_PROPERTIES_SIZE; |
| buf_len -= LZMA_PROPERTIES_SIZE; |
| |
| // get the length |
| ULONGLONG size; |
| memcpy(&size, pos, sizeof(size)); |
| |
| pos += sizeof(size); |
| buf_len -= sizeof(size); |
| |
| // allocate the dictionary buffer |
| CAutoVectorPtr<CProb> probs; |
| if (!probs.Allocate(LzmaGetNumProbs(&decoder.Properties))) { |
| return -1; |
| } |
| |
| CAutoVectorPtr<unsigned char> dict; |
| if (!dict.Allocate(decoder.Properties.DictionarySize)) { |
| return -1; |
| } |
| |
| // and initialize the decoder |
| decoder.Dictionary = dict.m_p; |
| decoder.Probs = probs.m_p; |
| |
| LzmaDecoderInit(&decoder); |
| |
| while (0 != size || 0 != buf_len) { |
| SizeT in_consumed = 0; |
| SizeT out_produced = 0; |
| unsigned char chunk[8192]; |
| |
| // extract a chunk - note that the decompresser barfs on us if we |
| // extract too much data from it, so make sure to bound out_len |
| // to the amount of data left. |
| SizeT out_size = std::min(static_cast<SizeT>(size), sizeof(chunk)); |
| res_info = LzmaDecode(&decoder, pos, buf_len, &in_consumed, chunk, |
| out_size, &out_produced, buf_len == 0); |
| if (LZMA_RESULT_OK != res_info) { |
| return -1; |
| } |
| |
| pos += in_consumed; |
| buf_len -= in_consumed; |
| size -= out_produced; |
| |
| DWORD written; |
| if (!::WriteFile(file, chunk, out_produced, &written, NULL)) { |
| return -1; |
| } |
| |
| if (written != out_produced) { |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| HINSTANCE instance_; |
| CString cmd_line_; |
| CString exe_path_; |
| DWORD exit_code_; |
| CSimpleArray<CString> files_to_delete_; |
| CString temp_dir_; |
| CString temp_root_dir_; |
| }; |
| |
| HRESULT CheckOSRequirements() { |
| return SystemInfo::OSWin2KSP4OrLater() ? S_OK : |
| GOOPDATE_E_RUNNING_INFERIOR_WINDOWS; |
| } |
| |
| HRESULT HandleError(HRESULT hr) { |
| CString msg_box_title; |
| CString msg_box_text; |
| |
| msg_box_title.LoadString(IDS_GENERIC_INSTALLER_DISPLAY_NAME); |
| switch (hr) { |
| case GOOPDATE_E_RUNNING_INFERIOR_WINDOWS: |
| msg_box_text.LoadString(IDS_RUNNING_INFERIOR_WINDOWS); |
| break; |
| |
| case GOOPDATE_E_UNTAGGED_METAINSTALLER: |
| default: |
| msg_box_text.LoadString(IDS_GENERIC_ERROR); |
| break; |
| } |
| |
| ::MessageBox(NULL, msg_box_text, msg_box_title, MB_OK); |
| return hr; |
| } |
| |
| } // namespace |
| |
| } // namespace omaha |
| |
| int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int) { |
| scoped_co_init init_com_apt; |
| HRESULT hr(init_com_apt.hresult()); |
| if (FAILED(hr)) { |
| return omaha::HandleError(hr); |
| } |
| |
| hr = omaha::CheckOSRequirements(); |
| if (FAILED(hr)) { |
| return omaha::HandleError(hr); |
| } |
| |
| omaha::MetaInstaller mi(hInstance, lpCmdLine); |
| int result = mi.ExtractAndRun(); |
| return result; |
| } |
| |