blob: a6b0ad5d74df0bfc02c7e232833fcd7a35a97926 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/crosapi/browser_data_migrator.h"
#include <string>
#include <utility>
#include "ash/constants/ash_switches.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/files/file.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "chrome/browser/ash/crosapi/browser_data_migrator_util.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/common/chrome_constants.h"
#include "chromeos/cryptohome/cryptohome_parameters.h"
#include "chromeos/dbus/session_manager/session_manager_client.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/user_manager/user_manager.h"
#include "components/version_info/version_info.h"
#include "google_apis/gaia/gaia_auth_util.h"
namespace ash {
namespace {
// The base names of files and directories directly under the original profile
// data directory that does not need to be copied nor need to remain in ash e.g.
// cache data.
constexpr const char* const kNoCopyPathsDeprecated[] = {kTmpDir, "Cache"};
constexpr const char* const kNoCopyPaths[] = {
kTmpDir,
"Cache",
"Code Cache",
"crash",
"blob_storage",
"GCache",
"data_reduction_proxy_leveldb",
"previews_opt_out.db",
"Download Service",
"Network Persistent State",
"Reporting and NEL",
"TransportSecurity",
"optimization_guide_hint_cache_store",
"Site Characteristics Database",
"Network Action Predictor"};
// The base names of files and directories that should remain in ash data
// directory.
constexpr const char* const kAshDataPathsDeprecated[]{"Downloads", "MyFiles"};
constexpr const char* const kAshDataPaths[] = {"FullRestoreData",
"Downloads",
"MyFiles",
"arc.apps",
"crostini.icons",
"PreferredApps",
"autobrightness",
"extension_install_log",
"google-assistant-library",
"login-times",
"logout-times",
"structured_metrics",
"PrintJobDatabase",
"PPDCache",
"BudgetDatabase",
"RLZ Data",
"app_ranker.pb",
"zero_state_group_ranker.pb",
"zero_state_local_files.pb"};
// The base names of files/dirs that are needed only by the browser part of
// chrome i.e. data that should be moved to lacros.
constexpr const char* const kLacrosDataPathsDeprecated[]{"Bookmarks"};
constexpr const char* const kLacrosDataPaths[]{"AutofillStrikeDatabase",
"Bookmarks",
"Cookies",
"Extension Cookies",
"Extension Rules",
"Extension State",
"Extensions",
"Local App Settings",
"Local Extension Settings",
"Managed Extension Settings",
"Sync App Settings",
"DNR Extension Rules",
"Favicons",
"History",
"Top Sites",
"Shortcuts",
"Sessions"};
// Flag values for `switches::kForceBrowserDataMigrationForTesting`.
const char kBrowserDataMigrationForceSkip[] = "force-skip";
const char kBrowserDataMigrationForceMigration[] = "force-migration";
// The size of disk space that should be kept free after migration. This is
// important since crypotohome conducts an aggressive disk cleanup if free disk
// space becomes less than 768MB. The buffer is rounded up to 1GB.
const int64_t kBuffer = (int64_t)1024 * 1024 * 1024;
// Category names used for logging information about corresponding
// vector<TargetItem> in TargetInfo.
constexpr char kLacrosCategory[] = "lacros";
constexpr char kCommonCategory[] = "common";
// Enable these to fallback to an older version of paths lists.
const base::Feature kLacrosProfileMigrationUseDeprecatedNoCopyPaths{
"LacrosProfileMigrationUseDeprecatedNoCopyPaths",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kLacrosProfileMigrationUseDeprecatedAshDataPaths{
"LacrosProfileMigrationUseDeprecatedAshDataPaths",
base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kLacrosProfileMigrationUseDeprecatedLacrosDataPaths{
"LacrosProfileMigrationUseDeprecatedLacrosDataPaths",
base::FEATURE_DISABLED_BY_DEFAULT};
// Ensures that each path in UDD appears in one of `kNoCopyPaths`,
// `kAshDataPaths` or `kLacrosDataPaths`.
constexpr bool HasNoOverlapBetweenPathsSets() {
for (const char* no_copy_path : kNoCopyPaths) {
for (const char* ash_data_path : kAshDataPaths) {
if (base::StringPiece(no_copy_path) == base::StringPiece(ash_data_path))
return false;
}
}
for (const char* ash_data_path : kAshDataPaths) {
for (const char* lacros_data_path : kLacrosDataPaths) {
if (base::StringPiece(ash_data_path) ==
base::StringPiece(lacros_data_path))
return false;
}
}
for (const char* lacros_data_path : kLacrosDataPaths) {
for (const char* no_copy_path : kNoCopyPaths) {
if (base::StringPiece(lacros_data_path) ==
base::StringPiece(no_copy_path))
return false;
}
}
return true;
}
static_assert(HasNoOverlapBetweenPathsSets(),
"There must be no overlap between kNoCopyPaths, kAshDataPaths "
"and kLacrosDataPaths");
base::span<const char* const> GetNoCopyDataPaths() {
if (base::FeatureList::IsEnabled(
kLacrosProfileMigrationUseDeprecatedNoCopyPaths)) {
return base::make_span(kNoCopyPathsDeprecated);
}
return base::make_span(kNoCopyPaths);
}
base::span<const char* const> GetAshDataPaths() {
if (base::FeatureList::IsEnabled(
kLacrosProfileMigrationUseDeprecatedAshDataPaths)) {
return base::make_span(kAshDataPathsDeprecated);
}
return base::make_span(kAshDataPaths);
}
base::span<const char* const> GetLacrosDataPaths() {
if (base::FeatureList::IsEnabled(
kLacrosProfileMigrationUseDeprecatedLacrosDataPaths)) {
return base::make_span(kLacrosDataPathsDeprecated);
}
return base::make_span(kLacrosDataPaths);
}
} // namespace
CancelFlag::CancelFlag() : cancelled_(false) {}
CancelFlag::~CancelFlag() = default;
BrowserDataMigratorImpl::TargetInfo::TargetInfo()
: ash_data_size(0),
no_copy_data_size(0),
lacros_data_size(0),
common_data_size(0) {}
BrowserDataMigratorImpl::TargetInfo::TargetInfo(TargetInfo&&) = default;
BrowserDataMigratorImpl::TargetInfo::~TargetInfo() = default;
BrowserDataMigratorImpl::TargetItem::TargetItem(base::FilePath path,
int64_t size,
ItemType item_type)
: path(path), size(size), is_directory(item_type == ItemType::kDirectory) {}
bool BrowserDataMigratorImpl::TargetItem::operator==(
const TargetItem& rhs) const {
return this->path == rhs.path && this->size == rhs.size &&
this->is_directory == rhs.is_directory;
}
int64_t BrowserDataMigratorImpl::TargetInfo::TotalCopySize() const {
return lacros_data_size + common_data_size;
}
int64_t BrowserDataMigratorImpl::TargetInfo::TotalDirSize() const {
return no_copy_data_size + ash_data_size + lacros_data_size +
common_data_size;
}
// static
bool BrowserDataMigratorImpl::MaybeRestartToMigrate(
const AccountId& account_id,
const std::string& user_id_hash) {
// TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises, remove this
// log message.
LOG(WARNING) << "MaybeRestartToMigrate() is called.";
// If `MigrationStep` is not `kCheckStep`, `MaybeRestartToMigrate()` has
// already moved on to later steps. Namely either in the middle of migration
// or migration has already run.
MigrationStep step = GetMigrationStep(g_browser_process->local_state());
if (step != MigrationStep::kCheckStep) {
switch (step) {
case MigrationStep::kRestartCalled:
LOG(ERROR) << "RestartToMigrate() was called but Migrate() was not. "
"This indicates that eitehr "
"SessionManagerClient::RequestBrowserDataMigration() "
"failed or ash crashed before reaching Migrate(). Check "
"the previous chrome log and the one before.";
break;
case MigrationStep::kStarted:
LOG(ERROR) << "Migrate() was called but "
"MigrateInternalFinishedUIThread() was not indicating "
"that ash might have crashed during the migration.";
break;
case MigrationStep::kEnded:
default:
// TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises,
// remove
// this log message or reduce to VLOG(1).
LOG(WARNING)
<< "Migration has ended and either completed or failed. step = "
<< static_cast<int>(step);
break;
}
return false;
}
// Check if the switch for testing is present.
const std::string force_migration_switch =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kForceBrowserDataMigrationForTesting);
if (force_migration_switch == kBrowserDataMigrationForceSkip)
return false;
if (force_migration_switch == kBrowserDataMigrationForceMigration) {
LOG(WARNING) << "`kBrowserDataMigrationForceMigration` switch is present.";
return RestartToMigrate(account_id, user_id_hash);
}
const user_manager::User* user =
user_manager::UserManager::Get()->FindUser(account_id);
// Check if user exists i.e. not a guest session.
if (!user)
return false;
// Check if lacros is enabled. If not immediately return.
if (!crosapi::browser_util::IsLacrosEnabledForMigration(user)) {
// TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises, remove
// this log message.
LOG(WARNING)
<< "Lacros is disabled. Call ClearMigrationAttemptCountForUser() so "
"that the migration can be attempted again after once lacros is "
"enabled again.";
// If lacros is not enabled other than reaching the maximum retry count of
// profile migration, clear the retry count and
// `kProfileMigrationCompletedForUserPref`. This will allow users to retry
// profile migration after disabling and re-enabling lacros.
ClearMigrationAttemptCountForUser(g_browser_process->local_state(),
user_id_hash);
crosapi::browser_util::ClearProfileMigrationCompletedForUser(
g_browser_process->local_state(), user_id_hash);
return false;
}
// Currently we turn on profile migration only for Googlers. To test profile
// migration without @google.com account, enable feature
// `kLacrosProfileMigrationForAnyUser` defined in browser_util.
// TODO(crbug.com/1266669): Remove this check once profile migration is
// enabled for all users.
if (!crosapi::browser_util::IsProfileMigrationEnabled(account_id)) {
// TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises, remove
// this log message.
LOG(WARNING) << "Profile migration is disabled.";
return false;
}
// If the user is a new user, then there shouldn't be anything to migrate.
// Also mark the user as migration completed.
if (user_manager::UserManager::Get()->IsCurrentUserNew()) {
crosapi::browser_util::SetProfileMigrationCompletedForUser(
g_browser_process->local_state(), user_id_hash);
// TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises, remove
// this log message.
LOG(WARNING) << "Setting migration as completed since it is a new user.";
return false;
}
int attempts = GetMigrationAttemptCountForUser(
g_browser_process->local_state(), user_id_hash);
// TODO(crbug.com/1178702): Once BrowserDataMigrator stabilises, reduce the
// log level to VLOG(1).
LOG(WARNING) << "Attempt #" << attempts;
if (attempts >= kMaxMigrationAttemptCount) {
// TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises, remove
// this log message.
LOG(WARNING) << "Skipping profile migration since migration attemp count = "
<< attempts << " has exceeded " << kMaxMigrationAttemptCount;
return false;
}
if (crosapi::browser_util::IsDataWipeRequired(user_id_hash)) {
// TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises, remove
// this log message.
LOG(WARNING)
<< "Restarting to run profile migration since data wipe is required.";
// If data wipe is required, no need for a further check to determine if
// lacros data dir exists or not.
return RestartToMigrate(account_id, user_id_hash);
}
if (crosapi::browser_util::IsProfileMigrationCompletedForUser(
g_browser_process->local_state(), user_id_hash)) {
// TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises,
// remove this log message.
LOG(WARNING) << "Profile migration has been completed already.";
return false;
}
return RestartToMigrate(account_id, user_id_hash);
}
// static
bool BrowserDataMigratorImpl::RestartToMigrate(
const AccountId& account_id,
const std::string& user_id_hash) {
SetMigrationStep(g_browser_process->local_state(),
MigrationStep::kRestartCalled);
UpdateMigrationAttemptCountForUser(g_browser_process->local_state(),
user_id_hash);
crosapi::browser_util::ClearProfileMigrationCompletedForUser(
g_browser_process->local_state(), user_id_hash);
g_browser_process->local_state()->CommitPendingWrite();
// TODO(crbug.com/1277848): Once `BrowserDataMigrator` stabilises, remove
// this log message.
LOG(WARNING) << "Making a dbus method call to session_manager";
bool success = SessionManagerClient::Get()->RequestBrowserDataMigration(
cryptohome::CreateAccountIdentifierFromAccountId(account_id));
if (!success)
return false;
chrome::AttemptRestart();
return true;
}
BrowserDataMigratorImpl::BrowserDataMigratorImpl(
const base::FilePath& original_profile_dir,
const std::string& user_id_hash,
const ProgressCallback& progress_callback,
base::OnceClosure completion_callback,
PrefService* local_state)
: original_profile_dir_(original_profile_dir),
user_id_hash_(user_id_hash),
progress_tracker_(
std::make_unique<MigrationProgressTrackerImpl>(progress_callback)),
completion_callback_(std::move(completion_callback)),
cancel_flag_(base::MakeRefCounted<CancelFlag>()),
local_state_(local_state),
final_status_(ResultValue::kSkipped) {
DCHECK(local_state_);
}
BrowserDataMigratorImpl::~BrowserDataMigratorImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void BrowserDataMigratorImpl::Migrate() {
DCHECK(local_state_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(crbug.com/1178702): Once BrowserDataMigrator stabilises, reduce the
// log level to VLOG(1).
LOG(WARNING) << "BrowserDataMigratorImpl::Migrate() is called.";
DCHECK(GetMigrationStep(local_state_) == MigrationStep::kRestartCalled);
SetMigrationStep(local_state_, MigrationStep::kStarted);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&BrowserDataMigratorImpl::MigrateInternal,
original_profile_dir_, std::move(progress_tracker_),
cancel_flag_),
base::BindOnce(&BrowserDataMigratorImpl::MigrateInternalFinishedUIThread,
weak_factory_.GetWeakPtr()));
}
void BrowserDataMigratorImpl::Cancel() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cancel_flag_->Set();
}
// static
void BrowserDataMigratorImpl::RecordStatus(const FinalStatus& final_status,
const TargetInfo* target_info,
const base::ElapsedTimer* timer) {
// Record final status enum.
UMA_HISTOGRAM_ENUMERATION(kFinalStatus, final_status);
if (!target_info)
return;
// Record byte size. Range 0 ~ 10GB in MBs.
UMA_HISTOGRAM_CUSTOM_COUNTS(kCopiedDataSize,
target_info->TotalCopySize() / 1024 / 1024, 1,
10000, 100);
UMA_HISTOGRAM_CUSTOM_COUNTS(
kAshDataSize, target_info->ash_data_size / 1024 / 1024, 1, 10000, 100);
UMA_HISTOGRAM_CUSTOM_COUNTS(kLacrosDataSize,
target_info->lacros_data_size / 1024 / 1024, 1,
10000, 100);
UMA_HISTOGRAM_CUSTOM_COUNTS(kCommonDataSize,
target_info->common_data_size / 1024 / 1024, 1,
10000, 100);
UMA_HISTOGRAM_CUSTOM_COUNTS(kNoCopyDataSize,
target_info->no_copy_data_size / 1024 / 1024, 1,
10000, 100);
if (!timer || final_status != FinalStatus::kSuccess)
return;
// Record elapsed time only for successful cases.
UMA_HISTOGRAM_MEDIUM_TIMES(kTotalTime, timer->Elapsed());
}
// static
// TODO(crbug.com/1178702): Once testing phase is over and lacros becomes the
// only web browser, update the underlying logic of migration from copy to move.
// Note that during testing phase we are copying files and leaving files in
// original location intact. We will allow these two states to diverge.
BrowserDataMigratorImpl::MigrationResult
BrowserDataMigratorImpl::MigrateInternal(
const base::FilePath& original_user_dir,
std::unique_ptr<MigrationProgressTracker> progress_tracker,
scoped_refptr<CancelFlag> cancel_flag) {
ResultValue data_wipe_result = ResultValue::kSkipped;
const base::FilePath tmp_dir = original_user_dir.Append(kTmpDir);
const base::FilePath new_user_dir = original_user_dir.Append(kLacrosDir);
if (base::DirectoryExists(new_user_dir)) {
if (!base::DeletePathRecursively(new_user_dir)) {
PLOG(ERROR) << "Deleting " << new_user_dir.value() << " failed: ";
RecordStatus(FinalStatus::kDataWipeFailed);
return {ResultValue::kFailed, ResultValue::kFailed};
}
data_wipe_result = ResultValue::kSucceeded;
}
// Check if tmp directory already exists and delete if it does.
if (base::PathExists(tmp_dir)) {
LOG(WARNING) << kTmpDir
<< " already exists indicating migration was aborted on the"
"previous attempt.";
if (!base::DeletePathRecursively(tmp_dir)) {
PLOG(ERROR) << "Failed to delete tmp dir";
RecordStatus(FinalStatus::kDeleteTmpDirFailed);
return {data_wipe_result, ResultValue::kFailed};
}
}
TargetInfo target_info = GetTargetInfo(original_user_dir);
progress_tracker->SetTotalSizeToCopy(target_info.TotalCopySize());
base::ElapsedTimer timer;
if (!HasEnoughDiskSpace(target_info, original_user_dir, Mode::kCopy)) {
RecordStatus(FinalStatus::kNotEnoughSpace, &target_info);
return {data_wipe_result, ResultValue::kFailed};
}
// Copy files to `tmp_dir`.
if (!SetupTmpDir(target_info, original_user_dir, tmp_dir, cancel_flag.get(),
progress_tracker.get())) {
if (base::PathExists(tmp_dir)) {
base::DeletePathRecursively(tmp_dir);
}
if (cancel_flag->IsSet()) {
LOG(WARNING) << "Migration was cancelled.";
RecordStatus(FinalStatus::kCancelled, &target_info);
return {data_wipe_result, ResultValue::kCancelled};
}
RecordStatus(FinalStatus::kCopyFailed, &target_info);
return {data_wipe_result, ResultValue::kFailed};
}
// Move `tmp_dir` to `new_user_dir`.
if (!base::Move(tmp_dir, new_user_dir)) {
PLOG(ERROR) << "Move failed";
if (base::PathExists(tmp_dir)) {
base::DeletePathRecursively(tmp_dir);
}
RecordStatus(FinalStatus::kMoveFailed, &target_info);
return {data_wipe_result, ResultValue::kFailed};
}
LOG(WARNING) << "BrowserDataMigratorImpl::Migrate took "
<< timer.Elapsed().InMilliseconds() << " ms and migrated "
<< target_info.TotalCopySize() / (1024 * 1024) << " MB.";
RecordStatus(FinalStatus::kSuccess, &target_info, &timer);
return {data_wipe_result, ResultValue::kSucceeded};
}
void BrowserDataMigratorImpl::MigrateInternalFinishedUIThread(
MigrationResult result) {
DCHECK(local_state_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(GetMigrationStep(local_state_) == MigrationStep::kStarted);
SetMigrationStep(local_state_, MigrationStep::kEnded);
final_status_ = result.data_migration;
if (result.data_wipe != ResultValue::kFailed) {
// kSkipped means that the directory did not exist so record the current
// version as the data version.
crosapi::browser_util::RecordDataVer(local_state_, user_id_hash_,
version_info::GetVersion());
}
if (result.data_migration == ResultValue::kSucceeded) {
crosapi::browser_util::SetProfileMigrationCompletedForUser(local_state_,
user_id_hash_);
ClearMigrationAttemptCountForUser(local_state_, user_id_hash_);
}
// If migration has failed or skipped, we silently relaunch ash and send them
// to their home screen. In that case lacros will be disabled.
local_state_->CommitPendingWrite();
std::move(completion_callback_).Run();
}
BrowserDataMigratorImpl::ResultValue BrowserDataMigratorImpl::GetFinalStatus() {
return final_status_;
}
// static
// Copies `item` to location pointed by `dest`. Returns true on success and
// false on failure.
bool BrowserDataMigratorImpl::CopyTargetItem(
const BrowserDataMigratorImpl::TargetItem& item,
const base::FilePath& dest,
CancelFlag* cancel_flag,
MigrationProgressTracker* progress_tracker) {
if (cancel_flag->IsSet())
return false;
if (item.is_directory) {
if (CopyDirectory(item.path, dest, cancel_flag, progress_tracker))
return true;
} else {
if (base::CopyFile(item.path, dest)) {
progress_tracker->UpdateProgress(item.size);
return true;
}
}
PLOG(ERROR) << "Copy failed for " << item.path;
return false;
}
// static
BrowserDataMigratorImpl::TargetInfo BrowserDataMigratorImpl::GetTargetInfo(
const base::FilePath& original_user_dir) {
TargetInfo target_info;
const base::span<const char* const> no_copy_data_paths = GetNoCopyDataPaths();
const base::span<const char* const> ash_data_paths = GetAshDataPaths();
const base::span<const char* const> lacros_data_paths = GetLacrosDataPaths();
base::FileEnumerator enumerator(original_user_dir, false /* recursive */,
base::FileEnumerator::FILES |
base::FileEnumerator::DIRECTORIES |
base::FileEnumerator::SHOW_SYM_LINKS);
for (base::FilePath entry = enumerator.Next(); !entry.empty();
entry = enumerator.Next()) {
const base::FileEnumerator::FileInfo& info = enumerator.GetInfo();
int64_t size;
TargetItem::ItemType item_type;
if (S_ISREG(info.stat().st_mode)) {
size = info.GetSize();
item_type = TargetItem::ItemType::kFile;
} else if (S_ISDIR(info.stat().st_mode)) {
size =
browser_data_migrator_util::ComputeDirectorySizeWithoutLinks(entry);
item_type = TargetItem::ItemType::kDirectory;
} else {
// Skip if `entry` is not a file or directory such as a symlink.
continue;
}
if (base::Contains(ash_data_paths, entry.BaseName().value())) {
target_info.ash_data_items.emplace_back(
TargetItem{entry, size, item_type});
target_info.ash_data_size += size;
} else if (base::Contains(no_copy_data_paths, entry.BaseName().value())) {
target_info.no_copy_data_items.emplace_back(
TargetItem{entry, size, item_type});
target_info.no_copy_data_size += size;
} else if (base::Contains(lacros_data_paths, entry.BaseName().value())) {
// Items that should be moved to lacros.
target_info.lacros_data_items.emplace_back(
TargetItem{entry, size, item_type});
target_info.lacros_data_size += size;
} else {
// Items that are not explicitly ash, no_copy or lacros are put into
// common category.
target_info.common_data_items.emplace_back(
TargetItem{entry, size, item_type});
target_info.common_data_size += size;
}
}
return target_info;
}
// static
bool BrowserDataMigratorImpl::HasEnoughDiskSpace(
const TargetInfo& target_info,
const base::FilePath& original_user_dir,
Mode mode) {
const int64_t free_disk_space =
base::SysInfo::AmountOfFreeDiskSpace(original_user_dir);
int64_t required_space;
switch (mode) {
case Mode::kMove:
required_space = target_info.common_data_size;
break;
case Mode::kDeleteAndCopy:
required_space =
target_info.TotalCopySize() - target_info.no_copy_data_size;
break;
case Mode::kDeleteAndMove:
required_space =
target_info.common_data_size - target_info.no_copy_data_size;
break;
case Mode::kCopy:
default:
DCHECK_EQ(mode, Mode::kCopy);
required_space = target_info.TotalCopySize();
break;
}
if (free_disk_space < required_space + kBuffer) {
LOG(ERROR) << "Aborting migration. Need " << required_space
<< " bytes but only have " << free_disk_space << " bytes left.";
return false;
}
return true;
}
// static
bool BrowserDataMigratorImpl::CopyDirectory(
const base::FilePath& from_path,
const base::FilePath& to_path,
CancelFlag* cancel_flag,
MigrationProgressTracker* progress_tracker) {
if (cancel_flag->IsSet())
return false;
if (!base::PathExists(to_path) && !base::CreateDirectory(to_path)) {
PLOG(ERROR) << "CreateDirectory() failed for " << to_path.value();
return false;
}
base::FileEnumerator enumerator(from_path, false /* recursive */,
base::FileEnumerator::FILES |
base::FileEnumerator::DIRECTORIES |
base::FileEnumerator::SHOW_SYM_LINKS);
for (base::FilePath entry = enumerator.Next(); !entry.empty();
entry = enumerator.Next()) {
if (cancel_flag->IsSet())
return false;
const base::FileEnumerator::FileInfo& info = enumerator.GetInfo();
// Only copy a file or a dir i.e. skip other types like symlink since
// copying those might introdue a security risk.
if (S_ISREG(info.stat().st_mode)) {
if (!base::CopyFile(entry, to_path.Append(entry.BaseName())))
return false;
progress_tracker->UpdateProgress(info.GetSize());
} else if (S_ISDIR(info.stat().st_mode)) {
if (!CopyDirectory(entry, to_path.Append(entry.BaseName()), cancel_flag,
progress_tracker)) {
return false;
}
}
}
return true;
}
// static
bool BrowserDataMigratorImpl::CopyTargetItems(
const base::FilePath& to_dir,
const std::vector<TargetItem>& items,
CancelFlag* cancel_flag,
int64_t items_size,
base::StringPiece category_name,
MigrationProgressTracker* progress_tracker) {
base::ElapsedTimer timer;
for (const auto& item : items) {
if (cancel_flag->IsSet())
return false;
if (!CopyTargetItem(item, to_dir.Append(item.path.BaseName()), cancel_flag,
progress_tracker)) {
return false;
}
}
base::TimeDelta elapsed_time = timer.Elapsed();
// TODO(crbug.com/1178702): Once BrowserDataMigrator stabilises, reduce the
// log level to VLOG(1).
LOG(WARNING) << "Copied " << items_size / (1024 * 1024) << " MB of "
<< category_name << " data and it took "
<< elapsed_time.InMilliseconds() << " ms.";
if (category_name == kLacrosCategory) {
UMA_HISTOGRAM_MEDIUM_TIMES(kLacrosDataTime, elapsed_time);
} else if (category_name == kCommonCategory) {
UMA_HISTOGRAM_MEDIUM_TIMES(kCommonDataTime, elapsed_time);
} else {
NOTREACHED();
}
return true;
}
// static
bool BrowserDataMigratorImpl::SetupTmpDir(
const TargetInfo& target_info,
const base::FilePath& from_dir,
const base::FilePath& tmp_dir,
CancelFlag* cancel_flag,
MigrationProgressTracker* progress_tracker) {
base::File::Error error;
if (!base::CreateDirectoryAndGetError(tmp_dir.Append(kLacrosProfilePath),
&error)) {
PLOG(ERROR) << "CreateDirectoryFailed " << error;
// Maps to histogram enum `PlatformFileError`.
UMA_HISTOGRAM_ENUMERATION(kCreateDirectoryFail, -error,
-base::File::FILE_ERROR_MAX);
return false;
}
// Copy lacros items.
if (!CopyTargetItems(tmp_dir.Append(kLacrosProfilePath),
target_info.lacros_data_items, cancel_flag,
target_info.lacros_data_size, kLacrosCategory,
progress_tracker))
return false;
// Copy common items.
if (!CopyTargetItems(tmp_dir.Append(kLacrosProfilePath),
target_info.common_data_items, cancel_flag,
target_info.common_data_size, kCommonCategory,
progress_tracker))
return false;
// Create `First Run` sentinel file instead of copying. This avoids copying
// from outside of cryptohome.
if (!base::WriteFile(tmp_dir.Append(chrome::kFirstRunSentinel), ""))
return false;
return true;
}
// static
void BrowserDataMigratorImpl::RegisterLocalStatePrefs(
PrefRegistrySimple* registry) {
registry->RegisterIntegerPref(kMigrationStep,
static_cast<int>(MigrationStep::kCheckStep));
registry->RegisterDictionaryPref(kMigrationAttemptCountPref,
base::DictionaryValue());
}
// static
void BrowserDataMigratorImpl::SetMigrationStep(
PrefService* local_state,
BrowserDataMigratorImpl::MigrationStep step) {
local_state->SetInteger(kMigrationStep, static_cast<int>(step));
}
// static
void BrowserDataMigratorImpl::ClearMigrationStep(PrefService* local_state) {
local_state->ClearPref(kMigrationStep);
}
// static
BrowserDataMigratorImpl::MigrationStep
BrowserDataMigratorImpl::GetMigrationStep(PrefService* local_state) {
return static_cast<MigrationStep>(local_state->GetInteger(kMigrationStep));
}
// static
void BrowserDataMigratorImpl::UpdateMigrationAttemptCountForUser(
PrefService* local_state,
const std::string& user_id_hash) {
int count = GetMigrationAttemptCountForUser(local_state, user_id_hash);
count += 1;
DictionaryPrefUpdate update(local_state, kMigrationAttemptCountPref);
base::DictionaryValue* dict = update.Get();
dict->SetKey(user_id_hash, base::Value(count));
}
// static
int BrowserDataMigratorImpl::GetMigrationAttemptCountForUser(
PrefService* local_state,
const std::string& user_id_hash) {
return local_state->GetDictionary(kMigrationAttemptCountPref)
->FindIntPath(user_id_hash)
.value_or(0);
}
// static
void BrowserDataMigratorImpl::ClearMigrationAttemptCountForUser(
PrefService* local_state,
const std::string& user_id_hash) {
DictionaryPrefUpdate update(local_state, kMigrationAttemptCountPref);
base::DictionaryValue* dict = update.Get();
dict->RemoveKey(user_id_hash);
}
// static
void BrowserDataMigratorImpl::DryRunToCollectUMA(
const base::FilePath& profile_data_dir) {
TargetInfo target_info = GetTargetInfo(profile_data_dir);
base::UmaHistogramCustomCounts(kDryRunNoCopyDataSize,
target_info.no_copy_data_size / 1024 / 1024, 1,
10000, 100);
base::UmaHistogramCustomCounts(kDryRunAshDataSize,
target_info.ash_data_size / 1024 / 1024, 1,
10000, 100);
base::UmaHistogramCustomCounts(kDryRunLacrosDataSize,
target_info.lacros_data_size / 1024 / 1024, 1,
10000, 100);
base::UmaHistogramCustomCounts(kDryRunCommonDataSize,
target_info.common_data_size / 1024 / 1024, 1,
10000, 100);
browser_data_migrator_util::RecordTotalSize(target_info.TotalDirSize());
RecordTargetItemSizes(target_info.no_copy_data_items);
RecordTargetItemSizes(target_info.ash_data_items);
RecordTargetItemSizes(target_info.lacros_data_items);
RecordTargetItemSizes(target_info.common_data_items);
base::UmaHistogramBoolean(
kDryRunCopyMigrationHasEnoughDiskSpace,
HasEnoughDiskSpace(target_info, profile_data_dir, Mode::kCopy));
base::UmaHistogramBoolean(
kDryRunMoveMigrationHasEnoughDiskSpace,
HasEnoughDiskSpace(target_info, profile_data_dir, Mode::kMove));
base::UmaHistogramBoolean(
kDryRunDeleteAndCopyMigrationHasEnoughDiskSpace,
HasEnoughDiskSpace(target_info, profile_data_dir, Mode::kDeleteAndCopy));
base::UmaHistogramBoolean(
kDryRunDeleteAndMoveMigrationHasEnoughDiskSpace,
HasEnoughDiskSpace(target_info, profile_data_dir, Mode::kDeleteAndMove));
}
// staic
void BrowserDataMigratorImpl::RecordTargetItemSizes(
const std::vector<TargetItem>& items) {
for (auto& item : items)
browser_data_migrator_util::RecordUserDataSize(item.path, item.size);
}
} // namespace ash