// Copyright 2022 The Chromium Authors
// 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/move_migrator.h"
#include <errno.h>
#include <memory>
#include <string>
#include "base/containers/contains.h"
#include "base/debug/dump_without_crashing.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/timer/elapsed_timer.h"
#include "base/values.h"
#include "chrome/browser/ash/crosapi/browser_data_migrator.h"
#include "chrome/browser/ash/crosapi/browser_data_migrator_util.h"
#include "chrome/browser/extensions/extension_keeplist_chromeos.h"
#include "chrome/common/chrome_constants.h"
#include "chromeos/ash/components/standalone_browser/migration_progress_tracker.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
namespace ash {
MoveMigrator::MoveMigrator(
const base::FilePath& original_profile_dir,
const std::string& user_id_hash,
std::unique_ptr<standalone_browser::MigrationProgressTracker>
progress_tracker,
scoped_refptr<browser_data_migrator_util::CancelFlag> cancel_flag,
PrefService* local_state,
MigrationFinishedCallback finished_callback)
: original_profile_dir_(original_profile_dir),
user_id_hash_(user_id_hash),
progress_tracker_(std::move(progress_tracker)),
cancel_flag_(cancel_flag),
local_state_(local_state),
finished_callback_(std::move(finished_callback)) {
DCHECK(local_state_);
}
MoveMigrator::~MoveMigrator() = default;
void MoveMigrator::Migrate() {
ResumeStep resume_step = GetResumeStep(local_state_, user_id_hash_);
if (IsResumeStep(resume_step)) {
const int resume_count =
UpdateResumeAttemptCountForUser(local_state_, user_id_hash_);
if (resume_count > 0) {
LOG(WARNING) << "Resuming move migration for the " << resume_count
<< "th time.";
UMA_HISTOGRAM_COUNTS_100(kMoveMigratorResumeCount, resume_count);
}
if (resume_count > kMoveMigrationResumeCountLimit) {
LOG(ERROR) << "The number of resume attempt limit has reached. Marking "
"move migration as completed.";
base::UmaHistogramBoolean(kMoveMigratorMaxResumeReached, true);
base::debug::DumpWithoutCrashing();
SetResumeStep(local_state_, user_id_hash_, ResumeStep::kCompleted);
resume_step = ResumeStep::kCompleted;
}
}
// Start or resume migration.
UMA_HISTOGRAM_ENUMERATION(kMoveMigratorResumeStepUMA, resume_step);
switch (resume_step) {
case ResumeStep::kStart:
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&MoveMigrator::PreMigrationCleanUp,
original_profile_dir_),
base::BindOnce(&MoveMigrator::OnPreMigrationCleanUp,
weak_factory_.GetWeakPtr()));
return;
case ResumeStep::kMoveLacrosItems:
LOG(ERROR) << "Migration did not complete in the previous attempt. "
"Resuming migration from kMoveLacrosItems step.";
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&MoveMigrator::MoveLacrosItemsToNewDir,
original_profile_dir_),
base::BindOnce(&MoveMigrator::OnMoveLacrosItemsToNewDir,
weak_factory_.GetWeakPtr()));
return;
case ResumeStep::kMoveSplitItems:
LOG(ERROR) << "Migration did not complete in the previous attempt. "
"Resuming migration from kMoveSplitItems step.";
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&MoveMigrator::MoveSplitItemsToOriginalDir,
original_profile_dir_),
base::BindOnce(&MoveMigrator::OnMoveSplitItemsToOriginalDir,
weak_factory_.GetWeakPtr()));
return;
case ResumeStep::kMoveTmpDir:
LOG(ERROR) << "Migration did not complete in the previous attempt. "
"Resuming migration from kMoveTmpDir step.";
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&MoveMigrator::MoveTmpDirToLacrosDir,
original_profile_dir_),
base::BindOnce(&MoveMigrator::OnMoveTmpDirToLacrosDir,
weak_factory_.GetWeakPtr()));
return;
case ResumeStep::kCompleted:
LOG(ERROR)
<< "This state indicates that migration was marked as completed by"
"`MoveMigrator` but was not by `BrowserDataMigratorImpl`";
InvokeCallback({TaskStatus::kSucceeded});
return;
}
}
// static
bool MoveMigrator::ResumeRequired(PrefService* local_state,
const std::string& user_id_hash) {
ResumeStep resume_step = GetResumeStep(local_state, user_id_hash);
return IsResumeStep(resume_step);
}
bool MoveMigrator::IsResumeStep(ResumeStep resume_step) {
switch (resume_step) {
case ResumeStep::kStart:
return false;
case ResumeStep::kMoveLacrosItems:
return true;
case ResumeStep::kMoveSplitItems:
return true;
case ResumeStep::kMoveTmpDir:
return true;
case ResumeStep::kCompleted:
return false;
}
}
// static
void MoveMigrator::RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(kMoveMigrationResumeStepPref);
registry->RegisterDictionaryPref(kMoveMigrationResumeCountPref);
}
// static
MoveMigrator::ResumeStep MoveMigrator::GetResumeStep(
PrefService* local_state,
const std::string& user_id_hash) {
return static_cast<ResumeStep>(
local_state->GetDict(kMoveMigrationResumeStepPref)
.FindInt(user_id_hash)
.value_or(0));
}
// static
void MoveMigrator::SetResumeStep(PrefService* local_state,
const std::string& user_id_hash,
const ResumeStep step) {
ScopedDictPrefUpdate update(local_state, kMoveMigrationResumeStepPref);
base::Value::Dict& dict = update.Get();
dict.Set(user_id_hash, static_cast<int>(step));
local_state->CommitPendingWrite();
}
int MoveMigrator::UpdateResumeAttemptCountForUser(
PrefService* local_state,
const std::string& user_id_hash) {
int count = local_state->GetDict(kMoveMigrationResumeCountPref)
.FindIntByDottedPath(user_id_hash)
.value_or(0);
count += 1;
ScopedDictPrefUpdate update(local_state, kMoveMigrationResumeCountPref);
base::Value::Dict& dict = update.Get();
dict.Set(user_id_hash, count);
return count;
}
void MoveMigrator::ClearResumeAttemptCountForUser(
PrefService* local_state,
const std::string& user_id_hash) {
ScopedDictPrefUpdate update(local_state, kMoveMigrationResumeCountPref);
base::Value::Dict& dict = update.Get();
dict.Remove(user_id_hash);
}
// static
void MoveMigrator::ClearResumeStepForUser(PrefService* local_state,
const std::string& user_id_hash) {
ScopedDictPrefUpdate update(local_state, kMoveMigrationResumeStepPref);
base::Value::Dict& dict = update.Get();
dict.Remove(user_id_hash);
}
// static
MoveMigrator::TaskResult MoveMigrator::PreMigrationCleanUp(
const base::FilePath& original_profile_dir) {
LOG(WARNING) << "Running PreMigrationCleanUp()";
base::ElapsedTimer timer;
const base::FilePath new_user_dir =
original_profile_dir.Append(browser_data_migrator_util::kLacrosDir);
if (base::PathExists(new_user_dir)) {
// Delete an existing lacros profile before the migration.
if (!base::DeletePathRecursively(new_user_dir)) {
PLOG(ERROR) << "Deleting " << new_user_dir.value() << " failed: ";
return {TaskStatus::kPreMigrationCleanUpDeleteLacrosDirFailed, errno};
}
}
const base::FilePath tmp_user_dir =
original_profile_dir.Append(browser_data_migrator_util::kMoveTmpDir);
if (base::PathExists(tmp_user_dir)) {
// Delete tmp_user_dir if any were left from a previous failed move
// migration attempt. Note that if resuming move migration from later steps
// such as `MoveLacrosItemsToNewDir()`, this tmp_user_dir will not be
// deleted. This is an intended behaviour because we do not want to delete
// tmp_user_dir once we start deleting items from the Ash PDD.
if (!base::DeletePathRecursively(tmp_user_dir)) {
PLOG(ERROR) << "Deleting " << tmp_user_dir.value() << " failed: ";
return {TaskStatus::kPreMigrationCleanUpDeleteTmpDirFailed, errno};
}
}
const base::FilePath tmp_split_dir =
original_profile_dir.Append(browser_data_migrator_util::kSplitTmpDir);
if (base::PathExists(tmp_split_dir)) {
// Delete tmp_split_dir if any were left from a previous failed move
// migration attempt. Similar considerations to tmp_user_dir apply.
if (!base::DeletePathRecursively(tmp_split_dir)) {
PLOG(ERROR) << "Deleting" << tmp_split_dir.value() << " failed: ";
return {TaskStatus::kPreMigrationCleanUpDeleteTmpSplitDirFailed, errno};
}
}
// Delete deletable items to free up space.
browser_data_migrator_util::TargetItems deletable_items =
browser_data_migrator_util::GetTargetItems(
original_profile_dir,
browser_data_migrator_util::ItemType::kDeletable);
for (const auto& item : deletable_items.items) {
bool result = item.is_directory ? base::DeletePathRecursively(item.path)
: base::DeleteFile(item.path);
if (!result) {
// This is not critical to the migration so log the error but do stop the
// migration.
PLOG(ERROR) << "Could not delete " << item.path.value();
}
}
const int64_t estimated_extra_bytes_created =
browser_data_migrator_util::EstimatedExtraBytesCreated(
original_profile_dir);
// Now check if there is enough disk space for the migration to be carried
// out.
const int64_t extra_bytes_required_to_be_freed =
browser_data_migrator_util::ExtraBytesRequiredToBeFreed(
estimated_extra_bytes_created, original_profile_dir);
UMA_HISTOGRAM_MEDIUM_TIMES(kMoveMigratorPreMigrationCleanUpTimeUMA,
timer.Elapsed());
if (extra_bytes_required_to_be_freed > 0u) {
UMA_HISTOGRAM_CUSTOM_COUNTS(kMoveMigratorExtraSpaceRequiredMB,
extra_bytes_required_to_be_freed / 1024 / 1024,
1, 10000, 100);
LOG(ERROR) << "Not enough disk space available to carry out the migration "
"safely. Need to free up "
<< extra_bytes_required_to_be_freed << " bytes from "
<< original_profile_dir;
return {TaskStatus::kPreMigrationCleanUpNotEnoughSpace, std::nullopt,
extra_bytes_required_to_be_freed};
}
return {TaskStatus::kSucceeded, std::nullopt, std::nullopt,
estimated_extra_bytes_created};
}
void MoveMigrator::OnPreMigrationCleanUp(MoveMigrator::TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "PreMigrationCleanup() failed.";
InvokeCallback(result);
return;
}
estimated_extra_bytes_created_ = *result.estimated_extra_bytes_created;
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&MoveMigrator::SetupLacrosDir, original_profile_dir_,
std::move(progress_tracker_), cancel_flag_),
base::BindOnce(&MoveMigrator::OnSetupLacrosDir,
weak_factory_.GetWeakPtr()));
}
// static
MoveMigrator::TaskResult MoveMigrator::SetupLacrosDir(
const base::FilePath& original_profile_dir,
std::unique_ptr<standalone_browser::MigrationProgressTracker>
progress_tracker,
scoped_refptr<browser_data_migrator_util::CancelFlag> cancel_flag) {
LOG(WARNING) << "Running SetupLacrosDir()";
base::ElapsedTimer timer;
if (cancel_flag->IsSet()) {
LOG(WARNING) << "Migration is cancelled.";
return {TaskStatus::kCancelled};
}
const base::FilePath tmp_user_dir =
original_profile_dir.Append(browser_data_migrator_util::kMoveTmpDir);
const base::FilePath tmp_profile_dir =
tmp_user_dir.Append(browser_data_migrator_util::kLacrosProfilePath);
if (!base::CreateDirectory(tmp_user_dir)) {
PLOG(ERROR) << "CreateDirectory() failed for " << tmp_user_dir.value();
return {TaskStatus::kSetupLacrosDirCreateTmpDirFailed, errno};
}
if (!base::CreateDirectory(tmp_profile_dir)) {
PLOG(ERROR) << "CreateDirectory() failed for " << tmp_profile_dir.value();
return {TaskStatus::kSetupLacrosDirCreateTmpProfileDirFailed, errno};
}
browser_data_migrator_util::TargetItems need_copy_items =
browser_data_migrator_util::GetTargetItems(
original_profile_dir,
browser_data_migrator_util::ItemType::kNeedCopyForMove);
progress_tracker->SetTotalSizeToCopy(need_copy_items.total_size);
base::ElapsedTimer timer_for_copy;
if (!browser_data_migrator_util::CopyTargetItems(
tmp_profile_dir, need_copy_items, cancel_flag.get(),
progress_tracker.get())) {
if (cancel_flag->IsSet()) {
return {TaskStatus::kCancelled};
}
LOG(ERROR) << "CopyTargetItems() failed for need_copy_items.";
return {TaskStatus::kSetupLacrosDirCopyTargetItemsFailed, errno};
}
UMA_HISTOGRAM_MEDIUM_TIMES(kMoveMigratorSetupLacrosDirCopyTargetItemsTimeUMA,
timer_for_copy.Elapsed());
if (!base::WriteFile(tmp_user_dir.Append(chrome::kFirstRunSentinel), "")) {
PLOG(ERROR) << "WriteFile() failed for " << chrome::kFirstRunSentinel;
return {TaskStatus::kSetupLacrosDirWriteFirstRunSentinelFileFailed, errno};
}
return {TaskStatus::kSucceeded};
}
void MoveMigrator::OnSetupLacrosDir(TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "MoveMigrator::SetupLacrosDir() failed.";
if (cancel_flag_->IsSet()) {
UMA_HISTOGRAM_MEDIUM_TIMES(kMoveMigratorCancelledMigrationTimeUMA,
timer_.Elapsed());
}
InvokeCallback(result);
return;
}
DCHECK(estimated_extra_bytes_created_.has_value());
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&MoveMigrator::SetupAshSplitDir, original_profile_dir_,
*estimated_extra_bytes_created_),
base::BindOnce(&MoveMigrator::OnSetupAshSplitDir,
weak_factory_.GetWeakPtr()));
}
// static
MoveMigrator::TaskResult MoveMigrator::SetupAshSplitDir(
const base::FilePath& original_profile_dir,
const int64_t estimated_extra_bytes_created) {
LOG(WARNING) << "Running SetupAshSplitDir()";
const base::FilePath tmp_user_dir =
original_profile_dir.Append(browser_data_migrator_util::kMoveTmpDir);
const base::FilePath tmp_profile_dir =
tmp_user_dir.Append(browser_data_migrator_util::kLacrosProfilePath);
const base::FilePath tmp_split_dir =
original_profile_dir.Append(browser_data_migrator_util::kSplitTmpDir);
if (!base::CreateDirectory(tmp_split_dir)) {
PLOG(ERROR) << "CreateDirectory() failed for " << tmp_split_dir.value();
return {TaskStatus::kSetupAshDirCreateSplitDirFailed, errno};
}
// Copy extensions that have to be in both Ash and Lacros.
TaskResult task_result =
CopyBothChromesSubdirs(original_profile_dir, tmp_split_dir,
browser_data_migrator_util::kExtensionsFilePath,
TaskStatus::kSetupAshDirCopyExtensionsFailed);
if (task_result.status != TaskStatus::kSucceeded)
return task_result;
// Copy Storage objects for extensions that have to be both in Ash and Lacros.
task_result = CopyBothChromesSubdirs(
original_profile_dir, tmp_split_dir,
base::FilePath(browser_data_migrator_util::kStorageFilePath)
.Append(browser_data_migrator_util::kStorageExtFilePath)
.value(),
TaskStatus::kSetupAshDirCopyStorageFailed);
if (task_result.status != TaskStatus::kSucceeded)
return task_result;
// Copy IndexedDB objects for extensions that have to be both in
// Ash and Lacros.
const base::FilePath original_indexed_db_dir = original_profile_dir.Append(
browser_data_migrator_util::kIndexedDBFilePath);
if (base::PathExists(original_indexed_db_dir)) {
const base::FilePath split_indexed_db_dir =
tmp_split_dir.Append(browser_data_migrator_util::kIndexedDBFilePath);
if (!base::CreateDirectory(split_indexed_db_dir)) {
PLOG(ERROR) << "CreateDirectory() failed for "
<< split_indexed_db_dir.value();
return {TaskStatus::kSetupAshDirCreateDirFailed, errno};
}
for (const auto& extension_id :
extensions::GetExtensionsAndAppsRunInOSAndStandaloneBrowser()) {
if (!browser_data_migrator_util::MigrateAshIndexedDB(
original_profile_dir, split_indexed_db_dir, extension_id.data(),
/*copy=*/true)) {
return {TaskStatus::kSetupAshDirCopyIndexedDBFailed, errno};
}
}
}
// Create Ash's version of `Local Storage`, holding *only* the keys
// associated to the extensions that have to stay in Ash.
if (base::PathExists(
original_profile_dir
.Append(browser_data_migrator_util::kLocalStorageFilePath)
.Append(browser_data_migrator_util::kLocalStorageLeveldbName))) {
if (!browser_data_migrator_util::MigrateLevelDB(
original_profile_dir
.Append(browser_data_migrator_util::kLocalStorageFilePath)
.Append(browser_data_migrator_util::kLocalStorageLeveldbName),
tmp_split_dir
.Append(browser_data_migrator_util::kLocalStorageFilePath)
.Append(browser_data_migrator_util::kLocalStorageLeveldbName),
browser_data_migrator_util::LevelDBType::kLocalStorage)) {
LOG(ERROR) << "MigrateLevelDB() failed for `Local Storage`";
return {TaskStatus::kSetupAshDirMigrateLevelDBForLocalStateFailed};
}
}
// Create Ash's version of all the state stores (Extension State, etc.).
for (const char* path : browser_data_migrator_util::kStateStorePaths) {
if (base::PathExists(original_profile_dir.Append(path))) {
if (!browser_data_migrator_util::MigrateLevelDB(
original_profile_dir.Append(path), tmp_split_dir.Append(path),
browser_data_migrator_util::LevelDBType::kStateStore)) {
LOG(ERROR) << "MigrateLevelDB() failed for `" << path << "`";
return {TaskStatus::kSetupAshDirMigrateLevelDBForStateFailed};
}
}
}
// Split Preferences.
if (!browser_data_migrator_util::MigratePreferences(
original_profile_dir.Append(chrome::kPreferencesFilename),
tmp_split_dir.Append(chrome::kPreferencesFilename),
tmp_profile_dir.Append(chrome::kPreferencesFilename))) {
LOG(ERROR) << "MigratePreferences() failed.";
return {TaskStatus::kSetupAshDirMigratePreferencesFailed};
}
// Split Sync Data.
if (base::PathExists(
original_profile_dir
.Append(browser_data_migrator_util::kSyncDataFilePath)
.Append(browser_data_migrator_util::kSyncDataLeveldbName))) {
if (!browser_data_migrator_util::MigrateSyncDataLevelDB(
original_profile_dir
.Append(browser_data_migrator_util::kSyncDataFilePath)
.Append(browser_data_migrator_util::kSyncDataLeveldbName),
tmp_split_dir.Append(browser_data_migrator_util::kSyncDataFilePath)
.Append(browser_data_migrator_util::kSyncDataLeveldbName),
tmp_profile_dir
.Append(browser_data_migrator_util::kSyncDataFilePath)
.Append(browser_data_migrator_util::kSyncDataLeveldbName))) {
LOG(ERROR) << "MigrateSyncDataLevelDB() failed";
return {TaskStatus::kSetupAshDirMigrateSyncDataLevelDBFailed};
}
}
// Collect UMAs on sizes of artifacts created by migration.
const int64_t tmp_profile_dir_size =
base::ComputeDirectorySize(tmp_profile_dir);
const int64_t tmp_split_dir_size = base::ComputeDirectorySize(tmp_split_dir);
const int64_t extra_bytes_created = tmp_profile_dir_size + tmp_split_dir_size;
const int64_t extra_bytes_over_estimate =
extra_bytes_created - estimated_extra_bytes_created;
base::UmaHistogramCustomCounts(kMoveMigratorTmpProfileDirSize,
tmp_profile_dir_size / 1024 / 1024, 1, 10000,
100);
base::UmaHistogramCustomCounts(kMoveMigratorTmpSplitDirSize,
tmp_split_dir_size / 1024 / 1024, 1, 10000,
100);
base::UmaHistogramCustomCounts(kMoveMigratorExtraDiskSpaceOccupied,
extra_bytes_created / 1024 / 1024, 1, 10000,
100);
base::UmaHistogramCustomCounts(kMoveMigratorExtraDiskSpaceOccupiedDiffWithEst,
extra_bytes_over_estimate / 1024 / 1024,
-10000, 10000, 100);
return {TaskStatus::kSucceeded};
}
void MoveMigrator::OnSetupAshSplitDir(TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "MoveMigrator::SetupAshSplitDir() failed.";
InvokeCallback(result);
return;
}
// Once `MoveLacrosItemsToNewDir()` is started, it should be completed.
// Otherwise the profile in ash directory becomes fragmented. We store the
// resume step as `kMoveLacrosItems` in Local State so that if the migration
// is interrupted during `MoveLacrosItemsToNewDir()` then the migrator can
// resume the migration from that point.
SetResumeStep(local_state_, user_id_hash_, ResumeStep::kMoveLacrosItems);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&MoveMigrator::MoveLacrosItemsToNewDir,
original_profile_dir_),
base::BindOnce(&MoveMigrator::OnMoveLacrosItemsToNewDir,
weak_factory_.GetWeakPtr()));
}
// static
MoveMigrator::TaskResult MoveMigrator::MoveLacrosItemsToNewDir(
const base::FilePath& original_profile_dir) {
LOG(WARNING) << "Running MoveLacrosItemsToNewDir()";
base::ElapsedTimer timer;
browser_data_migrator_util::TargetItems lacros_items =
browser_data_migrator_util::GetTargetItems(
original_profile_dir, browser_data_migrator_util::ItemType::kLacros);
for (const auto& item : lacros_items.items) {
if (item.is_directory && !base::PathIsWritable(item.path)) {
PLOG(ERROR) << "The current process does not have write permission to "
"the directory "
<< item.path.value();
return {TaskStatus::kMoveLacrosItemsToNewDirNoWritePerm, errno};
}
}
const base::FilePath tmp_profile_dir =
original_profile_dir.Append(browser_data_migrator_util::kMoveTmpDir)
.Append(browser_data_migrator_util::kLacrosProfilePath);
// Nigori file needs special handling, because it's not stored directly under
// |original_profile_dir|.
const base::FilePath original_nigori_path =
original_profile_dir.Append(browser_data_migrator_util::kSyncDataFilePath)
.Append(browser_data_migrator_util::kSyncDataNigoriFileName);
if (base::PathExists(original_nigori_path)) {
// In theory, `Sync Data` directory should be already created by
// SplitSyncDataLevelDB() as long as DB exists. It still needs to be created
// manually here, to handle the case when DB doesn't exist, but Nigori file
// does.
if (!base::CreateDirectory(tmp_profile_dir.Append(
browser_data_migrator_util::kSyncDataFilePath))) {
PLOG(ERROR) << "Failure while creating "
<< tmp_profile_dir.Append(
browser_data_migrator_util::kSyncDataFilePath)
<< " directory.";
return {TaskStatus::kMoveLacrosItemsCreateDirFailed, errno};
}
const base::FilePath target_nigori_path =
tmp_profile_dir.Append(browser_data_migrator_util::kSyncDataFilePath)
.Append(browser_data_migrator_util::kSyncDataNigoriFileName);
if (!base::Move(original_nigori_path, target_nigori_path)) {
PLOG(ERROR) << "Failed to move item " << original_nigori_path.value()
<< " to " << target_nigori_path << ": ";
return {TaskStatus::kMoveLacrosItemsToNewDirMoveFailed, errno};
}
}
for (const auto& item : lacros_items.items) {
if (!base::Move(item.path, tmp_profile_dir.Append(item.path.BaseName()))) {
PLOG(ERROR) << "Failed to move item " << item.path.value() << " to "
<< tmp_profile_dir.Append(item.path.BaseName()) << ": ";
return {TaskStatus::kMoveLacrosItemsToNewDirMoveFailed, errno};
}
}
UMA_HISTOGRAM_MEDIUM_TIMES(kMoveMigratorMoveLacrosItemsTimeUMA,
timer.Elapsed());
return {TaskStatus::kSucceeded};
}
void MoveMigrator::OnMoveLacrosItemsToNewDir(TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "Moving Lacros items to temporary directory failed.";
InvokeCallback(result);
return;
}
SetResumeStep(local_state_, user_id_hash_, ResumeStep::kMoveSplitItems);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&MoveMigrator::MoveSplitItemsToOriginalDir,
original_profile_dir_),
base::BindOnce(&MoveMigrator::OnMoveSplitItemsToOriginalDir,
weak_factory_.GetWeakPtr()));
}
// static
MoveMigrator::TaskResult MoveMigrator::MoveSplitItemsToOriginalDir(
const base::FilePath& original_profile_dir) {
LOG(WARNING) << "Running MoveSplitItemsToOriginalDir()";
const base::FilePath tmp_split_dir =
original_profile_dir.Append(browser_data_migrator_util::kSplitTmpDir);
const base::FilePath tmp_profile_dir =
original_profile_dir.Append(browser_data_migrator_util::kMoveTmpDir)
.Append(browser_data_migrator_util::kLacrosProfilePath);
// Move everything inside tmp_split_dir to Ash's profile directory.
base::FileEnumerator e(
tmp_split_dir, false,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
for (base::FilePath path = e.Next(); !path.empty(); path = e.Next()) {
base::FilePath ash_path = original_profile_dir.Append(path.BaseName());
if (base::DirectoryExists(ash_path) && !DeletePathRecursively(ash_path)) {
PLOG(ERROR) << "Failed deleting " << ash_path.value();
return {TaskStatus::kMoveSplitItemsToOriginalDirMoveSplitItemsFailed,
errno};
}
if (!base::Move(path, ash_path)) {
PLOG(ERROR) << "Failed moving " << path.value() << " to "
<< ash_path.value();
return {TaskStatus::kMoveSplitItemsToOriginalDirMoveSplitItemsFailed,
errno};
}
}
// Delete tmp_split_dir.
if (!base::DeleteFile(tmp_split_dir)) {
PLOG(ERROR) << "Failed removing " << tmp_split_dir.value();
}
// Move extensions in the keeplist back to Ash's profile directory.
TaskResult task_result = MoveAshSubdirs(
tmp_profile_dir, original_profile_dir,
browser_data_migrator_util::kExtensionsFilePath,
TaskStatus::kMoveSplitItemsToOriginalDirMoveExtensionsFailed);
if (task_result.status != TaskStatus::kSucceeded)
return task_result;
// Move Storage objects related to extensions in the keeplist back to Ash's
// profile directory.
task_result = MoveAshSubdirs(
tmp_profile_dir, original_profile_dir,
base::FilePath(browser_data_migrator_util::kStorageFilePath)
.Append(browser_data_migrator_util::kStorageExtFilePath)
.value(),
TaskStatus::kMoveSplitItemsToOriginalDirMoveStorageFailed);
if (task_result.status != TaskStatus::kSucceeded)
return task_result;
// Move IndexedDB objects related to extensions in the keeplist back to Ash's
// profile directory.
const base::FilePath lacros_indexed_db_dir =
tmp_profile_dir.Append(browser_data_migrator_util::kIndexedDBFilePath);
if (base::PathExists(lacros_indexed_db_dir)) {
const base::FilePath ash_indexed_db_dir = original_profile_dir.Append(
browser_data_migrator_util::kIndexedDBFilePath);
if (!base::CreateDirectory(ash_indexed_db_dir)) {
PLOG(ERROR) << "CreateDirectory() failed for "
<< ash_indexed_db_dir.value();
return {TaskStatus::kMoveSplitItemsToOriginalDirCreateDirFailed, errno};
}
for (const char* extension_id :
browser_data_migrator_util::kExtensionsAshOnly) {
if (!browser_data_migrator_util::MigrateAshIndexedDB(
tmp_profile_dir, ash_indexed_db_dir, extension_id,
/*copy=*/false)) {
return {TaskStatus::kMoveSplitItemsToOriginalDirMoveIndexedDBFailed,
errno};
}
}
}
return {TaskStatus::kSucceeded};
}
void MoveMigrator::OnMoveSplitItemsToOriginalDir(TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "Moving split objects has failed.";
InvokeCallback(result);
return;
}
SetResumeStep(local_state_, user_id_hash_, ResumeStep::kMoveTmpDir);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&MoveMigrator::MoveTmpDirToLacrosDir,
original_profile_dir_),
base::BindOnce(&MoveMigrator::OnMoveTmpDirToLacrosDir,
weak_factory_.GetWeakPtr()));
}
// static
MoveMigrator::TaskResult MoveMigrator::MoveTmpDirToLacrosDir(
const base::FilePath& original_profile_dir) {
LOG(WARNING) << "Running MoveTmpDirToLacrosDir()";
// Move the newly created lacros user data dir into the final location where
// lacros chrome can access.
if (!base::Move(
original_profile_dir.Append(browser_data_migrator_util::kMoveTmpDir),
original_profile_dir.Append(
browser_data_migrator_util::kLacrosDir))) {
PLOG(ERROR) << "Failed moving "
<< original_profile_dir
.Append(browser_data_migrator_util::kMoveTmpDir)
.value()
<< " to "
<< original_profile_dir
.Append(browser_data_migrator_util::kLacrosDir)
.value();
return {TaskStatus::kMoveTmpDirToLacrosDirMoveFailed, errno};
}
return {TaskStatus::kSucceeded};
}
void MoveMigrator::OnMoveTmpDirToLacrosDir(TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "Moving tmp dir to lacros dir failed.";
InvokeCallback(result);
return;
}
UMA_HISTOGRAM_MEDIUM_TIMES(kMoveMigratorSuccessfulMigrationTimeUMA,
timer_.Elapsed());
SetResumeStep(local_state_, user_id_hash_, ResumeStep::kCompleted);
LOG(WARNING) << "Move migration completed successfully.";
InvokeCallback(result);
}
// static
MoveMigrator::TaskResult MoveMigrator::CopyBothChromesSubdirs(
const base::FilePath& original_profile_dir,
const base::FilePath& tmp_split_dir,
const std::string& target_dir,
TaskStatus copy_fail_status) {
const base::FilePath original_target_dir =
original_profile_dir.Append(target_dir);
if (base::PathExists(original_target_dir)) {
const base::FilePath split_target_dir = tmp_split_dir.Append(target_dir);
if (!base::CreateDirectory(split_target_dir)) {
PLOG(ERROR) << "CreateDirectory() failed for "
<< split_target_dir.value();
return {TaskStatus::kSetupAshDirCreateDirFailed, errno};
}
// Copy objects that belong to both Ash and Lacros.
for (const auto& extension_id :
extensions::GetExtensionsAndAppsRunInOSAndStandaloneBrowser()) {
base::FilePath original_target_path =
original_target_dir.Append(extension_id);
if (base::PathExists(original_target_path)) {
base::FilePath split_target_path =
split_target_dir.Append(extension_id);
if (!base::CopyDirectory(original_target_path, split_target_path,
/*recursive=*/true)) {
PLOG(ERROR) << "Failed copying " << original_target_path.value()
<< " to " << split_target_path.value();
return {copy_fail_status, errno};
}
}
}
}
return {TaskStatus::kSucceeded};
}
// static
MoveMigrator::TaskResult MoveMigrator::MoveAshSubdirs(
const base::FilePath& tmp_profile_dir,
const base::FilePath& original_profile_dir,
const std::string& target_dir,
TaskStatus move_fail_status) {
const base::FilePath lacros_target_dir = tmp_profile_dir.Append(target_dir);
if (base::PathExists(lacros_target_dir)) {
const base::FilePath ash_target_dir =
original_profile_dir.Append(target_dir);
if (!base::CreateDirectory(ash_target_dir)) {
PLOG(ERROR) << "CreateDirectory() failed for " << ash_target_dir.value();
return {TaskStatus::kMoveSplitItemsToOriginalDirCreateDirFailed, errno};
}
// Move Ash-only objects.
for (const char* extension_id :
browser_data_migrator_util::kExtensionsAshOnly) {
base::FilePath lacros_path = lacros_target_dir.Append(extension_id);
if (base::PathExists(lacros_path)) {
base::FilePath ash_path = ash_target_dir.Append(extension_id);
if (!base::Move(lacros_path, ash_path)) {
PLOG(ERROR) << "Failed moving " << lacros_path.value() << " to "
<< ash_path.value();
return {move_fail_status, errno};
}
}
}
}
return {TaskStatus::kSucceeded};
}
void MoveMigrator::InvokeCallback(TaskResult result) {
UMA_HISTOGRAM_ENUMERATION(kMoveMigratorTaskStatusUMA, result.status);
if (result.status != TaskStatus::kSucceeded &&
result.posix_errno.has_value()) {
RecordPosixErrnoUMA(result.status, result.posix_errno.value());
}
std::move(finished_callback_)
.Run(ToBrowserDataMigratorMigrationResult(result));
}
BrowserDataMigratorImpl::MigrationResult
MoveMigrator::ToBrowserDataMigratorMigrationResult(TaskResult result) {
switch (result.status) {
case TaskStatus::kSucceeded:
return {BrowserDataMigratorImpl::DataWipeResult::kSucceeded,
{BrowserDataMigratorImpl::ResultKind::kSucceeded}};
case TaskStatus::kCancelled:
return {BrowserDataMigratorImpl::DataWipeResult::kSucceeded,
{BrowserDataMigratorImpl::ResultKind::kCancelled}};
case TaskStatus::kPreMigrationCleanUpDeleteLacrosDirFailed:
case TaskStatus::kPreMigrationCleanUpDeleteTmpDirFailed:
case TaskStatus::kPreMigrationCleanUpDeleteTmpSplitDirFailed:
return {BrowserDataMigratorImpl::DataWipeResult::kFailed,
{BrowserDataMigratorImpl::ResultKind::kFailed}};
case TaskStatus::kPreMigrationCleanUpNotEnoughSpace:
return {BrowserDataMigratorImpl::DataWipeResult::kSucceeded,
{BrowserDataMigratorImpl::ResultKind::kFailed,
result.extra_bytes_required_to_be_freed.value()}};
case TaskStatus::kSetupLacrosDirCreateTmpDirFailed:
case TaskStatus::kSetupLacrosDirCreateTmpProfileDirFailed:
case TaskStatus::kSetupLacrosDirCopyTargetItemsFailed:
case TaskStatus::kSetupLacrosDirWriteFirstRunSentinelFileFailed:
case TaskStatus::kSetupAshDirCreateSplitDirFailed:
case TaskStatus::kSetupAshDirMigrateLevelDBForLocalStateFailed:
case TaskStatus::kSetupAshDirMigrateLevelDBForStateFailed:
case TaskStatus::kSetupAshDirMigratePreferencesFailed:
case TaskStatus::kMoveLacrosItemsToNewDirNoWritePerm:
case TaskStatus::kMoveLacrosItemsToNewDirMoveFailed:
case TaskStatus::kMoveSplitItemsToOriginalDirMoveSplitItemsFailed:
case TaskStatus::kMoveSplitItemsToOriginalDirCreateDirFailed:
case TaskStatus::kMoveSplitItemsToOriginalDirMoveExtensionsFailed:
case TaskStatus::kMoveSplitItemsToOriginalDirMoveIndexedDBFailed:
case TaskStatus::kMoveTmpDirToLacrosDirMoveFailed:
case TaskStatus::kSetupAshDirCreateDirFailed:
case TaskStatus::kSetupAshDirCopyExtensionsFailed:
case TaskStatus::kSetupAshDirCopyIndexedDBFailed:
case TaskStatus::kSetupAshDirMigrateSyncDataLevelDBFailed:
case TaskStatus::kSetupAshDirCopyStorageFailed:
case TaskStatus::kMoveSplitItemsToOriginalDirMoveStorageFailed:
case TaskStatus::kMoveLacrosItemsCreateDirFailed:
return {BrowserDataMigratorImpl::DataWipeResult::kSucceeded,
{BrowserDataMigratorImpl::ResultKind::kFailed}};
}
}
// static
void MoveMigrator::RecordPosixErrnoUMA(TaskStatus task_status,
const int posix_errno) {
if (posix_errno == 0)
return;
std::string uma_name =
kMoveMigratorPosixErrnoUMA + TaskStatusToString(task_status);
base::UmaHistogramSparse(uma_name, posix_errno);
}
// static
std::string MoveMigrator::TaskStatusToString(TaskStatus task_status) {
switch (task_status) {
#define MAPPING(name) \
case TaskStatus::k##name: \
return #name
MAPPING(Succeeded);
MAPPING(Cancelled);
MAPPING(PreMigrationCleanUpDeleteLacrosDirFailed);
MAPPING(PreMigrationCleanUpDeleteTmpDirFailed);
MAPPING(PreMigrationCleanUpDeleteTmpSplitDirFailed);
MAPPING(PreMigrationCleanUpNotEnoughSpace);
MAPPING(SetupLacrosDirCreateTmpDirFailed);
MAPPING(SetupLacrosDirCreateTmpProfileDirFailed);
MAPPING(SetupLacrosDirCopyTargetItemsFailed);
MAPPING(SetupLacrosDirWriteFirstRunSentinelFileFailed);
MAPPING(SetupAshDirCreateSplitDirFailed);
MAPPING(SetupAshDirMigrateLevelDBForLocalStateFailed);
MAPPING(SetupAshDirMigrateLevelDBForStateFailed);
MAPPING(SetupAshDirMigratePreferencesFailed);
MAPPING(MoveLacrosItemsToNewDirNoWritePerm);
MAPPING(MoveLacrosItemsToNewDirMoveFailed);
MAPPING(MoveSplitItemsToOriginalDirMoveSplitItemsFailed);
MAPPING(MoveSplitItemsToOriginalDirCreateDirFailed);
MAPPING(MoveSplitItemsToOriginalDirMoveExtensionsFailed);
MAPPING(MoveSplitItemsToOriginalDirMoveIndexedDBFailed);
MAPPING(MoveTmpDirToLacrosDirMoveFailed);
MAPPING(SetupAshDirCreateDirFailed);
MAPPING(SetupAshDirCopyExtensionsFailed);
MAPPING(SetupAshDirCopyIndexedDBFailed);
MAPPING(SetupAshDirMigrateSyncDataLevelDBFailed);
MAPPING(SetupAshDirCopyStorageFailed);
MAPPING(MoveSplitItemsToOriginalDirMoveStorageFailed);
MAPPING(MoveLacrosItemsCreateDirFailed);
#undef MAPPING
}
}
} // namespace ash