| // 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/delete_after_reboot_helper.h" |
| |
| #include <windows.h> |
| #include <shlobj.h> |
| #include <stddef.h> |
| |
| #include <memory> |
| |
| #include "base/files/file_util.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/win/registry.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: |
| void SetUp() override { |
| // Create a temporary directory for testing and fill it with some files. |
| base::CreateNewTempDirectory(base::FilePath::StringType(), &temp_dir_); |
| base::CreateTemporaryFileInDir(temp_dir_, &temp_file_); |
| |
| temp_subdir_ = temp_dir_.Append(L"subdir"); |
| base::CreateDirectory(temp_subdir_); |
| base::CreateTemporaryFileInDir(temp_subdir_, &temp_subdir_file_); |
| |
| // Copy the current pending moves and then clear it if we can: |
| if (IsUserAnAdmin()) { |
| GetPendingMovesValue(&original_pending_moves_); |
| } |
| } |
| void TearDown() override { |
| // Delete the temporary directory if it's still there. |
| base::DeleteFile(temp_dir_, true); |
| |
| // Try and restore the pending moves value, if we have one. |
| if (IsUserAnAdmin() && original_pending_moves_.size() > 1) { |
| base::win::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], |
| static_cast<int>(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_; |
| |
| base::FilePath temp_dir_; |
| base::FilePath temp_file_; |
| base::FilePath temp_subdir_; |
| base::FilePath temp_subdir_file_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(DeleteAfterRebootHelperTest, TestStringListToMultiSZConversions) { |
| struct StringTest { |
| const wchar_t* test_name; |
| const 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 (size_t i = 0; i < base::size(tests); i++) { |
| std::vector<PendingMove> string_list; |
| EXPECT_TRUE(SUCCEEDED( |
| MultiSZBytesToStringArray(reinterpret_cast<const 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(const_cast<const char*>(&buffer[0]), |
| reinterpret_cast<const char*>(tests[i].str), |
| tests[i].length)) |
| << tests[i].test_name; |
| } |
| |
| StringTest failures[] = { |
| {L"malformed", reinterpret_cast<const wchar_t*>("oddnumb\0\0"), 9, 1}, |
| }; |
| |
| for (size_t i = 0; i < base::size(failures); i++) { |
| std::vector<PendingMove> string_list; |
| EXPECT_FALSE(SUCCEEDED(MultiSZBytesToStringArray( |
| reinterpret_cast<const char*>(failures[i].str), |
| failures[i].length, |
| &string_list))) |
| << failures[i].test_name; |
| } |
| } |
| |
| |
| TEST_F(DeleteAfterRebootHelperTest, TestFileDeleteScheduleAndUnschedule) { |
| if (!IsUserAnAdmin()) { |
| return; |
| } |
| |
| EXPECT_TRUE(ScheduleDirectoryForDeletion(temp_dir_)); |
| |
| 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_GT(pending_moves.size(), 3U); |
| |
| // Get the short form of temp_file_ and use that to match. |
| base::FilePath short_temp_file(GetShortPathName(temp_file_)); |
| |
| // Scan for the first expected delete. |
| std::vector<PendingMove>::const_iterator iter(pending_moves.begin()); |
| for (; iter != pending_moves.end(); iter++) { |
| base::FilePath move_path(iter->first); |
| if (MatchPendingDeletePath(short_temp_file, move_path)) |
| break; |
| } |
| |
| // Check that each of the deletes we expect are there in order. |
| base::FilePath expected_paths[] = |
| { temp_file_, temp_subdir_file_, temp_subdir_, temp_dir_ }; |
| for (size_t i = 0; i < base::size(expected_paths); ++i) { |
| EXPECT_FALSE(iter == pending_moves.end()); |
| if (iter != pending_moves.end()) { |
| base::FilePath short_path_name(GetShortPathName(expected_paths[i])); |
| base::FilePath move_path(iter->first); |
| EXPECT_TRUE(MatchPendingDeletePath(short_path_name, move_path)); |
| ++iter; |
| } |
| } |
| |
| // Test that we can remove the pending deletes. |
| EXPECT_TRUE(RemoveFromMovesPendingReboot(temp_dir_)); |
| 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) { |
| base::FilePath move_path(check_iter->first); |
| EXPECT_FALSE(MatchPendingDeletePath(short_temp_file, move_path)); |
| } |
| } |
| |
| TEST_F(DeleteAfterRebootHelperTest, TestFileDeleteSchedulingWithActualDeletes) { |
| if (!IsUserAnAdmin()) { |
| return; |
| } |
| |
| std::vector<PendingMove> initial_pending_moves; |
| GetPendingMovesValue(&initial_pending_moves); |
| size_t initial_pending_moves_size = initial_pending_moves.size(); |
| |
| EXPECT_TRUE(ScheduleDirectoryForDeletion(temp_dir_)); |
| |
| 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. |
| base::FilePath short_temp_file(GetShortPathName(temp_file_)); |
| |
| // Scan for the first expected delete. |
| std::vector<PendingMove>::const_iterator iter(pending_moves.begin()); |
| for (; iter != pending_moves.end(); iter++) { |
| base::FilePath move_path(iter->first); |
| if (MatchPendingDeletePath(short_temp_file, move_path)) |
| break; |
| } |
| |
| // Check that each of the deletes we expect are there in order. |
| base::FilePath expected_paths[] = |
| { temp_file_, temp_subdir_file_, temp_subdir_, temp_dir_ }; |
| for (size_t i = 0; i < base::size(expected_paths); ++i) { |
| EXPECT_FALSE(iter == pending_moves.end()); |
| if (iter != pending_moves.end()) { |
| base::FilePath short_path_name(GetShortPathName(expected_paths[i])); |
| base::FilePath move_path(iter->first); |
| EXPECT_TRUE(MatchPendingDeletePath(short_path_name, move_path)); |
| ++iter; |
| } |
| } |
| |
| // Delete the temporary directory. |
| base::DeleteFile(temp_dir_, true); |
| |
| // Test that we can remove the pending deletes. |
| EXPECT_TRUE(RemoveFromMovesPendingReboot(temp_dir_)); |
| HRESULT hr = GetPendingMovesValue(&pending_moves); |
| EXPECT_TRUE(hr == S_OK || hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); |
| |
| EXPECT_EQ(initial_pending_moves_size, pending_moves.size()); |
| |
| std::vector<PendingMove>::const_iterator check_iter(pending_moves.begin()); |
| for (; check_iter != pending_moves.end(); ++check_iter) { |
| base::FilePath move_path(check_iter->first); |
| EXPECT_FALSE(MatchPendingDeletePath(short_temp_file, move_path)); |
| } |
| } |
| |