blob: b95512cb51925511ccadb50fb6a6788b5bc6bc24 [file] [log] [blame]
// Copyright (c) 2012 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 "components/drive/chromeos/file_cache.h"
#include <linux/fs.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/xattr.h>
#include <string>
#include <vector>
#include "base/callback_helpers.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/md5.h"
#include "base/path_service.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/drive/chromeos/drive_test_util.h"
#include "components/drive/chromeos/fake_free_disk_space_getter.h"
#include "components/drive/drive.pb.h"
#include "components/drive/file_system_core_util.h"
#include "components/drive/resource_metadata_storage.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "google_apis/drive/test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace drive {
namespace internal {
namespace {
typedef long FileAttributes; // NOLINT(runtime/int)
const base::FilePath::CharType kCacheFileDirectory[] =
FILE_PATH_LITERAL("files");
const int kTemporaryFileSizeInBytes = 10;
FileAttributes GetFileAttributes(const base::FilePath& file_path) {
base::File file(file_path, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
ADD_FAILURE() << "Failed to open file: " << file_path.value();
return -1;
}
FileAttributes flags = 0;
if (ioctl(file.GetPlatformFile(), FS_IOC_GETFLAGS, &flags) < 0) {
ADD_FAILURE() << "Failed to get attributes: " << file_path.value();
return -1;
}
return flags;
}
bool HasRemovableFlag(const base::FilePath& file_path) {
return (GetFileAttributes(file_path) & FS_NODUMP_FL) == FS_NODUMP_FL;
}
} // namespace
// Tests FileCache methods working with the blocking task runner.
class FileCacheTest : public testing::Test {
protected:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
const base::FilePath metadata_dir = temp_dir_.path().AppendASCII("meta");
cache_files_dir_ = temp_dir_.path().Append(kCacheFileDirectory);
ASSERT_TRUE(base::CreateDirectory(metadata_dir));
ASSERT_TRUE(base::CreateDirectory(cache_files_dir_));
fake_free_disk_space_getter_.reset(new FakeFreeDiskSpaceGetter);
metadata_storage_.reset(new ResourceMetadataStorage(
metadata_dir,
base::ThreadTaskRunnerHandle::Get().get()));
ASSERT_TRUE(metadata_storage_->Initialize());
cache_.reset(new FileCache(metadata_storage_.get(), cache_files_dir_,
base::ThreadTaskRunnerHandle::Get().get(),
fake_free_disk_space_getter_.get()));
ASSERT_TRUE(cache_->Initialize());
}
static bool RenameCacheFilesToNewFormat(FileCache* cache) {
return cache->RenameCacheFilesToNewFormat();
}
base::FilePath GetCacheFilePath(const std::string& id) {
return cache_->GetCacheFilePath(id);
}
base::FilePath AddTestEntry(const std::string id,
const std::string md5,
const time_t last_accessed,
const base::FilePath& src_file) {
ResourceEntry entry;
entry.set_local_id(id);
entry.mutable_file_info()->set_last_accessed(last_accessed);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
EXPECT_EQ(FILE_ERROR_OK,
cache_->Store(id, md5, src_file, FileCache::FILE_OPERATION_COPY));
base::FilePath path;
EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &path));
// Update last modified and accessed time.
base::Time time = base::Time::FromTimeT(last_accessed);
EXPECT_TRUE(base::TouchFile(path, time, time));
return path;
}
content::TestBrowserThreadBundle thread_bundle_;
base::ScopedTempDir temp_dir_;
base::FilePath cache_files_dir_;
std::unique_ptr<ResourceMetadataStorage, test_util::DestroyHelperForTests>
metadata_storage_;
std::unique_ptr<FileCache, test_util::DestroyHelperForTests> cache_;
std::unique_ptr<FakeFreeDiskSpaceGetter> fake_free_disk_space_getter_;
};
TEST_F(FileCacheTest, RecoverFilesFromCacheDirectory) {
base::FilePath dir_source_root;
EXPECT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &dir_source_root));
const base::FilePath src_path =
dir_source_root.AppendASCII("chrome/test/data/chromeos/drive/image.png");
// Store files. This file should not be moved.
ResourceEntry entry;
entry.set_local_id("id_foo");
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
EXPECT_EQ(FILE_ERROR_OK, cache_->Store("id_foo", "md5", src_path,
FileCache::FILE_OPERATION_COPY));
// Set up files in the cache directory. These files should be moved.
const base::FilePath file_directory =
temp_dir_.path().Append(kCacheFileDirectory);
ASSERT_TRUE(base::CopyFile(src_path, file_directory.AppendASCII("id_bar")));
ASSERT_TRUE(base::CopyFile(src_path, file_directory.AppendASCII("id_baz")));
// Insert a dirty entry with "id_baz" to |recovered_cache_info|.
// This should not prevent the file from being recovered.
ResourceMetadataStorage::RecoveredCacheInfoMap recovered_cache_info;
recovered_cache_info["id_baz"].is_dirty = true;
recovered_cache_info["id_baz"].title = "baz.png";
// Recover files.
const base::FilePath dest_directory = temp_dir_.path().AppendASCII("dest");
EXPECT_TRUE(cache_->RecoverFilesFromCacheDirectory(dest_directory,
recovered_cache_info));
// Only two files should be recovered.
EXPECT_TRUE(base::PathExists(dest_directory));
// base::FileEnumerator does not guarantee the order.
if (base::PathExists(dest_directory.AppendASCII("baz00000001.png"))) {
EXPECT_TRUE(base::ContentsEqual(
src_path,
dest_directory.AppendASCII("baz00000001.png")));
EXPECT_TRUE(base::ContentsEqual(
src_path,
dest_directory.AppendASCII("image00000002.png")));
} else {
EXPECT_TRUE(base::ContentsEqual(
src_path,
dest_directory.AppendASCII("image00000001.png")));
EXPECT_TRUE(base::ContentsEqual(
src_path,
dest_directory.AppendASCII("baz00000002.png")));
}
EXPECT_FALSE(base::PathExists(
dest_directory.AppendASCII("image00000003.png")));
}
TEST_F(FileCacheTest, FreeDiskSpaceIfNeededFor) {
base::FilePath src_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
// Store a file as a 'temporary' file and remember the path.
const std::string id_tmp = "id_tmp", md5_tmp = "md5_tmp";
const time_t last_accessed_tmp = 1;
const base::FilePath& tmp_path =
AddTestEntry(id_tmp, md5_tmp, last_accessed_tmp, src_file);
// Store a file as a pinned file and remember the path.
const std::string id_pinned = "id_pinned", md5_pinned = "md5_pinned";
const time_t last_accessed_pinned = 1;
const base::FilePath& pinned_path =
AddTestEntry(id_pinned, md5_pinned, last_accessed_pinned, src_file);
ASSERT_EQ(FILE_ERROR_OK, cache_->Pin(id_pinned));
// Call FreeDiskSpaceIfNeededFor().
fake_free_disk_space_getter_->set_default_value(test_util::kLotsOfSpace);
fake_free_disk_space_getter_->PushFakeValue(0);
fake_free_disk_space_getter_->PushFakeValue(0);
const int64_t kNeededBytes = 1;
EXPECT_TRUE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes));
// Only 'temporary' file gets removed.
ResourceEntry entry;
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_tmp, &entry));
EXPECT_FALSE(entry.file_specific_info().cache_state().is_present());
EXPECT_FALSE(base::PathExists(tmp_path));
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_pinned, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
EXPECT_TRUE(base::PathExists(pinned_path));
// Returns false when disk space cannot be freed.
fake_free_disk_space_getter_->set_default_value(0);
EXPECT_FALSE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes));
}
TEST_F(FileCacheTest, EvictDriveCacheInLRU) {
// Create temporary file.
base::FilePath src_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
ASSERT_EQ(kTemporaryFileSizeInBytes,
base::WriteFile(src_file, "abcdefghij", kTemporaryFileSizeInBytes));
// Add entries.
const std::string id_a = "id_a", md5_a = "md5_a";
const time_t last_accessed_a = 1;
const base::FilePath& a_path =
AddTestEntry(id_a, md5_a, last_accessed_a, src_file);
const std::string id_pinned = "id_pinned", md5_pinned = "md5_pinned";
const time_t last_accessed_pinned = 2;
const base::FilePath& pinned_path =
AddTestEntry(id_pinned, md5_pinned, last_accessed_pinned, src_file);
ASSERT_EQ(FILE_ERROR_OK, cache_->Pin(id_pinned));
const std::string id_b = "id_b", md5_b = "md5_b";
const time_t last_accessed_b = 3;
const base::FilePath& b_path =
AddTestEntry(id_b, md5_b, last_accessed_b, src_file);
const std::string id_c = "id_c", md5_c = "md5_c";
const time_t last_accessed_c = 4;
const base::FilePath& c_path =
AddTestEntry(id_c, md5_c, last_accessed_c, src_file);
// Call FreeDiskSpaceIfNeededFor.
fake_free_disk_space_getter_->set_default_value(test_util::kLotsOfSpace);
fake_free_disk_space_getter_->PushFakeValue(kMinFreeSpaceInBytes);
fake_free_disk_space_getter_->PushFakeValue(kMinFreeSpaceInBytes);
const int64_t kNeededBytes = kTemporaryFileSizeInBytes * 3 / 2;
EXPECT_TRUE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes));
// Entry A is evicted.
ResourceEntry entry;
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_a, &entry));
EXPECT_FALSE(entry.file_specific_info().cache_state().is_present());
EXPECT_FALSE(base::PathExists(a_path));
// Pinned entry should not be evicted.
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_pinned, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
EXPECT_TRUE(base::PathExists(pinned_path));
// Entry B is evicted.
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_b, &entry));
EXPECT_FALSE(entry.file_specific_info().cache_state().is_present());
EXPECT_FALSE(base::PathExists(b_path));
// Entry C should not be evicted.
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_c, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
EXPECT_TRUE(base::PathExists(c_path));
}
// Test case for deleting invalid cache files which don't have corresponding
// metadata.
TEST_F(FileCacheTest, EvictInvalidCacheFile) {
base::FilePath src_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
// Add entries.
const std::string id_a = "id_a", md5_a = "md5_a";
const time_t last_accessed_a = 1;
const base::FilePath& a_path =
AddTestEntry(id_a, md5_a, last_accessed_a, src_file);
const std::string id_b = "id_b", md5_b = "md5_b";
const time_t last_accessed_b = 2;
const base::FilePath& b_path =
AddTestEntry(id_b, md5_b, last_accessed_b, src_file);
// Remove metadata of entry B.
ASSERT_EQ(FILE_ERROR_OK, metadata_storage_->RemoveEntry(id_b));
// Confirm cache file of entry B exists.
ASSERT_TRUE(base::PathExists(b_path));
// Run FreeDiskSpaceIfNeededFor.
fake_free_disk_space_getter_->set_default_value(test_util::kLotsOfSpace);
fake_free_disk_space_getter_->PushFakeValue(kMinFreeSpaceInBytes);
const int64_t kNeededBytes = 1;
EXPECT_TRUE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes));
// Entry A is not evicted.
ResourceEntry entry;
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_a, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
EXPECT_TRUE(base::PathExists(a_path));
// Entry B is evicted.
EXPECT_EQ(FILE_ERROR_NOT_FOUND, metadata_storage_->GetEntry(id_b, &entry));
EXPECT_FALSE(base::PathExists(b_path));
}
TEST_F(FileCacheTest, TooManyCacheFiles) {
const size_t kMaxNumOfEvictedCacheFiles = 50;
cache_->SetMaxNumOfEvictedCacheFilesForTest(kMaxNumOfEvictedCacheFiles);
// Create temporary file.
base::FilePath src_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
ASSERT_EQ(kTemporaryFileSizeInBytes,
base::WriteFile(src_file, "abcdefghij", kTemporaryFileSizeInBytes));
// Add kNumOfTestFiles=kMaxNumOfEvictedCacheFiles*2 entries.
std::vector<base::FilePath> paths;
const int32_t kNumOfTestFiles = kMaxNumOfEvictedCacheFiles * 2;
for (int i = 0; i < kNumOfTestFiles; ++i) {
// Set last accessed in reverse order to the file name. i.e. If you sort
// files in name-asc order, they will be last access desc order.
paths.push_back(AddTestEntry(
base::StringPrintf("id_%02d", i), base::StringPrintf("md5_%02d", i),
kNumOfTestFiles - i /* last accessed */, src_file));
}
// Confirm cache files of kNumOfTestFiles actually exist.
for (const auto& path : paths) {
ASSERT_TRUE(base::PathExists(path)) << path.value();
}
// Try to free kMaxNumOfEvictedCacheFiles * 3 / 2.
fake_free_disk_space_getter_->set_default_value(test_util::kLotsOfSpace);
fake_free_disk_space_getter_->PushFakeValue(kMinFreeSpaceInBytes);
fake_free_disk_space_getter_->PushFakeValue(kMinFreeSpaceInBytes);
fake_free_disk_space_getter_->PushFakeValue(
kMinFreeSpaceInBytes +
(kMaxNumOfEvictedCacheFiles * kTemporaryFileSizeInBytes));
const int64_t kNeededBytes =
(kMaxNumOfEvictedCacheFiles * 3 / 2) * kTemporaryFileSizeInBytes;
EXPECT_FALSE(cache_->FreeDiskSpaceIfNeededFor(kNeededBytes));
for (uint32_t i = 0; i < kNumOfTestFiles; ++i) {
// Assert that only first kMaxNumOfEvictedCacheFiles exist.
ASSERT_EQ(i < kMaxNumOfEvictedCacheFiles, base::PathExists(paths[i]));
}
}
TEST_F(FileCacheTest, GetFile) {
const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
const std::string src_contents = "test";
EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
src_contents));
std::string id("id1");
std::string md5(base::MD5String(src_contents));
const base::FilePath cache_file_directory =
temp_dir_.path().Append(kCacheFileDirectory);
// Try to get an existing file from cache.
ResourceEntry entry;
entry.set_local_id(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
EXPECT_EQ(FILE_ERROR_OK, cache_->Store(id, md5, src_file_path,
FileCache::FILE_OPERATION_COPY));
base::FilePath cache_file_path;
EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path));
EXPECT_EQ(
cache_file_directory.AppendASCII(util::EscapeCacheFileName(id)).value(),
cache_file_path.value());
std::string contents;
EXPECT_TRUE(base::ReadFileToString(cache_file_path, &contents));
EXPECT_EQ(src_contents, contents);
// Get file from cache with different id.
id = "id2";
entry.Clear();
entry.set_local_id(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
EXPECT_EQ(FILE_ERROR_NOT_FOUND, cache_->GetFile(id, &cache_file_path));
// Pin a non-existent file.
EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(id));
// Get the non-existent pinned file from cache.
EXPECT_EQ(FILE_ERROR_NOT_FOUND, cache_->GetFile(id, &cache_file_path));
// Get a previously pinned and stored file from cache.
EXPECT_EQ(FILE_ERROR_OK, cache_->Store(id, md5, src_file_path,
FileCache::FILE_OPERATION_COPY));
EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path));
EXPECT_EQ(
cache_file_directory.AppendASCII(util::EscapeCacheFileName(id)).value(),
cache_file_path.value());
contents.clear();
EXPECT_TRUE(base::ReadFileToString(cache_file_path, &contents));
EXPECT_EQ(src_contents, contents);
}
TEST_F(FileCacheTest, Store) {
const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
const std::string src_contents = "test";
EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
src_contents));
std::string id("id");
std::string md5(base::MD5String(src_contents));
// Store a file.
ResourceEntry entry;
entry.set_local_id(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
id, md5, src_file_path, FileCache::FILE_OPERATION_COPY));
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
EXPECT_EQ(md5, entry.file_specific_info().cache_state().md5());
base::FilePath cache_file_path;
EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path));
EXPECT_TRUE(base::ContentsEqual(src_file_path, cache_file_path));
base::FilePath dest_file_path = GetCacheFilePath(id);
EXPECT_TRUE(HasRemovableFlag((dest_file_path)));
// Store a non-existent file.
EXPECT_EQ(FILE_ERROR_FAILED, cache_->Store(
id, md5, base::FilePath::FromUTF8Unsafe("non_existent_file"),
FileCache::FILE_OPERATION_COPY));
// Passing empty MD5 marks the entry as dirty.
EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
id, std::string(), src_file_path, FileCache::FILE_OPERATION_COPY));
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().is_present());
EXPECT_TRUE(entry.file_specific_info().cache_state().md5().empty());
EXPECT_TRUE(entry.file_specific_info().cache_state().is_dirty());
EXPECT_FALSE(HasRemovableFlag((dest_file_path)));
// No free space available.
fake_free_disk_space_getter_->set_default_value(0);
EXPECT_EQ(FILE_ERROR_NO_LOCAL_SPACE, cache_->Store(
id, md5, src_file_path, FileCache::FILE_OPERATION_COPY));
}
TEST_F(FileCacheTest, PinAndUnpin) {
const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
const std::string src_contents = "test";
EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
src_contents));
std::string id("id_present");
std::string md5(base::MD5String(src_contents));
// Store a file.
ResourceEntry entry;
entry.set_local_id(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
id, md5, src_file_path, FileCache::FILE_OPERATION_COPY));
const base::FilePath dest_file_path = GetCacheFilePath(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_FALSE(entry.file_specific_info().cache_state().is_pinned());
EXPECT_TRUE(HasRemovableFlag((dest_file_path)));
// Pin the existing file.
EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(id));
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().is_pinned());
EXPECT_FALSE(HasRemovableFlag((dest_file_path)));
// Unpin the file.
EXPECT_EQ(FILE_ERROR_OK, cache_->Unpin(id));
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_FALSE(entry.file_specific_info().cache_state().is_pinned());
EXPECT_TRUE(HasRemovableFlag((dest_file_path)));
// Pin a non-present file.
std::string id_non_present = "id_non_present";
entry.Clear();
entry.set_local_id(id_non_present);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
EXPECT_EQ(FILE_ERROR_OK, cache_->Pin(id_non_present));
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_non_present, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().is_pinned());
// Unpin the previously pinned non-existent file.
EXPECT_EQ(FILE_ERROR_OK, cache_->Unpin(id_non_present));
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_non_present, &entry));
EXPECT_FALSE(entry.file_specific_info().has_cache_state());
// Unpin a file that doesn't exist in cache and is not pinned.
EXPECT_EQ(FILE_ERROR_NOT_FOUND, cache_->Unpin("id_non_existent"));
}
TEST_F(FileCacheTest, MountUnmount) {
const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
const std::string src_contents = "test";
EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
src_contents));
std::string id("id_present");
std::string md5(base::MD5String(src_contents));
// Store a file.
ResourceEntry entry;
entry.set_local_id(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
id, md5, src_file_path, FileCache::FILE_OPERATION_COPY));
// Mark the file mounted.
base::FilePath cache_file_path;
EXPECT_EQ(FILE_ERROR_OK, cache_->MarkAsMounted(id, &cache_file_path));
// Try to remove it.
EXPECT_EQ(FILE_ERROR_IN_USE, cache_->Remove(id));
// Clear mounted state of the file.
EXPECT_EQ(FILE_ERROR_OK, cache_->MarkAsUnmounted(cache_file_path));
// Try to remove again.
EXPECT_EQ(FILE_ERROR_OK, cache_->Remove(id));
}
TEST_F(FileCacheTest, OpenForWrite) {
// Prepare a file.
base::FilePath src_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
const std::string id = "id";
ResourceEntry entry;
entry.set_local_id(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
ASSERT_EQ(FILE_ERROR_OK, cache_->Store(id, "md5", src_file,
FileCache::FILE_OPERATION_COPY));
EXPECT_EQ(0, entry.file_info().last_modified());
// Entry is not dirty nor opened.
EXPECT_FALSE(cache_->IsOpenedForWrite(id));
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
const base::FilePath dest_file = GetCacheFilePath(id);
EXPECT_TRUE(HasRemovableFlag((dest_file)));
// Open (1).
std::unique_ptr<base::ScopedClosureRunner> file_closer1;
EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer1));
EXPECT_TRUE(cache_->IsOpenedForWrite(id));
// Entry is dirty.
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().is_dirty());
EXPECT_FALSE(HasRemovableFlag((dest_file)));
// Open (2).
std::unique_ptr<base::ScopedClosureRunner> file_closer2;
EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer2));
EXPECT_TRUE(cache_->IsOpenedForWrite(id));
// Close (1).
file_closer1.reset();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(cache_->IsOpenedForWrite(id));
// last_modified is updated.
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_NE(0, entry.file_info().last_modified());
// Close (2).
file_closer2.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(cache_->IsOpenedForWrite(id));
// Try to open non-existent file.
EXPECT_EQ(FILE_ERROR_NOT_FOUND,
cache_->OpenForWrite("nonexistent_id", &file_closer1));
}
TEST_F(FileCacheTest, UpdateMd5) {
// Store test data.
const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
const std::string contents_before = "before";
EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
contents_before));
std::string id("id1");
ResourceEntry entry;
entry.set_local_id(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
EXPECT_EQ(FILE_ERROR_OK, cache_->Store(id, base::MD5String(contents_before),
src_file_path,
FileCache::FILE_OPERATION_COPY));
// Modify the cache file.
std::unique_ptr<base::ScopedClosureRunner> file_closer;
EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer));
base::FilePath cache_file_path;
EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path));
const std::string contents_after = "after";
EXPECT_TRUE(google_apis::test_util::WriteStringToFile(cache_file_path,
contents_after));
// Cannot update MD5 of an opend file.
EXPECT_EQ(FILE_ERROR_IN_USE, cache_->UpdateMd5(id));
// Close file.
file_closer.reset();
base::RunLoop().RunUntilIdle();
// MD5 was cleared by OpenForWrite().
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().md5().empty());
// Update MD5.
EXPECT_EQ(FILE_ERROR_OK, cache_->UpdateMd5(id));
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_EQ(base::MD5String(contents_after),
entry.file_specific_info().cache_state().md5());
}
TEST_F(FileCacheTest, ClearDirty) {
// Prepare a file.
base::FilePath src_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
const std::string id = "id";
ResourceEntry entry;
entry.set_local_id(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
ASSERT_EQ(FILE_ERROR_OK, cache_->Store(id, "md5", src_file,
FileCache::FILE_OPERATION_COPY));
const base::FilePath dest_file = GetCacheFilePath(id);
EXPECT_TRUE(HasRemovableFlag((dest_file)));
// Open the file.
std::unique_ptr<base::ScopedClosureRunner> file_closer;
EXPECT_EQ(FILE_ERROR_OK, cache_->OpenForWrite(id, &file_closer));
// Entry is dirty.
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_TRUE(entry.file_specific_info().cache_state().is_dirty());
EXPECT_FALSE(HasRemovableFlag((dest_file)));
// Cannot clear the dirty bit of an opened entry.
EXPECT_EQ(FILE_ERROR_IN_USE, cache_->ClearDirty(id));
EXPECT_FALSE(HasRemovableFlag((dest_file)));
// Close the file and clear the dirty bit.
file_closer.reset();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(FILE_ERROR_OK, cache_->ClearDirty(id));
// Entry is not dirty.
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id, &entry));
EXPECT_FALSE(entry.file_specific_info().cache_state().is_dirty());
EXPECT_TRUE(HasRemovableFlag((dest_file)));
}
TEST_F(FileCacheTest, Remove) {
const base::FilePath src_file_path = temp_dir_.path().Append("test.dat");
const std::string src_contents = "test";
EXPECT_TRUE(google_apis::test_util::WriteStringToFile(src_file_path,
src_contents));
std::string id("id");
std::string md5(base::MD5String(src_contents));
// First store a file to cache.
ResourceEntry entry;
entry.set_local_id(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
base::FilePath src_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
EXPECT_EQ(FILE_ERROR_OK, cache_->Store(
id, md5, src_file_path, FileCache::FILE_OPERATION_COPY));
base::FilePath cache_file_path;
EXPECT_EQ(FILE_ERROR_OK, cache_->GetFile(id, &cache_file_path));
// Then try to remove existing file from cache.
EXPECT_EQ(FILE_ERROR_OK, cache_->Remove(id));
EXPECT_FALSE(base::PathExists(cache_file_path));
}
TEST_F(FileCacheTest, RenameCacheFilesToNewFormat) {
const base::FilePath file_directory =
temp_dir_.path().Append(kCacheFileDirectory);
// File with an old style "<prefix>:<ID>.<MD5>" name.
ASSERT_TRUE(google_apis::test_util::WriteStringToFile(
file_directory.AppendASCII("file:id_koo.md5"), "koo"));
// File with multiple extensions should be removed.
ASSERT_TRUE(google_apis::test_util::WriteStringToFile(
file_directory.AppendASCII("id_kyu.md5.mounted"), "kyu (mounted)"));
ASSERT_TRUE(google_apis::test_util::WriteStringToFile(
file_directory.AppendASCII("id_kyu.md5"), "kyu"));
// Rename and verify the result.
EXPECT_TRUE(RenameCacheFilesToNewFormat(cache_.get()));
std::string contents;
EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_koo"),
&contents));
EXPECT_EQ("koo", contents);
contents.clear();
EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_kyu"),
&contents));
EXPECT_EQ("kyu", contents);
// Rename again.
EXPECT_TRUE(RenameCacheFilesToNewFormat(cache_.get()));
// Files with new style names are not affected.
contents.clear();
EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_koo"),
&contents));
EXPECT_EQ("koo", contents);
contents.clear();
EXPECT_TRUE(base::ReadFileToString(file_directory.AppendASCII("id_kyu"),
&contents));
EXPECT_EQ("kyu", contents);
}
TEST_F(FileCacheTest, FixMetadataAndFileAttributes) {
// Create test files and metadata.
base::FilePath temp_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &temp_file));
// Entry A: pinned cache file.
const std::string id_a = "id_a";
ResourceEntry entry_a;
entry_a.set_local_id(id_a);
FileCacheEntry* file_cache_entry_a =
entry_a.mutable_file_specific_info()->mutable_cache_state();
file_cache_entry_a->set_is_present(true);
file_cache_entry_a->set_is_pinned(true);
file_cache_entry_a->set_is_dirty(false);
ASSERT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry_a));
const base::FilePath file_path_a = GetCacheFilePath(id_a);
ASSERT_TRUE(base::CopyFile(temp_file, file_path_a));
// Entry B: dirty cache file.
const std::string id_b = "id_b";
ResourceEntry entry_b;
entry_b.set_local_id(id_b);
FileCacheEntry* file_cache_entry_b =
entry_b.mutable_file_specific_info()->mutable_cache_state();
file_cache_entry_b->set_is_present(true);
file_cache_entry_b->set_is_pinned(false);
file_cache_entry_b->set_is_dirty(true);
ASSERT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry_b));
const base::FilePath file_path_b = GetCacheFilePath(id_b);
ASSERT_TRUE(base::CopyFile(temp_file, file_path_b));
// Entry C: not pinned nor dirty cache file.
const std::string id_c = "id_c";
ResourceEntry entry_c;
entry_c.set_local_id(id_c);
FileCacheEntry* file_cache_entry_c =
entry_c.mutable_file_specific_info()->mutable_cache_state();
file_cache_entry_c->set_is_present(true);
file_cache_entry_c->set_is_pinned(false);
file_cache_entry_c->set_is_dirty(false);
ASSERT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry_c));
const base::FilePath file_path_c = GetCacheFilePath(id_c);
ASSERT_TRUE(base::CopyFile(temp_file, file_path_c));
// Entry D: pinned cache file somehow having removable flag.
const std::string id_d = "id_d";
ResourceEntry entry_d;
entry_d.set_local_id(id_d);
FileCacheEntry* file_cache_entry_d =
entry_d.mutable_file_specific_info()->mutable_cache_state();
file_cache_entry_d->set_is_present(true);
file_cache_entry_d->set_is_pinned(true);
file_cache_entry_d->set_is_dirty(false);
ASSERT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry_d));
const base::FilePath file_path_d = GetCacheFilePath(id_d);
ASSERT_TRUE(base::CopyFile(temp_file, file_path_d));
// Set removable flag.
FileAttributes flags = GetFileAttributes(file_path_d);
ASSERT_GE(flags, 0);
flags |= FS_NODUMP_FL;
base::File file_d(file_path_d, base::File::FLAG_OPEN | base::File::FLAG_READ);
ASSERT_EQ(ioctl(file_d.GetPlatformFile(), FS_IOC_SETFLAGS, &flags), 0);
// Entry E: there is no file; removed by cryptohome.
const std::string id_e = "id_e";
ResourceEntry entry_e;
entry_e.set_local_id(id_e);
FileCacheEntry* file_cache_entry_e =
entry_e.mutable_file_specific_info()->mutable_cache_state();
file_cache_entry_e->set_is_present(true);
file_cache_entry_e->set_is_pinned(false);
file_cache_entry_e->set_is_dirty(false);
ASSERT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry_e));
const base::FilePath file_path_e = GetCacheFilePath(id_e);
// Entry F: there is a file, but metadata says not.
const std::string id_f = "id_f";
ResourceEntry entry_f;
entry_f.set_local_id(id_f);
entry_f.mutable_file_specific_info()->mutable_cache_state()->set_is_present(
false);
ASSERT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry_f));
const base::FilePath file_path_f = GetCacheFilePath(id_f);
ASSERT_TRUE(base::CopyFile(temp_file, file_path_f));
// Entry G: no file nor metadata.
const std::string id_g = "id_g";
ResourceEntry entry_g;
entry_g.set_local_id(id_g);
entry_f.mutable_file_specific_info()->mutable_cache_state()->set_is_present(
false);
ASSERT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry_f));
// Initialize fixes inconsistency between metadata and cache file attributes
// as well as adding specific file attributes to the cache directory.
ASSERT_TRUE(cache_->Initialize());
// Check result.
EXPECT_FALSE(HasRemovableFlag(file_path_a));
EXPECT_FALSE(HasRemovableFlag((file_path_b)));
EXPECT_TRUE(HasRemovableFlag((file_path_c)));
EXPECT_FALSE(HasRemovableFlag((file_path_d)));
EXPECT_FALSE(base::PathExists(file_path_f));
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_e, &entry_e));
EXPECT_FALSE(entry_e.file_specific_info().cache_state().is_present());
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->GetEntry(id_f, &entry_f));
EXPECT_FALSE(entry_f.file_specific_info().cache_state().is_present());
// Check the cache dir has appropriate attributes.
EXPECT_TRUE(HasRemovableFlag((cache_files_dir_)));
EXPECT_GE(getxattr(cache_files_dir_.value().c_str(),
FileCache::kGCacheFilesAttribute, nullptr, 0), 0);
}
TEST_F(FileCacheTest, ClearAll) {
const std::string id("1a2b");
const std::string md5("abcdef0123456789");
// Store an existing file.
ResourceEntry entry;
entry.set_local_id(id);
EXPECT_EQ(FILE_ERROR_OK, metadata_storage_->PutEntry(entry));
base::FilePath src_file;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &src_file));
ASSERT_EQ(FILE_ERROR_OK,
cache_->Store(id, md5, src_file, FileCache::FILE_OPERATION_COPY));
// Clear cache.
EXPECT_TRUE(cache_->ClearAll());
// Verify that the cache is removed.
EXPECT_TRUE(base::IsDirectoryEmpty(cache_files_dir_));
}
} // namespace internal
} // namespace drive