blob: 952fd49bed7a74153e82704f865e075efdd65607 [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 "cryptohome/disk_cleanup_routines.h"
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <base/files/file_path.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "cryptohome/homedirs.h"
#include "cryptohome/mock_homedirs.h"
#include "cryptohome/mock_platform.h"
#include "cryptohome/platform.h"
using ::testing::_;
using ::testing::EndsWith;
using ::testing::HasSubstr;
using ::testing::InvokeWithoutArgs;
using ::testing::NiceMock;
using ::testing::Property;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::SetArgPointee;
using ::testing::SetArrayArgument;
using ::testing::StrictMock;
namespace {
const base::FilePath kTestShadowRoot = base::FilePath("/test/routines/root");
const char* kTestUser = "d5510a8dda6d743c46dadd979a61ae5603529742";
const base::FilePath kTestUserPath = kTestShadowRoot.Append(kTestUser);
NiceMock<cryptohome::MockFileEnumerator>* CreateMockFileEnumerator() {
return new NiceMock<cryptohome::MockFileEnumerator>;
}
NiceMock<cryptohome::MockFileEnumerator>* CreateMockFileEnumeratorWithEntries(
const std::vector<base::FilePath>& children) {
auto* mock = new NiceMock<cryptohome::MockFileEnumerator>;
for (const auto& child : children) {
#if BASE_VER < 780000
struct stat stat = {};
#else
base::stat_wrapper_t stat = {};
#endif
mock->entries_.push_back(cryptohome::FileEnumerator::FileInfo(child, stat));
}
return mock;
}
} // namespace
namespace cryptohome {
TEST(DiskCleanupRoutinesInitialization, Init) {
StrictMock<MockPlatform> platform_;
StrictMock<MockHomeDirs> homedirs_;
DiskCleanupRoutines routines(&homedirs_, &platform_);
}
class DiskCleanupRoutinesTest
: public ::testing::TestWithParam<bool /* should_test_ecryptfs */> {
public:
DiskCleanupRoutinesTest() : routines_(&homedirs_, &platform_) {}
virtual ~DiskCleanupRoutinesTest() = default;
void SetUp() {
EXPECT_CALL(homedirs_, shadow_root())
.WillRepeatedly(ReturnRef(kTestShadowRoot));
EXPECT_CALL(platform_, DirectoryExists(Property(
&FilePath::value, EndsWith(kEcryptfsVaultDir))))
.WillRepeatedly(Return(ShouldTestEcryptfs()));
EXPECT_CALL(platform_, HasExtendedFileAttribute(_, _))
.WillRepeatedly(Return(false));
}
protected:
// Returns true if the test is running for eCryptfs, false if for dircrypto.
bool ShouldTestEcryptfs() const { return GetParam(); }
// Sets up expectations for the given tracked directories which belong to the
// same parent directory.
void ExpectTrackedDirectoryEnumeration(
const std::vector<base::FilePath>& child_directories) {
if (ShouldTestEcryptfs()) // No expecations needed for eCryptfs.
return;
ASSERT_FALSE(child_directories.empty());
base::FilePath parent_directory = child_directories[0].DirName();
// xattr is used to track directories.
for (const auto& child : child_directories) {
ASSERT_EQ(parent_directory.value(), child.DirName().value());
EXPECT_CALL(platform_, GetExtendedFileAttributeAsString(
child, kTrackedDirectoryNameAttribute, _))
.WillRepeatedly(
DoAll(SetArgPointee<2>(child.BaseName().value()), Return(true)));
EXPECT_CALL(platform_, HasExtendedFileAttribute(
child, kTrackedDirectoryNameAttribute))
.WillRepeatedly(Return(true));
}
EXPECT_CALL(platform_, GetFileEnumerator(parent_directory, false,
base::FileEnumerator::DIRECTORIES))
.WillRepeatedly(InvokeWithoutArgs(
std::bind(CreateMockFileEnumeratorWithEntries, child_directories)));
}
StrictMock<MockPlatform> platform_;
StrictMock<MockHomeDirs> homedirs_;
DiskCleanupRoutines routines_;
};
INSTANTIATE_TEST_SUITE_P(WithEcryptfs,
DiskCleanupRoutinesTest,
::testing::Values(true));
INSTANTIATE_TEST_SUITE_P(WithDircrypto,
DiskCleanupRoutinesTest,
::testing::Values(false));
TEST_P(DiskCleanupRoutinesTest, DeleteUserCache) {
base::FilePath mount = kTestUserPath.Append(kMountDir);
base::FilePath user = mount.Append(kUserHomeSuffix);
base::FilePath cache = user.Append(kCacheDir);
ExpectTrackedDirectoryEnumeration({user});
ExpectTrackedDirectoryEnumeration({cache});
std::vector<base::FilePath> entriesToClean{base::FilePath("abc"),
base::FilePath("efg")};
EXPECT_CALL(platform_, GetFileEnumerator(Property(&FilePath::value,
HasSubstr("user/Cache")),
false, _))
.WillRepeatedly(InvokeWithoutArgs(
std::bind(CreateMockFileEnumeratorWithEntries, entriesToClean)));
// Don't delete anything else.
EXPECT_CALL(platform_, DeleteFile(_, _)).Times(0);
for (const auto& entry : entriesToClean)
EXPECT_CALL(platform_, DeleteFile(entry, true)).WillOnce(Return(true));
routines_.DeleteUserCache(kTestUser);
}
TEST_P(DiskCleanupRoutinesTest, DeleteUserGCacheV1) {
base::FilePath mount = kTestUserPath.Append(kMountDir);
base::FilePath user = mount.Append(kUserHomeSuffix);
base::FilePath gcache = user.Append(kGCacheDir);
base::FilePath gcache_version1 = gcache.Append(kGCacheVersion1Dir);
base::FilePath gcache_version2 = gcache.Append(kGCacheVersion2Dir);
base::FilePath gcache_tmp = gcache_version1.Append(kGCacheTmpDir);
ExpectTrackedDirectoryEnumeration({user});
ExpectTrackedDirectoryEnumeration({gcache});
ExpectTrackedDirectoryEnumeration({gcache_version1, gcache_version2});
ExpectTrackedDirectoryEnumeration({gcache_tmp});
std::vector<base::FilePath> entriesToClean{base::FilePath("abc"),
base::FilePath("efg")};
EXPECT_CALL(platform_,
GetFileEnumerator(
Property(&FilePath::value, HasSubstr("user/GCache/v1/tmp")),
false, _))
.WillRepeatedly(InvokeWithoutArgs(
std::bind(CreateMockFileEnumeratorWithEntries, entriesToClean)));
EXPECT_CALL(platform_, GetFileEnumerator(Property(&FilePath::value,
EndsWith("user/GCache/v1")),
true, base::FileEnumerator::FILES))
.WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
EXPECT_CALL(platform_, GetFileEnumerator(Property(&FilePath::value,
EndsWith("user/GCache/v2")),
true, base::FileEnumerator::FILES))
.WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
// Don't delete anything else.
EXPECT_CALL(platform_, DeleteFile(_, _)).Times(0);
for (const auto& entry : entriesToClean)
EXPECT_CALL(platform_, DeleteFile(entry, true)).WillOnce(Return(true));
routines_.DeleteUserGCache(kTestUser);
}
TEST_P(DiskCleanupRoutinesTest, DeleteUserGCacheV2) {
base::FilePath mount = kTestUserPath.Append(kMountDir);
base::FilePath user = mount.Append(kUserHomeSuffix);
base::FilePath gcache = user.Append(kGCacheDir);
base::FilePath gcache_version1 = gcache.Append(kGCacheVersion1Dir);
base::FilePath gcache_version2 = gcache.Append(kGCacheVersion2Dir);
base::FilePath gcache_tmp = gcache_version1.Append(kGCacheTmpDir);
ExpectTrackedDirectoryEnumeration({user});
ExpectTrackedDirectoryEnumeration({gcache});
ExpectTrackedDirectoryEnumeration({gcache_version1, gcache_version2});
ExpectTrackedDirectoryEnumeration({gcache_tmp});
std::vector<base::FilePath> entriesToClean{
base::FilePath("abc"), base::FilePath("efg"), base::FilePath("hij")};
std::vector<base::FilePath> v1Entries, v2Entries;
for (const auto& entry : entriesToClean) {
v1Entries.push_back(gcache_version1.Append(entry));
v2Entries.push_back(gcache_version2.Append(entry));
}
EXPECT_CALL(platform_,
GetFileEnumerator(
Property(&FilePath::value, HasSubstr("user/GCache/v1/tmp")),
false, _))
.WillRepeatedly(InvokeWithoutArgs(CreateMockFileEnumerator));
EXPECT_CALL(platform_, GetFileEnumerator(Property(&FilePath::value,
EndsWith("user/GCache/v1")),
true, base::FileEnumerator::FILES))
.WillRepeatedly(InvokeWithoutArgs(
std::bind(CreateMockFileEnumeratorWithEntries, v1Entries)));
EXPECT_CALL(platform_, GetFileEnumerator(Property(&FilePath::value,
EndsWith("user/GCache/v2")),
true, base::FileEnumerator::FILES))
.WillRepeatedly(InvokeWithoutArgs(
std::bind(CreateMockFileEnumeratorWithEntries, v2Entries)));
EXPECT_CALL(platform_,
HasExtendedFileAttribute(v1Entries[0], kRemovableFileAttribute))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_,
HasExtendedFileAttribute(v2Entries[0], kRemovableFileAttribute))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, HasNoDumpFileAttribute(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(platform_, HasNoDumpFileAttribute(v1Entries[1]))
.WillRepeatedly(Return(true));
EXPECT_CALL(platform_, HasNoDumpFileAttribute(v2Entries[1]))
.WillRepeatedly(Return(true));
// Don't delete anything else.
EXPECT_CALL(platform_, DeleteFile(_, _)).Times(0);
EXPECT_CALL(platform_, DeleteFile(v1Entries[0], false))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(v1Entries[1], false))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(v2Entries[0], false))
.WillOnce(Return(true));
EXPECT_CALL(platform_, DeleteFile(v2Entries[1], false))
.WillOnce(Return(true));
routines_.DeleteUserGCache(kTestUser);
}
TEST_P(DiskCleanupRoutinesTest, DeleteAndroidCache) {
base::FilePath mount = kTestUserPath.Append(kMountDir);
base::FilePath root = mount.Append(kRootHomeSuffix);
ExpectTrackedDirectoryEnumeration({root});
std::vector<base::FilePath> entriesToDelete{base::FilePath("abc"),
base::FilePath("efg")};
base::FilePath codeCacheInodeFile = root.Append("accache");
base::FilePath cacheInodeFile = root.Append("acache");
std::vector<base::FilePath> entriesToClean{codeCacheInodeFile.Append("code"),
cacheInodeFile.Append("cache")};
uint64_t codeCacheInode = 4;
uint64_t cacheInode = 5;
for (auto& entry : entriesToClean) {
std::vector<base::FilePath> entries;
for (const auto& entryToDelete : entriesToDelete)
entries.push_back(entry.Append(entryToDelete));
EXPECT_CALL(platform_, EnumerateDirectoryEntries(entry, false, _))
.WillOnce(DoAll(SetArgPointee<2>(entries), Return(true)));
for (const auto& entry : entries)
EXPECT_CALL(platform_, DeleteFile(entry, true)).WillOnce(Return(true));
}
auto* enumerator = new NiceMock<cryptohome::MockFileEnumerator>;
ASSERT_EQ(entriesToClean.size(), 2);
#if BASE_VER < 780000
struct stat stat = {};
#else
base::stat_wrapper_t stat = {};
#endif
stat.st_ino = 1;
enumerator->entries_.push_back(
cryptohome::FileEnumerator::FileInfo(codeCacheInodeFile, stat));
enumerator->entries_.push_back(
cryptohome::FileEnumerator::FileInfo(cacheInodeFile, stat));
stat.st_ino = codeCacheInode;
enumerator->entries_.push_back(
cryptohome::FileEnumerator::FileInfo(entriesToClean[0], stat));
stat.st_ino = cacheInode;
enumerator->entries_.push_back(
cryptohome::FileEnumerator::FileInfo(entriesToClean[1], stat));
EXPECT_CALL(platform_,
HasExtendedFileAttribute(codeCacheInodeFile,
kAndroidCodeCacheInodeAttribute))
.WillOnce(Return(true));
char* array = reinterpret_cast<char*>(&codeCacheInode);
EXPECT_CALL(platform_,
GetExtendedFileAttribute(codeCacheInodeFile,
kAndroidCodeCacheInodeAttribute, _, _))
.WillOnce(
DoAll(SetArrayArgument<2>(array, array + sizeof(codeCacheInode)),
Return(true)));
EXPECT_CALL(platform_, HasExtendedFileAttribute(cacheInodeFile,
kAndroidCacheInodeAttribute))
.WillOnce(Return(true));
array = reinterpret_cast<char*>(&cacheInode);
EXPECT_CALL(platform_, GetExtendedFileAttribute(
cacheInodeFile, kAndroidCacheInodeAttribute, _, _))
.WillOnce(DoAll(SetArrayArgument<2>(array, array + sizeof(cacheInode)),
Return(true)));
EXPECT_CALL(
platform_,
GetFileEnumerator(
Property(&FilePath::value,
EndsWith(std::string(ShouldTestEcryptfs() ? kEcryptfsVaultDir
: kMountDir) +
"/root")),
true, base::FileEnumerator::DIRECTORIES))
.WillRepeatedly(Return(enumerator));
EXPECT_TRUE(routines_.DeleteUserAndroidCache(kTestUser));
}
TEST_P(DiskCleanupRoutinesTest, DeleteUserProfile) {
EXPECT_CALL(homedirs_, RemoveLECredentials(kTestUser)).Times(1);
EXPECT_CALL(platform_, DeleteFile(kTestUserPath, true))
.WillOnce(Return(true));
EXPECT_TRUE(routines_.DeleteUserProfile(kTestUser));
}
TEST_P(DiskCleanupRoutinesTest, DeleteUserProfileFail) {
EXPECT_CALL(homedirs_, RemoveLECredentials(kTestUser)).Times(1);
EXPECT_CALL(platform_, DeleteFile(kTestUserPath, true))
.WillOnce(Return(false));
EXPECT_FALSE(routines_.DeleteUserProfile(kTestUser));
}
} // namespace cryptohome