diff options
author | tommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-16 20:43:26 +0000 |
---|---|---|
committer | tommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-03-16 20:43:26 +0000 |
commit | c66931ce8606697a5c500b02dff1ef2e15fdbf75 (patch) | |
tree | bef1128adcdcfe6ba7c0de287c8b7173e16da101 | |
parent | b5d13670b02672a8eb4c77115b42e7f4e1a1dbf4 (diff) | |
download | chromium_src-c66931ce8606697a5c500b02dff1ef2e15fdbf75.zip chromium_src-c66931ce8606697a5c500b02dff1ef2e15fdbf75.tar.gz chromium_src-c66931ce8606697a5c500b02dff1ef2e15fdbf75.tar.bz2 |
Clean up temporary files and folders left over from previous installations.
Installation failures due to out-of-disk-space seem to be getting more
and more frequent and upon inspection, I found that we can in some cases
do a terrible job and leave cruft behind. Worst of all we can do this if
an installation fails due to low free disk space, thus only adding to the
problem. I suspect there maybe users stuck on a specific revision of
Chrome because of this.
I'm also implementing Robert's idea of supporting for a --cleanup command
line arg that can be passed to the mini installer to only do the cleanup
and not the installation.
BUG=74777
TEST=This should reduce the number of crashes we're getting due to out of disk space.
Review URL: http://codereview.chromium.org/6670023
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@78424 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/chrome_installer.gypi | 3 | ||||
-rw-r--r-- | chrome/installer/mini_installer.gyp | 4 | ||||
-rw-r--r-- | chrome/installer/mini_installer/mini_installer.cc | 649 | ||||
-rw-r--r-- | chrome/installer/mini_installer/mini_installer.h | 15 | ||||
-rw-r--r-- | chrome/installer/mini_installer/mini_string.cc | 162 | ||||
-rw-r--r-- | chrome/installer/mini_installer/mini_string.h | 147 | ||||
-rw-r--r-- | chrome/installer/mini_installer/mini_string_test.cc | 77 |
7 files changed, 784 insertions, 273 deletions
diff --git a/chrome/chrome_installer.gypi b/chrome/chrome_installer.gypi index e270f7d..1e41e30 100644 --- a/chrome/chrome_installer.gypi +++ b/chrome/chrome_installer.gypi @@ -341,6 +341,9 @@ # below into a separate lib and then link both setup.exe and # setup_unittests.exe against that. 'sources': [ + 'installer/mini_installer/mini_string.cc', + 'installer/mini_installer/mini_string.h', + 'installer/mini_installer/mini_string_test.cc', 'installer/setup/install_worker.cc', # Move to lib 'installer/setup/install_worker.h', # Move to lib 'installer/setup/install_worker_unittest.cc', diff --git a/chrome/installer/mini_installer.gyp b/chrome/installer/mini_installer.gyp index f58911d..7ab32c6 100644 --- a/chrome/installer/mini_installer.gyp +++ b/chrome/installer/mini_installer.gyp @@ -31,6 +31,8 @@ 'mini_installer/mini_installer.rc', 'mini_installer/mini_installer_exe_version.rc.version', 'mini_installer/mini_installer_resource.h', + 'mini_installer/mini_string.cc', + 'mini_installer/mini_string.h', 'mini_installer/pe_resource.cc', 'mini_installer/pe_resource.h', ], @@ -65,14 +67,12 @@ 'crt\\src\\intel\\mt_lib\\memset.obj"', '"$(VCInstallDir)..\\..\\Microsoft Visual Studio 9.0\\VC\\' 'crt\\src\\intel\\mt_lib\\P4_memset.obj"', - 'shlwapi.lib', 'setupapi.lib', ], },{ 'AdditionalDependencies': [ '"$(VCInstallDir)crt\\src\\intel\\mt_lib\\memset.obj"', '"$(VCInstallDir)crt\\src\\intel\\mt_lib\\P4_memset.obj"', - 'shlwapi.lib', 'setupapi.lib', ], }], diff --git a/chrome/installer/mini_installer/mini_installer.cc b/chrome/installer/mini_installer/mini_installer.cc index a898f30..a06fa9a 100644 --- a/chrome/installer/mini_installer/mini_installer.cc +++ b/chrome/installer/mini_installer/mini_installer.cc @@ -31,30 +31,34 @@ #include <windows.h> #include <setupapi.h> #include <shellapi.h> -#include <shlwapi.h> #include "chrome/installer/mini_installer/appid.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" +// arraysize borrowed from basictypes.h +template <typename T, size_t N> +char (&ArraySizeHelper(T (&array)[N]))[N]; +#define arraysize(array) (sizeof(ArraySizeHelper(array))) + // Required linker symbol. See remarks above. extern "C" unsigned int __sse2_available = 0; 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. - wchar_t* chrome_resource_path; - // Size of chrome_resource_path buffer - size_t chrome_resource_path_size; + PathString* chrome_resource_path; // Second output from call back method. Full path of Setup archive/exe. - wchar_t* setup_resource_path; - // Size of setup_resource_path buffer - size_t setup_resource_path_size; + PathString* setup_resource_path; }; // A helper class used to manipulate the Windows registry. Typically, members @@ -134,97 +138,6 @@ void RegKey::Close() { } } -// Returns true if the given two ASCII characters are same (ignoring case). -bool EqualASCIICharI(wchar_t a, wchar_t b) { - if (a >= L'A' && a <= L'Z') - a = a + (L'a' - L'A'); - if (b >= L'A' && b <= L'Z') - b = b + (L'a' - L'A'); - return (a == b); -} - -// Takes the path to file and returns a pointer to the filename component. For -// exmaple for input of c:\full\path\to\file.ext it returns pointer to file.ext. -// It returns NULL if extension or path separator is not found. -wchar_t* GetNameFromPathExt(wchar_t* path, size_t size) { - if (size <= 1) - return NULL; - - wchar_t* current = &path[size - 1]; - while (current != path && L'\\' != *current) - --current; - - return (current == path) ? NULL : (current + 1); -} - - -// Simple replacement for CRT string copy method that does not overflow. -// Returns true if the source was copied successfully otherwise returns false. -// Parameter src is assumed to be NULL terminated and the NULL character is -// copied over to string dest. -bool SafeStrCopy(wchar_t* dest, size_t dest_size, const wchar_t* src) { - for (size_t length = 0; length < dest_size; ++dest, ++src, ++length) { - *dest = *src; - if (L'\0' == *src) - return true; - } - return false; -} - -// Safer replacement for lstrcat function. -bool SafeStrCat(wchar_t* dest, size_t dest_size, const wchar_t* src) { - int str_len = ::lstrlen(dest); - return SafeStrCopy(dest + str_len, dest_size - str_len, src); -} - -// Function to check if a string (specified by str) ends with another string -// (specified by end_str). -bool StrEndsWith(const wchar_t* str, const wchar_t* end_str) { - if (str == NULL || end_str == NULL) - return false; - - for (int i = lstrlen(str) - 1, j = lstrlen(end_str) - 1; j >= 0; --i, --j) { - if (i < 0 || !EqualASCIICharI(str[i], end_str[j])) - return false; - } - - return true; -} - -// Function to check if a string (specified by str) starts with another string -// (specified by start_str). -bool StrStartsWith(const wchar_t* str, const wchar_t* start_str) { - if (str == NULL || start_str == NULL) - return false; - - for (int i = 0; start_str[i] != L'\0'; ++i) { - if (!EqualASCIICharI(str[i], start_str[i])) - return false; - } - - return true; -} - -// Searches for |tag| within |str|. Returns true if |tag| is found and is -// immediately followed by '-' or is at the end of the string. If |position| -// is non-NULL, the location of the tag is returned in |*position| on success. -bool FindTagInStr(const wchar_t* str, - const wchar_t* tag, - const wchar_t** position) { - int tag_length = ::lstrlen(tag); - const wchar_t* scan = str; - for (const wchar_t* tag_start = StrStrI(scan, tag); tag_start != NULL; - tag_start = StrStrI(scan, tag)) { - scan = tag_start + tag_length; - if (*scan == L'-' || *scan == L'\0') { - if (position != NULL) - *position = tag_start; - return true; - } - } - return false; -} - // 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. @@ -243,12 +156,10 @@ bool ReadValueFromRegistry(HKEY root_key, const wchar_t *sub_key, // Opens the Google Update ClientState key for a product. bool OpenClientStateKey(HKEY root_key, const wchar_t* app_guid, REGSAM access, RegKey* key) { - wchar_t client_state_key[128]; - - return SafeStrCopy(client_state_key, _countof(client_state_key), - kApRegistryKeyBase) && - SafeStrCat(client_state_key, _countof(client_state_key), app_guid) && - (key->Open(root_key, client_state_key, access) == ERROR_SUCCESS); + 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) == ERROR_SUCCESS); } // TODO(grt): Write a unit test for this that uses registry virtualization. @@ -258,7 +169,7 @@ void SetInstallerFlagsHelper(int args_num, const wchar_t* const* args) { const REGSAM key_access = KEY_QUERY_VALUE | KEY_SET_VALUE; const wchar_t* app_guid = google_update::kAppGuid; HKEY root_key = HKEY_CURRENT_USER; - wchar_t value[128]; + StackString<128> value; LONG ret; for (int i = 1; i < args_num; ++i) { @@ -287,10 +198,10 @@ void SetInstallerFlagsHelper(int args_num, const wchar_t* const* args) { if (multi_install) { if (OpenClientStateKey(root_key, app_guid, key_access, &key)) { // The app is installed. See if it's a single-install. - ret = key.ReadValue(kApRegistryValueName, value, _countof(value)); + ret = key.ReadValue(kApRegistryValueName, value.get(), value.capacity()); if (ret != ERROR_FILE_NOT_FOUND && (ret != ERROR_SUCCESS || - FindTagInStr(value, kMultiInstallTag, NULL))) { + FindTagInStr(value.get(), kMultiInstallTag, NULL))) { // Error or case 2: modify the multi-installer's value. key.Close(); app_guid = google_update::kMultiInstallAppGuid; @@ -306,7 +217,8 @@ void SetInstallerFlagsHelper(int args_num, const wchar_t* const* args) { if (!OpenClientStateKey(root_key, app_guid, key_access, &key)) return; - ret = key.ReadValue(kApRegistryValueName, value, _countof(value)); + value.clear(); + ret = key.ReadValue(kApRegistryValueName, value.get(), value.capacity()); } // The conditions below are handling two cases: @@ -316,24 +228,25 @@ void SetInstallerFlagsHelper(int args_num, const wchar_t* const* args) { // tags. if ((ret == ERROR_SUCCESS) || (ret == ERROR_FILE_NOT_FOUND)) { if (ret == ERROR_FILE_NOT_FOUND) - value[0] = L'\0'; + value.clear(); bool success = true; if (multi_install && - !FindTagInStr(value, kMultifailInstallerSuffix, NULL)) { + !FindTagInStr(value.get(), kMultifailInstallerSuffix, NULL)) { // We want -multifail to immediately precede -full. Chop off the latter // if it's already present so that we can simply do two appends. - if (StrEndsWith(value, kFullInstallerSuffix)) { - int suffix_len = ::lstrlen(kFullInstallerSuffix); - int value_len = ::lstrlen(value); - value[value_len - suffix_len] = L'\0'; + if (StrEndsWith(value.get(), kFullInstallerSuffix)) { + size_t suffix_len = (arraysize(kFullInstallerSuffix) - 1); + size_t value_len = value.length(); + value.truncate_at(value_len - suffix_len); } - success = SafeStrCat(value, _countof(value), kMultifailInstallerSuffix); + success = value.append(kMultifailInstallerSuffix); + } + if (success && !StrEndsWith(value.get(), kFullInstallerSuffix) && + (value.append(kFullInstallerSuffix))) { + key.WriteValue(kApRegistryValueName, value.get()); } - if (success && !StrEndsWith(value, kFullInstallerSuffix) && - (SafeStrCat(value, _countof(value), kFullInstallerSuffix))) - key.WriteValue(kApRegistryValueName, value); } } @@ -355,7 +268,7 @@ void SetInstallerFlags() { // Gets the setup.exe path from Registry by looking the value of Uninstall // string, strips the arguments for uninstall and returns only the full path // to setup.exe. |size| is measured in wchar_t units. -bool GetSetupExePathFromRegistry(wchar_t *path, size_t size) { +bool GetSetupExePathFromRegistry(wchar_t* path, size_t size) { if (!ReadValueFromRegistry(HKEY_CURRENT_USER, kUninstallRegistryKey, kUninstallRegistryValueName, path, size)) { if (!ReadValueFromRegistry(HKEY_LOCAL_MACHINE, kUninstallRegistryKey, @@ -363,7 +276,7 @@ bool GetSetupExePathFromRegistry(wchar_t *path, size_t size) { return false; } } - wchar_t *tmp = StrStr(path, L" --"); + wchar_t* tmp = const_cast<wchar_t*>(SearchStringI(path, L" --")); if (tmp) { *tmp = L'\0'; } else { @@ -373,7 +286,6 @@ bool GetSetupExePathFromRegistry(wchar_t *path, size_t size) { return true; } - // 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, @@ -381,21 +293,25 @@ bool RunProcessAndWait(const wchar_t* exe_path, wchar_t* cmdline, 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; - - DWORD wr = ::WaitForSingleObject(pi.hProcess, INFINITE); - if (WAIT_OBJECT_0 != wr) + NULL, NULL, &si, &pi)) { return false; + } + + ::CloseHandle(pi.hThread); bool ret = true; - if (exit_code) { + 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))) + reinterpret_cast<DWORD*>(exit_code))) { ret = false; + } } + ::CloseHandle(pi.hProcess); - ::CloseHandle(pi.hThread); + return ret; } @@ -406,9 +322,9 @@ bool RunProcessAndWait(const wchar_t* exe_path, wchar_t* cmdline, // 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) { + if (NULL == context) return FALSE; - } + Context* ctx = reinterpret_cast<Context*>(context); PEResource resource(name, type, module); @@ -418,22 +334,20 @@ BOOL CALLBACK OnResourceFound(HMODULE module, const wchar_t* type, return FALSE; } - wchar_t full_path[MAX_PATH]; - if (!SafeStrCopy(full_path, _countof(full_path), ctx->base_path) || - !SafeStrCat(full_path, _countof(full_path), name) || - !resource.WriteToDisk(full_path)) + 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 (!SafeStrCopy(ctx->chrome_resource_path, - ctx->chrome_resource_path_size, full_path)) + if (!ctx->chrome_resource_path->assign(full_path.get())) return FALSE; } else if (StrStartsWith(name, kSetupPrefix)) { - if (!SafeStrCopy(ctx->setup_resource_path, - ctx->setup_resource_path_size, full_path)) + if (!ctx->setup_resource_path->assign(full_path.get())) return FALSE; } else { - // Resources should either start with 'chrome' or 'setup'. We dont handle + // Resources should either start with 'chrome' or 'setup'. We don't handle // anything else. return FALSE; } @@ -449,108 +363,121 @@ BOOL CALLBACK OnResourceFound(HMODULE module, const wchar_t* type, // - 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(HMODULE module, const wchar_t* base_path, - wchar_t* archive_path, size_t archive_path_size, - wchar_t* setup_path, size_t setup_path_size) { + 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, archive_path_size, - setup_path, setup_path_size}; + Context context = { + base_path, + archive_path, + setup_path, + }; - // Get the resources of type 'B7'. + // 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 error. - if ((!::EnumResourceNames(module, kLZMAResourceType, OnResourceFound, - LONG_PTR(&context))) || - (::lstrlen(archive_path) <= 0)) - return false; - - // Generate the setup.exe path where we patch/uncompress setup resource. - wchar_t setup_dest_path[MAX_PATH] = {0}; - if (!SafeStrCopy(setup_dest_path, _countof(setup_dest_path), - context.base_path) || - !SafeStrCat(setup_dest_path, _countof(setup_dest_path), kSetupName)) + // 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 (::lstrlen(setup_path) > 0) { - wchar_t cmd_line[MAX_PATH * 3] = {0}; + if (setup_path->length() > 0) { + CommandString cmd_line; // Get the path to setup.exe first. - if (!GetSetupExePathFromRegistry(cmd_line, _countof(cmd_line))) - return false; - - if (!SafeStrCat(cmd_line, _countof(cmd_line), kCmdUpdateSetupExe) || - !SafeStrCat(cmd_line, _countof(cmd_line), L"=\"") || - !SafeStrCat(cmd_line, _countof(cmd_line), setup_path) || - !SafeStrCat(cmd_line, _countof(cmd_line), L"\"") || - !SafeStrCat(cmd_line, _countof(cmd_line), kCmdNewSetupExe) || - !SafeStrCat(cmd_line, _countof(cmd_line), L"=\"") || - !SafeStrCat(cmd_line, _countof(cmd_line), setup_dest_path) || - !SafeStrCat(cmd_line, _countof(cmd_line), L"\"")) - return false; + bool success = true; + if (!GetSetupExePathFromRegistry(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; + } int exit_code = 0; - if (!RunProcessAndWait(NULL, cmd_line, &exit_code) || - (exit_code != 0)) - return false; + if (success && + (!RunProcessAndWait(NULL, cmd_line.get(), &exit_code) || + exit_code != ERROR_SUCCESS)) { + success = false; + } - if (!SafeStrCopy(setup_path, setup_path_size, setup_dest_path)) - return false; + if (!success) + DeleteFile(setup_path->get()); - return true; + return success && setup_path->assign(setup_dest_path.get()); } // setup.exe wasn't sent as 'B7', lets see if it was sent as 'BL' - if ((!::EnumResourceNames(module, kLZCResourceType, OnResourceFound, - LONG_PTR(&context))) && - (::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND)) + // (compressed setup). + if (!::EnumResourceNames(module, kLZCResourceType, OnResourceFound, + reinterpret_cast<LONG_PTR>(&context)) && + ::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND) return false; - if (::lstrlen(setup_path) > 0) { + if (setup_path->length() > 0) { // Uncompress LZ compressed resource. Setup is packed with 'MSCF' // as opposed to old DOS way of 'SZDD'. Hence use SetupInstallFile // instead of LZCopy. // Note that the API will automatically delete the original file // if the extraction was successful. - if (!SetupInstallFile(NULL, NULL, setup_path, NULL, setup_dest_path, + // TODO(tommi): Use the cabinet API directly. + if (!SetupInstallFile(NULL, NULL, setup_path->get(), NULL, + setup_dest_path.get(), SP_COPY_DELETESOURCE | SP_COPY_SOURCE_ABSOLUTE, - NULL, NULL)) - return false; - - if (!SafeStrCopy(setup_path, setup_path_size, setup_dest_path)) + NULL, NULL)) { + DeleteFile(setup_path->get()); return false; + } - return true; + return setup_path->assign(setup_dest_path.get()); } - // setup.exe still not found. So finally check is it was sent as 'BN' - if ((!::EnumResourceNames(module, kBinResourceType, OnResourceFound, - LONG_PTR(&context))) && - (::GetLastError() != ERROR_RESOURCE_TYPE_NOT_FOUND)) + // 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 (::lstrlen(setup_path) > 0) { - if (!::lstrcmpi(setup_path, setup_dest_path)) { - ::CopyFile(setup_path, setup_dest_path, false); - if (!SafeStrCopy(setup_path, setup_path_size, setup_dest_path)) - 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 true; } - return true; + return setup_path->length() > 0; } // 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(wchar_t* buffer, int size) { - wchar_t full_exe_path[MAX_PATH]; - int len = ::GetModuleFileName(NULL, full_exe_path, _countof(full_exe_path)); - if (len <= 0 || len >= _countof(full_exe_path)) +void AppendCommandLineFlags(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; - wchar_t* exe_name = GetNameFromPathExt(full_exe_path, len); + const wchar_t* exe_name = GetNameFromPathExt(full_exe_path.get(), len); if (exe_name == NULL) return; @@ -560,19 +487,18 @@ void AppendCommandLineFlags(wchar_t* buffer, int size) { if (args_num <= 0) return; - wchar_t* cmd_to_append = L""; + const wchar_t* cmd_to_append = L""; if (!StrEndsWith(args[0], exe_name)) { // Current executable name not in the command line so just append // the whole command line. cmd_to_append = cmd_line; } else if (args_num > 1) { - wchar_t* tmp = StrStr(cmd_line, exe_name); - tmp = StrStr(tmp, L" "); + const wchar_t* tmp = SearchStringI(cmd_line, exe_name); + tmp = SearchStringI(tmp, L" "); cmd_to_append = tmp; } - if (size > ::lstrlen(cmd_to_append)) - ::lstrcat(buffer, cmd_to_append); + buffer->append(cmd_to_append); LocalFree(args); } @@ -583,30 +509,31 @@ bool RunSetup(const wchar_t* archive_path, const wchar_t* setup_path, // 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. - wchar_t cmd_line[MAX_PATH * 4]; + CommandString cmd_line; // Get the path to setup.exe first. if (::lstrlen(setup_path) > 0) { - if (!SafeStrCopy(cmd_line, _countof(cmd_line), L"\"") || - !SafeStrCat(cmd_line, _countof(cmd_line), setup_path) || - !SafeStrCat(cmd_line, _countof(cmd_line), L"\"")) + if (!cmd_line.assign(L"\"") || + !cmd_line.append(setup_path) || + !cmd_line.append(L"\"")) return false; - } else if (!GetSetupExePathFromRegistry(cmd_line, _countof(cmd_line))) { + } else if (!GetSetupExePathFromRegistry(cmd_line.get(), + cmd_line.capacity())) { return false; } // Append the command line param for chrome archive file - if (!SafeStrCat(cmd_line, _countof(cmd_line), kCmdInstallArchive) || - !SafeStrCat(cmd_line, _countof(cmd_line), L"=\"") || - !SafeStrCat(cmd_line, _countof(cmd_line), archive_path) || - !SafeStrCat(cmd_line, _countof(cmd_line), L"\"")) + 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(cmd_line, _countof(cmd_line) - lstrlen(cmd_line)); + AppendCommandLineFlags(&cmd_line); - return (RunProcessAndWait(NULL, cmd_line, exit_code)); + return RunProcessAndWait(NULL, cmd_line.get(), exit_code); } // Deletes given files and working dir. @@ -621,52 +548,245 @@ void DeleteExtractedFiles(const wchar_t* 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. -bool CreateWorkDir(const wchar_t* base_path, wchar_t* work_dir) { - wchar_t temp_name[MAX_PATH]; - if (!GetTempFileName(base_path, kTempPrefix, 0, temp_name)) - return false; // Didn't get any temp name to use. Return error. - - DWORD len = GetLongPathName(temp_name, work_dir, _countof(temp_name)); - if (len >= _countof(temp_name) || len <= 0) - return false; // Couldn't get full path to temp dir. Return error. - - // GetTempFileName creates the file as well so delete it before creating - // the directory in its place. - if (!::DeleteFile(work_dir) || !::CreateDirectory(work_dir, NULL)) - return false; // What's the use of temp dir if we can not create it? - ::lstrcat(work_dir, L"\\"); +// 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; - return true; + // 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, wchar_t* work_dir) { - wchar_t base_path[MAX_PATH]; - DWORD len = ::GetTempPath(_countof(base_path), base_path); - if (len >= _countof(base_path) || len <= 0 || - !CreateWorkDir(base_path, work_dir)) { - // Problem in creating work dir under TEMP path, so try using current - // directory as base path. - len = ::GetModuleFileName(module, base_path, _countof(base_path)); - if (len >= _countof(base_path) || len <= 0) - return false; // Can't even get current directory? Return with error. - - wchar_t* name = GetNameFromPathExt(base_path, len); +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, work_dir); + 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 ProcessMiniInstallerCommandLine(int* exit_code) { + bool ret = false; + const wchar_t* cmd_line = ::GetCommandLineW(); + int num_args = 0; + wchar_t** args = ::CommandLineToArgvW(cmd_line, &num_args); + if (args) { + for (int i = 1; i < num_args && !ret; ++i) { + // Currently there's only one mini installer specific switch defined. + if (lstrcmpiW(args[i], kMiniCmdCleanup) == 0) { + *exit_code = 0; + ret = true; + } + } + ::LocalFree(args); + } + + 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) { + // 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(); + + // If the --cleanup switch was specified on the command line, then that means + // we should only do the cleanup and then exit. + int exit_code = 101; + if (ProcessMiniInstallerCommandLine(&exit_code)) + return exit_code; + // First get a path where we can extract payload - wchar_t base_path[MAX_PATH]; - if (!GetWorkDir(module, base_path)) + PathString base_path; + if (!GetWorkDir(module, &base_path)) return 101; #if defined(GOOGLE_CHROME_BUILD) @@ -677,29 +797,28 @@ int WMain(HMODULE module) { SetInstallerFlags(); #endif - wchar_t archive_path[MAX_PATH] = {0}; - wchar_t setup_path[MAX_PATH] = {0}; - if (!UnpackBinaryResources(module, base_path, archive_path, MAX_PATH, - setup_path, MAX_PATH)) - return 102; - - int exit_code = 103; - if (!RunSetup(archive_path, setup_path, &exit_code)) - return exit_code; + PathString archive_path; + PathString setup_path; + if (!UnpackBinaryResources(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(archive_path.get(), setup_path.get(), &exit_code)) + exit_code = 103; + } - wchar_t value[2]; - if ((!ReadValueFromRegistry(HKEY_CURRENT_USER, kCleanupRegistryKey, - kCleanupRegistryValueName, value, - _countof(value))) || - (value[0] != L'0')) - DeleteExtractedFiles(base_path, archive_path, setup_path); + 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); diff --git a/chrome/installer/mini_installer/mini_installer.h b/chrome/installer/mini_installer/mini_installer.h index 13bc3c6..27cea55 100644 --- a/chrome/installer/mini_installer/mini_installer.h +++ b/chrome/installer/mini_installer/mini_installer.h @@ -6,11 +6,6 @@ #define CHROME_INSTALLER_MINI_INSTALLER_MINI_INSTALLER_H_ #pragma once -// The windows command line to uncompress a LZ compressed file. It is a define -// because we need the string to be writable. We don't need the full path -// since it is located in windows\system32 and is available since windows2k. -#define UNCOMPRESS_CMD L"expand.exe " - namespace mini_installer { // Various filenames @@ -18,11 +13,19 @@ const wchar_t kSetupName[] = L"setup.exe"; const wchar_t kChromePrefix[] = L"chrome"; const wchar_t kSetupPrefix[] = L"setup"; -// setup.exe command line arguements +// setup.exe command line arguments const wchar_t kCmdInstallArchive[] = L" --install-archive"; const wchar_t kCmdUpdateSetupExe[] = L" --update-setup-exe"; const wchar_t kCmdNewSetupExe[] = L" --new-setup-exe"; +// Command line arguments specific only to the mini installer. +// Note that these constants differ from the kCmdXxx constants above in that +// they do not have leading whitespace. +// Pass --cleanup to the mini installer to delete temporary directories that +// might be left over from previous installation and then exit (i.e. do not +// extract and run setup.exe). +const wchar_t kMiniCmdCleanup[] = L"--cleanup"; + // Temp directory prefix that this process creates const wchar_t kTempPrefix[] = L"CR_"; // Google Update will use the full installer if this suffix is found in the ap diff --git a/chrome/installer/mini_installer/mini_string.cc b/chrome/installer/mini_installer/mini_string.cc new file mode 100644 index 0000000..c5e401a1 --- /dev/null +++ b/chrome/installer/mini_installer/mini_string.cc @@ -0,0 +1,162 @@ +// Copyright (c) 2011 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. + +#include "chrome/installer/mini_installer/mini_string.h" + +#include <windows.h> + +namespace { + +// Returns true if the given two ASCII characters are same (ignoring case). +bool EqualASCIICharI(wchar_t a, wchar_t b) { + if (a >= L'A' && a <= L'Z') + a += (L'a' - L'A'); + if (b >= L'A' && b <= L'Z') + b += (L'a' - L'A'); + return (a == b); +} + +} // namespace + +namespace mini_installer { + +// Formats a sequence of |bytes| as hex. The |str| buffer must have room for +// at least 2*|size| + 1. +bool HexEncode(const void* bytes, size_t size, wchar_t* str, size_t str_size) { + if (str_size <= (size * 2)) + return false; + + static const wchar_t kHexChars[] = L"0123456789ABCDEF"; + + str[size * 2] = L'\0'; + + for (size_t i = 0; i < size; ++i) { + char b = reinterpret_cast<const char*>(bytes)[i]; + str[(i * 2)] = kHexChars[(b >> 4) & 0xf]; + str[(i * 2) + 1] = kHexChars[b & 0xf]; + } + + return true; +} + +size_t SafeStrLen(const wchar_t* str, size_t alloc_size) { + if (!str || !alloc_size) + return 0; + size_t len = 0; + while (--alloc_size && str[len] != L'\0') + ++len; + return len; +} + +bool SafeStrCopy(wchar_t* dest, size_t dest_size, const wchar_t* src) { + if (!dest || !dest_size) + return false; + + wchar_t* write = dest; + for (size_t remaining = dest_size; remaining != 0; --remaining) { + if ((*write++ = *src++) == L'\0') + return true; + } + + // If we fail, we do not want to leave the string with partially copied + // contents. The reason for this is that we use these strings mostly for + // named objects such as files. If we copy a partial name, then that could + // match with something we do not want it to match with. + // Furthermore, since SafeStrCopy is called from SafeStrCat, we do not + // want to mutate the string in case the caller handles the error of a + // failed concatenation. For example: + // + // wchar_t buf[5] = {0}; + // if (!SafeStrCat(buf, arraysize(buf), kLongName)) + // SafeStrCat(buf, arraysize(buf), kShortName); + // + // If we were to return false in the first call to SafeStrCat but still + // mutate the buffer, the buffer will be in an unexpected state. + *dest = L'\0'; + return false; +} + +// Safer replacement for lstrcat function. +bool SafeStrCat(wchar_t* dest, size_t dest_size, const wchar_t* src) { + // Use SafeStrLen instead of lstrlen just in case the |dest| buffer isn't + // terminated. + int str_len = SafeStrLen(dest, dest_size); + return SafeStrCopy(dest + str_len, dest_size - str_len, src); +} + +bool StrEndsWith(const wchar_t* str, const wchar_t* end_str) { + if (str == NULL || end_str == NULL) + return false; + + for (int i = lstrlen(str) - 1, j = lstrlen(end_str) - 1; j >= 0; --i, --j) { + if (i < 0 || !EqualASCIICharI(str[i], end_str[j])) + return false; + } + + return true; +} + +bool StrStartsWith(const wchar_t* str, const wchar_t* start_str) { + if (str == NULL || start_str == NULL) + return false; + + for (int i = 0; start_str[i] != L'\0'; ++i) { + if (!EqualASCIICharI(str[i], start_str[i])) + return false; + } + + return true; +} + +const wchar_t* SearchStringI(const wchar_t* source, const wchar_t* find) { + if (!find || find[0] == L'\0') + return source; + + const wchar_t* scan = source; + while (*scan) { + const wchar_t* s = scan; + const wchar_t* f = find; + + while (*s && *f && EqualASCIICharI(*s, *f)) + ++s, ++f; + + if (!*f) + return scan; + + ++scan; + } + + return NULL; +} + +bool FindTagInStr(const wchar_t* str, + const wchar_t* tag, + const wchar_t** position) { + int tag_length = ::lstrlen(tag); + const wchar_t* scan = str; + for (const wchar_t* tag_start = SearchStringI(scan, tag); + tag_start != NULL; + tag_start = SearchStringI(scan, tag)) { + scan = tag_start + tag_length; + if (*scan == L'-' || *scan == L'\0') { + if (position != NULL) + *position = tag_start; + return true; + } + } + return false; +} + +wchar_t* GetNameFromPathExt(wchar_t* path, size_t size) { + if (size <= 1) + return NULL; + + wchar_t* current = &path[size - 1]; + while (current != path && L'\\' != *current) + --current; + + return (current == path) ? NULL : (current + 1); +} + +} // namespace mini_installer diff --git a/chrome/installer/mini_installer/mini_string.h b/chrome/installer/mini_installer/mini_string.h new file mode 100644 index 0000000..0e3dc8d --- /dev/null +++ b/chrome/installer/mini_installer/mini_string.h @@ -0,0 +1,147 @@ +// Copyright (c) 2011 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. + +#ifndef CHROME_INSTALLER_MINI_INSTALLER_MINI_STRING_H_ +#define CHROME_INSTALLER_MINI_INSTALLER_MINI_STRING_H_ +#pragma once + +#ifndef COMPILE_ASSERT +// COMPILE_ASSERT macro borrowed from basictypes.h +template <bool> +struct CompileAssert {}; +#define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] +#endif + +namespace mini_installer { + +// NOTE: Do not assume that these string functions support UTF encoding. +// This is fine for the purposes of the mini_installer, but you have +// been warned! + +// Formats a sequence of |bytes| as hex. The |str| buffer must have room for +// at least 2*|size| + 1. +bool HexEncode(const void* bytes, size_t size, wchar_t* str, size_t str_size); + +// Counts the number of characters in the string up to a maximum of +// alloc_size. The highest return value from this function can therefore be +// alloc_size - 1 since |alloc_size| includes the \0 terminator. +size_t SafeStrLen(const wchar_t* str, size_t alloc_size); + +// Simple replacement for CRT string copy method that does not overflow. +// Returns true if the source was copied successfully otherwise returns false. +// Parameter src is assumed to be NULL terminated and the NULL character is +// copied over to string dest. +bool SafeStrCopy(wchar_t* dest, size_t dest_size, const wchar_t* src); + +// Simple replacement for CRT string copy method that does not overflow. +// Returns true if the source was copied successfully otherwise returns false. +// Parameter src is assumed to be NULL terminated and the NULL character is +// copied over to string dest. If the return value is false, the |dest| +// string should be the same as it was before. +bool SafeStrCat(wchar_t* dest, size_t dest_size, const wchar_t* src); + +// Function to check if a string (specified by str) ends with another string +// (specified by end_str). +bool StrEndsWith(const wchar_t* str, const wchar_t* end_str); + +// Function to check if a string (specified by str) starts with another string +// (specified by start_str). +bool StrStartsWith(const wchar_t* str, const wchar_t* start_str); + +// Case insensitive search of the first occurrence of |find| in |source|. +const wchar_t* SearchStringI(const wchar_t* source, const wchar_t* find); + +// Searches for |tag| within |str|. Returns true if |tag| is found and is +// immediately followed by '-' or is at the end of the string. If |position| +// is non-NULL, the location of the tag is returned in |*position| on success. +bool FindTagInStr(const wchar_t* str, const wchar_t* tag, + const wchar_t** position); + +// Takes the path to file and returns a pointer to the filename component. For +// example for input of c:\full\path\to\file.ext it returns pointer to file.ext. +// It returns NULL if extension or path separator is not found. +// |size| is the number of characters in |path| not including the string +// terminator. +wchar_t* GetNameFromPathExt(wchar_t* path, size_t size); + +// A string class that manages a fixed size buffer on the stack. +// The methods in the class are based on the above string methods and the +// class additionally is careful about proper buffer termination. +template <size_t kCapacity> +class StackString { + public: + StackString() { + COMPILE_ASSERT(kCapacity != 0, invalid_buffer_size); + buffer_[kCapacity] = L'\0'; // We always reserve 1 more than asked for. + clear(); + } + + // We do not expose a constructor that accepts a string pointer on purpose. + // We expect the caller to call assign() and handle failures. + + // Returns the number of reserved characters in this buffer, _including_ + // the reserved char for the terminator. + size_t capacity() const { + return kCapacity; + } + + wchar_t* get() { + return buffer_; + } + + bool assign(const wchar_t* str) { + return SafeStrCopy(buffer_, kCapacity, str); + } + + bool append(const wchar_t* str) { + return SafeStrCat(buffer_, kCapacity, str); + } + + void clear() { + buffer_[0] = L'\0'; + } + + size_t length() const { + return SafeStrLen(buffer_, kCapacity); + } + + // Does a case insensitive search for a substring. + const wchar_t* findi(const wchar_t* find) const { + return SearchStringI(buffer_, find); + } + + // Case insensitive string compare. + int comparei(const wchar_t* str) const { + return lstrcmpiW(buffer_, str); + } + + // Case sensitive string compare. + int compare(const wchar_t* str) const { + return lstrcmpW(buffer_, str); + } + + // Terminates the string at the specified location. + // Note: this method has no effect if this object's length is less than + // |location|. + bool truncate_at(size_t location) { + if (location >= kCapacity) + return false; + buffer_[location] = L'\0'; + return true; + } + + protected: + // We reserve 1 more than what is asked for as a safeguard against + // off-by-one errors. + wchar_t buffer_[kCapacity + 1]; + + private: + StackString(const StackString&); + StackString& operator=(const StackString&); +}; + +} // namespace mini_installer + +#endif // CHROME_INSTALLER_MINI_INSTALLER_MINI_STRING_H_ diff --git a/chrome/installer/mini_installer/mini_string_test.cc b/chrome/installer/mini_installer/mini_string_test.cc new file mode 100644 index 0000000..67b11f9 --- /dev/null +++ b/chrome/installer/mini_installer/mini_string_test.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2011 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. + +#include <windows.h> + +#include <string> + +#include "base/basictypes.h" +#include "chrome/installer/mini_installer/mini_string.h" +#include "testing/gtest/include/gtest/gtest.h" + +using mini_installer::StackString; + +namespace { +class MiniInstallerStringTest : public testing::Test { + protected: + virtual void SetUp() { + } + virtual void TearDown() { + } +}; +} + +// Tests the strcat/strcpy/length support of the StackString class. +TEST_F(MiniInstallerStringTest, StackStringOverflow) { + static const wchar_t kTestString[] = L"1234567890"; + + StackString<MAX_PATH> str; + EXPECT_EQ(MAX_PATH, str.capacity()); + + std::wstring compare_str; + + EXPECT_EQ(str.length(), compare_str.length()); + EXPECT_EQ(0, compare_str.compare(str.get())); + + size_t max_chars = str.capacity() - 1; + + while ((str.length() + (arraysize(kTestString) - 1)) <= max_chars) { + EXPECT_TRUE(str.append(kTestString)); + compare_str.append(kTestString); + EXPECT_EQ(str.length(), compare_str.length()); + EXPECT_EQ(0, compare_str.compare(str.get())); + } + + EXPECT_GT(static_cast<size_t>(MAX_PATH), str.length()); + + // Now we've exhausted the space we allocated for the string, + // so append should fail. + EXPECT_FALSE(str.append(kTestString)); + + // ...and remain unchanged. + EXPECT_EQ(0, compare_str.compare(str.get())); + EXPECT_EQ(str.length(), compare_str.length()); + + // Last test for fun. + str.clear(); + compare_str.clear(); + EXPECT_EQ(0, compare_str.compare(str.get())); + EXPECT_EQ(str.length(), compare_str.length()); +} + +// Tests the case insensitive find support of the StackString class. +TEST_F(MiniInstallerStringTest, StackStringFind) { + static const wchar_t kTestStringSource[] = L"1234ABcD567890"; + static const wchar_t kTestStringFind[] = L"abcd"; + static const wchar_t kTestStringNotFound[] = L"80"; + + StackString<MAX_PATH> str; + EXPECT_TRUE(str.assign(kTestStringSource)); + EXPECT_EQ(str.get(), str.findi(kTestStringSource)); + EXPECT_EQ(static_cast<const wchar_t*>(NULL), str.findi(kTestStringNotFound)); + const wchar_t* found = str.findi(kTestStringFind); + EXPECT_NE(static_cast<const wchar_t*>(NULL), found); + std::wstring check(found, arraysize(kTestStringFind) - 1); + EXPECT_EQ(0, lstrcmpi(check.c_str(), kTestStringFind)); +} |