summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/installer/installer.gyp3
-rw-r--r--chrome/installer/setup/install.cc12
-rw-r--r--chrome/installer/setup/setup_main.cc46
-rw-r--r--chrome/installer/setup/uninstall.cc62
-rw-r--r--chrome/installer/util/delete_after_reboot_helper.cc375
-rw-r--r--chrome/installer/util/delete_after_reboot_helper.h66
-rw-r--r--chrome/installer/util/delete_after_reboot_helper_unittest.cc186
-rw-r--r--chrome/installer/util/util_constants.h3
8 files changed, 736 insertions, 17 deletions
diff --git a/chrome/installer/installer.gyp b/chrome/installer/installer.gyp
index b49f877..df05ed1 100644
--- a/chrome/installer/installer.gyp
+++ b/chrome/installer/installer.gyp
@@ -41,6 +41,8 @@
'util/create_dir_work_item.h',
'util/create_reg_key_work_item.cc',
'util/create_reg_key_work_item.h',
+ 'util/delete_after_reboot_helper.cc',
+ 'util/delete_after_reboot_helper.h',
'util/delete_reg_value_work_item.cc',
'util/delete_reg_value_work_item.h',
'util/delete_tree_work_item.cc',
@@ -150,6 +152,7 @@
'util/copy_tree_work_item_unittest.cc',
'util/create_dir_work_item_unittest.cc',
'util/create_reg_key_work_item_unittest.cc',
+ 'util/delete_after_reboot_helper_unittest.cc',
'util/delete_reg_value_work_item_unittest.cc',
'util/delete_tree_work_item_unittest.cc',
'util/google_chrome_distribution_unittest.cc',
diff --git a/chrome/installer/setup/install.cc b/chrome/installer/setup/install.cc
index 24d2f80..1bc8164 100644
--- a/chrome/installer/setup/install.cc
+++ b/chrome/installer/setup/install.cc
@@ -15,6 +15,7 @@
#include "chrome/installer/setup/setup_constants.h"
#include "chrome/installer/util/browser_distribution.h"
#include "chrome/installer/util/create_reg_key_work_item.h"
+#include "chrome/installer/util/delete_after_reboot_helper.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/install_util.h"
@@ -95,7 +96,7 @@ void AddUninstallShortcutWorkItems(HKEY reg_root,
uninstall_cmd.append(L"\" --");
uninstall_cmd.append(installer_util::switches::kUninstall);
-#ifdef CHROME_FRAME_BUILD
+#if defined(CHROME_FRAME_BUILD)
uninstall_cmd.append(L" --");
uninstall_cmd.append(installer_util::switches::kForceUninstall);
uninstall_cmd.append(L" --");
@@ -246,7 +247,7 @@ bool CreateOrUpdateChromeShortcuts(const std::wstring& exe_path,
std::wstring arguments(L" --");
arguments.append(installer_util::switches::kUninstall);
-#ifdef CHROME_FRAME_BUILD
+#if defined(CHROME_FRAME_BUILD)
arguments.append(L" --");
arguments.append(installer_util::switches::kForceUninstall);
arguments.append(L" --");
@@ -459,6 +460,13 @@ bool InstallNewVersion(const std::wstring& exe_path,
if (reg_root != HKEY_LOCAL_MACHINE && reg_root != HKEY_CURRENT_USER)
return false;
+#if defined(CHROME_FRAME_BUILD)
+ // Make sure that we don't end up deleting installed files on next reboot.
+ if (!RemoveFromMovesPendingReboot(install_path.c_str())) {
+ LOG(ERROR) << "Error accessing pending moves value.";
+ }
+#endif
+
scoped_ptr<WorkItemList> install_list(WorkItem::CreateWorkItemList());
// A temp directory that work items need and the actual install directory.
install_list->AddCreateDirWorkItem(FilePath::FromWStringHack(temp_dir));
diff --git a/chrome/installer/setup/setup_main.cc b/chrome/installer/setup/setup_main.cc
index 8b70a1b..3b5377d 100644
--- a/chrome/installer/setup/setup_main.cc
+++ b/chrome/installer/setup/setup_main.cc
@@ -5,6 +5,7 @@
#include <string>
#include <windows.h>
#include <msi.h>
+#include <shellapi.h>
#include <shlobj.h>
#include "base/at_exit.h"
@@ -13,6 +14,7 @@
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/registry.h"
+#include "base/scoped_handle_win.h"
#include "base/string_util.h"
#include "base/win_util.h"
#include "chrome/installer/setup/install.h"
@@ -413,7 +415,7 @@ bool HandleNonInstallCmdLineOptions(const CommandLine& cmd_line,
exit_code = ShowEULADialog(inner_frame);
if (installer_util::EULA_REJECTED != exit_code)
GoogleUpdateSettings::SetEULAConsent(true);
- return true;;
+ return true;
} else if (cmd_line.HasSwitch(
installer_util::switches::kRegisterChromeBrowser)) {
// If --register-chrome-browser option is specified, register all
@@ -464,6 +466,40 @@ bool HandleNonInstallCmdLineOptions(const CommandLine& cmd_line,
return false;
}
+bool ShowRebootDialog() {
+ // Get a token for this process.
+ HANDLE token;
+ if (!OpenProcessToken(GetCurrentProcess(),
+ TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
+ &token)) {
+ LOG(ERROR) << "Failed to open token.";
+ return false;
+ }
+
+ // Use a ScopedHandle to keep track of and eventually close our handle.
+ // TODO(robertshield): Add a Receive() method to base's ScopedHandle.
+ ScopedHandle scoped_handle(token);
+
+ // Get the LUID for the shutdown privilege.
+ TOKEN_PRIVILEGES tkp = {0};
+ LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
+ tkp.PrivilegeCount = 1;
+ tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
+
+ // Get the shutdown privilege for this process.
+ AdjustTokenPrivileges(token, FALSE, &tkp, 0,
+ reinterpret_cast<PTOKEN_PRIVILEGES>(NULL), 0);
+ if (GetLastError() != ERROR_SUCCESS) {
+ LOG(ERROR) << "Unable to get shutdown privileges.";
+ return false;
+ }
+
+ // Popup a dialog that will prompt to reboot using the default system message.
+ // TODO(robertshield): Add a localized, more specific string to the prompt.
+ RestartDialog(NULL, NULL, EWX_REBOOT | EWX_FORCEIFHUNG);
+ return true;
+}
+
} // namespace
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance,
@@ -550,7 +586,15 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE prev_instance,
prefs.get());
}
+ if (install_status == installer_util::UNINSTALL_REQUIRES_REBOOT) {
+ install_status = installer_util::UNINSTALL_SUCCESSFUL;
+#if defined(CHROME_FRAME_BUILD)
+ ShowRebootDialog();
+#endif
+ }
+
CoUninitialize();
+
BrowserDistribution* dist = BrowserDistribution::GetDistribution();
return dist->GetInstallReturnCode(install_status);
}
diff --git a/chrome/installer/setup/uninstall.cc b/chrome/installer/setup/uninstall.cc
index 978c056..2ad8cf2 100644
--- a/chrome/installer/setup/uninstall.cc
+++ b/chrome/installer/setup/uninstall.cc
@@ -17,6 +17,7 @@
#include "chrome/installer/setup/install.h"
#include "chrome/installer/setup/setup_constants.h"
#include "chrome/installer/util/browser_distribution.h"
+#include "chrome/installer/util/delete_after_reboot_helper.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/logging_installer.h"
@@ -137,18 +138,27 @@ bool DeleteEmptyParentDir(const std::wstring& path) {
return ret;
}
-// Deletes all installed files of Chromium and Folders. Before deleting it
+enum DeleteResult {
+ DELETE_SUCCEEDED,
+ DELETE_FAILED,
+ DELETE_REQUIRES_REBOOT
+};
+
+// Deletes all installed files of Chromium and Folders or schedules them for
+// deletion on reboot if they are in use. Before deleting it
// needs to move setup.exe in a temp folder because the current process
-// is using that file. It returns false when it can not get the path to
-// installation folder, in all other cases it returns true even in case
-// of error (only logs the error).
-bool DeleteFilesAndFolders(const std::wstring& exe_path, bool system_uninstall,
- const installer::Version& installed_version,
+// is using that file.
+// Returns DELETE_SUCCEEDED if all files were successfully delete.
+// Returns DELETE_FAILED if it could not get the path to the install dir.
+// Returns DELETE_REQUIRES_REBOOT if the files were in use and so were
+// scheduled for deletion on next reboot.
+DeleteResult DeleteFilesAndFolders(const std::wstring& exe_path,
+ bool system_uninstall, const installer::Version& installed_version,
std::wstring* local_state_path, bool delete_profile) {
std::wstring install_path(installer::GetChromeInstallPath(system_uninstall));
if (install_path.empty()) {
LOG(ERROR) << "Could not get installation destination path.";
- return false; // Nothing else we can do for uninstall, so we return.
+ return DELETE_FAILED; // Nothing else we can do to uninstall, so we return.
} else {
LOG(INFO) << "install destination path: " << install_path;
}
@@ -179,27 +189,46 @@ bool DeleteFilesAndFolders(const std::wstring& exe_path, bool system_uninstall,
file_util::CopyFile(user_local_file, path);
}
+ DeleteResult result = DELETE_SUCCEEDED;
+
LOG(INFO) << "Deleting install path " << install_path;
if (!file_util::Delete(install_path, true)) {
LOG(ERROR) << "Failed to delete folder (1st try): " << install_path;
+#if defined(CHROME_FRAME_BUILD)
+ // We don't try killing Chrome processes for Chrome Frame builds since
+ // that is unlikely to help. Instead, schedule files for deletion and
+ // return a value that will trigger a reboot prompt.
+ ScheduleDirectoryForDeletion(install_path.c_str());
+ result = DELETE_REQUIRES_REBOOT;
+#else
// Try closing any running chrome processes and deleting files once again.
CloseAllChromeProcesses();
- if (!file_util::Delete(install_path, true))
+ if (!file_util::Delete(install_path, true)) {
LOG(ERROR) << "Failed to delete folder (2nd try): " << install_path;
+ result = DELETE_FAILED;
+ }
+#endif
}
if (delete_profile) {
LOG(INFO) << "Deleting user profile" << user_local_state.value();
- if (!file_util::Delete(user_local_state, true))
- LOG(ERROR) << "Failed to delete user profle dir: "
+ if (!file_util::Delete(user_local_state, true)) {
+ LOG(ERROR) << "Failed to delete user profile dir: "
<< user_local_state.value();
+#if defined(CHROME_FRAME_BUILD)
+ ScheduleDirectoryForDeletion(user_local_state.value().c_str());
+ result = DELETE_REQUIRES_REBOOT;
+#else
+ result = DELETE_FAILED;
+#endif
+ }
DeleteEmptyParentDir(user_local_state.value());
}
// Now check and delete if the parent directories are empty
// For example Google\Chrome or Chromium
DeleteEmptyParentDir(install_path);
- return true;
+ return result;
}
// This method tries to delete a registry key and logs an error message
@@ -368,7 +397,9 @@ installer_util::InstallStatus installer_setup::UninstallChrome(
if (force_uninstall) {
// Since --force-uninstall command line option is used, we are going to
// do silent uninstall. Try to close all running Chrome instances.
+#if !defined(CHROME_FRAME_BUILD)
CloseAllChromeProcesses();
+#endif
} else { // no --force-uninstall so lets show some UI dialog boxes.
status = IsChromeActiveOrUserCancelled(system_uninstall);
if (status != installer_util::UNINSTALL_CONFIRMED &&
@@ -469,9 +500,14 @@ installer_util::InstallStatus installer_setup::UninstallChrome(
(cmd_line.HasSwitch(installer_util::switches::kDeleteProfile));
std::wstring local_state_path;
ret = installer_util::UNINSTALL_SUCCESSFUL;
- if (!DeleteFilesAndFolders(exe_path, system_uninstall, *installed_version,
- &local_state_path, delete_profile))
+
+ DeleteResult delete_result = DeleteFilesAndFolders(exe_path,
+ system_uninstall, *installed_version, &local_state_path, delete_profile);
+ if (delete_result == DELETE_FAILED) {
ret = installer_util::UNINSTALL_FAILED;
+ } else if (delete_result == DELETE_REQUIRES_REBOOT) {
+ ret = installer_util::UNINSTALL_REQUIRES_REBOOT;
+ }
if (!force_uninstall) {
LOG(INFO) << "Uninstallation complete. Launching Uninstall survey.";
diff --git a/chrome/installer/util/delete_after_reboot_helper.cc b/chrome/installer/util/delete_after_reboot_helper.cc
new file mode 100644
index 0000000..a54acb3
--- /dev/null
+++ b/chrome/installer/util/delete_after_reboot_helper.cc
@@ -0,0 +1,375 @@
+// Copyright (c) 2009 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.
+//
+// This file defines helper methods used to schedule files for deletion
+// on next reboot. The code here is heavily borrowed and simplified from
+// http://code.google.com/p/omaha/source/browse/trunk/common/file.cc and
+// http://code.google.com/p/omaha/source/browse/trunk/common/utils.cc
+//
+// This implementation really is not fast, so do not use it where that will
+// matter.
+
+#include "chrome/installer/util/delete_after_reboot_helper.h"
+
+#include <string>
+#include <vector>
+
+#include "base/file_util.h"
+#include "base/registry.h"
+#include "base/string_util.h"
+
+// The moves-pending-reboot is a MULTISZ registry key in the HKLM part of the
+// registry.
+const wchar_t kSessionManagerKey[] =
+ L"SYSTEM\\CurrentControlSet\\Control\\Session Manager";
+const wchar_t kPendingFileRenameOps[] = L"PendingFileRenameOperations";
+
+namespace {
+
+// Returns true if this directory name is 'safe' for deletion (doesn't contain
+// "..", doesn't specify a drive root)
+bool IsSafeDirectoryNameForDeletion(const wchar_t* dir_name) {
+ DCHECK(dir_name);
+
+ // empty name isn't allowed
+ if (!(dir_name && *dir_name)) {
+ return false;
+ }
+
+ // require a character other than \/:. after the last :
+ // disallow anything with ".."
+ bool ok = false;
+ for (const wchar_t* s = dir_name; *s; ++s) {
+ if (*s != L'\\' && *s != L'/' && *s != L':' && *s != L'.') {
+ ok = true;
+ }
+ if (*s == L'.' && s > dir_name && *(s - 1) == L'.') {
+ return false;
+ }
+ if (*s == L':') {
+ ok = false;
+ }
+ }
+ return ok;
+}
+
+// Must only be called for regular files or directories that will be empty.
+bool ScheduleFileSystemEntityForDeletion(const wchar_t* path) {
+ // Check if the file exists, return false if not.
+ WIN32_FILE_ATTRIBUTE_DATA attrs = {0};
+ if (!::GetFileAttributesEx(path, ::GetFileExInfoStandard, &attrs)) {
+ LOG(ERROR) << path << " for deletion does not exist." << GetLastError();
+ return false;
+ }
+
+ DWORD flags = MOVEFILE_DELAY_UNTIL_REBOOT;
+ if (!file_util::DirectoryExists(path)) {
+ // This flag valid only for files
+ flags |= MOVEFILE_REPLACE_EXISTING;
+ }
+
+ if (!::MoveFileEx(path, NULL, flags)) {
+ LOG(ERROR) << "Could not schedule " << path << " for deletion.";
+ return false;
+ }
+
+ LOG(INFO) << "Scheduled for deletion: " << path;
+ return true;
+}
+} // end namespace
+
+bool ScheduleDirectoryForDeletion(const wchar_t* dir_name) {
+ if (!IsSafeDirectoryNameForDeletion(dir_name)) {
+ LOG(ERROR) << "Unsafe directory name for deletion: " << dir_name;
+ return false;
+ }
+
+ // Make sure the directory exists (it is ok if it doesn't)
+ DWORD dir_attributes = ::GetFileAttributes(dir_name);
+ if (dir_attributes == INVALID_FILE_ATTRIBUTES) {
+ if (::GetLastError() == ERROR_FILE_NOT_FOUND) {
+ return true; // Ok if directory is missing
+ } else {
+ LOG(ERROR) << "Could not GetFileAttributes for " << dir_name;
+ return false;
+ }
+ }
+ // Confirm it is a directory
+ if (!(dir_attributes & FILE_ATTRIBUTE_DIRECTORY)) {
+ LOG(ERROR) << "Scheduled directory is not a directory: " << dir_name;
+ return false;
+ }
+
+ // First schedule all the normal files for deletion.
+ {
+ bool success = true;
+ file_util::FileEnumerator file_enum(FilePath(dir_name), false,
+ file_util::FileEnumerator::FILES);
+ for (FilePath file = file_enum.Next(); !file.empty();
+ file = file_enum.Next()) {
+ success = ScheduleFileSystemEntityForDeletion(file.value().c_str());
+ if (!success) {
+ LOG(ERROR) << "Failed to schedule file for deletion: " << file.value();
+ return false;
+ }
+ }
+ }
+
+ // Then recurse to all the subdirectories.
+ {
+ bool success = true;
+ file_util::FileEnumerator dir_enum(FilePath(dir_name), false,
+ file_util::FileEnumerator::DIRECTORIES);
+ for (FilePath sub_dir = dir_enum.Next(); !sub_dir.empty();
+ sub_dir = dir_enum.Next()) {
+ success = ScheduleDirectoryForDeletion(sub_dir.value().c_str());
+ if (!success) {
+ LOG(ERROR) << "Failed to schedule subdirectory for deletion: "
+ << sub_dir.value();
+ return false;
+ }
+ }
+ }
+
+ // Now schedule the empty directory itself
+ if (!ScheduleFileSystemEntityForDeletion(dir_name)) {
+ LOG(ERROR) << "Failed to schedule directory for deletion: " << dir_name;
+ }
+
+ return true;
+}
+
+// Converts the strings found in |buffer| to a list of wstrings that is returned
+// in |value|.
+// |buffer| points to a series of pairs of null-terminated wchar_t strings
+// followed by a terminating null character.
+// |byte_count| is the length of |buffer| in bytes.
+// |value| is a pointer to an empty vector of wstrings. On success, this vector
+// contains all of the strings extracted from |buffer|.
+// Returns S_OK on success, E_INVALIDARG if buffer does not meet tha above
+// specification.
+HRESULT MultiSZBytesToStringArray(const char* buffer, size_t byte_count,
+ std::vector<PendingMove>* value) {
+ DCHECK(buffer);
+ DCHECK(value);
+ DCHECK(value->empty());
+
+ DWORD data_len = byte_count / sizeof(wchar_t);
+ const wchar_t* data = reinterpret_cast<const wchar_t*>(buffer);
+ const wchar_t* data_end = data + data_len;
+ if (data_len > 1) {
+ // must be terminated by two null characters
+ if (data[data_len - 1] != 0 || data[data_len - 2] != 0) {
+ DLOG(ERROR) << "Invalid MULTI_SZ found.";
+ return E_INVALIDARG;
+ }
+
+ // put null-terminated strings into arrays
+ while (data < data_end) {
+ std::wstring str_from(data);
+ data += str_from.length() + 1;
+ if (data < data_end) {
+ std::wstring str_to(data);
+ data += str_to.length() + 1;
+ value->push_back(std::make_pair(str_from, str_to));
+ }
+ }
+ }
+ return S_OK;
+}
+
+void StringArrayToMultiSZBytes(const std::vector<PendingMove>& strings,
+ std::vector<char>* buffer) {
+ DCHECK(buffer);
+ buffer->clear();
+
+ if (strings.size() == 0) {
+ // Leave buffer empty if we have no strings.
+ return;
+ }
+
+ size_t total_wchars = 0;
+ {
+ std::vector<PendingMove>::const_iterator iter(strings.begin());
+ for (; iter != strings.end(); ++iter) {
+ total_wchars += iter->first.length();
+ total_wchars++; // Space for the null char.
+ total_wchars += iter->second.length();
+ total_wchars++; // Space for the null char.
+ }
+ total_wchars++; // Space for the extra terminating null char.
+ }
+
+ size_t total_length = total_wchars * sizeof(wchar_t);
+ buffer->resize(total_length);
+ wchar_t* write_pointer = reinterpret_cast<wchar_t*>(&((*buffer)[0]));
+ // Keep an end pointer around for sanity checking.
+ wchar_t* end_pointer = write_pointer + total_wchars;
+
+ std::vector<PendingMove>::const_iterator copy_iter(strings.begin());
+ for (; copy_iter != strings.end() && write_pointer < end_pointer;
+ copy_iter++) {
+ // First copy the source string.
+ size_t string_length = copy_iter->first.length() + 1;
+ memcpy(write_pointer, copy_iter->first.c_str(),
+ string_length * sizeof(wchar_t));
+ write_pointer += string_length;
+ // Now copy the destination string.
+ string_length = copy_iter->second.length() + 1;
+ memcpy(write_pointer, copy_iter->second.c_str(),
+ string_length * sizeof(wchar_t));
+ write_pointer += string_length;
+
+ // We should never run off the end while in this loop.
+ DCHECK(write_pointer < end_pointer);
+ }
+ *write_pointer = L'\0'; // Explicitly set the final null char.
+ DCHECK(++write_pointer == end_pointer);
+}
+
+std::wstring GetShortPathName(const wchar_t* path) {
+ std::wstring short_path;
+ DWORD length = GetShortPathName(path, WriteInto(&short_path, MAX_PATH),
+ MAX_PATH);
+ DLOG_IF(WARNING, length == 0) << __FUNCTION__ << " gle=" << GetLastError();
+ short_path.resize(length);
+ return short_path;
+}
+
+HRESULT GetPendingMovesValue(
+ std::vector<PendingMove>* pending_moves) {
+ DCHECK(pending_moves);
+ pending_moves->clear();
+
+ // Get the current value of the key
+ // If the Key is missing, that's totally acceptable.
+ RegKey session_manager_key(HKEY_LOCAL_MACHINE, kSessionManagerKey,
+ KEY_QUERY_VALUE);
+ HKEY session_manager_handle = session_manager_key.Handle();
+ if (!session_manager_handle) {
+ return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);
+ }
+
+ // The base::RegKey Read code squashes the return code from
+ // ReqQueryValueEx, we have to do things ourselves:
+ DWORD buffer_size = 0;
+ std::vector<char> buffer;
+ buffer.resize(1);
+ DWORD type;
+ DWORD result = RegQueryValueEx(session_manager_handle, kPendingFileRenameOps,
+ 0, &type, reinterpret_cast<BYTE*>(&buffer[0]),
+ &buffer_size);
+
+ if (result == ERROR_FILE_NOT_FOUND) {
+ // No pending moves were found.
+ return HRESULT_FROM_WIN32(result);
+ } else if (result == ERROR_MORE_DATA) {
+ if (type != REG_MULTI_SZ) {
+ DLOG(ERROR) << "Found PendingRename value of unexpected type.";
+ return E_UNEXPECTED;
+ }
+ if (buffer_size % 2) {
+ // The buffer size should be an even number (since we expect wchar_ts).
+ // If this is not the case, fail here.
+ DLOG(ERROR) << "Corrupt PendingRename value.";
+ return E_UNEXPECTED;
+ }
+
+ // There are pending file renames. Read them in.
+ buffer.resize(buffer_size);
+ result = RegQueryValueEx(session_manager_handle, kPendingFileRenameOps,
+ 0, &type, reinterpret_cast<LPBYTE>(&buffer[0]),
+ &buffer_size);
+ if (result != ERROR_SUCCESS) {
+ DLOG(ERROR) << "Failed to read from " << kPendingFileRenameOps;
+ return HRESULT_FROM_WIN32(result);
+ }
+ } else {
+ // That was unexpected.
+ DLOG(ERROR) << "Unexpected result from RegQueryValueEx: " << result;
+ return HRESULT_FROM_WIN32(result);
+ }
+
+ // We now have a buffer of bytes that is actually a sequence of
+ // null-terminated wchar_t strings terminated by an additional null character.
+ // Stick this into a vector of strings for clarity.
+ HRESULT hr = MultiSZBytesToStringArray(&buffer[0], buffer.size(),
+ pending_moves);
+ return hr;
+}
+
+bool MatchPendingDeletePath(const std::wstring& short_form_needle,
+ const std::wstring& reg_path) {
+ std::wstring match_path(reg_path); // Stores the path stored in each entry.
+
+ // First chomp the prefix since that will mess up GetShortPathName.
+ std::wstring prefix(L"\\??\\");
+ if (StartsWith(match_path, prefix, false)) {
+ match_path = match_path.substr(4);
+ }
+
+ // Get the short path name of the entry.
+ std::wstring short_match_path(GetShortPathName(match_path.c_str()));
+
+ // Now compare the paths. If it isn't one we're looking for, add it
+ // to the list to keep.
+ return StartsWith(short_match_path, short_form_needle, false);
+}
+
+// Removes all pending moves for the given |directory| and any contained
+// files or subdirectories. Returns true on success
+bool RemoveFromMovesPendingReboot(const wchar_t* directory) {
+ DCHECK(directory);
+ std::vector<PendingMove> pending_moves;
+ HRESULT hr = GetPendingMovesValue(&pending_moves);
+ if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
+ // No pending moves, nothing to do.
+ return true;
+ } else if (FAILED(hr)) {
+ // Couldn't read the key or the key was corrupt.
+ return false;
+ }
+
+ // Get the short form of |directory| and use that to match.
+ std::wstring short_directory(GetShortPathName(directory));
+
+ std::vector<PendingMove> strings_to_keep;
+ std::vector<PendingMove>::const_iterator iter(pending_moves.begin());
+ for (; iter != pending_moves.end(); iter++) {
+ if (!MatchPendingDeletePath(short_directory, iter->first)) {
+ // This doesn't match the deletions we are looking for. Preserve
+ // this string pair, making sure that it is in fact a pair.
+ strings_to_keep.push_back(*iter);
+ }
+ }
+
+ if (strings_to_keep.size() == pending_moves.size()) {
+ // Nothing to remove, return true.
+ return true;
+ }
+
+ // Write the key back into a buffer.
+ RegKey session_manager_key(HKEY_LOCAL_MACHINE, kSessionManagerKey,
+ KEY_CREATE_SUB_KEY | KEY_SET_VALUE);
+ if (!session_manager_key.Handle()) {
+ // Couldn't open / create the key.
+ LOG(ERROR) << "Failed to open session manager key for writing.";
+ return false;
+ }
+
+ if (strings_to_keep.size() > 1) {
+ std::vector<char> buffer;
+ StringArrayToMultiSZBytes(strings_to_keep, &buffer);
+ DCHECK(buffer.size() > 0);
+ if (buffer.size() > 0) {
+ return session_manager_key.WriteValue(kPendingFileRenameOps, &buffer[0],
+ buffer.size(), REG_MULTI_SZ);
+ } else {
+ return false;
+ }
+ } else {
+ // We have only the trailing NULL string. Don't bother writing that.
+ return session_manager_key.DeleteValue(kPendingFileRenameOps);
+ }
+}
diff --git a/chrome/installer/util/delete_after_reboot_helper.h b/chrome/installer/util/delete_after_reboot_helper.h
new file mode 100644
index 0000000..064a634
--- /dev/null
+++ b/chrome/installer/util/delete_after_reboot_helper.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2009 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.
+//
+// This file declares helper methods used to schedule files for deletion
+// on next reboot.
+
+#ifndef CHROME_INSTALLER_UTIL_DELETE_AFTER_REBOOT_HELPER_H_
+#define CHROME_INSTALLER_UTIL_DELETE_AFTER_REBOOT_HELPER_H_
+
+#include <string>
+#include <vector>
+
+#include <windows.h>
+
+// Used by the unit tests.
+extern const wchar_t kSessionManagerKey[];
+extern const wchar_t kPendingFileRenameOps[];
+
+typedef std::pair<std::wstring, std::wstring> PendingMove;
+
+// Attempts to schedule the directory for deletion.
+bool ScheduleDirectoryForDeletion(const wchar_t* dir_name);
+
+// Removes all pending moves that are registered for |directory| and all
+// elements contained in |directory|.
+bool RemoveFromMovesPendingReboot(const wchar_t* directory);
+
+// Retrieves the list of pending renames from the registry and returns a vector
+// containing pairs of strings that represent the operations. If the list
+// contains only deletes then every other element will be an empty string
+// as per http://msdn.microsoft.com/en-us/library/aa365240(VS.85).aspx.
+HRESULT GetPendingMovesValue(std::vector<PendingMove>* pending_moves);
+
+// This returns true if |short_form_needle| is contained in |reg_path| where
+// |short_form_needle| is a file system path that has been shortened by
+// GetShortPathName and |reg_path| is a path stored in the
+// PendingFileRenameOperations key.
+bool MatchPendingDeletePath(const std::wstring& short_form_needle,
+ const std::wstring& reg_path);
+
+// Converts the strings found in |buffer| to a list of PendingMoves that is
+// returned in |value|.
+// |buffer| points to a series of pairs of null-terminated wchar_t strings
+// followed by a terminating null character.
+// |byte_count| is the length of |buffer| in bytes.
+// |value| is a pointer to an empty vector of PendingMoves (string pairs).
+// On success, this vector contains all of the string pairs extracted from
+// |buffer|.
+// Returns S_OK on success, E_INVALIDARG if buffer does not meet the above
+// specification.
+HRESULT MultiSZBytesToStringArray(const char* buffer, size_t byte_count,
+ std::vector<PendingMove>* value);
+
+// The inverse of MultiSZBytesToStringArray, this function converts a list
+// of string pairs into a byte array format suitable for writing to the
+// kPendingFileRenameOps registry value. It concatenates the strings and
+// appends an additional terminating null character.
+void StringArrayToMultiSZBytes(const std::vector<PendingMove>& strings,
+ std::vector<char>* buffer);
+
+// A helper function for the win32 GetShortPathName that more conveniently
+// returns a correctly sized wstring.
+std::wstring GetShortPathName(const wchar_t* path);
+
+#endif // CHROME_INSTALLER_UTIL_DELETE_AFTER_REBOOT_HELPER_H_
diff --git a/chrome/installer/util/delete_after_reboot_helper_unittest.cc b/chrome/installer/util/delete_after_reboot_helper_unittest.cc
new file mode 100644
index 0000000..114825c
--- /dev/null
+++ b/chrome/installer/util/delete_after_reboot_helper_unittest.cc
@@ -0,0 +1,186 @@
+// Copyright (c) 2009 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 <shlobj.h>
+
+#include "base/file_util.h"
+#include "base/registry.h"
+#include "base/scoped_ptr.h"
+#include "base/string_util.h"
+#include "chrome/installer/util/delete_after_reboot_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// These tests exercise the Delete-After-Reboot code which requires
+// modifications to HKLM. This will fail on Vista and above if the user
+// is not an admin or if UAC is on.
+// I tried using RegOverridePredefKey to test, but MoveFileEx ignore this
+// even on 32 bit machines :-( As such, running this test may pollute
+// your PendingFileRenameOperations value.
+class DeleteAfterRebootHelperTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Create a temporary directory for testing and fill it with some files.
+ std::wstring no_prefix;
+ file_util::CreateNewTempDirectory(no_prefix, &temp_dir_);
+ file_util::CreateTemporaryFileInDir(temp_dir_, &temp_file_);
+
+ temp_subdir_ = temp_dir_.Append(L"subdir");
+ file_util::CreateDirectory(temp_subdir_);
+ file_util::CreateTemporaryFileInDir(temp_subdir_, &temp_subdir_file_);
+
+ // Copy the current pending moves and then clear it if we can:
+ if (IsUserAnAdmin()) {
+ GetPendingMovesValue(&original_pending_moves_);
+ }
+ }
+ virtual void TearDown() {
+ // Delete the temporary directory if it's still there.
+ file_util::Delete(temp_dir_, true);
+
+ // Try and restore the pending moves value, if we have one.
+ if (IsUserAnAdmin() && original_pending_moves_.size() > 1) {
+ RegKey session_manager_key(HKEY_LOCAL_MACHINE, kSessionManagerKey,
+ KEY_CREATE_SUB_KEY | KEY_SET_VALUE);
+ if (!session_manager_key.Handle()) {
+ // Couldn't open / create the key.
+ DLOG(ERROR) << "Failed to open session manager key for writing.";
+ }
+
+ std::vector<char> buffer;
+ StringArrayToMultiSZBytes(original_pending_moves_, &buffer);
+ session_manager_key.WriteValue(kPendingFileRenameOps, &buffer[0],
+ buffer.size(), REG_MULTI_SZ);
+ }
+ }
+
+ // Compares two buffers of size len. Returns true if they are equal,
+ // false otherwise. Standard warnings about making sure the buffers
+ // are at least len chars long apply.
+ template<class Type>
+ bool CompareBuffers(Type* buf1, Type* buf2, int len) {
+ Type* comp1 = buf1;
+ Type* comp2 = buf2;
+ for (int i = 0; i < len; i++) {
+ if (*comp1 != *comp2)
+ return false;
+ comp1++;
+ comp2++;
+ }
+ return true;
+ }
+
+ // Returns the size of the given list of wstrings in bytes, including
+ // null chars, plus an additional terminating null char.
+ // e.g. the length of all the strings * sizeof(wchar_t).
+ virtual size_t WStringPairListSize(
+ const std::vector<PendingMove>& string_list) {
+ size_t length = 0;
+ std::vector<PendingMove>::const_iterator iter(string_list.begin());
+ for (; iter != string_list.end(); ++iter) {
+ length += iter->first.size() + 1; // +1 for the null char.
+ length += iter->second.size() + 1; // +1 for the null char.
+ }
+ length++; // for the additional null char.
+ return length * sizeof(wchar_t);
+ }
+
+ std::vector<PendingMove> original_pending_moves_;
+
+ FilePath temp_dir_;
+ FilePath temp_file_;
+ FilePath temp_subdir_;
+ FilePath temp_subdir_file_;
+};
+}
+
+TEST_F(DeleteAfterRebootHelperTest, TestStringListToMultiSZConversions) {
+ struct StringTest {
+ wchar_t* test_name;
+ wchar_t* str;
+ DWORD length;
+ size_t count;
+ } tests[] = {
+ { L"basic", L"foo\0bar\0fee\0bee\0boo\0bong\0\0", 26 * sizeof(wchar_t), 3 },
+ { L"empty", L"\0\0", 2 * sizeof(wchar_t), 1 },
+ { L"deletes", L"foo\0\0bar\0\0bizz\0\0", 16 * sizeof(wchar_t), 3 },
+ };
+
+ for (int i = 0; i < arraysize(tests); i++) {
+ std::vector<PendingMove> string_list;
+ EXPECT_TRUE(SUCCEEDED(
+ MultiSZBytesToStringArray(reinterpret_cast<char*>(tests[i].str),
+ tests[i].length, &string_list)))
+ << tests[i].test_name;
+ EXPECT_EQ(tests[i].count, string_list.size()) << tests[i].test_name;
+ std::vector<char> buffer;
+ buffer.resize(WStringPairListSize(string_list));
+ StringArrayToMultiSZBytes(string_list, &buffer);
+ EXPECT_TRUE(CompareBuffers(&buffer[0],
+ reinterpret_cast<char*>(tests[i].str),
+ tests[i].length)) << tests[i].test_name;
+ }
+
+ StringTest failures[] =
+ { L"malformed", reinterpret_cast<wchar_t*>("oddnumb\0\0"), 9, 1 };
+
+ for (int i = 0; i < arraysize(failures); i++) {
+ std::vector<PendingMove> string_list;
+ EXPECT_FALSE(SUCCEEDED(
+ MultiSZBytesToStringArray(reinterpret_cast<char*>(failures[i].str),
+ failures[i].length, &string_list)))
+ << failures[i].test_name;
+ }
+}
+
+
+TEST_F(DeleteAfterRebootHelperTest, TestFileDeletes) {
+ if (!IsUserAnAdmin()) {
+ return;
+ }
+
+ EXPECT_TRUE(ScheduleDirectoryForDeletion(temp_dir_.value().c_str()));
+
+ std::vector<PendingMove> pending_moves;
+ EXPECT_TRUE(SUCCEEDED(GetPendingMovesValue(&pending_moves)));
+
+ // We should see, somewhere in this key, deletion writs for
+ // temp_file_, temp_subdir_file_, temp_subdir_ and temp_dir_ in that order.
+ EXPECT_TRUE(pending_moves.size() > 3);
+
+ // Get the short form of temp_file_ and use that to match.
+ std::wstring short_temp_file(GetShortPathName(temp_file_.value().c_str()));
+
+ // Scan for the first expected delete.
+ std::vector<PendingMove>::const_iterator iter(pending_moves.begin());
+ for (; iter != pending_moves.end(); iter++) {
+ if (MatchPendingDeletePath(short_temp_file, iter->first))
+ break;
+ }
+
+ // Check that each of the deletes we expect are there in order.
+ FilePath expected_paths[] =
+ { temp_file_, temp_subdir_file_, temp_subdir_, temp_dir_ };
+ for (int i = 0; i < arraysize(expected_paths); i++) {
+ EXPECT_FALSE(iter == pending_moves.end());
+ if (iter != pending_moves.end()) {
+ std::wstring short_path_name(
+ GetShortPathName(expected_paths[i].value().c_str()));
+ EXPECT_TRUE(MatchPendingDeletePath(short_path_name, iter->first));
+ iter++;
+ }
+ }
+
+ // Test that we can remove the pending deletes.
+ EXPECT_TRUE(RemoveFromMovesPendingReboot(temp_dir_.value().c_str()));
+ HRESULT hr = GetPendingMovesValue(&pending_moves);
+ EXPECT_TRUE(hr == S_OK || hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
+
+ std::vector<PendingMove>::const_iterator check_iter(pending_moves.begin());
+ for (; check_iter != pending_moves.end(); ++check_iter) {
+ EXPECT_FALSE(MatchPendingDeletePath(short_temp_file, check_iter->first));
+ }
+}
diff --git a/chrome/installer/util/util_constants.h b/chrome/installer/util/util_constants.h
index 534d770..61f6805 100644
--- a/chrome/installer/util/util_constants.h
+++ b/chrome/installer/util/util_constants.h
@@ -39,7 +39,8 @@ enum InstallStatus {
EULA_REJECTED, // EULA dialog was not accepted by user.
EULA_ACCEPTED, // EULA dialog was accepted by user.
EULA_ACCEPTED_OPT_IN, // EULA accepted wtih the crash optin selected.
- INSTALL_DIR_IN_USE // Installation directory is in use by another process
+ INSTALL_DIR_IN_USE, // Installation directory is in use by another process
+ UNINSTALL_REQUIRES_REBOOT // Uninstallation required a reboot.
};
namespace switches {