blob: 30ebeb2ae52595bd5a60aa3254d51d5ca5e16331 [file] [log] [blame]
// Copyright 2016 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/dircrypto_data_migrator/migration_helper.h"
#include <string>
#include <vector>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/rand_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/synchronization/waitable_event.h>
#include <base/threading/thread.h>
#include "cryptohome/migration_type.h"
#include "cryptohome/mock_platform.h"
#include "cryptohome/platform.h"
extern "C" {
#include <linux/fs.h>
}
using base::FilePath;
using base::ScopedTempDir;
using testing::_;
using testing::DoDefault;
using testing::Invoke;
using testing::InvokeWithoutArgs;
using testing::NiceMock;
using testing::Return;
using testing::SetErrnoAndReturn;
using testing::Values;
namespace cryptohome {
namespace dircrypto_data_migrator {
namespace {
constexpr uint64_t kDefaultChunkSize = 128;
constexpr char kMtimeXattrName[] = "user.mtime";
constexpr char kAtimeXattrName[] = "user.atime";
// Connects all used calls on MockPlatform to the concrete implementations in
// real_platform by default. Tests wishing to mock out only some methods from
// platform may call this initially and then set mock expectations for only the
// methods they care about.
void PassThroughPlatformMethods(MockPlatform* mock_platform,
Platform* real_platform) {
ON_CALL(*mock_platform, TouchFileDurable(_))
.WillByDefault(Invoke(real_platform, &Platform::TouchFileDurable));
ON_CALL(*mock_platform, DeleteFile(_))
.WillByDefault(Invoke(real_platform, &Platform::DeleteFile));
ON_CALL(*mock_platform, DeletePathRecursively(_))
.WillByDefault(Invoke(real_platform, &Platform::DeletePathRecursively));
ON_CALL(*mock_platform, SyncDirectory(_))
.WillByDefault(Invoke(real_platform, &Platform::SyncDirectory));
ON_CALL(*mock_platform, DataSyncFile(_))
.WillByDefault(Invoke(real_platform, &Platform::DataSyncFile));
ON_CALL(*mock_platform, SyncFile(_))
.WillByDefault(Invoke(real_platform, &Platform::SyncFile));
ON_CALL(*mock_platform, GetFileEnumerator(_, _, _))
.WillByDefault(Invoke(real_platform, &Platform::GetFileEnumerator));
ON_CALL(*mock_platform, SetPermissions(_, _))
.WillByDefault(Invoke(real_platform, &Platform::SetPermissions));
ON_CALL(*mock_platform, GetPermissions(_, _))
.WillByDefault(Invoke(real_platform, &Platform::GetPermissions));
ON_CALL(*mock_platform, FileExists(_))
.WillByDefault(Invoke(real_platform, &Platform::FileExists));
ON_CALL(*mock_platform, DirectoryExists(_))
.WillByDefault(Invoke(real_platform, &Platform::DirectoryExists));
ON_CALL(*mock_platform, CreateDirectory(_))
.WillByDefault(Invoke(real_platform, &Platform::CreateDirectory));
ON_CALL(*mock_platform, HasExtendedFileAttribute(_, _))
.WillByDefault(
Invoke(real_platform, &Platform::HasExtendedFileAttribute));
ON_CALL(*mock_platform, ListExtendedFileAttributes(_, _))
.WillByDefault(
Invoke(real_platform, &Platform::ListExtendedFileAttributes));
ON_CALL(*mock_platform, SetExtendedFileAttribute(_, _, _, _))
.WillByDefault(
Invoke(real_platform, &Platform::SetExtendedFileAttribute));
ON_CALL(*mock_platform, GetExtendedFileAttribute(_, _, _, _))
.WillByDefault(
Invoke(real_platform, &Platform::GetExtendedFileAttribute));
ON_CALL(*mock_platform, GetExtendedFileAttributeAsString(_, _, _))
.WillByDefault(
Invoke(real_platform, &Platform::GetExtendedFileAttributeAsString));
ON_CALL(*mock_platform, GetExtFileAttributes(_, _))
.WillByDefault(Invoke(real_platform, &Platform::GetExtFileAttributes));
ON_CALL(*mock_platform, SetExtFileAttributes(_, _))
.WillByDefault(Invoke(real_platform, &Platform::SetExtFileAttributes));
ON_CALL(*mock_platform, GetOwnership(_, _, _, _))
.WillByDefault(Invoke(real_platform, &Platform::GetOwnership));
ON_CALL(*mock_platform, SetOwnership(_, _, _, _))
.WillByDefault(Invoke(real_platform, &Platform::SetOwnership));
ON_CALL(*mock_platform, SetFileTimes(_, _, _, _))
.WillByDefault(Invoke(real_platform, &Platform::SetFileTimes));
ON_CALL(*mock_platform, Stat(_, _))
.WillByDefault(Invoke(real_platform, &Platform::Stat));
ON_CALL(*mock_platform, SendFile(_, _, _, _))
.WillByDefault(Invoke(real_platform, &Platform::SendFile));
ON_CALL(*mock_platform, AmountOfFreeDiskSpace(_))
.WillByDefault(Invoke(real_platform, &Platform::AmountOfFreeDiskSpace));
ON_CALL(*mock_platform, InitializeFile(_, _, _))
.WillByDefault(Invoke(real_platform, &Platform::InitializeFile));
ON_CALL(*mock_platform, LockFile(_))
.WillByDefault(Invoke(real_platform, &Platform::LockFile));
ON_CALL(*mock_platform, RemoveExtendedFileAttribute(_, _))
.WillByDefault(
Invoke(real_platform, &Platform::RemoveExtendedFileAttribute));
}
} // namespace
class MigrationHelperTest : public ::testing::Test {
public:
virtual ~MigrationHelperTest() {}
void SetUp() override {
ASSERT_TRUE(status_files_dir_.CreateUniqueTempDir());
ASSERT_TRUE(from_dir_.CreateUniqueTempDir());
ASSERT_TRUE(to_dir_.CreateUniqueTempDir());
}
void TearDown() override {
EXPECT_TRUE(status_files_dir_.Delete());
EXPECT_TRUE(from_dir_.Delete());
EXPECT_TRUE(to_dir_.Delete());
}
void ProgressCaptor(
const user_data_auth::DircryptoMigrationProgress& progress) {
migrated_values_.push_back(progress.current_bytes());
total_values_.push_back(progress.total_bytes());
status_values_.push_back(progress.status());
}
protected:
ScopedTempDir status_files_dir_;
ScopedTempDir from_dir_;
ScopedTempDir to_dir_;
std::vector<uint64_t> migrated_values_;
std::vector<uint64_t> total_values_;
std::vector<user_data_auth::DircryptoMigrationStatus> status_values_;
};
TEST_F(MigrationHelperTest, EmptyTest) {
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
ASSERT_TRUE(base::IsDirectoryEmpty(from_dir_.GetPath()));
ASSERT_TRUE(base::IsDirectoryEmpty(to_dir_.GetPath()));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
}
TEST_F(MigrationHelperTest, CopyAttributesDirectory) {
// This test only covers permissions and xattrs. Ownership copying requires
// more extensive mocking and is covered in CopyOwnership test.
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kDirectory[] = "directory";
const FilePath kFromDirPath = from_dir_.GetPath().Append(kDirectory);
ASSERT_TRUE(platform.CreateDirectory(kFromDirPath));
// Set some attributes to this directory.
mode_t mode = S_ISVTX | S_IRUSR | S_IWUSR | S_IXUSR;
ASSERT_TRUE(platform.SetPermissions(kFromDirPath, mode));
// GetPermissions call is needed because some bits to mode are applied
// automatically, so our original |mode| value is not what the resulting file
// actually has.
ASSERT_TRUE(platform.GetPermissions(kFromDirPath, &mode));
constexpr char kAttrName[] = "user.attr";
constexpr char kValue[] = "value";
ASSERT_EQ(0, lsetxattr(kFromDirPath.value().c_str(), kAttrName, kValue,
sizeof(kValue), XATTR_CREATE));
// Set ext2 attributes
base::ScopedFD from_fd(
HANDLE_EINTR(::open(kFromDirPath.value().c_str(), O_RDONLY)));
ASSERT_TRUE(from_fd.is_valid());
int ext2_attrs = FS_SYNC_FL | FS_NODUMP_FL;
ASSERT_EQ(0, ::ioctl(from_fd.get(), FS_IOC_SETFLAGS, &ext2_attrs));
base::stat_wrapper_t from_stat;
ASSERT_TRUE(platform.Stat(kFromDirPath, &from_stat));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
const FilePath kToDirPath = to_dir_.GetPath().Append(kDirectory);
base::stat_wrapper_t to_stat;
ASSERT_TRUE(platform.Stat(kToDirPath, &to_stat));
EXPECT_TRUE(platform.DirectoryExists(kToDirPath));
// Verify mtime was coppied. atime for directories is not
// well-preserved because we have to traverse the directories to determine
// migration size.
EXPECT_EQ(from_stat.st_mtim.tv_sec, to_stat.st_mtim.tv_sec);
EXPECT_EQ(from_stat.st_mtim.tv_nsec, to_stat.st_mtim.tv_nsec);
// Verify Permissions and xattrs were copied
mode_t to_mode;
ASSERT_TRUE(platform.GetPermissions(kToDirPath, &to_mode));
EXPECT_EQ(mode, to_mode);
char value[sizeof(kValue) + 1];
EXPECT_EQ(sizeof(kValue), lgetxattr(kToDirPath.value().c_str(), kAttrName,
&value, sizeof(kValue)));
value[sizeof(kValue)] = '\0';
EXPECT_STREQ(kValue, value);
// Verify ext2 flags were copied
base::ScopedFD to_fd(
HANDLE_EINTR(::open(kToDirPath.value().c_str(), O_RDONLY)));
ASSERT_TRUE(to_fd.is_valid());
int new_ext2_attrs;
ASSERT_EQ(0, ::ioctl(to_fd.get(), FS_IOC_GETFLAGS, &new_ext2_attrs));
EXPECT_EQ(ext2_attrs, new_ext2_attrs & ext2_attrs);
}
TEST_F(MigrationHelperTest, DirectoryPartiallyMigrated) {
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kDirectory[] = "directory";
const FilePath kFromDirPath = from_dir_.GetPath().Append(kDirectory);
ASSERT_TRUE(platform.CreateDirectory(kFromDirPath));
constexpr struct timespec kMtime = {123, 456};
constexpr struct timespec kAtime = {234, 567};
ASSERT_EQ(0, lsetxattr(to_dir_.GetPath().value().c_str(), kMtimeXattrName,
&kMtime, sizeof(kMtime), XATTR_CREATE));
ASSERT_EQ(0, lsetxattr(to_dir_.GetPath().value().c_str(), kAtimeXattrName,
&kAtime, sizeof(kAtime), XATTR_CREATE));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
base::stat_wrapper_t to_stat;
// Verify that stored timestamps for in-progress migrations are respected
ASSERT_TRUE(platform.Stat(to_dir_.GetPath(), &to_stat));
EXPECT_EQ(kMtime.tv_sec, to_stat.st_mtim.tv_sec);
EXPECT_EQ(kMtime.tv_nsec, to_stat.st_mtim.tv_nsec);
EXPECT_EQ(kAtime.tv_sec, to_stat.st_atim.tv_sec);
EXPECT_EQ(kAtime.tv_nsec, to_stat.st_atim.tv_nsec);
// Verify subdirectory was migrated
const FilePath kToDirPath = to_dir_.GetPath().Append(kDirectory);
EXPECT_TRUE(platform.DirectoryExists(kToDirPath));
}
TEST_F(MigrationHelperTest, CopySymlink) {
// This test does not cover setting ownership values as that requires more
// extensive mocking. Ownership copying instead is covered by the
// CopyOwnership test.
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
FilePath target;
constexpr char kFileName[] = "file";
constexpr char kAbsLinkTarget[] = "/dev/null";
const FilePath kTargetInMigrationDirAbsLinkTarget =
from_dir_.GetPath().Append(kFileName);
const FilePath kRelLinkTarget = base::FilePath(kFileName);
constexpr char kRelLinkName[] = "link1";
constexpr char kAbsLinkName[] = "link2";
constexpr char kTargetInMigrationDirAbsLinkName[] = "link3";
const FilePath kFromRelLinkPath = from_dir_.GetPath().Append(kRelLinkName);
const FilePath kFromAbsLinkPath = from_dir_.GetPath().Append(kAbsLinkName);
const FilePath kFromTargetInMigrationDirAbsLinkPath =
from_dir_.GetPath().Append(kTargetInMigrationDirAbsLinkName);
ASSERT_TRUE(base::CreateSymbolicLink(kRelLinkTarget, kFromRelLinkPath));
ASSERT_TRUE(base::CreateSymbolicLink(base::FilePath(kAbsLinkTarget),
kFromAbsLinkPath));
ASSERT_TRUE(base::CreateSymbolicLink(kTargetInMigrationDirAbsLinkTarget,
kFromTargetInMigrationDirAbsLinkPath));
base::stat_wrapper_t from_stat;
ASSERT_TRUE(platform.Stat(kFromRelLinkPath, &from_stat));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
const FilePath kToFilePath = to_dir_.GetPath().Append(kFileName);
const FilePath kToRelLinkPath = to_dir_.GetPath().Append(kRelLinkName);
const FilePath kToAbsLinkPath = to_dir_.GetPath().Append(kAbsLinkName);
const FilePath kToTargetInMigrationDirAbsLinkPath =
to_dir_.GetPath().Append(kTargetInMigrationDirAbsLinkName);
const FilePath kExpectedTargetInMigrationDirAbsLinkTarget =
to_dir_.GetPath().Append(kFileName);
// Verify that timestamps were updated appropriately.
base::stat_wrapper_t to_stat;
ASSERT_TRUE(platform.Stat(kToRelLinkPath, &to_stat));
EXPECT_EQ(from_stat.st_atim.tv_sec, to_stat.st_atim.tv_sec);
EXPECT_EQ(from_stat.st_atim.tv_nsec, to_stat.st_atim.tv_nsec);
EXPECT_EQ(from_stat.st_mtim.tv_sec, to_stat.st_mtim.tv_sec);
EXPECT_EQ(from_stat.st_mtim.tv_nsec, to_stat.st_mtim.tv_nsec);
// Verify that all links have been copied correctly
EXPECT_TRUE(base::IsLink(kToRelLinkPath));
EXPECT_TRUE(base::IsLink(kToAbsLinkPath));
EXPECT_TRUE(base::IsLink(kToTargetInMigrationDirAbsLinkPath));
EXPECT_TRUE(base::ReadSymbolicLink(kToRelLinkPath, &target));
EXPECT_EQ(kRelLinkTarget.value(), target.value());
EXPECT_TRUE(base::ReadSymbolicLink(kToAbsLinkPath, &target));
EXPECT_EQ(kAbsLinkTarget, target.value());
EXPECT_TRUE(
base::ReadSymbolicLink(kToTargetInMigrationDirAbsLinkPath, &target));
EXPECT_EQ(kExpectedTargetInMigrationDirAbsLinkTarget.value(), target.value());
}
TEST_F(MigrationHelperTest, OneEmptyFile) {
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kFileName[] = "empty_file";
ASSERT_TRUE(platform.TouchFileDurable(from_dir_.GetPath().Append(kFileName)));
ASSERT_TRUE(base::IsDirectoryEmpty(to_dir_.GetPath()));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
// The file is moved.
EXPECT_FALSE(platform.FileExists(from_dir_.GetPath().Append(kFileName)));
EXPECT_TRUE(platform.FileExists(to_dir_.GetPath().Append(kFileName)));
}
TEST_F(MigrationHelperTest, OneEmptyFileInDirectory) {
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kDir1[] = "directory1";
constexpr char kDir2[] = "directory2";
constexpr char kFileName[] = "empty_file";
// Create directory1/directory2/empty_file in from_dir_.
ASSERT_TRUE(platform.CreateDirectory(
from_dir_.GetPath().Append(kDir1).Append(kDir2)));
ASSERT_TRUE(platform.TouchFileDurable(
from_dir_.GetPath().Append(kDir1).Append(kDir2).Append(kFileName)));
ASSERT_TRUE(base::IsDirectoryEmpty(to_dir_.GetPath()));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
// The file is moved.
EXPECT_FALSE(platform.FileExists(
from_dir_.GetPath().Append(kDir1).Append(kDir2).Append(kFileName)));
EXPECT_TRUE(base::IsDirectoryEmpty(from_dir_.GetPath().Append(kDir1)));
EXPECT_TRUE(platform.FileExists(
to_dir_.GetPath().Append(kDir1).Append(kDir2).Append(kFileName)));
}
TEST_F(MigrationHelperTest, UnreadableFile) {
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kDir1[] = "directory1";
constexpr char kDir2[] = "directory2";
constexpr char kFileName[] = "empty_file";
// Create directory1/directory2/empty_file in from_dir_. File will be
// unreadable to test failure case.
ASSERT_TRUE(platform.CreateDirectory(
from_dir_.GetPath().Append(kDir1).Append(kDir2)));
ASSERT_TRUE(platform.TouchFileDurable(
from_dir_.GetPath().Append(kDir1).Append(kDir2).Append(kFileName)));
ASSERT_TRUE(base::IsDirectoryEmpty(to_dir_.GetPath()));
ASSERT_TRUE(platform.SetPermissions(
from_dir_.GetPath().Append(kDir1).Append(kDir2).Append(kFileName),
S_IWUSR));
EXPECT_FALSE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
// The file is not moved.
EXPECT_TRUE(platform.FileExists(
from_dir_.GetPath().Append(kDir1).Append(kDir2).Append(kFileName)));
}
TEST_F(MigrationHelperTest, CopyAttributesFile) {
// This test does not cover setting ownership values as that requires more
// extensive mocking. Ownership copying instead is covered by the
// CopyOwnership test.
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kFileName[] = "file";
const FilePath kFromFilePath = from_dir_.GetPath().Append(kFileName);
const FilePath kToFilePath = to_dir_.GetPath().Append(kFileName);
ASSERT_TRUE(platform.TouchFileDurable(from_dir_.GetPath().Append(kFileName)));
// Set some attributes to this file.
mode_t mode = S_ISVTX | S_IRUSR | S_IWUSR | S_IXUSR;
ASSERT_TRUE(platform.SetPermissions(kFromFilePath, mode));
// GetPermissions call is needed because some bits to mode are applied
// automatically, so our original |mode| value is not what the resulting file
// actually has.
ASSERT_TRUE(platform.GetPermissions(kFromFilePath, &mode));
constexpr char kAttrName[] = "user.attr";
constexpr char kValue[] = "value";
ASSERT_EQ(0, lsetxattr(kFromFilePath.value().c_str(), kAttrName, kValue,
sizeof(kValue), XATTR_CREATE));
ASSERT_EQ(0, lsetxattr(kFromFilePath.value().c_str(), kSourceURLXattrName,
kValue, sizeof(kValue), XATTR_CREATE));
ASSERT_EQ(0, lsetxattr(kFromFilePath.value().c_str(), kReferrerURLXattrName,
kValue, sizeof(kValue), XATTR_CREATE));
// Set ext2 attributes
base::ScopedFD from_fd(
HANDLE_EINTR(::open(kFromFilePath.value().c_str(), O_RDONLY)));
ASSERT_TRUE(from_fd.is_valid());
int ext2_attrs = FS_SYNC_FL | FS_NODUMP_FL;
EXPECT_EQ(0, ::ioctl(from_fd.get(), FS_IOC_SETFLAGS, &ext2_attrs));
base::stat_wrapper_t from_stat;
ASSERT_TRUE(platform.Stat(kFromFilePath, &from_stat));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
base::stat_wrapper_t to_stat;
ASSERT_TRUE(platform.Stat(kToFilePath, &to_stat));
EXPECT_EQ(from_stat.st_atim.tv_sec, to_stat.st_atim.tv_sec);
EXPECT_EQ(from_stat.st_atim.tv_nsec, to_stat.st_atim.tv_nsec);
EXPECT_EQ(from_stat.st_mtim.tv_sec, to_stat.st_mtim.tv_sec);
EXPECT_EQ(from_stat.st_mtim.tv_nsec, to_stat.st_mtim.tv_nsec);
EXPECT_TRUE(platform.FileExists(kToFilePath));
mode_t permission;
ASSERT_TRUE(platform.GetPermissions(kToFilePath, &permission));
EXPECT_EQ(mode, permission);
char value[sizeof(kValue) + 1];
EXPECT_EQ(sizeof(kValue), lgetxattr(kToFilePath.value().c_str(), kAttrName,
&value, sizeof(kValue)));
value[sizeof(kValue)] = '\0';
EXPECT_STREQ(kValue, value);
// The temporary xatttrs for storing mtime/atime should be removed.
EXPECT_EQ(
-1, lgetxattr(kToFilePath.value().c_str(), kMtimeXattrName, nullptr, 0));
EXPECT_EQ(ENODATA, errno);
EXPECT_EQ(
-1, lgetxattr(kToFilePath.value().c_str(), kAtimeXattrName, nullptr, 0));
EXPECT_EQ(ENODATA, errno);
// Quarantine xattrs storing the origin and referrer of downloaded files
// should also be removed.
EXPECT_EQ(-1, lgetxattr(kToFilePath.value().c_str(), kSourceURLXattrName,
nullptr, 0));
EXPECT_EQ(ENODATA, errno);
EXPECT_EQ(-1, lgetxattr(kToFilePath.value().c_str(), kReferrerURLXattrName,
nullptr, 0));
EXPECT_EQ(ENODATA, errno);
base::ScopedFD to_fd(
HANDLE_EINTR(::open(kToFilePath.value().c_str(), O_RDONLY)));
EXPECT_TRUE(to_fd.is_valid());
int new_ext2_attrs;
EXPECT_EQ(0, ::ioctl(to_fd.get(), FS_IOC_GETFLAGS, &new_ext2_attrs));
EXPECT_EQ(ext2_attrs, new_ext2_attrs & ext2_attrs);
}
TEST_F(MigrationHelperTest, CopyOwnership) {
// Ownership changes for regular files and symlinks can't be tested normally
// due to how we get ownership information via file enumerator. Instead we
// directly test CopyAttributes with modified FileInfo arguments.
NiceMock<MockPlatform> mock_platform;
Platform real_platform;
PassThroughPlatformMethods(&mock_platform, &real_platform);
MigrationHelper helper(&mock_platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
const base::FilePath kLinkTarget = base::FilePath("foo");
const base::FilePath kLink("link");
const base::FilePath kFile("file");
const base::FilePath kDir("dir");
const base::FilePath kFromLink = from_dir_.GetPath().Append(kLink);
const base::FilePath kFromFile = from_dir_.GetPath().Append(kFile);
const base::FilePath kFromDir = from_dir_.GetPath().Append(kDir);
const base::FilePath kToLink = to_dir_.GetPath().Append(kLink);
const base::FilePath kToFile = to_dir_.GetPath().Append(kFile);
const base::FilePath kToDir = to_dir_.GetPath().Append(kDir);
uid_t file_uid = 1;
gid_t file_gid = 2;
uid_t link_uid = 3;
gid_t link_gid = 4;
uid_t dir_uid = 5;
gid_t dir_gid = 6;
ASSERT_TRUE(real_platform.TouchFileDurable(kFromFile));
ASSERT_TRUE(base::CreateSymbolicLink(kLinkTarget, kFromLink));
ASSERT_TRUE(real_platform.CreateDirectory(kFromDir));
ASSERT_TRUE(real_platform.TouchFileDurable(kToFile));
ASSERT_TRUE(base::CreateSymbolicLink(kLinkTarget, kToLink));
ASSERT_TRUE(real_platform.CreateDirectory(kToDir));
base::stat_wrapper_t stat;
ASSERT_TRUE(real_platform.Stat(kFromFile, &stat));
stat.st_uid = file_uid;
stat.st_gid = file_gid;
EXPECT_CALL(mock_platform, SetOwnership(kToFile, file_uid, file_gid, false))
.WillOnce(Return(true));
EXPECT_TRUE(
helper.CopyAttributes(kFile, FileEnumerator::FileInfo(kFromFile, stat)));
ASSERT_TRUE(real_platform.Stat(kFromLink, &stat));
stat.st_uid = link_uid;
stat.st_gid = link_gid;
EXPECT_CALL(mock_platform, SetOwnership(kToLink, link_uid, link_gid, false))
.WillOnce(Return(true));
EXPECT_TRUE(
helper.CopyAttributes(kLink, FileEnumerator::FileInfo(kFromLink, stat)));
ASSERT_TRUE(real_platform.Stat(kFromDir, &stat));
stat.st_uid = dir_uid;
stat.st_gid = dir_gid;
EXPECT_CALL(mock_platform, SetOwnership(kToDir, dir_uid, dir_gid, false))
.WillOnce(Return(true));
EXPECT_TRUE(
helper.CopyAttributes(kDir, FileEnumerator::FileInfo(kFromDir, stat)));
}
TEST_F(MigrationHelperTest, MigrateNestedDir) {
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kDir1[] = "directory1";
constexpr char kDir2[] = "directory2";
constexpr char kFileName[] = "empty_file";
// Create directory1/directory2/empty_file in from_dir_.
ASSERT_TRUE(platform.CreateDirectory(
from_dir_.GetPath().Append(kDir1).Append(kDir2)));
ASSERT_TRUE(platform.TouchFileDurable(
from_dir_.GetPath().Append(kDir1).Append(kDir2).Append(kFileName)));
ASSERT_TRUE(base::IsDirectoryEmpty(to_dir_.GetPath()));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
// The file is moved.
EXPECT_TRUE(platform.FileExists(
to_dir_.GetPath().Append(kDir1).Append(kDir2).Append(kFileName)));
EXPECT_FALSE(platform.FileExists(
from_dir_.GetPath().Append(kDir1).Append(kDir2).Append(kFileName)));
EXPECT_TRUE(base::IsDirectoryEmpty(from_dir_.GetPath().Append(kDir1)));
}
TEST_F(MigrationHelperTest, MigrateInProgress) {
// Test the case where the migration was interrupted part way through, but in
// a clean way such that the two directory trees are consistent (files are
// only present in one or the other)
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kFile1[] = "kFile1";
constexpr char kFile2[] = "kFile2";
ASSERT_TRUE(platform.TouchFileDurable(from_dir_.GetPath().Append(kFile1)));
ASSERT_TRUE(platform.TouchFileDurable(to_dir_.GetPath().Append(kFile2)));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
// Both files have been moved to to_dir_
EXPECT_TRUE(platform.FileExists(to_dir_.GetPath().Append(kFile1)));
EXPECT_TRUE(platform.FileExists(to_dir_.GetPath().Append(kFile2)));
EXPECT_FALSE(platform.FileExists(from_dir_.GetPath().Append(kFile1)));
EXPECT_FALSE(platform.FileExists(from_dir_.GetPath().Append(kFile2)));
}
TEST_F(MigrationHelperTest, MigrateInProgressDuplicateFile) {
// Test the case where the migration was interrupted part way through,
// resulting in files that were successfully written to destination but not
// yet removed from the source.
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kFile1[] = "kFile1";
constexpr char kFile2[] = "kFile2";
ASSERT_TRUE(platform.TouchFileDurable(from_dir_.GetPath().Append(kFile1)));
ASSERT_TRUE(platform.TouchFileDurable(to_dir_.GetPath().Append(kFile1)));
ASSERT_TRUE(platform.TouchFileDurable(to_dir_.GetPath().Append(kFile2)));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
// Both files have been moved to to_dir_
EXPECT_TRUE(platform.FileExists(to_dir_.GetPath().Append(kFile1)));
EXPECT_TRUE(platform.FileExists(to_dir_.GetPath().Append(kFile2)));
EXPECT_FALSE(platform.FileExists(from_dir_.GetPath().Append(kFile1)));
EXPECT_FALSE(platform.FileExists(from_dir_.GetPath().Append(kFile2)));
}
TEST_F(MigrationHelperTest, MigrateInProgressPartialFile) {
// Test the case where the migration was interrupted part way through, with a
// file having been partially copied to the destination but not fully.
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kFileName[] = "file";
const FilePath kFromFilePath = from_dir_.GetPath().Append(kFileName);
const FilePath kToFilePath = to_dir_.GetPath().Append(kFileName);
const size_t kFinalFileSize = kDefaultChunkSize * 2;
const size_t kFromFileSize = kDefaultChunkSize;
const size_t kToFileSize = kDefaultChunkSize;
char full_contents[kFinalFileSize];
base::RandBytes(full_contents, kFinalFileSize);
ASSERT_EQ(kDefaultChunkSize,
base::WriteFile(kFromFilePath, full_contents, kFromFileSize));
base::File kToFile =
base::File(kToFilePath, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
kToFile.SetLength(kFinalFileSize);
const size_t kToFileOffset = kFinalFileSize - kToFileSize;
ASSERT_EQ(
kToFileSize,
kToFile.Write(kToFileOffset, full_contents + kToFileOffset, kToFileSize));
ASSERT_EQ(kFinalFileSize, kToFile.GetLength());
kToFile.Close();
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
// File has been moved to to_dir_
char to_contents[kFinalFileSize];
EXPECT_EQ(kFinalFileSize,
base::ReadFile(kToFilePath, to_contents, kFinalFileSize));
EXPECT_EQ(std::string(full_contents, kFinalFileSize),
std::string(to_contents, kFinalFileSize));
EXPECT_FALSE(platform.FileExists(kFromFilePath));
}
TEST_F(MigrationHelperTest, MigrateInProgressPartialFileDuplicateData) {
// Test the case where the migration was interrupted part way through, with a
// file having been partially copied to the destination but the source file
// not yet having been truncated to reflect that.
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kFileName[] = "file";
const FilePath kFromFilePath = from_dir_.GetPath().Append(kFileName);
const FilePath kToFilePath = to_dir_.GetPath().Append(kFileName);
const size_t kFinalFileSize = kDefaultChunkSize * 2;
const size_t kFromFileSize = kFinalFileSize;
const size_t kToFileSize = kDefaultChunkSize;
char full_contents[kFinalFileSize];
base::RandBytes(full_contents, kFinalFileSize);
ASSERT_EQ(kFromFileSize,
base::WriteFile(kFromFilePath, full_contents, kFromFileSize));
base::File kToFile =
base::File(kToFilePath, base::File::FLAG_CREATE | base::File::FLAG_WRITE);
kToFile.SetLength(kFinalFileSize);
const size_t kToFileOffset = kFinalFileSize - kToFileSize;
ASSERT_EQ(
kDefaultChunkSize,
kToFile.Write(kToFileOffset, full_contents + kToFileOffset, kToFileSize));
ASSERT_EQ(kFinalFileSize, kToFile.GetLength());
kToFile.Close();
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
// File has been moved to to_dir_
char to_contents[kFinalFileSize];
EXPECT_EQ(kFinalFileSize,
base::ReadFile(kToFilePath, to_contents, kFinalFileSize));
EXPECT_EQ(std::string(full_contents, kFinalFileSize),
std::string(to_contents, kFinalFileSize));
EXPECT_FALSE(platform.FileExists(kFromFilePath));
}
TEST_F(MigrationHelperTest, ProgressCallback) {
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kFileName[] = "file";
constexpr char kLinkName[] = "link";
constexpr char kDirName[] = "dir";
const FilePath kFromSubdir = from_dir_.GetPath().Append(kDirName);
const FilePath kFromFile = kFromSubdir.Append(kFileName);
const FilePath kFromLink = kFromSubdir.Append(kLinkName);
const FilePath kToSubdir = to_dir_.GetPath().Append(kDirName);
const FilePath kToFile = kToSubdir.Append(kFileName);
const size_t kFileSize = kDefaultChunkSize;
char from_contents[kFileSize];
base::RandBytes(from_contents, kFileSize);
ASSERT_TRUE(base::CreateDirectory(kFromSubdir));
ASSERT_TRUE(base::CreateSymbolicLink(kFromFile.BaseName(), kFromLink));
ASSERT_EQ(kFileSize, base::WriteFile(kFromFile, from_contents, kFileSize));
int64_t expected_size = kFileSize;
expected_size += kFromFile.BaseName().value().length();
int64_t dir_size;
ASSERT_TRUE(platform.GetFileSize(kFromSubdir, &dir_size));
expected_size += dir_size;
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
ASSERT_EQ(migrated_values_.size(), total_values_.size());
int callbacks = migrated_values_.size();
EXPECT_GT(callbacks, 2);
EXPECT_EQ(callbacks, total_values_.size());
EXPECT_EQ(callbacks, status_values_.size());
// Verify that the progress goes from initializing to in_progress.
EXPECT_EQ(user_data_auth::DIRCRYPTO_MIGRATION_INITIALIZING,
status_values_[0]);
for (int i = 1; i < callbacks; i++) {
SCOPED_TRACE(i);
EXPECT_EQ(user_data_auth::DIRCRYPTO_MIGRATION_IN_PROGRESS,
status_values_[i]);
}
// Verify that migrated value starts at 0 and increases to total
EXPECT_EQ(0, migrated_values_[1]);
for (int i = 2; i < callbacks - 1; i++) {
SCOPED_TRACE(i);
EXPECT_GE(migrated_values_[i], migrated_values_[i - 1]);
}
EXPECT_EQ(expected_size, migrated_values_[callbacks - 1]);
// Verify that total always matches the expected size
EXPECT_EQ(callbacks, total_values_.size());
for (int i = 1; i < callbacks; i++) {
SCOPED_TRACE(i);
EXPECT_EQ(expected_size, total_values_[i]);
}
}
TEST_F(MigrationHelperTest, NotEnoughFreeSpace) {
NiceMock<MockPlatform> mock_platform;
Platform real_platform;
PassThroughPlatformMethods(&mock_platform, &real_platform);
MigrationHelper helper(&mock_platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
EXPECT_CALL(mock_platform, AmountOfFreeDiskSpace(_)).WillOnce(Return(0));
EXPECT_FALSE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
}
TEST_F(MigrationHelperTest, ForceSmallerChunkSize) {
NiceMock<MockPlatform> mock_platform;
Platform real_platform;
PassThroughPlatformMethods(&mock_platform, &real_platform);
constexpr int kMaxChunkSize = 128 << 20; // 128MB
constexpr int kNumJobThreads = 2;
MigrationHelper helper(&mock_platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kMaxChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
helper.set_num_job_threads_for_testing(kNumJobThreads);
constexpr int kFreeSpace = 13 << 20;
// Chunk size should be limited to a multiple of 4MB (kErasureBlockSize)
// smaller than (kFreeSpace - kFreeSpaceBuffer) / kNumJobThreads (4MB)
constexpr int kExpectedChunkSize = 4 << 20;
constexpr int kFileSize = 7 << 20;
const FilePath kFromFilePath = from_dir_.GetPath().Append("file");
base::File from_file(kFromFilePath,
base::File::FLAG_CREATE | base::File::FLAG_WRITE);
from_file.SetLength(kFileSize);
from_file.Close();
EXPECT_CALL(mock_platform, AmountOfFreeDiskSpace(_))
.WillOnce(Return(kFreeSpace));
EXPECT_CALL(mock_platform, SendFile(_, _, kExpectedChunkSize,
kFileSize - kExpectedChunkSize))
.WillOnce(Return(true));
EXPECT_CALL(mock_platform, SendFile(_, _, 0, kExpectedChunkSize))
.WillOnce(Return(true));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
}
TEST_F(MigrationHelperTest, SkipInvalidSQLiteFiles) {
NiceMock<MockPlatform> mock_platform;
Platform real_platform;
PassThroughPlatformMethods(&mock_platform, &real_platform);
MigrationHelper helper(&mock_platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
const char kCorruptedFilePath[] =
"root/android-data/data/user/0/com.google.android.gms/"
"databases/playlog.db-shm";
const FilePath kFromSQLiteShm =
from_dir_.GetPath().Append(kCorruptedFilePath);
const FilePath kToSQLiteShm = to_dir_.GetPath().Append(kCorruptedFilePath);
const FilePath kSkippedFileLog =
to_dir_.GetPath().Append(kSkippedFileListFileName);
ASSERT_TRUE(base::CreateDirectory(kFromSQLiteShm.DirName()));
ASSERT_TRUE(real_platform.TouchFileDurable(kFromSQLiteShm));
EXPECT_CALL(mock_platform, InitializeFile(_, _, _))
.WillRepeatedly(DoDefault());
EXPECT_CALL(mock_platform, InitializeFile(_, kFromSQLiteShm, _))
.WillOnce(
Invoke([](base::File* file, const FilePath& path, uint32_t mode) {
*file = base::File(base::File::FILE_ERROR_IO);
}));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
EXPECT_TRUE(real_platform.DirectoryExists(kToSQLiteShm.DirName()));
EXPECT_FALSE(real_platform.FileExists(kToSQLiteShm));
EXPECT_FALSE(real_platform.FileExists(kFromSQLiteShm));
EXPECT_TRUE(real_platform.FileExists(kSkippedFileLog));
std::string contents;
ASSERT_TRUE(real_platform.ReadFileToString(kSkippedFileLog, &contents));
EXPECT_EQ(std::string(kCorruptedFilePath) + "\n", contents);
}
TEST_F(MigrationHelperTest, AllJobThreadsFailing) {
NiceMock<MockPlatform> mock_platform;
Platform real_platform;
PassThroughPlatformMethods(&mock_platform, &real_platform);
MigrationHelper helper(&mock_platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr int kNumJobThreads = 2;
helper.set_num_job_threads_for_testing(kNumJobThreads);
helper.set_max_job_list_size_for_testing(1);
// Create more files than the job threads.
for (int i = 0; i < kNumJobThreads * 2; ++i) {
ASSERT_TRUE(real_platform.TouchFileDurable(
from_dir_.GetPath().AppendASCII(base::NumberToString(i))));
}
// All job threads will stop processing jobs because of errors. Also, set
// errno to avoid confusing base::File::OSErrorToFileError(). crbug.com/731809
EXPECT_CALL(mock_platform, DeleteFile(_))
.WillRepeatedly(SetErrnoAndReturn(EIO, false));
// Migrate() still returns the result without deadlocking. crbug.com/731575
EXPECT_FALSE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
}
TEST_F(MigrationHelperTest, SkipDuppedGCacheTmpDir) {
NiceMock<MockPlatform> mock_platform;
Platform real_platform;
PassThroughPlatformMethods(&mock_platform, &real_platform);
MigrationHelper helper(&mock_platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
// Prepare the problematic path.
const base::FilePath v1_path(
from_dir_.GetPath().AppendASCII("user/GCache/v1"));
ASSERT_TRUE(real_platform.CreateDirectory(v1_path.AppendASCII("tmp/foobar")));
ASSERT_TRUE(real_platform.TouchFileDurable(
v1_path.AppendASCII("tmp/foobar/tmp.gdoc")));
// Mock the situation that user/GCache/v1/tmp is enumerated twice.
base::stat_wrapper_t stat_data = {};
stat_data.st_mode = S_IFDIR;
const FileEnumerator::FileInfo info(v1_path.AppendASCII("tmp"), stat_data);
NiceMock<MockFileEnumerator>* mock_v1 = new NiceMock<MockFileEnumerator>();
mock_v1->entries_.push_back(info);
mock_v1->entries_.push_back(info);
EXPECT_CALL(mock_platform, GetFileEnumerator(_, _, _))
.WillRepeatedly(DoDefault());
EXPECT_CALL(mock_platform, GetFileEnumerator(v1_path, false, _))
.WillOnce(Return(mock_v1));
// Ensure that the inner path is never visited.
EXPECT_CALL(mock_platform, DeleteFile(_)).WillRepeatedly(DoDefault());
EXPECT_CALL(mock_platform, DeletePathRecursively(_))
.WillRepeatedly(DoDefault());
EXPECT_CALL(mock_platform,
DeleteFile(v1_path.AppendASCII("tmp/foobar/tmp.gdoc")))
.Times(0);
EXPECT_CALL(mock_platform,
DeletePathRecursively(v1_path.AppendASCII("tmp/foobar/tmp.gdoc")))
.Times(0);
// Test the migration.
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
}
TEST_F(MigrationHelperTest, MinimalMigration) {
NiceMock<MockPlatform> mock_platform;
Platform real_platform;
PassThroughPlatformMethods(&mock_platform, &real_platform);
MigrationHelper helper(&mock_platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::MINIMAL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
std::vector<FilePath> expect_kept_dirs;
std::vector<FilePath> expect_kept_files;
std::vector<FilePath> expect_skipped_dirs;
std::vector<FilePath> expect_skipped_files;
// Set up expectations about what is skipped and what is kept.
// Random stuff not on the allowlist is skipped.
expect_skipped_dirs.emplace_back("user/Application Cache");
expect_skipped_dirs.emplace_back("root/android-data");
expect_skipped_files.emplace_back("user/Application Cache/subfile");
expect_skipped_files.emplace_back("user/skipped_file");
expect_skipped_files.emplace_back("root/skipped_file");
// session_manager/policy in the root section is kept along with children.
expect_kept_dirs.emplace_back("root/session_manager/policy");
expect_kept_files.emplace_back("root/session_manager/policy/subfile1");
expect_kept_files.emplace_back("root/session_manager/policy/subfile2");
expect_kept_dirs.emplace_back("user/log");
// .pki directory is kept along with contents
expect_kept_dirs.emplace_back("user/.pki");
expect_kept_dirs.emplace_back("user/.pki/nssdb");
expect_kept_files.emplace_back("user/.pki/nssdb/subfile1");
expect_kept_files.emplace_back("user/.pki/nssdb/subfile2");
// top-level Web Data is kept
expect_kept_files.emplace_back("user/Web Data");
// Create all directories
for (const auto& path : expect_kept_dirs)
ASSERT_TRUE(real_platform.CreateDirectory(from_dir_.GetPath().Append(path)))
<< path.value();
for (const auto& path : expect_skipped_dirs)
ASSERT_TRUE(real_platform.CreateDirectory(from_dir_.GetPath().Append(path)))
<< path.value();
// Create all files
for (const auto& path : expect_kept_files)
ASSERT_TRUE(
real_platform.TouchFileDurable(from_dir_.GetPath().Append(path)))
<< path.value();
for (const auto& path : expect_skipped_files)
ASSERT_TRUE(
real_platform.TouchFileDurable(from_dir_.GetPath().Append(path)))
<< path.value();
// Test the minimal migration.
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
// Only the expected files and directories are moved.
for (const auto& path : expect_kept_dirs)
EXPECT_TRUE(real_platform.DirectoryExists(to_dir_.GetPath().Append(path)))
<< path.value();
for (const auto& path : expect_kept_files)
EXPECT_TRUE(real_platform.FileExists(to_dir_.GetPath().Append(path)))
<< path.value();
for (const auto& path : expect_skipped_dirs)
EXPECT_FALSE(real_platform.FileExists(to_dir_.GetPath().Append(path)))
<< path.value();
for (const auto& path : expect_skipped_files)
EXPECT_FALSE(real_platform.FileExists(to_dir_.GetPath().Append(path)))
<< path.value();
// The source is empty.
EXPECT_TRUE(base::IsDirectoryEmpty(from_dir_.GetPath()));
}
TEST_F(MigrationHelperTest, CancelMigrationBeforeStart) {
NiceMock<MockPlatform> mock_platform;
Platform real_platform;
PassThroughPlatformMethods(&mock_platform, &real_platform);
MigrationHelper helper(&mock_platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
// Cancel migration before starting, and migration just fails.
helper.Cancel();
EXPECT_FALSE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
}
TEST_F(MigrationHelperTest, CancelMigrationOnAnotherThread) {
NiceMock<MockPlatform> mock_platform;
Platform real_platform;
PassThroughPlatformMethods(&mock_platform, &real_platform);
MigrationHelper helper(&mock_platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
// One empty file to migrate.
constexpr char kFileName[] = "empty_file";
ASSERT_TRUE(
real_platform.TouchFileDurable(from_dir_.GetPath().Append(kFileName)));
// Wait in SyncFile so that cancellation happens before migration finishes.
base::WaitableEvent syncfile_is_called_event(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
base::WaitableEvent cancel_is_called_event(
base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
EXPECT_CALL(mock_platform, SyncFile(to_dir_.GetPath().Append(kFileName)))
.WillOnce(InvokeWithoutArgs(
[&syncfile_is_called_event, &cancel_is_called_event]() {
syncfile_is_called_event.Signal();
cancel_is_called_event.Wait();
return true;
}));
// Cancel on another thread after waiting for SyncFile to get called.
base::Thread thread("Canceller thread");
ASSERT_TRUE(thread.Start());
thread.task_runner()->PostTask(
FROM_HERE, base::Bind(&base::WaitableEvent::Wait,
base::Unretained(&syncfile_is_called_event)));
thread.task_runner()->PostTask(
FROM_HERE,
base::Bind(&MigrationHelper::Cancel, base::Unretained(&helper)));
thread.task_runner()->PostTask(
FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
base::Unretained(&cancel_is_called_event)));
// Migration gets cancelled.
EXPECT_FALSE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
}
class DataMigrationTest : public MigrationHelperTest,
public ::testing::WithParamInterface<size_t> {};
TEST_P(DataMigrationTest, CopyFileData) {
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
constexpr char kFileName[] = "file";
const FilePath kFromFile = from_dir_.GetPath().Append(kFileName);
const FilePath kToFile = to_dir_.GetPath().Append(kFileName);
const size_t kFileSize = GetParam();
char from_contents[kFileSize];
base::RandBytes(from_contents, kFileSize);
ASSERT_EQ(kFileSize, base::WriteFile(kFromFile, from_contents, kFileSize));
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
char to_contents[kFileSize];
EXPECT_EQ(kFileSize, base::ReadFile(kToFile, to_contents, kFileSize));
EXPECT_EQ(0, strncmp(from_contents, to_contents, kFileSize));
EXPECT_FALSE(platform.FileExists(kFromFile));
}
INSTANTIATE_TEST_SUITE_P(WithRandomData,
DataMigrationTest,
Values(kDefaultChunkSize / 2,
kDefaultChunkSize,
kDefaultChunkSize * 2,
kDefaultChunkSize * 2 + kDefaultChunkSize / 2,
kDefaultChunkSize * 10,
kDefaultChunkSize * 100,
123456,
1,
2));
// MigrationHelperJobListTest verifies that the job list size limit doesn't
// cause dead lock, however small (or big) the limit is.
class MigrationHelperJobListTest
: public MigrationHelperTest,
public ::testing::WithParamInterface<size_t> {};
TEST_P(MigrationHelperJobListTest, ProcessJobs) {
Platform platform;
MigrationHelper helper(&platform, from_dir_.GetPath(), to_dir_.GetPath(),
status_files_dir_.GetPath(), kDefaultChunkSize,
MigrationType::FULL);
helper.set_namespaced_mtime_xattr_name_for_testing(kMtimeXattrName);
helper.set_namespaced_atime_xattr_name_for_testing(kAtimeXattrName);
helper.set_max_job_list_size_for_testing(GetParam());
// Prepare many files and directories.
constexpr int kNumDirectories = 100;
constexpr int kNumFilesPerDirectory = 10;
for (int i = 0; i < kNumDirectories; ++i) {
SCOPED_TRACE(i);
FilePath dir = from_dir_.GetPath().AppendASCII(base::NumberToString(i));
ASSERT_TRUE(platform.CreateDirectory(dir));
for (int j = 0; j < kNumFilesPerDirectory; ++j) {
SCOPED_TRACE(j);
const std::string data =
base::NumberToString(i * kNumFilesPerDirectory + j);
ASSERT_EQ(data.size(),
base::WriteFile(dir.AppendASCII(base::NumberToString(j)),
data.data(), data.size()));
}
}
// Migrate.
EXPECT_TRUE(helper.Migrate(base::Bind(&MigrationHelperTest::ProgressCaptor,
base::Unretained(this))));
// The files and directories are moved.
for (int i = 0; i < kNumDirectories; ++i) {
SCOPED_TRACE(i);
FilePath dir = to_dir_.GetPath().AppendASCII(base::NumberToString(i));
EXPECT_TRUE(platform.DirectoryExists(dir));
for (int j = 0; j < kNumFilesPerDirectory; ++j) {
SCOPED_TRACE(j);
std::string data;
EXPECT_TRUE(base::ReadFileToString(
dir.AppendASCII(base::NumberToString(j)), &data));
EXPECT_EQ(base::NumberToString(i * kNumFilesPerDirectory + j), data);
}
}
EXPECT_TRUE(base::IsDirectoryEmpty(from_dir_.GetPath()));
}
INSTANTIATE_TEST_SUITE_P(JobListSize,
MigrationHelperJobListTest,
Values(1, 10, 100, 1000));
} // namespace dircrypto_data_migrator
} // namespace cryptohome