diff options
author | grt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-18 02:47:17 +0000 |
---|---|---|
committer | grt@chromium.org <grt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-09-18 02:47:17 +0000 |
commit | a6f66bf26ec3118c12710b3bdd325a53e69d1da1 (patch) | |
tree | 70ce82d288d7a031cfed00290cab461810c1f8e0 /chrome/installer | |
parent | c135686acf4c9b2a2b9cd10ba6a6ecf1b67a8d05 (diff) | |
download | chromium_src-a6f66bf26ec3118c12710b3bdd325a53e69d1da1.zip chromium_src-a6f66bf26ec3118c12710b3bdd325a53e69d1da1.tar.gz chromium_src-a6f66bf26ec3118c12710b3bdd325a53e69d1da1.tar.bz2 |
Added CopyRegKeyWorkItem in support of IE low rights policy fixes.
A subsequent CL will contain the rest of the fixes for the bug.
BUG=95810
TEST=installer_util_unittests.exe
Review URL: http://codereview.chromium.org/7890069
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@101684 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/installer')
-rw-r--r-- | chrome/installer/util/copy_reg_key_work_item.cc | 100 | ||||
-rw-r--r-- | chrome/installer/util/copy_reg_key_work_item.h | 51 | ||||
-rw-r--r-- | chrome/installer/util/copy_reg_key_work_item_unittest.cc | 141 | ||||
-rw-r--r-- | chrome/installer/util/delete_reg_key_work_item.cc | 290 | ||||
-rw-r--r-- | chrome/installer/util/delete_reg_key_work_item.h | 12 | ||||
-rw-r--r-- | chrome/installer/util/delete_reg_key_work_item_unittest.cc | 112 | ||||
-rw-r--r-- | chrome/installer/util/registry_key_backup.cc | 321 | ||||
-rw-r--r-- | chrome/installer/util/registry_key_backup.h | 57 | ||||
-rw-r--r-- | chrome/installer/util/registry_key_backup_unittest.cc | 85 | ||||
-rw-r--r-- | chrome/installer/util/registry_test_data.cc | 108 | ||||
-rw-r--r-- | chrome/installer/util/registry_test_data.h | 59 | ||||
-rw-r--r-- | chrome/installer/util/work_item.cc | 9 | ||||
-rw-r--r-- | chrome/installer/util/work_item.h | 7 | ||||
-rw-r--r-- | chrome/installer/util/work_item_list.cc | 11 | ||||
-rw-r--r-- | chrome/installer/util/work_item_list.h | 15 |
15 files changed, 1013 insertions, 365 deletions
diff --git a/chrome/installer/util/copy_reg_key_work_item.cc b/chrome/installer/util/copy_reg_key_work_item.cc new file mode 100644 index 0000000..a0d2b1f --- /dev/null +++ b/chrome/installer/util/copy_reg_key_work_item.cc @@ -0,0 +1,100 @@ +// 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/util/copy_reg_key_work_item.h" + +#include <shlwapi.h> + +#include "base/logging.h" +#include "base/win/registry.h" + +using base::win::RegKey; + +CopyRegKeyWorkItem::~CopyRegKeyWorkItem() { +} + +CopyRegKeyWorkItem::CopyRegKeyWorkItem(HKEY predefined_root, + const std::wstring& source_key_path, + const std::wstring& dest_key_path) + : predefined_root_(predefined_root), + source_key_path_(source_key_path), + dest_key_path_(dest_key_path) { + DCHECK(predefined_root); + // It's a safe bet that we don't want to copy or overwrite one of the root + // trees. + DCHECK(!source_key_path.empty()); + DCHECK(!dest_key_path.empty()); +} + +bool CopyRegKeyWorkItem::Do() { + if (source_key_path_.empty() || dest_key_path_.empty()) + return false; + + RegistryKeyBackup backup; + RegKey dest_key; + + // Only try to make a backup if we're not configured to ignore failures. + if (!ignore_failure_) { + if (!backup.Initialize(predefined_root_, dest_key_path_.c_str())) { + LOG(ERROR) << "Failed to backup destination for registry key copy."; + return false; + } + } + + // Delete the destination before attempting to copy. + LONG result = SHDeleteKey(predefined_root_, dest_key_path_.c_str()); + if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) { + LOG(ERROR) << "Failed to delete key at " << dest_key_path_ << ", result: " + << result; + } else { + // We've just modified the registry, so remember any backup we may have + // made so that Rollback can take us back where we started. + backup_.swap(backup); + // Make the copy. + result = dest_key.Create(predefined_root_, dest_key_path_.c_str(), + KEY_WRITE); + if (result != ERROR_SUCCESS) { + LOG(ERROR) << "Failed to open destination key at " << dest_key_path_ + << ", result: " << result; + } else { + result = SHCopyKey(predefined_root_, source_key_path_.c_str(), + dest_key.Handle(), 0); + switch (result) { + case ERROR_FILE_NOT_FOUND: + // The source didn't exist, so neither should the destination. + dest_key.Close(); + SHDeleteKey(predefined_root_, dest_key_path_.c_str()); + // Handle like a success. + result = ERROR_SUCCESS; + // -- FALL THROUGH TO SUCCESS CASE -- + case ERROR_SUCCESS: + break; + default: + LOG(ERROR) << "Failed to copy key from " << source_key_path_ << " to " + << dest_key_path_ << ", result: " << result; + break; + } + } + } + + return ignore_failure_ ? true : (result == ERROR_SUCCESS); +} + +void CopyRegKeyWorkItem::Rollback() { + if (ignore_failure_) + return; + + // Delete anything in the key before restoring the backup in case someone else + // put new data in the key after Do(). + LONG result = SHDeleteKey(predefined_root_, dest_key_path_.c_str()); + if (result != ERROR_SUCCESS && result != ERROR_FILE_NOT_FOUND) { + LOG(ERROR) << "Failed to delete key at " << dest_key_path_ + << " in rollback, result: " << result; + } + + // Restore the old contents. The restoration takes on its default security + // attributes; any custom attributes are lost. + if (!backup_.WriteTo(predefined_root_, dest_key_path_.c_str())) + LOG(ERROR) << "Failed to restore key in rollback."; +} diff --git a/chrome/installer/util/copy_reg_key_work_item.h b/chrome/installer/util/copy_reg_key_work_item.h new file mode 100644 index 0000000..b1d4631 --- /dev/null +++ b/chrome/installer/util/copy_reg_key_work_item.h @@ -0,0 +1,51 @@ +// 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_UTIL_COPY_REG_KEY_WORK_ITEM_H_ +#define CHROME_INSTALLER_UTIL_COPY_REG_KEY_WORK_ITEM_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/installer/util/registry_key_backup.h" +#include "chrome/installer/util/work_item.h" + +// A WorkItem subclass that replaces the contents of one registry key with that +// of another (i.e., the destination is erased prior to the copy). Be aware +// that in the event of rollback the destination key's values and subkeys are +// restored but the key and its subkeys take on their default security +// descriptors. +class CopyRegKeyWorkItem : public WorkItem { + public: + virtual ~CopyRegKeyWorkItem(); + virtual bool Do() OVERRIDE; + virtual void Rollback() OVERRIDE; + + private: + friend class WorkItem; + + // Neither |source_key_path| nor |dest_key_path| may be empty. + CopyRegKeyWorkItem(HKEY predefined_key, + const std::wstring& source_key_path, + const std::wstring& dest_key_path); + + // Root key in which we operate. The root key must be one of the predefined + // keys on Windows. + HKEY predefined_root_; + + // Path of the key to be copied. + std::wstring source_key_path_; + + // Path of the destination key. + std::wstring dest_key_path_; + + // Backup of the destination key. + RegistryKeyBackup backup_; + + DISALLOW_COPY_AND_ASSIGN(CopyRegKeyWorkItem); +}; + +#endif // CHROME_INSTALLER_UTIL_COPY_REG_KEY_WORK_ITEM_H_ diff --git a/chrome/installer/util/copy_reg_key_work_item_unittest.cc b/chrome/installer/util/copy_reg_key_work_item_unittest.cc new file mode 100644 index 0000000..84a6f64 --- /dev/null +++ b/chrome/installer/util/copy_reg_key_work_item_unittest.cc @@ -0,0 +1,141 @@ +// 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 <shlwapi.h> // NOLINT + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/registry.h" +#include "chrome/installer/util/copy_reg_key_work_item.h" +#include "chrome/installer/util/registry_test_data.h" +#include "chrome/installer/util/work_item.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::win::RegKey; + +class CopyRegKeyWorkItemTest : public testing::Test { + protected: + static void TearDownTestCase() { + logging::CloseLogFile(); + } + + virtual void SetUp() { + ASSERT_TRUE(test_data_.Initialize(HKEY_CURRENT_USER, L"SOFTWARE\\TmpTmp")); + destination_path_.assign(test_data_.base_path()).append(L"\\Destination"); + } + + RegistryTestData test_data_; + std::wstring destination_path_; +}; + +// Test that copying a key that doesn't exist succeeds, and that rollback does +// nothing. +TEST_F(CopyRegKeyWorkItemTest, TestNoKey) { + const std::wstring key_paths[] = { + std::wstring(test_data_.base_path() + L"\\NoKeyHere"), + std::wstring(test_data_.base_path() + L"\\NoKeyHere\\OrHere") + }; + RegKey key; + for (size_t i = 0; i < arraysize(key_paths); ++i) { + const std::wstring& key_path = key_paths[i]; + scoped_ptr<CopyRegKeyWorkItem> item( + WorkItem::CreateCopyRegKeyWorkItem(test_data_.root_key(), key_path, + destination_path_)); + EXPECT_TRUE(item->Do()); + EXPECT_NE(ERROR_SUCCESS, + key.Open(test_data_.root_key(), destination_path_.c_str(), + KEY_READ)); + item->Rollback(); + item.reset(); + EXPECT_NE(ERROR_SUCCESS, + key.Open(test_data_.root_key(), destination_path_.c_str(), + KEY_READ)); + } +} + +// Test that copying an empty key succeeds, and that rollback removes the copy. +TEST_F(CopyRegKeyWorkItemTest, TestEmptyKey) { + RegKey key; + scoped_ptr<CopyRegKeyWorkItem> item( + WorkItem::CreateCopyRegKeyWorkItem(test_data_.root_key(), + test_data_.empty_key_path(), + destination_path_)); + EXPECT_TRUE(item->Do()); + RegistryTestData::ExpectEmptyKey(test_data_.root_key(), + destination_path_.c_str()); + item->Rollback(); + item.reset(); + EXPECT_NE(ERROR_SUCCESS, + key.Open(test_data_.root_key(), destination_path_.c_str(), + KEY_READ)); +} + +// Test that copying a key with subkeys and values succeeds, and that rollback +// removes the copy. +TEST_F(CopyRegKeyWorkItemTest, TestNonEmptyKey) { + RegKey key; + scoped_ptr<CopyRegKeyWorkItem> item( + WorkItem::CreateCopyRegKeyWorkItem(test_data_.root_key(), + test_data_.non_empty_key_path(), + destination_path_)); + EXPECT_TRUE(item->Do()); + test_data_.ExpectMatchesNonEmptyKey(test_data_.root_key(), + destination_path_.c_str()); + item->Rollback(); + item.reset(); + EXPECT_NE(ERROR_SUCCESS, + key.Open(test_data_.root_key(), destination_path_.c_str(), + KEY_READ)); +} + +// Test that copying an empty key over a key with data succeeds, and that +// rollback restores the original data. +TEST_F(CopyRegKeyWorkItemTest, TestOverwriteAndRestore) { + RegKey key; + // First copy the data into the dest. + EXPECT_EQ(ERROR_SUCCESS, + key.Create(test_data_.root_key(), destination_path_.c_str(), + KEY_WRITE)); + EXPECT_EQ(ERROR_SUCCESS, + SHCopyKey(test_data_.root_key(), + test_data_.non_empty_key_path().c_str(), key.Handle(), + 0)); + key.Close(); + + // Now copy the empty key into the dest. + scoped_ptr<CopyRegKeyWorkItem> item( + WorkItem::CreateCopyRegKeyWorkItem(test_data_.root_key(), + test_data_.empty_key_path(), + destination_path_)); + EXPECT_TRUE(item->Do()); + + // Make sure the dest is now empty. + RegistryTestData::ExpectEmptyKey(test_data_.root_key(), + destination_path_.c_str()); + + // Restore the data. + item->Rollback(); + item.reset(); + + // Make sure it's all there. + test_data_.ExpectMatchesNonEmptyKey(test_data_.root_key(), + destination_path_.c_str()); +} + +// Test that Rollback does nothing when the item is configured to ignore +// failures. +TEST_F(CopyRegKeyWorkItemTest, TestIgnoreFailRollback) { + // Copy the empty key onto the non-empty key, ignoring failures. + scoped_ptr<CopyRegKeyWorkItem> item( + WorkItem::CreateCopyRegKeyWorkItem(test_data_.root_key(), + test_data_.empty_key_path(), + test_data_.non_empty_key_path())); + item->set_ignore_failure(true); + EXPECT_TRUE(item->Do()); + item->Rollback(); + item.reset(); + RegistryTestData::ExpectEmptyKey(test_data_.root_key(), + test_data_.non_empty_key_path().c_str()); +} diff --git a/chrome/installer/util/delete_reg_key_work_item.cc b/chrome/installer/util/delete_reg_key_work_item.cc index b044f44..462698f 100644 --- a/chrome/installer/util/delete_reg_key_work_item.cc +++ b/chrome/installer/util/delete_reg_key_work_item.cc @@ -5,265 +5,12 @@ #include "chrome/installer/util/delete_reg_key_work_item.h" #include <shlwapi.h> -#include <algorithm> -#include <limits> -#include <vector> #include "base/logging.h" -#include "base/rand_util.h" -#include "base/stringprintf.h" #include "base/win/registry.h" -#include "chrome/installer/util/logging_installer.h" using base::win::RegKey; -namespace { -const REGSAM kKeyReadNoNotify = (KEY_READ) & ~(KEY_NOTIFY); -} - -// A container for a registry key, its values, and its subkeys. We don't use -// more obvious methods for various reasons: -// - RegCopyTree isn't supported pre-Vista, so we'd have to do something -// different for XP anyway. -// - SHCopyKey can't copy subkeys into a volatile destination, so we'd have to -// worry about polluting the registry. -// We don't persist security attributes since we only delete keys that we own, -// and we don't set custom attributes on them anyway. -class DeleteRegKeyWorkItem::RegKeyBackup { - public: - RegKeyBackup(); - bool Initialize(const RegKey& key); - bool WriteTo(RegKey* key) const; - - private: - // A container for a registry value. - class RegValueBackup { - public: - RegValueBackup(); - void Initialize(const wchar_t* name_buffer, DWORD name_size, - DWORD type, const uint8* data, DWORD data_size); - const std::wstring& name_str() const { return name_; } - const wchar_t* name() const { return name_.empty() ? NULL : name_.c_str(); } - DWORD type() const { return type_; } - const uint8* data() const { return data_.empty() ? NULL : &data_[0]; } - DWORD data_len() const { return static_cast<DWORD>(data_.size()); } - - private: - std::wstring name_; - std::vector<uint8> data_; - DWORD type_; - - DISALLOW_COPY_AND_ASSIGN(RegValueBackup); - }; - - scoped_array<RegValueBackup> values_; - scoped_array<std::wstring> subkey_names_; - scoped_array<RegKeyBackup> subkeys_; - ptrdiff_t num_values_; - ptrdiff_t num_subkeys_; - - DISALLOW_COPY_AND_ASSIGN(RegKeyBackup); -}; - -DeleteRegKeyWorkItem::RegKeyBackup::RegValueBackup::RegValueBackup() - : type_(REG_NONE) { -} - -void DeleteRegKeyWorkItem::RegKeyBackup::RegValueBackup::Initialize( - const wchar_t* name_buffer, - DWORD name_size, - DWORD type, const uint8* data, - DWORD data_size) { - name_.assign(name_buffer, name_size); - type_ = type; - data_.assign(data, data + data_size); -} - -DeleteRegKeyWorkItem::RegKeyBackup::RegKeyBackup() - : num_values_(0), - num_subkeys_(0) { -} - -// Initializes this object by reading the values and subkeys of |key|. -// Security descriptors are not backed up. -bool DeleteRegKeyWorkItem::RegKeyBackup::Initialize(const RegKey& key) { - DCHECK(key.Valid()); - - scoped_array<RegValueBackup> values; - scoped_array<std::wstring> subkey_names; - scoped_array<RegKeyBackup> subkeys; - - DWORD num_subkeys = 0; - DWORD max_subkey_name_len = 0; - DWORD num_values = 0; - DWORD max_value_name_len = 0; - DWORD max_value_len = 0; - LONG result = RegQueryInfoKey(key.Handle(), NULL, NULL, NULL, - &num_subkeys, &max_subkey_name_len, NULL, - &num_values, &max_value_name_len, - &max_value_len, NULL, NULL); - if (result != ERROR_SUCCESS) { - LOG(ERROR) << "Failed getting info of key to backup, result: " << result; - return false; - } - if (max_subkey_name_len >= std::numeric_limits<DWORD>::max() - 1 || - max_value_name_len >= std::numeric_limits<DWORD>::max() - 1) { - LOG(ERROR) - << "Failed backing up key; subkeys and/or names are out of range."; - return false; - } - DWORD max_name_len = std::max(max_subkey_name_len, max_value_name_len) + 1; - scoped_array<wchar_t> name_buffer(new wchar_t[max_name_len]); - - // Backup the values. - if (num_values != 0) { - values.reset(new RegValueBackup[num_values]); - scoped_array<uint8> value_buffer(new uint8[max_value_len]); - DWORD name_size = 0; - DWORD value_type = REG_NONE; - DWORD value_size = 0; - - for (DWORD i = 0; i < num_values; ) { - name_size = max_name_len; - value_size = max_value_len; - result = RegEnumValue(key.Handle(), i, name_buffer.get(), &name_size, - NULL, &value_type, value_buffer.get(), &value_size); - switch (result) { - case ERROR_NO_MORE_ITEMS: - num_values = i; - break; - case ERROR_SUCCESS: - values[i].Initialize(name_buffer.get(), name_size, value_type, - value_buffer.get(), value_size); - ++i; - break; - case ERROR_MORE_DATA: - if (value_size > max_value_len) { - max_value_len = value_size; - value_buffer.reset(new uint8[max_value_len]); - } else { - DCHECK(max_name_len - 1 < name_size); - if (name_size >= std::numeric_limits<DWORD>::max() - 1) { - LOG(ERROR) << "Failed backing up key; value name out of range."; - return false; - } - max_name_len = name_size + 1; - name_buffer.reset(new wchar_t[max_name_len]); - } - break; - default: - LOG(ERROR) << "Failed backing up value " << i << ", result: " - << result; - return false; - } - } - DLOG_IF(WARNING, RegEnumValue(key.Handle(), num_values, name_buffer.get(), - &name_size, NULL, &value_type, NULL, - NULL) != ERROR_NO_MORE_ITEMS) - << "Concurrent modifications to registry key during backup operation."; - } - - // Backup the subkeys. - if (num_subkeys != 0) { - subkey_names.reset(new std::wstring[num_subkeys]); - subkeys.reset(new RegKeyBackup[num_subkeys]); - DWORD name_size = 0; - - // Get the names of them. - for (DWORD i = 0; i < num_subkeys; ) { - name_size = max_name_len; - result = RegEnumKeyEx(key.Handle(), i, name_buffer.get(), &name_size, - NULL, NULL, NULL, NULL); - switch (result) { - case ERROR_NO_MORE_ITEMS: - num_subkeys = i; - break; - case ERROR_SUCCESS: - subkey_names[i].assign(name_buffer.get(), name_size); - ++i; - break; - case ERROR_MORE_DATA: - if (name_size >= std::numeric_limits<DWORD>::max() - 1) { - LOG(ERROR) << "Failed backing up key; subkey name out of range."; - return false; - } - max_name_len = name_size + 1; - name_buffer.reset(new wchar_t[max_name_len]); - break; - default: - LOG(ERROR) << "Failed getting name of subkey " << i - << " for backup, result: " << result; - return false; - } - } - DLOG_IF(WARNING, - RegEnumKeyEx(key.Handle(), num_subkeys, NULL, &name_size, NULL, - NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS) - << "Concurrent modifications to registry key during backup operation."; - - // Get their values. - RegKey subkey; - for (DWORD i = 0; i < num_subkeys; ++i) { - result = subkey.Open(key.Handle(), subkey_names[i].c_str(), - kKeyReadNoNotify); - if (result != ERROR_SUCCESS) { - LOG(ERROR) << "Failed opening subkey \"" << subkey_names[i] - << "\" for backup, result: " << result; - return false; - } - if (!subkeys[i].Initialize(subkey)) { - LOG(ERROR) << "Failed backing up subkey \"" << subkey_names[i] << "\""; - return false; - } - } - } - - values_.swap(values); - subkey_names_.swap(subkey_names); - subkeys_.swap(subkeys); - num_values_ = num_values; - num_subkeys_ = num_subkeys; - - return true; -} - -// Writes the values and subkeys of this object into |key|. -bool DeleteRegKeyWorkItem::RegKeyBackup::WriteTo(RegKey* key) const { - LONG result = ERROR_SUCCESS; - - // Write the values. - for (int i = 0; i < num_values_; ++i) { - const RegValueBackup& value = values_[i]; - result = RegSetValueEx(key->Handle(), value.name(), 0, value.type(), - value.data(), value.data_len()); - if (result != ERROR_SUCCESS) { - LOG(ERROR) << "Failed writing value \"" << value.name_str() - << "\", result: " << result; - return false; - } - } - - // Write the subkeys. - RegKey subkey; - for (int i = 0; i < num_subkeys_; ++i) { - const std::wstring& name = subkey_names_[i]; - - result = subkey.Create(key->Handle(), name.c_str(), KEY_WRITE); - if (result != ERROR_SUCCESS) { - LOG(ERROR) << "Failed creating subkey \"" << name << "\", result: " - << result; - return false; - } - if (!subkeys_[i].WriteTo(&subkey)) { - LOG(ERROR) << "Failed writing subkey \"" << name << "\", result: " - << result; - return false; - } - } - - return true; -} - DeleteRegKeyWorkItem::~DeleteRegKeyWorkItem() { } @@ -271,30 +18,22 @@ DeleteRegKeyWorkItem::DeleteRegKeyWorkItem(HKEY predefined_root, const std::wstring& path) : predefined_root_(predefined_root), path_(path) { + DCHECK(predefined_root); // It's a safe bet that we don't want to delete one of the root trees. DCHECK(!path.empty()); } bool DeleteRegKeyWorkItem::Do() { - scoped_ptr<RegKeyBackup> backup; + if (path_.empty()) + return false; + + RegistryKeyBackup backup; // Only try to make a backup if we're not configured to ignore failures. if (!ignore_failure_) { - RegKey original_key; - - // Does the key exist? - LONG result = original_key.Open(predefined_root_, path_.c_str(), - kKeyReadNoNotify); - if (result == ERROR_SUCCESS) { - backup.reset(new RegKeyBackup()); - if (!backup->Initialize(original_key)) { - LOG(ERROR) << "Failed to backup key at " << path_; - return ignore_failure_; - } - } else if (result != ERROR_FILE_NOT_FOUND) { - LOG(ERROR) << "Failed to open key at " << path_ - << " to create backup, result: " << result; - return ignore_failure_; + if (!backup.Initialize(predefined_root_, path_.c_str())) { + LOG(ERROR) << "Failed to backup destination for registry key copy."; + return false; } } @@ -313,7 +52,7 @@ bool DeleteRegKeyWorkItem::Do() { } void DeleteRegKeyWorkItem::Rollback() { - if (ignore_failure_ || backup_.get() == NULL) + if (ignore_failure_) return; // Delete anything in the key before restoring the backup in case someone else @@ -326,13 +65,6 @@ void DeleteRegKeyWorkItem::Rollback() { // Restore the old contents. The restoration takes on its default security // attributes; any custom attributes are lost. - RegKey original_key; - result = original_key.Create(predefined_root_, path_.c_str(), KEY_WRITE); - if (result != ERROR_SUCCESS) { - LOG(ERROR) << "Failed to create original key at " << path_ - << " in rollback, result: " << result; - } else { - if (!backup_->WriteTo(&original_key)) - LOG(ERROR) << "Failed to restore key in rollback, result: " << result; - } + if (!backup_.WriteTo(predefined_root_, path_.c_str())) + LOG(ERROR) << "Failed to restore key in rollback."; } diff --git a/chrome/installer/util/delete_reg_key_work_item.h b/chrome/installer/util/delete_reg_key_work_item.h index 02bc02b..ef92364 100644 --- a/chrome/installer/util/delete_reg_key_work_item.h +++ b/chrome/installer/util/delete_reg_key_work_item.h @@ -11,9 +11,12 @@ #include <string> #include "base/basictypes.h" -#include "base/memory/scoped_ptr.h" +#include "base/compiler_specific.h" +#include "chrome/installer/util/registry_key_backup.h" #include "chrome/installer/util/work_item.h" +class RegistryKeyBackup; + // A WorkItem subclass that deletes a registry key at the given path. Be aware // that in the event of rollback the key's values and subkeys are restored but // the key and its subkeys take on their default security descriptors. @@ -21,12 +24,11 @@ class DeleteRegKeyWorkItem : public WorkItem { public: virtual ~DeleteRegKeyWorkItem(); - virtual bool Do(); + virtual bool Do() OVERRIDE; - virtual void Rollback(); + virtual void Rollback() OVERRIDE; private: - class RegKeyBackup; friend class WorkItem; DeleteRegKeyWorkItem(HKEY predefined_root, const std::wstring& path); @@ -39,7 +41,7 @@ class DeleteRegKeyWorkItem : public WorkItem { std::wstring path_; // Backup of the deleted key. - scoped_ptr<RegKeyBackup> backup_; + RegistryKeyBackup backup_; DISALLOW_COPY_AND_ASSIGN(DeleteRegKeyWorkItem); }; diff --git a/chrome/installer/util/delete_reg_key_work_item_unittest.cc b/chrome/installer/util/delete_reg_key_work_item_unittest.cc index 827cc86..2127e3d 100644 --- a/chrome/installer/util/delete_reg_key_work_item_unittest.cc +++ b/chrome/installer/util/delete_reg_key_work_item_unittest.cc @@ -8,85 +8,59 @@ #include "base/memory/scoped_ptr.h" #include "base/win/registry.h" #include "chrome/installer/util/delete_reg_key_work_item.h" +#include "chrome/installer/util/registry_test_data.h" #include "chrome/installer/util/work_item.h" #include "testing/gtest/include/gtest/gtest.h" using base::win::RegKey; -namespace { -wchar_t test_root[] = L"SOFTWARE\\TmpTmp"; -} - class DeleteRegKeyWorkItemTest : public testing::Test { protected: - virtual void SetUp() { - // Create a temporary key for testing - RegKey key(HKEY_CURRENT_USER, L"", KEY_ALL_ACCESS); - key.DeleteKey(test_root); - ASSERT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, test_root, KEY_READ)); - ASSERT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, test_root, - KEY_READ)); + static void TearDownTestCase() { + logging::CloseLogFile(); } - virtual void TearDown() { - logging::CloseLogFile(); - // Clean up the temporary key - RegKey key(HKEY_CURRENT_USER, L"", KEY_ALL_ACCESS); - ASSERT_EQ(ERROR_SUCCESS, key.DeleteKey(test_root)); + virtual void SetUp() { + ASSERT_TRUE(test_data_.Initialize(HKEY_CURRENT_USER, L"SOFTWARE\\TmpTmp")); } + + RegistryTestData test_data_; }; // Test that deleting a key that doesn't exist succeeds, and that rollback does // nothing. TEST_F(DeleteRegKeyWorkItemTest, TestNoKey) { + const std::wstring key_paths[] = { + std::wstring(test_data_.base_path() + L"\\NoKeyHere"), + std::wstring(test_data_.base_path() + L"\\NoKeyHere\\OrHere") + }; RegKey key; - std::wstring key_name(std::wstring(test_root) + L"\\NoKeyHere"); - EXPECT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), - KEY_READ)); - scoped_ptr<DeleteRegKeyWorkItem> item( - WorkItem::CreateDeleteRegKeyWorkItem(HKEY_CURRENT_USER, key_name)); - EXPECT_TRUE(item->Do()); - EXPECT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), - KEY_READ)); - item->Rollback(); - item.reset(); - EXPECT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), - KEY_READ)); -} - -// Test that deleting a subkey of a key that doesn't exist succeeds, and that -// rollback does nothing. -TEST_F(DeleteRegKeyWorkItemTest, TestNoSubkey) { - RegKey key; - std::wstring key_name(std::wstring(test_root) + L"\\NoKeyHere\\OrHere"); - EXPECT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), - KEY_READ)); - scoped_ptr<DeleteRegKeyWorkItem> item( - WorkItem::CreateDeleteRegKeyWorkItem(HKEY_CURRENT_USER, key_name)); - EXPECT_TRUE(item->Do()); - EXPECT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), - KEY_READ)); - item->Rollback(); - item.reset(); - EXPECT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), - KEY_READ)); + for (size_t i = 0; i < arraysize(key_paths); ++i) { + const std::wstring& key_path = key_paths[i]; + scoped_ptr<DeleteRegKeyWorkItem> item( + WorkItem::CreateDeleteRegKeyWorkItem(test_data_.root_key(), key_path)); + EXPECT_TRUE(item->Do()); + EXPECT_NE(ERROR_SUCCESS, key.Open(test_data_.root_key(), key_path.c_str(), + KEY_READ)); + item->Rollback(); + item.reset(); + EXPECT_NE(ERROR_SUCCESS, key.Open(test_data_.root_key(), key_path.c_str(), + KEY_READ)); + } } // Test that deleting an empty key succeeds, and that rollback brings it back. TEST_F(DeleteRegKeyWorkItemTest, TestEmptyKey) { RegKey key; - std::wstring key_name(std::wstring(test_root) + L"\\EmptyKey"); - EXPECT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, key_name.c_str(), - KEY_WRITE)); - key.Close(); + const std::wstring& key_path = test_data_.empty_key_path(); scoped_ptr<DeleteRegKeyWorkItem> item( - WorkItem::CreateDeleteRegKeyWorkItem(HKEY_CURRENT_USER, key_name)); + WorkItem::CreateDeleteRegKeyWorkItem(test_data_.root_key(), key_path)); EXPECT_TRUE(item->Do()); - EXPECT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), + EXPECT_NE(ERROR_SUCCESS, key.Open(test_data_.root_key(), key_path.c_str(), KEY_READ)); item->Rollback(); item.reset(); - EXPECT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), + EXPECT_EQ(ERROR_SUCCESS, key.Open(test_data_.root_key(), key_path.c_str(), KEY_READ)); } @@ -94,29 +68,15 @@ TEST_F(DeleteRegKeyWorkItemTest, TestEmptyKey) { // brings them all back. TEST_F(DeleteRegKeyWorkItemTest, TestNonEmptyKey) { RegKey key; - std::wstring key_name(std::wstring(test_root) + L"\\NonEmptyKey"); - EXPECT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, key_name.c_str(), - KEY_WRITE)); - EXPECT_EQ(ERROR_SUCCESS, key.WriteValue(NULL, key_name.c_str())); - EXPECT_EQ(ERROR_SUCCESS, key.CreateKey(L"Subkey", KEY_WRITE)); - EXPECT_EQ(ERROR_SUCCESS, key.WriteValue(L"SomeValue", 1U)); - key.Close(); + const std::wstring& key_path = test_data_.non_empty_key_path(); scoped_ptr<DeleteRegKeyWorkItem> item( - WorkItem::CreateDeleteRegKeyWorkItem(HKEY_CURRENT_USER, key_name)); + WorkItem::CreateDeleteRegKeyWorkItem(test_data_.root_key(), key_path)); EXPECT_TRUE(item->Do()); - EXPECT_NE(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), + EXPECT_NE(ERROR_SUCCESS, key.Open(test_data_.root_key(), key_path.c_str(), KEY_READ)); item->Rollback(); item.reset(); - EXPECT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), - KEY_READ)); - std::wstring str_value; - EXPECT_EQ(ERROR_SUCCESS, key.ReadValue(NULL, &str_value)); - EXPECT_EQ(key_name, str_value); - EXPECT_EQ(ERROR_SUCCESS, key.OpenKey(L"Subkey", KEY_READ)); - DWORD dw_value = 0; - EXPECT_EQ(ERROR_SUCCESS, key.ReadValueDW(L"SomeValue", &dw_value)); - EXPECT_EQ(1U, dw_value); + test_data_.ExpectMatchesNonEmptyKey(test_data_.root_key(), key_path.c_str()); } // Test that deleting a key with subkeys we can't delete fails, and that @@ -125,8 +85,8 @@ TEST_F(DeleteRegKeyWorkItemTest, TestNonEmptyKey) { // http://crbug.com/74654 TEST_F(DeleteRegKeyWorkItemTest, DISABLED_TestUndeletableKey) { RegKey key; - std::wstring key_name(std::wstring(test_root) + L"\\UndeletableKey"); - EXPECT_EQ(ERROR_SUCCESS, key.Create(HKEY_CURRENT_USER, key_name.c_str(), + std::wstring key_name(test_data_.base_path() + L"\\UndeletableKey"); + EXPECT_EQ(ERROR_SUCCESS, key.Create(test_data_.root_key(), key_name.c_str(), KEY_WRITE)); EXPECT_EQ(ERROR_SUCCESS, key.WriteValue(NULL, key_name.c_str())); DWORD dw_value = 1; @@ -153,13 +113,13 @@ TEST_F(DeleteRegKeyWorkItemTest, DISABLED_TestUndeletableKey) { subkey.Close(); key.Close(); scoped_ptr<DeleteRegKeyWorkItem> item( - WorkItem::CreateDeleteRegKeyWorkItem(HKEY_CURRENT_USER, key_name)); + WorkItem::CreateDeleteRegKeyWorkItem(test_data_.root_key(), key_name)); EXPECT_FALSE(item->Do()); - EXPECT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), + EXPECT_EQ(ERROR_SUCCESS, key.Open(test_data_.root_key(), key_name.c_str(), KEY_QUERY_VALUE)); item->Rollback(); item.reset(); - EXPECT_EQ(ERROR_SUCCESS, key.Open(HKEY_CURRENT_USER, key_name.c_str(), + EXPECT_EQ(ERROR_SUCCESS, key.Open(test_data_.root_key(), key_name.c_str(), KEY_QUERY_VALUE)); std::wstring str_value; EXPECT_EQ(ERROR_SUCCESS, key.ReadValue(NULL, &str_value)); diff --git a/chrome/installer/util/registry_key_backup.cc b/chrome/installer/util/registry_key_backup.cc new file mode 100644 index 0000000..93734b3 --- /dev/null +++ b/chrome/installer/util/registry_key_backup.cc @@ -0,0 +1,321 @@ +// 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/util/registry_key_backup.h" + +#include <algorithm> +#include <limits> +#include <vector> + +#include "base/logging.h" +#include "base/win/registry.h" + +using base::win::RegKey; + +namespace { +const REGSAM kKeyReadNoNotify = (KEY_READ) & ~(KEY_NOTIFY); +} // namespace + +// A container for a registry key, its values, and its subkeys. +class RegistryKeyBackup::KeyData { + public: + KeyData(); + ~KeyData(); + bool Initialize(const RegKey& key); + bool WriteTo(RegKey* key) const; + + private: + class ValueData; + + scoped_array<ValueData> values_; + scoped_array<std::wstring> subkey_names_; + scoped_array<KeyData> subkeys_; + DWORD num_values_; + DWORD num_subkeys_; + + DISALLOW_COPY_AND_ASSIGN(KeyData); +}; + +// A container for a registry value. +class RegistryKeyBackup::KeyData::ValueData { + public: + ValueData(); + ~ValueData(); + void Initialize(const wchar_t* name_buffer, DWORD name_size, + DWORD type, const uint8* data, DWORD data_size); + const std::wstring& name_str() const { return name_; } + const wchar_t* name() const { return name_.empty() ? NULL : name_.c_str(); } + DWORD type() const { return type_; } + const uint8* data() const { return data_.empty() ? NULL : &data_[0]; } + DWORD data_len() const { return static_cast<DWORD>(data_.size()); } + + private: + std::wstring name_; + std::vector<uint8> data_; + DWORD type_; + + DISALLOW_COPY_AND_ASSIGN(ValueData); +}; + +RegistryKeyBackup::KeyData::ValueData::ValueData() + : type_(REG_NONE) { +} + +RegistryKeyBackup::KeyData::ValueData::~ValueData() +{ +} + +void RegistryKeyBackup::KeyData::ValueData::Initialize( + const wchar_t* name_buffer, + DWORD name_size, + DWORD type, + const uint8* data, + DWORD data_size) { + name_.assign(name_buffer, name_size); + type_ = type; + data_.assign(data, data + data_size); +} + +RegistryKeyBackup::KeyData::KeyData() + : num_values_(0), + num_subkeys_(0) { +} + +RegistryKeyBackup::KeyData::~KeyData() +{ +} + +// Initializes this object by reading the values and subkeys of |key|. +// Security descriptors are not backed up. +bool RegistryKeyBackup::KeyData::Initialize(const RegKey& key) { + scoped_array<ValueData> values; + scoped_array<std::wstring> subkey_names; + scoped_array<KeyData> subkeys; + + DWORD num_subkeys = 0; + DWORD max_subkey_name_len = 0; + DWORD num_values = 0; + DWORD max_value_name_len = 0; + DWORD max_value_len = 0; + LONG result = RegQueryInfoKey(key.Handle(), NULL, NULL, NULL, + &num_subkeys, &max_subkey_name_len, NULL, + &num_values, &max_value_name_len, + &max_value_len, NULL, NULL); + if (result != ERROR_SUCCESS) { + LOG(ERROR) << "Failed getting info of key to backup, result: " << result; + return false; + } + if (max_subkey_name_len >= std::numeric_limits<DWORD>::max() - 1 || + max_value_name_len >= std::numeric_limits<DWORD>::max() - 1) { + LOG(ERROR) + << "Failed backing up key; subkeys and/or names are out of range."; + return false; + } + DWORD max_name_len = std::max(max_subkey_name_len, max_value_name_len) + 1; + scoped_array<wchar_t> name_buffer(new wchar_t[max_name_len]); + + // Backup the values. + if (num_values != 0) { + values.reset(new ValueData[num_values]); + scoped_array<uint8> value_buffer(new uint8[max_value_len]); + DWORD name_size = 0; + DWORD value_type = REG_NONE; + DWORD value_size = 0; + + for (DWORD i = 0; i < num_values; ) { + name_size = max_name_len; + value_size = max_value_len; + result = RegEnumValue(key.Handle(), i, name_buffer.get(), &name_size, + NULL, &value_type, value_buffer.get(), &value_size); + switch (result) { + case ERROR_NO_MORE_ITEMS: + num_values = i; + break; + case ERROR_SUCCESS: + values[i].Initialize(name_buffer.get(), name_size, value_type, + value_buffer.get(), value_size); + ++i; + break; + case ERROR_MORE_DATA: + if (value_size > max_value_len) { + max_value_len = value_size; + value_buffer.reset(); // Release to heap before new allocation. + value_buffer.reset(new uint8[max_value_len]); + } else { + DCHECK_LT(max_name_len - 1, name_size); + if (name_size >= std::numeric_limits<DWORD>::max() - 1) { + LOG(ERROR) << "Failed backing up key; value name out of range."; + return false; + } + max_name_len = name_size + 1; + name_buffer.reset(); // Release to heap before new allocation. + name_buffer.reset(new wchar_t[max_name_len]); + } + break; + default: + LOG(ERROR) << "Failed backing up value " << i << ", result: " + << result; + return false; + } + } + DLOG_IF(WARNING, RegEnumValue(key.Handle(), num_values, name_buffer.get(), + &name_size, NULL, &value_type, NULL, + NULL) != ERROR_NO_MORE_ITEMS) + << "Concurrent modifications to registry key during backup operation."; + } + + // Backup the subkeys. + if (num_subkeys != 0) { + subkey_names.reset(new std::wstring[num_subkeys]); + subkeys.reset(new KeyData[num_subkeys]); + DWORD name_size = 0; + + // Get the names of them. + for (DWORD i = 0; i < num_subkeys; ) { + name_size = max_name_len; + result = RegEnumKeyEx(key.Handle(), i, name_buffer.get(), &name_size, + NULL, NULL, NULL, NULL); + switch (result) { + case ERROR_NO_MORE_ITEMS: + num_subkeys = i; + break; + case ERROR_SUCCESS: + subkey_names[i].assign(name_buffer.get(), name_size); + ++i; + break; + case ERROR_MORE_DATA: + if (name_size >= std::numeric_limits<DWORD>::max() - 1) { + LOG(ERROR) << "Failed backing up key; subkey name out of range."; + return false; + } + max_name_len = name_size + 1; + name_buffer.reset(new wchar_t[max_name_len]); + break; + default: + LOG(ERROR) << "Failed getting name of subkey " << i + << " for backup, result: " << result; + return false; + } + } + DLOG_IF(WARNING, + RegEnumKeyEx(key.Handle(), num_subkeys, NULL, &name_size, NULL, + NULL, NULL, NULL) != ERROR_NO_MORE_ITEMS) + << "Concurrent modifications to registry key during backup operation."; + + // Get their values. + RegKey subkey; + for (DWORD i = 0; i < num_subkeys; ++i) { + result = subkey.Open(key.Handle(), subkey_names[i].c_str(), + kKeyReadNoNotify); + if (result != ERROR_SUCCESS) { + LOG(ERROR) << "Failed opening subkey \"" << subkey_names[i] + << "\" for backup, result: " << result; + return false; + } + if (!subkeys[i].Initialize(subkey)) { + LOG(ERROR) << "Failed backing up subkey \"" << subkey_names[i] << "\""; + return false; + } + } + } + + values_.swap(values); + subkey_names_.swap(subkey_names); + subkeys_.swap(subkeys); + num_values_ = num_values; + num_subkeys_ = num_subkeys; + + return true; +} + +// Writes the values and subkeys of this object into |key|. +bool RegistryKeyBackup::KeyData::WriteTo(RegKey* key) const { + DCHECK(key); + + LONG result = ERROR_SUCCESS; + + // Write the values. + for (DWORD i = 0; i < num_values_; ++i) { + const ValueData& value = values_[i]; + result = RegSetValueEx(key->Handle(), value.name(), 0, value.type(), + value.data(), value.data_len()); + if (result != ERROR_SUCCESS) { + LOG(ERROR) << "Failed writing value \"" << value.name_str() + << "\", result: " << result; + return false; + } + } + + // Write the subkeys. + RegKey subkey; + for (DWORD i = 0; i < num_subkeys_; ++i) { + const std::wstring& name = subkey_names_[i]; + + result = subkey.Create(key->Handle(), name.c_str(), KEY_WRITE); + if (result != ERROR_SUCCESS) { + LOG(ERROR) << "Failed creating subkey \"" << name << "\", result: " + << result; + return false; + } + if (!subkeys_[i].WriteTo(&subkey)) { + LOG(ERROR) << "Failed writing subkey \"" << name << "\", result: " + << result; + return false; + } + } + + return true; +} + +RegistryKeyBackup::RegistryKeyBackup() { +} + +RegistryKeyBackup::~RegistryKeyBackup() { +} + +bool RegistryKeyBackup::Initialize(HKEY root, const wchar_t* key_path) { + DCHECK(key_path); + + RegKey key; + scoped_ptr<KeyData> key_data; + + // Does the key exist? + LONG result = key.Open(root, key_path, kKeyReadNoNotify); + if (result == ERROR_SUCCESS) { + key_data.reset(new KeyData()); + if (!key_data->Initialize(key)) { + LOG(ERROR) << "Failed to backup key at " << key_path; + return false; + } + } else if (result != ERROR_FILE_NOT_FOUND) { + LOG(ERROR) << "Failed to open key at " << key_path + << " to create backup, result: " << result; + return false; + } + + key_data_.swap(key_data); + return true; +} + +bool RegistryKeyBackup::WriteTo(HKEY root, const wchar_t* key_path) const { + DCHECK(key_path); + + bool success = false; + + if (key_data_.get() != NULL) { + RegKey dest_key; + LONG result = dest_key.Create(root, key_path, KEY_WRITE); + if (result != ERROR_SUCCESS) { + LOG(ERROR) << "Failed to create destination key at " << key_path + << " to write backup, result: " << result; + } else { + success = key_data_->WriteTo(&dest_key); + LOG_IF(ERROR, !success) << "Failed to write key data."; + } + } else { + success = true; + } + + return success; +} diff --git a/chrome/installer/util/registry_key_backup.h b/chrome/installer/util/registry_key_backup.h new file mode 100644 index 0000000..41221bd --- /dev/null +++ b/chrome/installer/util/registry_key_backup.h @@ -0,0 +1,57 @@ +// 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_UTIL_REGISTRY_KEY_BACKUP_H_ +#define CHROME_INSTALLER_UTIL_REGISTRY_KEY_BACKUP_H_ +#pragma once + +#include <windows.h> + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace base { +namespace win { +class RegKey; +} // namespace win +} // namespace base + +// A container for a registry key, its values, and its subkeys. We don't use +// more obvious methods for various reasons: +// - RegCopyTree isn't supported pre-Vista, so we'd have to do something +// different for XP anyway. +// - SHCopyKey can't copy subkeys into a volatile destination, so we'd have to +// worry about polluting the registry. +// We don't persist security attributes since we only delete keys that we own, +// and we don't set custom attributes on them anyway. +class RegistryKeyBackup { + public: + RegistryKeyBackup(); + ~RegistryKeyBackup(); + + // Recursively reads |key_path| into this instance. Backing up a non-existent + // key is valid. Returns true if the backup was successful; false otherwise, + // in which case the state of this instance is not modified. + bool Initialize(HKEY root, const wchar_t* key_path); + + // Writes the contents of this instance into |key|. The contents of + // |key_path| are not modified If this instance is uninitialized or was + // initialized from a non-existent key. + bool WriteTo(HKEY root, const wchar_t* key_path) const; + + void swap(RegistryKeyBackup& other) { + key_data_.swap(other.key_data_); + } + + private: + class KeyData; + + scoped_ptr<KeyData> key_data_; + + DISALLOW_COPY_AND_ASSIGN(RegistryKeyBackup); +}; + +#endif // CHROME_INSTALLER_UTIL_REGISTRY_KEY_BACKUP_H_ diff --git a/chrome/installer/util/registry_key_backup_unittest.cc b/chrome/installer/util/registry_key_backup_unittest.cc new file mode 100644 index 0000000..db33aa4 --- /dev/null +++ b/chrome/installer/util/registry_key_backup_unittest.cc @@ -0,0 +1,85 @@ +// 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 "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/registry.h" +#include "chrome/installer/util/registry_key_backup.h" +#include "chrome/installer/util/registry_test_data.h" +#include "chrome/installer/util/work_item.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::win::RegKey; + +class RegistryKeyBackupTest : public testing::Test { + protected: + static void TearDownTestCase() { + logging::CloseLogFile(); + } + + virtual void SetUp() { + ASSERT_TRUE(test_data_.Initialize(HKEY_CURRENT_USER, L"SOFTWARE\\TmpTmp")); + destination_path_.assign(test_data_.base_path()).append(L"\\Destination"); + } + + RegistryTestData test_data_; + std::wstring destination_path_; +}; + +// Test that writing an uninitialized backup does nothing. +TEST_F(RegistryKeyBackupTest, Uninitialized) { + RegistryKeyBackup backup; + + EXPECT_TRUE(backup.WriteTo(test_data_.root_key(), destination_path_.c_str())); + EXPECT_FALSE(RegKey(test_data_.root_key(), destination_path_.c_str(), + KEY_READ).Valid()); +} + +// Test that initializing a backup with a non-existent key works, and that +// writing it back out does nothing. +TEST_F(RegistryKeyBackupTest, MissingKey) { + std::wstring non_existent_key_path(test_data_.base_path() + L"\\NoKeyHere"); + RegistryKeyBackup backup; + + EXPECT_TRUE(backup.Initialize(test_data_.root_key(), + non_existent_key_path.c_str())); + EXPECT_TRUE(backup.WriteTo(test_data_.root_key(), destination_path_.c_str())); + EXPECT_FALSE(RegKey(test_data_.root_key(), destination_path_.c_str(), + KEY_READ).Valid()); +} + +// Test that reading some data then writing it out does the right thing. +TEST_F(RegistryKeyBackupTest, ReadWrite) { + RegistryKeyBackup backup; + + EXPECT_TRUE(backup.Initialize(test_data_.root_key(), + test_data_.non_empty_key_path().c_str())); + EXPECT_TRUE(backup.WriteTo(test_data_.root_key(), destination_path_.c_str())); + test_data_.ExpectMatchesNonEmptyKey(test_data_.root_key(), + destination_path_.c_str()); +} + +// Test that reading some data, swapping, then writing it out does the right +// thing. +TEST_F(RegistryKeyBackupTest, Swap) { + RegistryKeyBackup backup; + RegistryKeyBackup other_backup; + + EXPECT_TRUE(backup.Initialize(test_data_.root_key(), + test_data_.non_empty_key_path().c_str())); + backup.swap(other_backup); + EXPECT_TRUE(other_backup.WriteTo(test_data_.root_key(), + destination_path_.c_str())); + + // Now make sure the one we started with is truly empty. + EXPECT_EQ(ERROR_SUCCESS, + RegKey(test_data_.root_key(), L"", KEY_QUERY_VALUE) + .DeleteKey(destination_path_.c_str())); + EXPECT_TRUE(backup.WriteTo(test_data_.root_key(), + destination_path_.c_str())); + EXPECT_FALSE(RegKey(test_data_.root_key(), destination_path_.c_str(), + KEY_READ).Valid()); +} diff --git a/chrome/installer/util/registry_test_data.cc b/chrome/installer/util/registry_test_data.cc new file mode 100644 index 0000000..b28596a --- /dev/null +++ b/chrome/installer/util/registry_test_data.cc @@ -0,0 +1,108 @@ +// 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/util/registry_test_data.h" + +#include "base/logging.h" +#include "base/win/registry.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::win::RegKey; + +RegistryTestData::RegistryTestData() + : root_key_(NULL) { +} + +RegistryTestData::~RegistryTestData() { + Reset(); +} + +// static +bool RegistryTestData::DeleteKey(HKEY root_key, const wchar_t* path) { + LONG result = ERROR_SUCCESS; + if (root_key != NULL && path != NULL && *path != L'\0') { + result = RegKey(root_key, L"", KEY_QUERY_VALUE).DeleteKey(path); + if (result == ERROR_FILE_NOT_FOUND) { + result = ERROR_SUCCESS; + } else { + DLOG_IF(DFATAL, result != ERROR_SUCCESS) + << "Failed to delete registry key " << path << ", result: " << result; + } + } + return result == ERROR_SUCCESS; +} + +void RegistryTestData::Reset() { + // Clean up behind a previous use (ignore failures). + DeleteKey(root_key_, base_path_.c_str()); + + // Forget state. + root_key_ = NULL; + base_path_.clear(); + empty_key_path_.clear(); + non_empty_key_path_.clear(); +} + +bool RegistryTestData::Initialize(HKEY root_key, const wchar_t* base_path) { + LONG result = ERROR_SUCCESS; + + Reset(); + + // Take over the new registry location. + if (DeleteKey(root_key, base_path)) { + // Take on the new values. + root_key_ = root_key; + base_path_.assign(base_path); + + // Create our data. + empty_key_path_.assign(base_path_).append(L"\\EmptyKey"); + non_empty_key_path_.assign(base_path_).append(L"\\NonEmptyKey"); + + RegKey key; + + result = key.Create(root_key_, empty_key_path_.c_str(), KEY_QUERY_VALUE); + if (result == ERROR_SUCCESS) + result = key.Create(root_key_, non_empty_key_path_.c_str(), KEY_WRITE); + if (result == ERROR_SUCCESS) + result = key.WriteValue(NULL, non_empty_key_path_.c_str()); + if (result == ERROR_SUCCESS) + result = key.CreateKey(L"SubKey", KEY_WRITE); + if (result == ERROR_SUCCESS) + result = key.WriteValue(L"SomeValue", 1UL); + DLOG_IF(DFATAL, result != ERROR_SUCCESS) + << "Failed to create test registry data based at " << base_path_ + << ", result: " << result; + } else { + result = ERROR_INVALID_PARAMETER; + } + + return result == ERROR_SUCCESS; +} + +void RegistryTestData::ExpectMatchesNonEmptyKey(HKEY root_key, + const wchar_t* path) { + RegKey key; + + EXPECT_EQ(ERROR_SUCCESS, key.Open(root_key, path, KEY_READ)); + std::wstring str_value; + EXPECT_EQ(ERROR_SUCCESS, key.ReadValue(NULL, &str_value)); + EXPECT_EQ(non_empty_key_path_, str_value); + EXPECT_EQ(ERROR_SUCCESS, key.OpenKey(L"Subkey", KEY_READ)); + DWORD dw_value = 0; + EXPECT_EQ(ERROR_SUCCESS, key.ReadValueDW(L"SomeValue", &dw_value)); + EXPECT_EQ(1UL, dw_value); +} + +// static +void RegistryTestData::ExpectEmptyKey(HKEY root_key, const wchar_t* path) { + DWORD num_subkeys = 0; + DWORD num_values = 0; + RegKey key; + EXPECT_EQ(ERROR_SUCCESS, key.Open(root_key, path, KEY_READ)); + EXPECT_EQ(ERROR_SUCCESS, + RegQueryInfoKey(key.Handle(), NULL, NULL, NULL, &num_subkeys, + NULL, NULL, &num_values, NULL, NULL, NULL, NULL)); + EXPECT_EQ(0UL, num_subkeys); + EXPECT_EQ(0UL, num_values); +} diff --git a/chrome/installer/util/registry_test_data.h b/chrome/installer/util/registry_test_data.h new file mode 100644 index 0000000..cd5e0c4 --- /dev/null +++ b/chrome/installer/util/registry_test_data.h @@ -0,0 +1,59 @@ +// 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_UTIL_REGISTRY_TEST_DATA_H_ +#define CHROME_INSTALLER_UTIL_REGISTRY_TEST_DATA_H_ +#pragma once + +#include <windows.h> + +#include <string> + +#include "base/basictypes.h" + +// A helper class for use by unit tests that need some registry space and data. +// BEWARE: Instances of this class irrevocably and recursively delete keys and +// values from the registry. Carefully read the comments for Initialize and +// Reset before use. +class RegistryTestData { + public: + RegistryTestData(); + // Invokes Reset() on its way out. + ~RegistryTestData(); + + // Resets this instance, deletes the key rooted at |base_path|, and then + // populates |base_path| with: + // \EmptyKey + // \NonEmptyKey (default value = "|base_path|\NonEmptyKey") + // \NonEmptyKey\Subkey ("SomeValue" = DWORD 1) + bool Initialize(HKEY root_key, const wchar_t* base_path); + + // Deletes the key rooted at base_path and clears all state. + void Reset(); + + // Fires Google Test expectations in the hopes that |path| contains the same + // data as originally placed in |non_empty_key| by Initialize(). + void ExpectMatchesNonEmptyKey(HKEY root_key, const wchar_t* path); + + HKEY root_key() const { return root_key_; } + const std::wstring& base_path() const { return base_path_; } + const std::wstring& empty_key_path() const { return empty_key_path_; } + const std::wstring& non_empty_key_path() const { return non_empty_key_path_; } + + // Fires Google Test expectations in the hopes that |path| is an empty key + // (exists but has no values or subkeys). + static void ExpectEmptyKey(HKEY root_key, const wchar_t* path); + + private: + static bool DeleteKey(HKEY root_key, const wchar_t* path); + + HKEY root_key_; + std::wstring base_path_; + std::wstring empty_key_path_; + std::wstring non_empty_key_path_; + + DISALLOW_COPY_AND_ASSIGN(RegistryTestData); +}; + +#endif // CHROME_INSTALLER_UTIL_REGISTRY_TEST_DATA_H_ diff --git a/chrome/installer/util/work_item.cc b/chrome/installer/util/work_item.cc index 8a047b2..e4982f8 100644 --- a/chrome/installer/util/work_item.cc +++ b/chrome/installer/util/work_item.cc @@ -5,6 +5,7 @@ #include "chrome/installer/util/work_item.h" #include "chrome/installer/util/conditional_work_item_list.h" +#include "chrome/installer/util/copy_reg_key_work_item.h" #include "chrome/installer/util/copy_tree_work_item.h" #include "chrome/installer/util/create_dir_work_item.h" #include "chrome/installer/util/create_reg_key_work_item.h" @@ -22,6 +23,14 @@ WorkItem::WorkItem() : ignore_failure_(false) { WorkItem::~WorkItem() { } +CopyRegKeyWorkItem* WorkItem::CreateCopyRegKeyWorkItem( + HKEY predefined_root, + const std::wstring& source_key_path, + const std::wstring& dest_key_path) { + return new CopyRegKeyWorkItem(predefined_root, source_key_path, + dest_key_path); +} + CopyTreeWorkItem* WorkItem::CreateCopyTreeWorkItem( const FilePath& source_path, const FilePath& dest_path, diff --git a/chrome/installer/util/work_item.h b/chrome/installer/util/work_item.h index a4c8f6e..cbe9665 100644 --- a/chrome/installer/util/work_item.h +++ b/chrome/installer/util/work_item.h @@ -17,6 +17,7 @@ #include "base/basictypes.h" +class CopyRegKeyWorkItem; class CopyTreeWorkItem; class CreateDirWorkItem; class CreateRegKeyWorkItem; @@ -58,6 +59,12 @@ class WorkItem { virtual ~WorkItem(); + // Create a CopyRegKeyWorkItem that recursively copies a given registry key. + static CopyRegKeyWorkItem* CreateCopyRegKeyWorkItem( + HKEY predefined_root, + const std::wstring& source_key_path, + const std::wstring& dest_key_path); + // Create a CopyTreeWorkItem that recursively copies a file system hierarchy // from source path to destination path. // * If overwrite_option is ALWAYS, the created CopyTreeWorkItem always diff --git a/chrome/installer/util/work_item_list.cc b/chrome/installer/util/work_item_list.cc index d32f068..23ef381 100644 --- a/chrome/installer/util/work_item_list.cc +++ b/chrome/installer/util/work_item_list.cc @@ -7,6 +7,7 @@ #include "base/logging.h" #include "base/file_path.h" #include "chrome/installer/util/logging_installer.h" +#include "chrome/installer/util/copy_reg_key_work_item.h" #include "chrome/installer/util/copy_tree_work_item.h" #include "chrome/installer/util/create_dir_work_item.h" #include "chrome/installer/util/create_reg_key_work_item.h" @@ -72,6 +73,16 @@ void WorkItemList::AddWorkItem(WorkItem* work_item) { list_.push_back(work_item); } +WorkItem* WorkItemList::AddCopyRegKeyWorkItem( + HKEY predefined_root, + const std::wstring& source_key_path, + const std::wstring& dest_key_path) { + WorkItem* item = WorkItem::CreateCopyRegKeyWorkItem( + predefined_root, source_key_path, dest_key_path); + AddWorkItem(item); + return item; +} + WorkItem* WorkItemList::AddCopyTreeWorkItem( const std::wstring& source_path, const std::wstring& dest_path, diff --git a/chrome/installer/util/work_item_list.h b/chrome/installer/util/work_item_list.h index f1d7c8f..e73a1ac 100644 --- a/chrome/installer/util/work_item_list.h +++ b/chrome/installer/util/work_item_list.h @@ -37,6 +37,11 @@ class WorkItemList : public WorkItem { // Once a WorkItem is added to the list. The list owns the WorkItem. virtual void AddWorkItem(WorkItem* work_item); + // Add a CopyRegKeyWorkItem that recursively copies a given registry key. + virtual WorkItem* AddCopyRegKeyWorkItem(HKEY predefined_root, + const std::wstring& source_key_path, + const std::wstring& dest_key_path); + // Add a CopyTreeWorkItem to the list of work items. // See the NOTE in the documentation for the CopyTreeWorkItem class for // special considerations regarding |temp_dir|. @@ -102,11 +107,11 @@ class WorkItemList : public WorkItem { // Add a SetRegValueWorkItem that sets a registry value with REG_QWORD type // at the key with specified path. - WorkItem* AddSetRegValueWorkItem(HKEY predefined_root, - const std::wstring& key_path, - const std::wstring& value_name, - int64 value_data, - bool overwrite); + virtual WorkItem* AddSetRegValueWorkItem(HKEY predefined_root, + const std::wstring& key_path, + const std::wstring& value_name, + int64 value_data, + bool overwrite); // Add a SelfRegWorkItem that registers or unregisters a DLL at the // specified path. If user_level_registration is true, then alternate |