summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-03-16 20:43:26 +0000
committertommi@chromium.org <tommi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-03-16 20:43:26 +0000
commitc66931ce8606697a5c500b02dff1ef2e15fdbf75 (patch)
treebef1128adcdcfe6ba7c0de287c8b7173e16da101
parentb5d13670b02672a8eb4c77115b42e7f4e1a1dbf4 (diff)
downloadchromium_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.gypi3
-rw-r--r--chrome/installer/mini_installer.gyp4
-rw-r--r--chrome/installer/mini_installer/mini_installer.cc649
-rw-r--r--chrome/installer/mini_installer/mini_installer.h15
-rw-r--r--chrome/installer/mini_installer/mini_string.cc162
-rw-r--r--chrome/installer/mini_installer/mini_string.h147
-rw-r--r--chrome/installer/mini_installer/mini_string_test.cc77
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));
+}