// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // mini_installer.exe is the first exe that is run when chrome is being // installed or upgraded. It is designed to be extremely small (~5KB with no // extra resources linked) and it has two main jobs: // 1) unpack the resources (possibly decompressing some) // 2) run the real installer (setup.exe) with appropriate flags. // // In order to be really small the app doesn't link against the CRT and // defines the following compiler/linker flags: // EnableIntrinsicFunctions="true" compiler: /Oi // BasicRuntimeChecks="0" // BufferSecurityCheck="false" compiler: /GS- // EntryPointSymbol="MainEntryPoint" linker: /ENTRY // IgnoreAllDefaultLibraries="true" linker: /NODEFAULTLIB // OptimizeForWindows98="1" liker: /OPT:NOWIN98 // linker: /SAFESEH:NO // have the linker merge the sections, saving us ~500 bytes. #pragma comment(linker, "/MERGE:.rdata=.text") #include <windows.h> #include <shellapi.h> #include "chrome/installer/mini_installer/appid.h" #include "chrome/installer/mini_installer/configuration.h" #include "chrome/installer/mini_installer/decompress.h" #include "chrome/installer/mini_installer/mini_installer.h" #include "chrome/installer/mini_installer/mini_string.h" #include "chrome/installer/mini_installer/pe_resource.h" namespace mini_installer { typedef StackString<MAX_PATH> PathString; typedef StackString<MAX_PATH * 4> CommandString; // This structure passes data back and forth for the processing // of resource callbacks. struct Context { // Input to the call back method. Specifies the dir to save resources. const wchar_t* base_path; // First output from call back method. Full path of Chrome archive. PathString* chrome_resource_path; // Second output from call back method. Full path of Setup archive/exe. PathString* setup_resource_path; }; // A helper class used to manipulate the Windows registry. Typically, members // return Windows last-error codes a la the Win32 registry API. class RegKey { public: RegKey() : key_(NULL) { } ~RegKey() { Close(); } // Opens the key named |sub_key| with given |access| rights. Returns // ERROR_SUCCESS or some other error. LONG Open(HKEY key, const wchar_t* sub_key, REGSAM access); // Returns true if a key is open. bool is_valid() const { return key_ != NULL; } // Read a REG_SZ value from the registry into the memory indicated by |value| // (of |value_size| wchar_t units). Returns ERROR_SUCCESS, // ERROR_FILE_NOT_FOUND, ERROR_MORE_DATA, or some other error. |value| is // guaranteed to be null-terminated on success. LONG ReadValue(const wchar_t* value_name, wchar_t* value, size_t value_size) const; // Write a REG_SZ value to the registry. |value| must be null-terminated. // Returns ERROR_SUCCESS or an error code. LONG WriteValue(const wchar_t* value_name, const wchar_t* value); // Closes the key if it was open. void Close(); private: RegKey(const RegKey&); RegKey& operator=(const RegKey&); HKEY key_; }; // class RegKey LONG RegKey::Open(HKEY key, const wchar_t* sub_key, REGSAM access) { Close(); return ::RegOpenKeyEx(key, sub_key, NULL, access, &key_); } LONG RegKey::ReadValue(const wchar_t* value_name, wchar_t* value, size_t value_size) const { DWORD type; DWORD byte_length = static_cast<DWORD>(value_size * sizeof(wchar_t)); LONG result = ::RegQueryValueEx(key_, value_name, NULL, &type, reinterpret_cast<BYTE*>(value), &byte_length); if (result == ERROR_SUCCESS) { if (type != REG_SZ) { result = ERROR_NOT_SUPPORTED; } else if (byte_length == 0) { *value = L'\0'; } else if (value[byte_length/sizeof(wchar_t) - 1] != L'\0') { if ((byte_length / sizeof(wchar_t)) < value_size) value[byte_length / sizeof(wchar_t)] = L'\0'; else result = ERROR_MORE_DATA; } } return result; } LONG RegKey::WriteValue(const wchar_t* value_name, const wchar_t* value) { return ::RegSetValueEx(key_, value_name, 0, REG_SZ, reinterpret_cast<const BYTE*>(value), (lstrlen(value) + 1) * sizeof(wchar_t)); } void RegKey::Close() { if (key_ != NULL) { ::RegCloseKey(key_); key_ = NULL; } } // Helper function to read a value from registry. Returns true if value // is read successfully and stored in parameter value. Returns false otherwise. // |size| is measured in wchar_t units. bool ReadValueFromRegistry(HKEY root_key, const wchar_t *sub_key, const wchar_t *value_name, wchar_t *value, size_t size) { RegKey key; if (key.Open(root_key, sub_key, KEY_QUERY_VALUE) == ERROR_SUCCESS && key.ReadValue(value_name, value, size) == ERROR_SUCCESS) { return true; } return false; } // Opens the Google Update ClientState key for a product. bool OpenClientStateKey(HKEY root_key, const wchar_t* app_guid, REGSAM access, RegKey* key) { PathString client_state_key; return client_state_key.assign(kApRegistryKeyBase) && client_state_key.append(app_guid) && (key->Open(root_key, client_state_key.get(), access | KEY_WOW64_32KEY) == ERROR_SUCCESS); } // This function sets the flag in registry to indicate that Google Update // should try full installer next time. If the current installer works, this // flag is cleared by setup.exe at the end of install. The flag will by default // be written to HKCU, but if --system-level is included in the command line, // it will be written to HKLM instead. // TODO(grt): Write a unit test for this that uses registry virtualization. void SetInstallerFlags(const Configuration& configuration) { RegKey key; const REGSAM key_access = KEY_QUERY_VALUE | KEY_SET_VALUE; const HKEY root_key = configuration.is_system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; // This is ignored if multi-install is true. const wchar_t* app_guid = configuration.has_chrome_frame() ? google_update::kChromeFrameAppGuid : configuration.chrome_app_guid(); StackString<128> value; LONG ret; // When multi_install is true, we are potentially: // 1. Performing a multi-install of some product(s) on a clean machine. // Neither the product(s) nor the multi-installer will have a ClientState // key in the registry, so there is nothing to be done. // 2. Upgrading an existing multi-install. The multi-installer will have a // ClientState key in the registry. Only it need be modified. // 3. Migrating a single-install into a multi-install. The product will have // a ClientState key in the registry. Only it need be modified. // To handle all cases, we inspect the product's ClientState to see if it // exists and its "ap" value does not contain "-multi". This is case 3, so we // modify the product's ClientState. Otherwise, we check the // multi-installer's ClientState and modify it if it exists. if (configuration.is_multi_install()) { if (OpenClientStateKey(root_key, app_guid, key_access, &key)) { // The product has a client state key. See if it's a single-install. ret = key.ReadValue(kApRegistryValueName, value.get(), value.capacity()); if (ret != ERROR_FILE_NOT_FOUND && (ret != ERROR_SUCCESS || FindTagInStr(value.get(), kMultiInstallTag, NULL))) { // Error or case 2: modify the multi-installer's value. key.Close(); app_guid = google_update::kMultiInstallAppGuid; } // else case 3: modify this value. } else { // case 1 or 2: modify the multi-installer's value. key.Close(); app_guid = google_update::kMultiInstallAppGuid; } } if (!key.is_valid()) { if (!OpenClientStateKey(root_key, app_guid, key_access, &key)) return; value.clear(); ret = key.ReadValue(kApRegistryValueName, value.get(), value.capacity()); } // The conditions below are handling two cases: // 1. When ap value is present, we want to add the required tag only if it is // not present. // 2. When ap value is missing, we are going to create it with the required // tag. if ((ret == ERROR_SUCCESS) || (ret == ERROR_FILE_NOT_FOUND)) { if (ret == ERROR_FILE_NOT_FOUND) value.clear(); if (!StrEndsWith(value.get(), kFullInstallerSuffix) && value.append(kFullInstallerSuffix)) { key.WriteValue(kApRegistryValueName, value.get()); } } } // Gets the setup.exe path from Registry by looking the value of Uninstall // string. |size| is measured in wchar_t units. bool GetSetupExePathForGuidFromRegistry(bool system_level, const wchar_t* app_guid, wchar_t* path, size_t size) { const HKEY root_key = system_level ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; RegKey key; return OpenClientStateKey(root_key, app_guid, KEY_QUERY_VALUE, &key) && (key.ReadValue(kUninstallRegistryValueName, path, size) == ERROR_SUCCESS); } // Gets the setup.exe path from Registry by looking the value of Uninstall // string. |size| is measured in wchar_t units. bool GetSetupExePathFromRegistry(const Configuration& configuration, wchar_t* path, size_t size) { bool system_level = configuration.is_system_level(); // If this is a multi install, first try looking in the binaries for the path. if (configuration.is_multi_install() && GetSetupExePathForGuidFromRegistry( system_level, google_update::kMultiInstallAppGuid, path, size)) { return true; } // Failing that, look in Chrome Frame's client state key if --chrome-frame was // specified. if (configuration.has_chrome_frame() && GetSetupExePathForGuidFromRegistry( system_level, google_update::kChromeFrameAppGuid, path, size)) { return true; } // Make a last-ditch effort to look in the Chrome and App Host client state // keys. if (GetSetupExePathForGuidFromRegistry( system_level, configuration.chrome_app_guid(), path, size)) { return true; } if (configuration.has_app_host() && GetSetupExePathForGuidFromRegistry( system_level, google_update::kChromeAppHostAppGuid, path, size)) { return true; } return false; } // Calls CreateProcess with good default parameters and waits for the process // to terminate returning the process exit code. bool RunProcessAndWait(const wchar_t* exe_path, wchar_t* cmdline, int* exit_code) { STARTUPINFOW si = {sizeof(si)}; PROCESS_INFORMATION pi = {0}; if (!::CreateProcess(exe_path, cmdline, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) { return false; } ::CloseHandle(pi.hThread); bool ret = true; DWORD wr = ::WaitForSingleObject(pi.hProcess, INFINITE); if (WAIT_OBJECT_0 != wr) { ret = false; } else if (exit_code) { if (!::GetExitCodeProcess(pi.hProcess, reinterpret_cast<DWORD*>(exit_code))) { ret = false; } } ::CloseHandle(pi.hProcess); return ret; } // Append any command line params passed to mini_installer to the given buffer // so that they can be passed on to setup.exe. We do not return any error from // this method and simply skip making any changes in case of error. void AppendCommandLineFlags(const Configuration& configuration, CommandString* buffer) { PathString full_exe_path; size_t len = ::GetModuleFileName(NULL, full_exe_path.get(), full_exe_path.capacity()); if (!len || len >= full_exe_path.capacity()) return; const wchar_t* exe_name = GetNameFromPathExt(full_exe_path.get(), len); if (exe_name == NULL) return; const wchar_t* cmd_to_append = L""; if (!StrEndsWith(configuration.program(), exe_name)) { // Current executable name not in the command line so just append // the whole command line. cmd_to_append = configuration.command_line(); } else if (configuration.argument_count() > 1) { const wchar_t* tmp = SearchStringI(configuration.command_line(), exe_name); tmp = SearchStringI(tmp, L" "); cmd_to_append = tmp; } buffer->append(cmd_to_append); } // Windows defined callback used in the EnumResourceNames call. For each // matching resource found, the callback is invoked and at this point we write // it to disk. We expect resource names to start with 'chrome' or 'setup'. Any // other name is treated as an error. BOOL CALLBACK OnResourceFound(HMODULE module, const wchar_t* type, wchar_t* name, LONG_PTR context) { if (NULL == context) return FALSE; Context* ctx = reinterpret_cast<Context*>(context); PEResource resource(name, type, module); if ((!resource.IsValid()) || (resource.Size() < 1) || (resource.Size() > kMaxResourceSize)) { return FALSE; } PathString full_path; if (!full_path.assign(ctx->base_path) || !full_path.append(name) || !resource.WriteToDisk(full_path.get())) return FALSE; if (StrStartsWith(name, kChromePrefix)) { if (!ctx->chrome_resource_path->assign(full_path.get())) return FALSE; } else if (StrStartsWith(name, kSetupPrefix)) { if (!ctx->setup_resource_path->assign(full_path.get())) return FALSE; } else { // Resources should either start with 'chrome' or 'setup'. We don't handle // anything else. return FALSE; } return TRUE; } // Finds and writes to disk resources of various types. Returns false // if there is a problem in writing any resource to disk. setup.exe resource // can come in one of three possible forms: // - Resource type 'B7', compressed using LZMA (*.7z) // - Resource type 'BL', compressed using LZ (*.ex_) // - Resource type 'BN', uncompressed (*.exe) // If setup.exe is present in more than one form, the precedence order is // BN < BL < B7 // For more details see chrome/tools/build/win/create_installer_archive.py. bool UnpackBinaryResources(const Configuration& configuration, HMODULE module, const wchar_t* base_path, PathString* archive_path, PathString* setup_path) { // Generate the setup.exe path where we patch/uncompress setup resource. PathString setup_dest_path; if (!setup_dest_path.assign(base_path) || !setup_dest_path.append(kSetupName)) return false; // Prepare the input to OnResourceFound method that needs a location where // it will write all the resources. Context context = { base_path, archive_path, setup_path, }; // Get the resources of type 'B7' (7zip archive). // We need a chrome archive to do the installation. So if there // is a problem in fetching B7 resource, just return an error. if (!::EnumResourceNames(module, kLZMAResourceType, OnResourceFound, reinterpret_cast<LONG_PTR>(&context)) || archive_path->length() == 0) return false; // If we found setup 'B7' resource, handle it. if (setup_path->length() > 0) { CommandString cmd_line; // Get the path to setup.exe first. bool success = true; if (!GetSetupExePathFromRegistry(configuration, cmd_line.get(), cmd_line.capacity()) || !cmd_line.append(kCmdUpdateSetupExe) || !cmd_line.append(L"=\"") || !cmd_line.append(setup_path->get()) || !cmd_line.append(L"\"") || !cmd_line.append(kCmdNewSetupExe) || !cmd_line.append(L"=\"") || !cmd_line.append(setup_dest_path.get()) || !cmd_line.append(L"\"")) { success = false; } // Get any command line option specified for mini_installer and pass them // on to setup.exe. This is important since switches such as // --multi-install and --chrome-frame affect where setup.exe will write // installer results for consumption by Google Update. AppendCommandLineFlags(configuration, &cmd_line); int exit_code = 0; if (success && (!RunProcessAndWait(NULL, cmd_line.get(), &exit_code) || exit_code != ERROR_SUCCESS)) { success = false; } if (!success) DeleteFile(setup_path->get()); return success && setup_path->assign(setup_dest_path.get()); } // setup.exe wasn't sent as 'B7', lets see if it was sent as 'BL' // (compressed setup). if (!::EnumResourceNames(module, kLZCResourceType, OnResourceFound, reinterpret_cast<LONG_PTR>(&context)) && ::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND) return false; if (setup_path->length() > 0) { // Uncompress LZ compressed resource. Setup is packed with 'MSCF' // as opposed to old DOS way of 'SZDD'. Hence we don't use LZCopy. bool success = mini_installer::Expand(setup_path->get(), setup_dest_path.get()); ::DeleteFile(setup_path->get()); if (success) { if (!setup_path->assign(setup_dest_path.get())) { ::DeleteFile(setup_dest_path.get()); success = false; } } return success; } // setup.exe still not found. So finally check if it was sent as 'BN' // (uncompressed setup). // TODO(tommi): We don't need BN anymore so let's remove it (and remove // it from create_installer_archive.py). if (!::EnumResourceNames(module, kBinResourceType, OnResourceFound, reinterpret_cast<LONG_PTR>(&context)) && ::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND) return false; if (setup_path->length() > 0) { if (setup_path->comparei(setup_dest_path.get()) != 0) { if (!::MoveFileEx(setup_path->get(), setup_dest_path.get(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING)) { ::DeleteFile(setup_path->get()); setup_path->clear(); } else if (!setup_path->assign(setup_dest_path.get())) { ::DeleteFile(setup_dest_path.get()); } } } return setup_path->length() > 0; } // Executes setup.exe, waits for it to finish and returns the exit code. bool RunSetup(const Configuration& configuration, const wchar_t* archive_path, const wchar_t* setup_path, int* exit_code) { // There could be three full paths in the command line for setup.exe (path // to exe itself, path to archive and path to log file), so we declare // total size as three + one additional to hold command line options. CommandString cmd_line; // Get the path to setup.exe first. if (::lstrlen(setup_path) > 0) { if (!cmd_line.assign(L"\"") || !cmd_line.append(setup_path) || !cmd_line.append(L"\"")) return false; } else if (!GetSetupExePathFromRegistry(configuration, cmd_line.get(), cmd_line.capacity())) { return false; } // Append the command line param for chrome archive file if (!cmd_line.append(kCmdInstallArchive) || !cmd_line.append(L"=\"") || !cmd_line.append(archive_path) || !cmd_line.append(L"\"")) return false; // Get any command line option specified for mini_installer and pass them // on to setup.exe AppendCommandLineFlags(configuration, &cmd_line); return RunProcessAndWait(NULL, cmd_line.get(), exit_code); } // Deletes given files and working dir. void DeleteExtractedFiles(const wchar_t* base_path, const wchar_t* archive_path, const wchar_t* setup_path) { ::DeleteFile(archive_path); ::DeleteFile(setup_path); // Delete the temp dir (if it is empty, otherwise fail). ::RemoveDirectory(base_path); } // Creates a temporary directory under |base_path| and returns the full path // of created directory in |work_dir|. If successful return true, otherwise // false. When successful, the returned |work_dir| will always have a trailing // backslash and this function requires that |base_path| always includes a // trailing backslash as well. // We do not use GetTempFileName here to avoid running into AV software that // might hold on to the temp file as soon as we create it and then we can't // delete it and create a directory in its place. So, we use our own mechanism // for creating a directory with a hopefully-unique name. In the case of a // collision, we retry a few times with a new name before failing. bool CreateWorkDir(const wchar_t* base_path, PathString* work_dir) { if (!work_dir->assign(base_path) || !work_dir->append(kTempPrefix)) return false; // Store the location where we'll append the id. size_t end = work_dir->length(); // Check if we'll have enough buffer space to continue. // The name of the directory will use up 11 chars and then we need to append // the trailing backslash and a terminator. We've already added the prefix // to the buffer, so let's just make sure we've got enough space for the rest. if ((work_dir->capacity() - end) < (arraysize("fffff.tmp") + 1)) return false; // Generate a unique id. We only use the lowest 20 bits, so take the top // 12 bits and xor them with the lower bits. DWORD id = ::GetTickCount(); id ^= (id >> 12); int max_attempts = 10; while (max_attempts--) { // This converts 'id' to a string in the format "78563412" on windows // because of little endianness, but we don't care since it's just // a name. if (!HexEncode(&id, sizeof(id), work_dir->get() + end, work_dir->capacity() - end)) { return false; } // We only want the first 5 digits to remain within the 8.3 file name // format (compliant with previous implementation). work_dir->truncate_at(end + 5); // for consistency with the previous implementation which relied on // GetTempFileName, we append the .tmp extension. work_dir->append(L".tmp"); if (::CreateDirectory(work_dir->get(), NULL)) { // Yay! Now let's just append the backslash and we're done. return work_dir->append(L"\\"); } ++id; // Try a different name. } return false; } // Creates and returns a temporary directory that can be used to extract // mini_installer payload. bool GetWorkDir(HMODULE module, PathString* work_dir) { PathString base_path; DWORD len = ::GetTempPath(base_path.capacity(), base_path.get()); if (!len || len >= base_path.capacity() || !CreateWorkDir(base_path.get(), work_dir)) { // Problem creating the work dir under TEMP path, so try using the // current directory as the base path. len = ::GetModuleFileName(module, base_path.get(), base_path.capacity()); if (len >= base_path.capacity() || !len) return false; // Can't even get current directory? Return an error. wchar_t* name = GetNameFromPathExt(base_path.get(), len); if (!name) return false; *name = L'\0'; return CreateWorkDir(base_path.get(), work_dir); } return true; } // Returns true for ".." and "." directories. bool IsCurrentOrParentDirectory(const wchar_t* dir) { return dir && dir[0] == L'.' && (dir[1] == L'\0' || (dir[1] == L'.' && dir[2] == L'\0')); } // Best effort directory tree deletion including the directory specified // by |path|, which must not end in a separator. // The |path| argument is writable so that each recursion can use the same // buffer as was originally allocated for the path. The path will be unchanged // upon return. void RecursivelyDeleteDirectory(PathString* path) { // |path| will never have a trailing backslash. size_t end = path->length(); if (!path->append(L"\\*.*")) return; WIN32_FIND_DATA find_data = {0}; HANDLE find = ::FindFirstFile(path->get(), &find_data); if (find != INVALID_HANDLE_VALUE) { do { // Use the short name if available to make the most of our buffer. const wchar_t* name = find_data.cAlternateFileName[0] ? find_data.cAlternateFileName : find_data.cFileName; if (IsCurrentOrParentDirectory(name)) continue; path->truncate_at(end + 1); // Keep the trailing backslash. if (!path->append(name)) continue; // Continue in spite of too long names. if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { RecursivelyDeleteDirectory(path); } else { ::DeleteFile(path->get()); } } while (::FindNextFile(find, &find_data)); ::FindClose(find); } // Restore the path and delete the directory before we return. path->truncate_at(end); ::RemoveDirectory(path->get()); } // Enumerates subdirectories of |parent_dir| and deletes all subdirectories // that match with a given |prefix|. |parent_dir| must have a trailing // backslash. // The process is done on a best effort basis, so conceivably there might // still be matches left when the function returns. void DeleteDirectoriesWithPrefix(const wchar_t* parent_dir, const wchar_t* prefix) { // |parent_dir| is guaranteed to always have a trailing backslash. PathString spec; if (!spec.assign(parent_dir) || !spec.append(prefix) || !spec.append(L"*.*")) return; WIN32_FIND_DATA find_data = {0}; HANDLE find = ::FindFirstFileEx(spec.get(), FindExInfoStandard, &find_data, FindExSearchLimitToDirectories, NULL, 0); if (find == INVALID_HANDLE_VALUE) return; PathString path; do { if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { // Use the short name if available to make the most of our buffer. const wchar_t* name = find_data.cAlternateFileName[0] ? find_data.cAlternateFileName : find_data.cFileName; if (IsCurrentOrParentDirectory(name)) continue; if (path.assign(parent_dir) && path.append(name)) RecursivelyDeleteDirectory(&path); } } while (::FindNextFile(find, &find_data)); ::FindClose(find); } // Attempts to free up space by deleting temp directories that previous // installer runs have failed to clean up. void DeleteOldChromeTempDirectories() { static const wchar_t* const kDirectoryPrefixes[] = { kTempPrefix, L"chrome_" // Previous installers created directories with this prefix // and there are still some lying around. }; PathString temp; // GetTempPath always returns a path with a trailing backslash. DWORD len = ::GetTempPath(temp.capacity(), temp.get()); // GetTempPath returns 0 or number of chars copied, not including the // terminating '\0'. if (!len || len >= temp.capacity()) return; for (int i = 0; i < arraysize(kDirectoryPrefixes); ++i) { DeleteDirectoriesWithPrefix(temp.get(), kDirectoryPrefixes[i]); } } // Checks the command line for specific mini installer flags. // If the function returns true, the command line has been processed and all // required actions taken. The installer must exit and return the returned // |exit_code|. bool ProcessNonInstallOperations(const Configuration& configuration, int* exit_code) { bool ret = false; switch (configuration.operation()) { case Configuration::CLEANUP: // Cleanup has already taken place in DeleteOldChromeTempDirectories at // this point, so just tell our caller to exit early. *exit_code = 0; ret = true; break; default: break; } return ret; } // Returns true if we should delete the temp files we create (default). // Returns false iff the user has manually created a ChromeInstallerCleanup // string value in the registry under HKCU\\Software\\[Google|Chromium] // and set its value to "0". That explicitly forbids the mini installer from // deleting these files. // Support for this has been publicly mentioned in troubleshooting tips so // we continue to support it. bool ShouldDeleteExtractedFiles() { wchar_t value[2] = {0}; if (ReadValueFromRegistry(HKEY_CURRENT_USER, kCleanupRegistryKey, kCleanupRegistryValueName, value, arraysize(value)) && value[0] == L'0') { return false; } return true; } // Main function. First gets a working dir, unpacks the resources and finally // executes setup.exe to do the install/upgrade. int WMain(HMODULE module) { #if defined(COMPONENT_BUILD) if (::GetEnvironmentVariable(L"MINI_INSTALLER_TEST", NULL, 0) == 0) { static const wchar_t kComponentBuildIncompatibleMessage[] = L"mini_installer.exe is incompatible with the component build, please" L" run setup.exe with the same command line instead. See" L" http://crbug.com/127233#c17 for details."; ::MessageBox(NULL, kComponentBuildIncompatibleMessage, NULL, MB_ICONERROR); return 1; } #endif // Always start with deleting potential leftovers from previous installations. // This can make the difference between success and failure. We've seen // many installations out in the field fail due to out of disk space problems // so this could buy us some space. DeleteOldChromeTempDirectories(); // TODO(grt): Make the exit codes more granular so we know where the popular // errors truly are. int exit_code = 101; // Parse the command line. Configuration configuration; if (!configuration.Initialize()) return exit_code; if (configuration.query_component_build()) { // Exit immediately with an exit code of 1 to indicate component build and 0 // to indicate static build. This is used by the tests in // /src/chrome/test/mini_installer/. #if defined(COMPONENT_BUILD) return 1; #else return 0; #endif } // If the --cleanup switch was specified on the command line, then that means // we should only do the cleanup and then exit. if (ProcessNonInstallOperations(configuration, &exit_code)) return exit_code; // First get a path where we can extract payload PathString base_path; if (!GetWorkDir(module, &base_path)) return 101; #if defined(GOOGLE_CHROME_BUILD) // Set the magic suffix in registry to try full installer next time. We ignore // any errors here and we try to set the suffix for user level unless // --system-level is on the command line in which case we set it for system // level instead. This only applies to the Google Chrome distribution. SetInstallerFlags(configuration); #endif PathString archive_path; PathString setup_path; if (!UnpackBinaryResources(configuration, module, base_path.get(), &archive_path, &setup_path)) { exit_code = 102; } else { // While unpacking the binaries, we paged in a whole bunch of memory that // we don't need anymore. Let's give it back to the pool before running // setup. ::SetProcessWorkingSetSize(::GetCurrentProcess(), -1, -1); if (!RunSetup(configuration, archive_path.get(), setup_path.get(), &exit_code)) { exit_code = 103; } } if (ShouldDeleteExtractedFiles()) DeleteExtractedFiles(base_path.get(), archive_path.get(), setup_path.get()); return exit_code; } } // namespace mini_installer int MainEntryPoint() { int result = mini_installer::WMain(::GetModuleHandle(NULL)); ::ExitProcess(result); } // VC Express editions don't come with the memset CRT obj file and linking to // the obj files between versions becomes a bit problematic. Therefore, // simply implement memset. // // This also avoids having to explicitly set the __sse2_available hack when // linking with both the x64 and x86 obj files which is required when not // linking with the std C lib in certain instances (including Chromium) with // MSVC. __sse2_available determines whether to use SSE2 intructions with // std C lib routines, and is set by MSVC's std C lib implementation normally. extern "C" { #pragma function(memset) void* memset(void* dest, int c, size_t count) { void* start = dest; while (count--) { *reinterpret_cast<char*>(dest) = static_cast<char>(c); dest = reinterpret_cast<char*>(dest) + 1; } return start; } } // extern "C"