// 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.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/ash/crosapi/browser_data_back_migrator.h"
#include <errno.h>
#include <memory>
#include <string>
#include <string_view>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/containers/contains.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/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/timer/elapsed_timer.h"
#include "chrome/browser/ash/crosapi/browser_data_back_migrator_metrics.h"
#include "chrome/browser/ash/crosapi/browser_data_migrator_util.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/extensions/extension_keeplist_chromeos.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "chromeos/ash/components/cryptohome/cryptohome_parameters.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/components/standalone_browser/migrator_util.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
namespace ash {
namespace {
// Flag values for `switches::kForceBrowserDataBackwardMigrationForTesting`.
const char kBrowserDataBackwardMigrationForceSkip[] = "force-skip";
const char kBrowserDataBackwardMigrationForceMigration[] = "force-migration";
base::RepeatingClosure* g_back_migrator_attempt_restart_for_testing = nullptr;
// We set a generous recursion depth, that should never be reached, but this
// way we protect against file system loops.
const unsigned int kMaxRecursionDepth = 2000;
} // namespace
ScopedBackMigratorRestartAttemptForTesting::
ScopedBackMigratorRestartAttemptForTesting(
base::RepeatingClosure callback) {
DCHECK(!g_back_migrator_attempt_restart_for_testing);
g_back_migrator_attempt_restart_for_testing =
new base::RepeatingClosure(std::move(callback));
}
ScopedBackMigratorRestartAttemptForTesting::
~ScopedBackMigratorRestartAttemptForTesting() {
DCHECK(g_back_migrator_attempt_restart_for_testing);
delete g_back_migrator_attempt_restart_for_testing;
g_back_migrator_attempt_restart_for_testing = nullptr;
}
BrowserDataBackMigrator::BrowserDataBackMigrator(
const base::FilePath& ash_profile_dir,
const std::string& user_id_hash,
PrefService* local_state)
: ash_profile_dir_(ash_profile_dir),
user_id_hash_(user_id_hash),
local_state_(local_state) {}
BrowserDataBackMigrator::~BrowserDataBackMigrator() = default;
// static
void BrowserDataBackMigrator::AttemptRestart() {
if (g_back_migrator_attempt_restart_for_testing) {
g_back_migrator_attempt_restart_for_testing->Run();
return;
}
chrome::AttemptRestart();
}
void BrowserDataBackMigrator::Migrate(
BackMigrationProgressCallback progress_callback,
BackMigrationFinishedCallback finished_callback) {
LOG(WARNING) << "BrowserDataBackMigrator::Migrate() is called.";
DCHECK(!running_);
DCHECK(IsBackMigrationEnabled(
ash::standalone_browser::migrator_util::PolicyInitState::kBeforeInit));
browser_data_back_migrator_metrics::RecordNumberOfLacrosSecondaryProfiles(
ash_profile_dir_);
// Get the forward migration timestamp, record the delta and then clear the
// timestamp right away. This prevents the scenario in which the time is
// recorded, then backward migration fails and is retried and then the time is
// recorded again.
auto forward_migration_completion_time =
crosapi::browser_util::GetProfileMigrationCompletionTimeForUser(
local_state_, user_id_hash_);
browser_data_back_migrator_metrics::RecordBackwardMigrationTimeDelta(
forward_migration_completion_time);
browser_data_back_migrator_metrics::
RecordBackwardMigrationPrecededByForwardMigration(
forward_migration_completion_time);
crosapi::browser_util::ClearProfileMigrationCompletionTimeForUser(
local_state_, user_id_hash_);
running_ = true;
migration_start_time_ = base::TimeTicks::Now();
const base::FilePath lacros_dir =
ash_profile_dir_.Append(browser_data_migrator_util::kLacrosDir);
progress_callback_ = std::move(progress_callback);
finished_callback_ = std::move(finished_callback);
SetProgress(MigrationStep::kPreMigrationCleanUp);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&BrowserDataBackMigrator::PreMigrationCleanUp,
ash_profile_dir_, lacros_dir),
base::BindOnce(&BrowserDataBackMigrator::OnPreMigrationCleanUp,
weak_factory_.GetWeakPtr()));
}
void BrowserDataBackMigrator::SetProgress(MigrationStep step) {
int current_step = static_cast<int>(step);
int total_steps = static_cast<int>(MigrationStep::kMaxValue);
int percent = (current_step * 100) / total_steps;
progress_callback_.Run(percent);
}
// static
BrowserDataBackMigrator::TaskResult
BrowserDataBackMigrator::PreMigrationCleanUp(
const base::FilePath& ash_profile_dir,
const base::FilePath& lacros_dir) {
LOG(WARNING) << "Running PreMigrationCleanUp()";
base::ElapsedTimer timer;
const base::FilePath tmp_profile_dir =
ash_profile_dir.Append(browser_data_back_migrator::kTmpDir);
if (base::PathExists(tmp_profile_dir)) {
// Delete tmp_profile_dir if any were left from a previous failed back
// migration attempt.
if (!base::DeletePathRecursively(tmp_profile_dir)) {
PLOG(ERROR) << "Deleting " << tmp_profile_dir.value() << " failed: ";
return {TaskStatus::kPreMigrationCleanUpDeleteTmpDirFailed, errno};
}
}
// Delete ash deletable items to free up space.
browser_data_migrator_util::TargetItems ash_deletable_items =
browser_data_migrator_util::GetTargetItems(
ash_profile_dir, browser_data_migrator_util::ItemType::kDeletable);
for (const auto& item : ash_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 not stop
// the migration.
PLOG(ERROR) << "Could not delete " << item.path.value();
}
}
// Delete lacros deletable items to free up space.
browser_data_migrator_util::TargetItems lacros_deletable_items =
browser_data_migrator_util::GetTargetItems(
lacros_dir, browser_data_migrator_util::ItemType::kDeletable);
for (const auto& item : lacros_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 not stop
// the migration.
PLOG(ERROR) << "Could not delete " << item.path.value();
}
}
base::UmaHistogramMediumTimes(
browser_data_back_migrator_metrics::kPreMigrationCleanUpTimeUMA,
timer.Elapsed());
return {TaskStatus::kSucceeded};
}
void BrowserDataBackMigrator::OnPreMigrationCleanUp(
BrowserDataBackMigrator::TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "PreMigrationCleanup() failed.";
InvokeCallback(result);
return;
}
SetProgress(MigrationStep::kMergeSplitItems);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&BrowserDataBackMigrator::MergeSplitItems,
ash_profile_dir_),
base::BindOnce(&BrowserDataBackMigrator::OnMergeSplitItems,
weak_factory_.GetWeakPtr()));
}
// static
BrowserDataBackMigrator::TaskResult BrowserDataBackMigrator::MergeSplitItems(
const base::FilePath& ash_profile_dir) {
LOG(WARNING) << "Running MergeSplitItems()";
base::ElapsedTimer timer;
const base::FilePath tmp_profile_dir =
ash_profile_dir.Append(browser_data_back_migrator::kTmpDir);
if (!base::CreateDirectory(tmp_profile_dir)) {
PLOG(ERROR) << "CreateDirectory() failed for " << tmp_profile_dir.value();
return {TaskStatus::kMergeSplitItemsCreateTmpDirFailed, errno};
}
const base::FilePath lacros_default_profile_dir =
ash_profile_dir.Append(browser_data_migrator_util::kLacrosDir)
.Append(browser_data_migrator_util::kLacrosProfilePath);
// For extensions that exist in both Ash and Lacros, take the Lacros version.
if (!MergeCommonExtensionsDataFiles(
ash_profile_dir, lacros_default_profile_dir, tmp_profile_dir,
browser_data_migrator_util::kExtensionsFilePath)) {
PLOG(ERROR) << "MergeCommonExtensionsDataFiles() failed for extensions";
return {TaskStatus::kMergeSplitItemsCopyExtensionsFailed, errno};
}
// For Storage objects for extensions that exist in both Ash and Lacros, take
// the Lacros version.
if (!MergeCommonExtensionsDataFiles(
ash_profile_dir, lacros_default_profile_dir, tmp_profile_dir,
base::FilePath(browser_data_migrator_util::kStorageFilePath)
.Append(browser_data_migrator_util::kStorageExtFilePath)
.value())) {
PLOG(ERROR)
<< "MergeCommonExtensionsDataFiles() failed for extension storage";
return {TaskStatus::kMergeSplitItemsCopyExtensionStorageFailed, errno};
}
// Merge IndexedDB.
for (const auto& extension_id :
extensions::GetExtensionsAndAppsRunInOSAndStandaloneBrowser()) {
if (!MergeCommonIndexedDB(ash_profile_dir, lacros_default_profile_dir,
extension_id.data())) {
return {TaskStatus::kMergeSplitItemsMergeIndexedDBFailed, errno};
}
}
// During forward migration, LevelDB databases were copied from Ash to Lacros
// verbatim, after which the Ash versions were edited and non-Ash entries were
// removed. As a result Lacros has entries that it does not need and never
// updates, which don't break anything and don't take up a lot of space.
// During backward migration, we take the Lacros version as basis and then
// overwrite some entries with the Ash entries that might have been changed.
// Merge `Local Storage` LevelDB database.
if (!CopyLevelDBBase(
lacros_default_profile_dir.Append(
browser_data_migrator_util::kLocalStorageFilePath),
tmp_profile_dir.Append(
browser_data_migrator_util::kLocalStorageFilePath))) {
LOG(ERROR) << "CopyLevelDBBase() failed for `Local Storage`";
return {TaskStatus::kMergeSplitItemsMergeLocalStorageLevelDBFailed};
}
if (!MergeLevelDB(
ash_profile_dir
.Append(browser_data_migrator_util::kLocalStorageFilePath)
.Append(browser_data_migrator_util::kLocalStorageLeveldbName),
tmp_profile_dir
.Append(browser_data_migrator_util::kLocalStorageFilePath)
.Append(browser_data_migrator_util::kLocalStorageLeveldbName),
browser_data_migrator_util::LevelDBType::kLocalStorage)) {
LOG(ERROR) << "MergeLevelDB() failed for `Local Storage`";
return {TaskStatus::kMergeSplitItemsMergeLocalStorageLevelDBFailed};
}
// Merge `kStateStorePaths` LevelDB databases.
for (const char* path : browser_data_migrator_util::kStateStorePaths) {
if (base::PathExists(lacros_default_profile_dir.Append(path))) {
if (!CopyLevelDBBase(lacros_default_profile_dir.Append(path),
tmp_profile_dir.Append(path))) {
LOG(ERROR) << "CopyLevelDBBase() failed for `" << path << "`";
return {TaskStatus::kMergeSplitItemsMergeStateStoreLevelDBFailed};
}
if (!MergeLevelDB(ash_profile_dir.Append(path),
tmp_profile_dir.Append(path),
browser_data_migrator_util::LevelDBType::kStateStore)) {
LOG(ERROR) << "MergeLevelDB() failed for `" << path << "`";
return {TaskStatus::kMergeSplitItemsMergeStateStoreLevelDBFailed};
}
}
}
// Merge Preferences.
const base::FilePath ash_pref_path =
ash_profile_dir.Append(chrome::kPreferencesFilename);
const base::FilePath lacros_pref_path =
lacros_default_profile_dir.Append(chrome::kPreferencesFilename);
const base::FilePath tmp_pref_path =
tmp_profile_dir.Append(chrome::kPreferencesFilename);
if (!MergePreferences(ash_pref_path, lacros_pref_path, tmp_pref_path)) {
return {TaskStatus::kMergeSplitItemsMergePrefsFailed};
}
// Merge Sync Data.
const base::FilePath ash_sync_data_db_path =
ash_profile_dir.Append(browser_data_migrator_util::kSyncDataFilePath)
.Append(browser_data_migrator_util::kSyncDataLeveldbName);
const base::FilePath lacros_sync_data_db_path =
lacros_default_profile_dir
.Append(browser_data_migrator_util::kSyncDataFilePath)
.Append(browser_data_migrator_util::kSyncDataLeveldbName);
const base::FilePath tmp_sync_data_db_path =
tmp_profile_dir.Append(browser_data_migrator_util::kSyncDataFilePath)
.Append(browser_data_migrator_util::kSyncDataLeveldbName);
if (!MergeSyncDataLevelDB(ash_sync_data_db_path, lacros_sync_data_db_path,
tmp_sync_data_db_path)) {
return {TaskStatus::kMergeSplitItemsMergeSyncDataFailed};
}
base::UmaHistogramMediumTimes(
browser_data_back_migrator_metrics::kMergeSplitItemsTimeUMA,
timer.Elapsed());
return {TaskStatus::kSucceeded};
}
void BrowserDataBackMigrator::OnMergeSplitItems(
BrowserDataBackMigrator::TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "MergeSplitItems() failed.";
InvokeCallback(result);
return;
}
SetProgress(MigrationStep::kDeleteAshItems);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&BrowserDataBackMigrator::DeleteAshItems,
ash_profile_dir_),
base::BindOnce(&BrowserDataBackMigrator::OnDeleteAshItems,
weak_factory_.GetWeakPtr()));
}
// static
BrowserDataBackMigrator::TaskResult BrowserDataBackMigrator::DeleteAshItems(
const base::FilePath& ash_profile_dir) {
LOG(WARNING) << "Running DeleteAshItems()";
base::ElapsedTimer timer;
// For extensions that exist in both Ash and Lacros, take the Lacros version
// and delete the Ash version.
if (!RemoveAshCommonExtensionsDataFiles(
ash_profile_dir, browser_data_migrator_util::kExtensionsFilePath)) {
PLOG(ERROR) << "RemoveAshCommonExtensionsDataFiles() failed for extensions";
return {TaskStatus::kDeleteAshItemsDeleteExtensionsFailed, errno};
}
// `ItemType::kLacros` items should be deleted from Ash before they are
// overwritten by `MoveMergedItemsBackToAsh`. We call
// `base::DeletePathRecursively` because it deletes both files and directories
// and it does not fail if an item does not exist.
browser_data_migrator_util::TargetItems lacros_items =
browser_data_migrator_util::GetTargetItems(
ash_profile_dir, browser_data_migrator_util::ItemType::kLacros);
for (const auto& item : lacros_items.items) {
// Print permissions for debugging purposes to be able to compare with the
// persmissions of the directories created in `MoveMergedItemsBackToAsh`.
int permissions;
if (base::GetPosixFilePermissions(item.path, &permissions)) {
VLOG(5) << "Deleting " << item.path.value() << " with permissions "
<< permissions;
}
if (!base::DeletePathRecursively(item.path)) {
PLOG(ERROR) << "Failed to delete " << item.path.value();
return {TaskStatus::kDeleteAshItemsDeleteLacrosItemFailed, errno};
}
}
base::UmaHistogramMediumTimes(
browser_data_back_migrator_metrics::kDeleteAshItemsTimeUMA,
timer.Elapsed());
return {TaskStatus::kSucceeded};
}
void BrowserDataBackMigrator::OnDeleteAshItems(TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "DeleteAshItems() failed.";
InvokeCallback(result);
return;
}
SetProgress(MigrationStep::kMoveLacrosItemsToAshDir);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&BrowserDataBackMigrator::MoveLacrosItemsToAshDir,
ash_profile_dir_),
base::BindOnce(&BrowserDataBackMigrator::OnMoveLacrosItemsToAshDir,
weak_factory_.GetWeakPtr()));
}
// static
BrowserDataBackMigrator::TaskResult
BrowserDataBackMigrator::MoveLacrosItemsToAshDir(
const base::FilePath& ash_profile_dir) {
LOG(WARNING) << "Running MoveLacrosItemsToAshDir()";
base::ElapsedTimer timer;
const base::FilePath lacros_default_profile_dir =
ash_profile_dir.Append(browser_data_migrator_util::kLacrosDir)
.Append(browser_data_migrator_util::kLacrosProfilePath);
browser_data_migrator_util::TargetItems lacros_items =
browser_data_migrator_util::GetTargetItems(
lacros_default_profile_dir,
browser_data_migrator_util::ItemType::kLacros);
for (const auto& item : lacros_items.items) {
// The corresponding items in Ash are deleted in `DeleteAshItems` before
// they are overwritten by the Lacros items here.
const base::FilePath destination_path =
ash_profile_dir.Append(item.path.BaseName());
if (base::PathExists(destination_path)) {
PLOG(ERROR) << "Path " << destination_path << " already exists.";
return {TaskStatus::kMoveLacrosItemsToAshDirFailed, errno};
}
if (!base::Move(item.path, destination_path)) {
PLOG(ERROR) << "Failed to move item " << item.path.value() << " to "
<< destination_path << ": ";
return {TaskStatus::kMoveLacrosItemsToAshDirFailed, errno};
}
}
base::UmaHistogramMediumTimes(
browser_data_back_migrator_metrics::kMoveLacrosItemsToAshDirTimeUMA,
timer.Elapsed());
return {TaskStatus::kSucceeded};
}
void BrowserDataBackMigrator::OnMoveLacrosItemsToAshDir(
BrowserDataBackMigrator::TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "MoveLacrosItemsToAshDir() failed.";
InvokeCallback(result);
return;
}
SetProgress(MigrationStep::kMoveMergedItemsBackToAsh);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&BrowserDataBackMigrator::MoveMergedItemsBackToAsh,
ash_profile_dir_),
base::BindOnce(&BrowserDataBackMigrator::OnMoveMergedItemsBackToAsh,
weak_factory_.GetWeakPtr()));
}
// static
BrowserDataBackMigrator::TaskResult
BrowserDataBackMigrator::MoveMergedItemsBackToAsh(
const base::FilePath& ash_profile_dir) {
LOG(WARNING) << "Running MoveMergedItemsBackToAsh()";
base::ElapsedTimer timer;
const base::FilePath tmp_profile_dir =
ash_profile_dir.Append(browser_data_back_migrator::kTmpDir);
if (!MoveFilesToAshDirectory(tmp_profile_dir, ash_profile_dir, 1)) {
PLOG(ERROR) << "Failed moving " << tmp_profile_dir.value() << " to "
<< ash_profile_dir.value();
return {TaskStatus::kMoveMergedItemsBackToAshMoveFileFailed, errno};
}
base::UmaHistogramMediumTimes(
browser_data_back_migrator_metrics::kMoveMergedItemsBackToAshTimeUMA,
timer.Elapsed());
return {TaskStatus::kSucceeded};
}
// static
bool BrowserDataBackMigrator::MoveFilesToAshDirectory(
const base::FilePath& source_dir,
const base::FilePath& dest_dir,
unsigned int recursion_depth) {
VLOG(5) << "Calling MoveFilesToAshDirectory from " << source_dir.value()
<< " to " << dest_dir.value() << " at recursion depth "
<< recursion_depth;
if (recursion_depth >= kMaxRecursionDepth) {
LOG(WARNING) << "We have reached maximum recursion depth "
<< kMaxRecursionDepth
<< " and we are stopping MoveFilesToAshDirectory()";
return false;
}
base::FileEnumerator enumerator(
source_dir, false /* recursive */,
base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
for (base::FilePath entry = enumerator.Next(); !entry.empty();
entry = enumerator.Next()) {
const base::FileEnumerator::FileInfo& info = enumerator.GetInfo();
const base::FilePath source_path = source_dir.Append(entry.BaseName());
if (S_ISREG(info.stat().st_mode)) {
if (!base::Move(source_path, dest_dir.Append(entry.BaseName()))) {
PLOG(ERROR) << "Failed moving " << source_path.value() << " to "
<< dest_dir.value();
return false;
}
} else if (S_ISDIR(info.stat().st_mode)) {
const base::FilePath& new_source_dir =
source_dir.Append(entry.BaseName());
const base::FilePath& new_dest_dir = dest_dir.Append(entry.BaseName());
if (!base::PathExists(new_dest_dir)) {
if (!base::CreateDirectory(new_dest_dir)) {
PLOG(ERROR) << "Failed to create " << new_dest_dir.value();
return false;
}
// Print permissions for debugging purposes to be able to compare with
// the persmissions of the directories deleted in `DeleteAshItems`.
int permissions;
if (base::GetPosixFilePermissions(new_dest_dir, &permissions)) {
VLOG(5) << "Created " << new_dest_dir << " with permissions "
<< permissions;
}
}
if (!MoveFilesToAshDirectory(new_source_dir, new_dest_dir,
++recursion_depth)) {
return false;
}
} else {
PLOG(ERROR) << "Received an entry that is neither a file nor a directory "
<< entry;
return false;
}
}
return true;
}
void BrowserDataBackMigrator::OnMoveMergedItemsBackToAsh(
BrowserDataBackMigrator::TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "MoveMergedItemsBackToAsh() failed.";
InvokeCallback(result);
return;
}
SetProgress(MigrationStep::kDeleteLacrosDir);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&BrowserDataBackMigrator::DeleteLacrosDir,
ash_profile_dir_),
base::BindOnce(&BrowserDataBackMigrator::OnDeleteLacrosDir,
weak_factory_.GetWeakPtr()));
}
// static
BrowserDataBackMigrator::TaskResult BrowserDataBackMigrator::DeleteLacrosDir(
const base::FilePath& ash_profile_dir) {
LOG(WARNING) << "Running DeleteLacrosDir()";
base::ElapsedTimer timer;
const base::FilePath lacros_dir =
ash_profile_dir.Append(browser_data_migrator_util::kLacrosDir);
if (base::PathExists(lacros_dir)) {
if (!base::DeletePathRecursively(lacros_dir)) {
PLOG(ERROR) << "Deleting " << lacros_dir.value() << " failed: ";
return {TaskStatus::kDeleteLacrosDirDeleteFailed, errno};
}
}
base::UmaHistogramMediumTimes(
browser_data_back_migrator_metrics::kDeleteLacrosDirTimeUMA,
timer.Elapsed());
return {TaskStatus::kSucceeded};
}
void BrowserDataBackMigrator::OnDeleteLacrosDir(
BrowserDataBackMigrator::TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "DeleteLacrosDir() failed.";
InvokeCallback(result);
return;
}
SetProgress(MigrationStep::kDeleteTmpDir);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&BrowserDataBackMigrator::DeleteTmpDir, ash_profile_dir_),
base::BindOnce(&BrowserDataBackMigrator::OnDeleteTmpDir,
weak_factory_.GetWeakPtr()));
}
// static
BrowserDataBackMigrator::TaskResult BrowserDataBackMigrator::DeleteTmpDir(
const base::FilePath& ash_profile_dir) {
LOG(WARNING) << "Running DeleteTmpDir()";
base::ElapsedTimer timer;
const base::FilePath tmp_user_dir =
ash_profile_dir.Append(browser_data_back_migrator::kTmpDir);
if (base::PathExists(tmp_user_dir)) {
if (!base::DeletePathRecursively(tmp_user_dir)) {
PLOG(ERROR) << "Deleting " << tmp_user_dir.value() << " failed: ";
return {TaskStatus::kDeleteTmpDirDeleteFailed, errno};
}
}
base::UmaHistogramMediumTimes(
browser_data_back_migrator_metrics::kDeleteTmpDirTimeUMA,
timer.Elapsed());
return {TaskStatus::kSucceeded};
}
void BrowserDataBackMigrator::OnDeleteTmpDir(
BrowserDataBackMigrator::TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "DeleteTmpDir() failed.";
InvokeCallback(result);
return;
}
SetProgress(MigrationStep::kMarkMigrationComplete);
// MarkMigrationComplete needs to run on the UI thread first to update prefs.
MarkMigrationComplete();
}
// static
BrowserDataBackMigrator::TaskResult
BrowserDataBackMigrator::MarkMigrationComplete() {
LOG(WARNING) << "Running MarkMigrationComplete()";
crosapi::browser_util::SetProfileDataBackwardMigrationCompletedForUser(
local_state_, user_id_hash_);
local_state_->CommitPendingWrite(
base::BindOnce(&BrowserDataBackMigrator::OnMarkMigrationComplete,
weak_factory_.GetWeakPtr()));
return {TaskStatus::kSucceeded};
}
void BrowserDataBackMigrator::OnMarkMigrationComplete() {
LOG(WARNING) << "Backward migration completed successfully.";
SetProgress(MigrationStep::kDone);
InvokeCallback({TaskStatus::kSucceeded});
}
void BrowserDataBackMigrator::CancelMigration(
BackMigrationCanceledCallback canceled_callback) {
LOG(WARNING) << "BrowserDataBackMigrator::CancelMigration() is called.";
canceled_callback_ = std::move(canceled_callback);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN},
base::BindOnce(&BrowserDataBackMigrator::FailedMigrationCleanUp,
ash_profile_dir_),
base::BindOnce(&BrowserDataBackMigrator::OnFailedMigrationCleanUp,
weak_factory_.GetWeakPtr()));
}
// static
BrowserDataBackMigrator::TaskResult
BrowserDataBackMigrator::FailedMigrationCleanUp(
const base::FilePath& ash_profile_dir) {
LOG(WARNING) << "Running FailedMigrationCleanUp()";
auto delete_lacros_dir_result =
BrowserDataBackMigrator::DeleteLacrosDir(ash_profile_dir);
auto delete_tmp_dir_result =
BrowserDataBackMigrator::DeleteTmpDir(ash_profile_dir);
if (delete_lacros_dir_result.status != TaskStatus::kSucceeded) {
return delete_lacros_dir_result;
}
if (delete_tmp_dir_result.status != TaskStatus::kSucceeded) {
return delete_tmp_dir_result;
}
return {TaskStatus::kSucceeded};
}
void BrowserDataBackMigrator::OnFailedMigrationCleanUp(
BrowserDataBackMigrator::TaskResult result) {
if (result.status != TaskStatus::kSucceeded) {
LOG(ERROR) << "FailedMigrationCleanUp() failed.";
} else {
LOG(WARNING) << "Backward migration cancellation completed successfully";
}
// TODO(b/272017148): Add UMA metrics.
std::move(canceled_callback_).Run();
}
// static
bool BrowserDataBackMigrator::MergeCommonExtensionsDataFiles(
const base::FilePath& ash_profile_dir,
const base::FilePath& lacros_default_profile_dir,
const base::FilePath& tmp_profile_dir,
const std::string& target_dir) {
// For objects that are in both Chromes copy the Lacros version to the
// temporary folder.
const base::FilePath lacros_target_dir =
lacros_default_profile_dir.Append(target_dir);
if (base::PathExists(lacros_target_dir)) {
const base::FilePath tmp_target_dir = tmp_profile_dir.Append(target_dir);
if (!base::CreateDirectory(tmp_target_dir)) {
PLOG(ERROR) << "CreateDirectory() failed for " << tmp_target_dir.value();
return false;
}
for (const auto& extension_id :
extensions::GetExtensionsAndAppsRunInOSAndStandaloneBrowser()) {
base::FilePath lacros_target_path =
lacros_target_dir.Append(extension_id);
if (base::PathExists(lacros_target_path)) {
base::FilePath tmp_target_path = tmp_target_dir.Append(extension_id);
if (!base::CopyDirectory(lacros_target_path, tmp_target_path,
/*recursive=*/true)) {
PLOG(ERROR) << "Failed copying " << lacros_target_path.value()
<< " to " << tmp_target_path.value();
return false;
}
}
}
}
return true;
}
// static
bool BrowserDataBackMigrator::RemoveAshCommonExtensionsDataFiles(
const base::FilePath& ash_profile_dir,
const std::string& target_dir) {
// For objects that are in both Chromes delete the Ash version for those
// objects before it is overwritten by MoveMergedItemsBackToAsh.
const base::FilePath ash_target_dir = ash_profile_dir.Append(target_dir);
if (base::PathExists(ash_target_dir)) {
for (const auto& extension_id :
extensions::GetExtensionsAndAppsRunInOSAndStandaloneBrowser()) {
base::FilePath ash_target_path = ash_target_dir.Append(extension_id);
if (!base::DeletePathRecursively(ash_target_path)) {
PLOG(ERROR) << "Failed deleting " << ash_target_path.value();
return false;
}
}
}
return true;
}
// static
bool BrowserDataBackMigrator::MergeCommonIndexedDB(
const base::FilePath& ash_profile_dir,
const base::FilePath& lacros_default_profile_dir,
const char* extension_id) {
const auto& [ash_blob_path, ash_leveldb_path] =
browser_data_migrator_util::GetIndexedDBPaths(ash_profile_dir,
extension_id);
const auto& [lacros_blob_path, lacros_leveldb_path] =
browser_data_migrator_util::GetIndexedDBPaths(lacros_default_profile_dir,
extension_id);
if (base::PathExists(lacros_blob_path)) {
if (base::PathExists(ash_blob_path)) {
if (!base::DeletePathRecursively(ash_blob_path)) {
PLOG(ERROR) << "Failed deleting " << ash_blob_path.value();
return false;
}
}
if (!base::CreateDirectory(ash_blob_path)) {
PLOG(ERROR) << "Failed creating empty " << ash_blob_path.value();
return false;
}
if (!base::Move(lacros_blob_path, ash_blob_path)) {
PLOG(ERROR) << "Failed migrating " << lacros_blob_path.value() << " to "
<< ash_blob_path.value();
return false;
}
}
if (base::PathExists(lacros_leveldb_path)) {
if (base::PathExists(ash_leveldb_path)) {
if (!base::DeletePathRecursively(ash_leveldb_path)) {
PLOG(ERROR) << "Failed deleting " << ash_leveldb_path.value();
return false;
}
}
if (!base::CreateDirectory(ash_leveldb_path)) {
PLOG(ERROR) << "Failed creating empty " << ash_leveldb_path.value();
return false;
}
if (!base::Move(lacros_leveldb_path, ash_leveldb_path)) {
PLOG(ERROR) << "Failed migrating " << lacros_leveldb_path.value()
<< " to " << ash_leveldb_path.value();
return false;
}
}
return true;
}
// static
bool BrowserDataBackMigrator::MergePreferences(
const base::FilePath& ash_pref_path,
const base::FilePath& lacros_pref_path,
const base::FilePath& tmp_pref_path) {
// Get string contents of the Ash file.
std::string ash_contents;
if (!base::ReadFileToString(ash_pref_path, &ash_contents)) {
PLOG(ERROR) << "Failure while opening Ash Preferences: "
<< ash_pref_path.value();
return false;
}
// Parse the Ash JSON file.
std::optional<base::Value> ash_root = base::JSONReader::Read(ash_contents);
if (!ash_root) {
PLOG(ERROR) << "Failure while parsing Ash's Preferences";
return false;
}
base::Value::Dict* ash_root_dict = ash_root->GetIfDict();
if (!ash_root_dict) {
PLOG(ERROR) << "Failure while parsing Ash's Preferences root node";
return false;
}
// Get string contents of the Lacros file.
std::string lacros_contents;
if (!base::ReadFileToString(lacros_pref_path, &lacros_contents)) {
PLOG(ERROR) << "Failure while opening Lacros Preferences: "
<< lacros_pref_path.value();
return false;
}
// Parse the Lacros JSON file.
std::optional<base::Value> lacros_root =
base::JSONReader::Read(lacros_contents);
if (!lacros_root) {
PLOG(ERROR) << "Failure while parsing Lacros's Preferences";
return false;
}
base::Value::Dict* lacros_root_dict = lacros_root->GetIfDict();
if (!lacros_root_dict) {
PLOG(ERROR) << "Failure while parsing Lacros's Preferences root node";
return false;
}
MergeLacrosPreferences(*ash_root_dict, {}, lacros_root.value());
// Preferences that were split between Ash and Lacros relate to extensions.
// Here we need to take the preferences from Lacros that were removed from
// Ash during forward migration and put them back in Ash.
for (const char* key : browser_data_migrator_util::kSplitPreferencesKeys) {
base::Value* ash_value = ash_root_dict->FindByDottedPath(key);
base::Value* lacros_value = lacros_root_dict->FindByDottedPath(key);
// If there is nothing to copy back from Lacros, skip this preference.
if (!lacros_value)
continue;
// If there is no Ash counterpart for this preference, clone Lacros.
if (!ash_value) {
ash_root_dict->SetByDottedPath(key, lacros_value->Clone());
} else {
if (lacros_value->is_dict() && ash_value->is_dict()) {
for (const auto entry : lacros_value->GetDict()) {
const std::string_view extension_id = entry.first;
if (IsLacrosOnlyExtension(extension_id)) {
ash_value->GetDict().Set(extension_id, entry.second.Clone());
}
}
} else if (lacros_value->is_list() && ash_value->is_list()) {
ash_value->GetList().EraseIf([&](const base::Value& item) {
if (!item.is_string())
return false;
const std::string_view extension_id = item.GetString();
return IsLacrosOnlyExtension(extension_id);
});
for (const auto& entry : lacros_value->GetList()) {
if (!entry.is_string())
continue;
if (IsLacrosOnlyExtension(entry.GetString()))
ash_value->GetList().Append(entry.Clone());
}
}
}
}
// Generate the resulting JSON.
std::string merged_preferences;
if (!base::JSONWriter::Write(*ash_root, &merged_preferences)) {
PLOG(ERROR) << "Failure while generating resulting Preferences JSON";
return false;
}
// Write the resulting JSON to disk.
if (!base::WriteFile(tmp_pref_path, merged_preferences)) {
PLOG(ERROR) << "Failure while writing Preferences JSON to "
<< tmp_pref_path.value();
return false;
}
return true;
}
// static
bool BrowserDataBackMigrator::MergeLacrosPreferences(
base::Value::Dict& ash_root_dict,
const std::vector<std::string>& path,
const base::Value& current_value) {
if (path.size() >= kMaxRecursionDepth) {
LOG(WARNING) << "We have reached maximum recursion depth "
<< kMaxRecursionDepth
<< " and we are stopping MergeLacrosPreferences()";
return false;
}
// If the |path| was split or ash-only, then ignore it.
// For predefined path, each component should not contain '.' so filter
// them out to avoid finding wrong paths.
if (base::ranges::all_of(path, [](const std::string& component) {
return component.find('.') == std::string::npos;
})) {
const std::string dotted_path = base::JoinString(path, ".");
if (base::Contains(browser_data_migrator_util::kSplitPreferencesKeys,
dotted_path) ||
base::Contains(browser_data_migrator_util::kAshOnlyPreferencesKeys,
dotted_path)) {
return true;
}
}
// If current value is not a dictionary, then it is a final pref.
// Merge it into the |ash_root_dict|.
if (!current_value.is_dict()) {
// Guaranteed by the caller, that first value is a dict.
DCHECK(!path.empty());
base::Value::Dict* dict = &ash_root_dict;
for (const auto& key :
base::span<const std::string>(path.begin(), path.size() - 1)) {
base::Value* child = dict->Find(key);
dict = child ? child->GetIfDict() : dict->EnsureDict(key);
if (!dict) {
// There's an non-dict entry at non-last component of the path.
// Fail here. This behavior is to be compatible with SetDottedPath(),
// which is used in the orignal code not to change the detailed
// behavior for urgent fix.
return false;
}
}
dict->Set(path.back(), current_value.Clone());
return true;
}
// Otherwise, traverse all child elements of the current dictionary.
std::vector<std::string> child_path = path;
for (const auto [child_key, child_value] : current_value.GetDict()) {
child_path.push_back(child_key);
if (!MergeLacrosPreferences(ash_root_dict, child_path, child_value)) {
return false;
}
child_path.pop_back();
}
return true;
}
// static
bool BrowserDataBackMigrator::IsLacrosOnlyExtension(
const std::string_view extension_id) {
return !base::Contains(browser_data_migrator_util::kExtensionsAshOnly,
extension_id) &&
!base::Contains(
extensions::GetExtensionsAndAppsRunInOSAndStandaloneBrowser(),
extension_id);
}
// static
bool BrowserDataBackMigrator::CopyLevelDBBase(
const base::FilePath& lacros_leveldb_dir,
const base::FilePath& tmp_leveldb_dir) {
if (!base::CopyDirectory(lacros_leveldb_dir, tmp_leveldb_dir,
true /* recursive */)) {
PLOG(ERROR) << "CopyDirectory() failed from " << lacros_leveldb_dir.value()
<< " to " << tmp_leveldb_dir.value();
return false;
}
return true;
}
// static
bool BrowserDataBackMigrator::MergeLevelDB(
const base::FilePath& ash_db_path,
const base::FilePath& tmp_db_path,
const browser_data_migrator_util::LevelDBType leveldb_type) {
// If the Ash database does not exist we do not need to merge anything. We are
// sure that the Lacros database exists in the temporary directory at this
// step, otherwise the `CopyLevelDBBase` call would have already failed.
if (!base::PathExists(ash_db_path)) {
LOG(WARNING) << "Only Lacros LevelDB exists, not " << ash_db_path.value();
return true;
}
// Both databases exist so we need to merge them.
leveldb_env::Options options;
options.create_if_missing = false;
// Open Ash LevelDB database.
std::unique_ptr<leveldb::DB> ash_db;
leveldb::Status status =
leveldb_env::OpenDB(options, ash_db_path.value(), &ash_db);
if (!status.ok()) {
PLOG(ERROR) << "Failure while opening Ash leveldb: " << ash_db_path;
return false;
}
// Open temporary LevelDB database, which is the copy of Lacros one.
std::unique_ptr<leveldb::DB> tmp_db;
status = leveldb_env::OpenDB(options, tmp_db_path.value(), &tmp_db);
if (!status.ok()) {
PLOG(ERROR) << "Failure while opening Lacros leveldb: " << tmp_db_path;
return false;
}
// Retrieve all extensions' keys, indexed by extension id.
browser_data_migrator_util::ExtensionKeys ash_keys;
status = browser_data_migrator_util::GetExtensionKeys(
ash_db.get(), leveldb_type, &ash_keys);
if (!status.ok()) {
PLOG(ERROR) << "Failure while reading keys from Ash leveldb: "
<< ash_db_path;
return false;
}
// Collect all necessary changes to be written in a batch.
leveldb::WriteBatch write_batch;
for (const auto& [extension_id, keys] : ash_keys) {
if (!IsLacrosOnlyExtension(extension_id)) {
for (const std::string& key : keys) {
std::string value;
status = ash_db->Get(leveldb::ReadOptions(), key, &value);
if (!status.ok()) {
PLOG(ERROR) << "Failure while reading from Ash leveldb: "
<< ash_db_path;
return false;
}
write_batch.Put(key, value);
}
}
}
leveldb::WriteOptions write_options;
write_options.sync = true;
status = tmp_db->Write(write_options, &write_batch);
if (!status.ok()) {
PLOG(ERROR) << "Failure while writing into new leveldb: "
<< tmp_db_path.value();
return false;
}
return true;
}
// static
bool BrowserDataBackMigrator::MergeSyncDataLevelDB(
const base::FilePath& ash_db_path,
const base::FilePath& lacros_db_path,
const base::FilePath& tmp_db_path) {
// Create a directory for the result database.
if (!base::CreateDirectory(tmp_db_path.DirName().DirName()) ||
!base::CreateDirectory(tmp_db_path.DirName())) {
PLOG(ERROR) << "CreateDirectory() for " << tmp_db_path.value()
<< " failed.";
return false;
}
// If only one of the databases exists we do not need to merge anything and
// can just copy the database to the temp directory.
if (base::PathExists(ash_db_path) && !base::PathExists(lacros_db_path)) {
LOG(WARNING) << "Only Ash Sync Data LevelDB exists.";
if (!base::CopyFile(ash_db_path, tmp_db_path)) {
PLOG(ERROR) << "Failure to copy Ash Sync Data LevelDB: "
<< ash_db_path.value() << " to " << tmp_db_path.value();
return false;
}
return true;
}
if (!base::PathExists(ash_db_path) && base::PathExists(lacros_db_path)) {
LOG(WARNING) << "Only Lacros Sync Data LevelDB exists.";
if (!base::CopyFile(lacros_db_path, tmp_db_path)) {
PLOG(ERROR) << "Failure to copy Lacros Sync Data LevelDB: "
<< lacros_db_path.value() << " to " << tmp_db_path.value();
return false;
}
return true;
}
// Both databases exist so we need to merge them.
leveldb_env::Options options;
options.create_if_missing = false;
// Open Ash LevelDB database.
std::unique_ptr<leveldb::DB> ash_db;
leveldb::Status status =
leveldb_env::OpenDB(options, ash_db_path.value(), &ash_db);
if (!status.ok()) {
PLOG(ERROR) << "Failure while opening Ash Sync Data LevelDB: "
<< ash_db_path.value();
return false;
}
// Open Lacros LevelDB database.
std::unique_ptr<leveldb::DB> lacros_db;
status = leveldb_env::OpenDB(options, lacros_db_path.value(), &lacros_db);
if (!status.ok()) {
PLOG(ERROR) << "Failure while opening Lacros Sync Data LevelDB: "
<< lacros_db_path.value();
return false;
}
// Open the result LevelDB database.
std::unique_ptr<leveldb::DB> result_db;
options.create_if_missing = true;
options.error_if_exists = true;
status = leveldb_env::OpenDB(options, tmp_db_path.value(), &result_db);
if (!status.ok()) {
PLOG(ERROR) << "Failure while opening result Sync Data LevelDB: "
<< tmp_db_path;
return false;
}
// Get Ash Sync Data types from the Ash database.
leveldb::WriteBatch ash_write_batch;
{
std::unique_ptr<leveldb::Iterator> it(
ash_db->NewIterator(leveldb::ReadOptions()));
for (it->SeekToFirst(); it->Valid(); it->Next()) {
const std::string key = it->key().ToString();
const std::string value = it->value().ToString();
if (browser_data_migrator_util::IsAshOnlySyncDataType(key))
ash_write_batch.Put(key, value);
}
if (!it->status().ok()) {
PLOG(ERROR) << "Failure while reading from Ash Sync Data LevelDB: "
<< ash_db_path;
return false;
}
}
// Get Lacros Sync Data types from the Lacros database.
leveldb::WriteBatch lacros_write_batch;
{
std::unique_ptr<leveldb::Iterator> it(
lacros_db->NewIterator(leveldb::ReadOptions()));
for (it->SeekToFirst(); it->Valid(); it->Next()) {
const std::string key = it->key().ToString();
const std::string value = it->value().ToString();
if (!browser_data_migrator_util::IsAshOnlySyncDataType(key))
lacros_write_batch.Put(key, value);
}
if (!it->status().ok()) {
PLOG(ERROR) << "Failure while reading from Lacros Sync Data LevelDB: "
<< lacros_db_path;
return false;
}
}
// Merge all the data types into the resulting database.
leveldb::WriteOptions write_options;
write_options.sync = true;
// Write Lacros data first, i.e. prefer Ash data if there are duplicates.
status = result_db->Write(write_options, &lacros_write_batch);
if (!status.ok()) {
PLOG(ERROR)
<< "Failure while writing Lacros Sync Data into result database: "
<< tmp_db_path;
return false;
}
status = result_db->Write(write_options, &ash_write_batch);
if (!status.ok()) {
PLOG(ERROR) << "Failure while writing Ash Sync Data into result database: "
<< tmp_db_path;
return false;
}
return true;
}
// static
bool BrowserDataBackMigrator::IsBackMigrationForceEnabled() {
const std::string force_migration_switch =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kForceBrowserDataBackwardMigration);
return force_migration_switch == kBrowserDataBackwardMigrationForceMigration;
}
// static
bool BrowserDataBackMigrator::IsBackMigrationEnabled(
ash::standalone_browser::migrator_util::PolicyInitState policy_init_state) {
if (IsBackMigrationForceEnabled()) {
VLOG(1) << "Lacros backward migration is force enabled";
return true;
}
// Check if migration should be force skipped.
const std::string force_migration_switch =
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
switches::kForceBrowserDataBackwardMigration);
if (force_migration_switch == kBrowserDataBackwardMigrationForceSkip) {
VLOG(1) << "Lacros backward migration is force skipped";
return false;
}
crosapi::browser_util::LacrosDataBackwardMigrationMode migration_mode =
crosapi::browser_util::LacrosDataBackwardMigrationMode::kNone;
if (policy_init_state ==
ash::standalone_browser::migrator_util::PolicyInitState::kBeforeInit) {
std::optional<crosapi::browser_util::LacrosDataBackwardMigrationMode>
parsed = std::nullopt;
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
crosapi::browser_util::
kLacrosDataBackwardMigrationModePolicySwitch)) {
parsed = crosapi::browser_util::ParseLacrosDataBackwardMigrationMode(
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
crosapi::browser_util::
kLacrosDataBackwardMigrationModePolicySwitch));
}
migration_mode =
parsed.has_value()
? parsed.value()
: crosapi::browser_util::LacrosDataBackwardMigrationMode::kNone;
} else {
DCHECK_EQ(
policy_init_state,
ash::standalone_browser::migrator_util::PolicyInitState::kAfterInit);
migration_mode =
crosapi::browser_util::GetCachedLacrosDataBackwardMigrationMode();
}
// Backward migration can be explicitly enabled by using the
// LacrosDataBackwardMigrationMode policy.
if (migration_mode ==
crosapi::browser_util::LacrosDataBackwardMigrationMode::kKeepAll) {
VLOG(1) << "Lacros backward migration mode is keep_all";
return true;
}
// Modes beside none do not go through backward migration.
// None is the default, fall back to the feature instead.
if (migration_mode !=
crosapi::browser_util::LacrosDataBackwardMigrationMode::kNone) {
VLOG(1) << "Lacros backward migration mode is not none";
return false;
}
bool is_feature_enabled = base::FeatureList::IsEnabled(
ash::features::kLacrosProfileBackwardMigration);
VLOG(1) << "Lacros backward migration feature flag is " << is_feature_enabled;
return is_feature_enabled;
}
// static
bool BrowserDataBackMigrator::ShouldMigrateBack(
const AccountId& account_id,
const std::string& user_id_hash,
ash::standalone_browser::migrator_util::PolicyInitState policy_init_state) {
if (IsBackMigrationForceEnabled()) {
LOG(WARNING) << "Lacros backward migration has been force enabled";
// Skipping other checks, except for lacros folder presence.
} else {
// Check if the backward migration is enabled.
if (!IsBackMigrationEnabled(policy_init_state)) {
VLOG(1) << "Lacros backward migration is disabled, not triggering";
return false;
}
const user_manager::User* user =
user_manager::UserManager::Get()->FindUser(account_id);
if (!user) {
VLOG(1) << "Failed to find user, not triggering backward migration";
return false;
}
// Backward migration should not run for secondary users.
const auto* primary_user =
user_manager::UserManager::Get()->GetPrimaryUser();
// `ShouldMigrateBack()` is called from `MaybeRestartToMigrateBack()`, which
// is called either before or after profile initialization. In the former
// case it is called from `PreProfileInit()` and this is only called for the
// primary profile so we can assume that the user is the primary user if
// `primary_user == nullptr`. If primary_user is not null then we check if
// `user != primary_user`.
if (primary_user && (user != primary_user)) {
VLOG(1) << "Skip backward migration for secondary users.";
return false;
}
if (crosapi::browser_util::IsLacrosEnabledForMigration(user,
policy_init_state)) {
VLOG(1) << "Lacros is enabled, not triggering backward migration";
return false;
}
}
// Forced migration still needs the lacros dir to be present. Otherwise
// we will continuously migrate until the flag is removed.
base::FilePath user_data_dir;
if (!base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir)) {
LOG(ERROR) << "Could not get the original user data dir path. Not "
"triggering backward migration";
return false;
}
const base::FilePath ash_data_dir =
user_data_dir.Append(ProfileHelper::GetUserProfileDir(user_id_hash));
const base::FilePath lacros_dir =
ash_data_dir.Append(browser_data_migrator_util::kLacrosDir);
{
// Temporarily allow blocking since we need to check if we need to migrate
// the data from lacros to ash before the user has a chance to use it.
base::ScopedAllowBlocking allow_blocking;
// Synchronously check if the lacros folder is present.
if (!DirectoryExists(lacros_dir)) {
VLOG(1) << "Lacros folder not found at '" << lacros_dir.value()
<< "', not triggering backward migration";
return false;
}
}
return true;
}
// static
bool BrowserDataBackMigrator::RestartToMigrateBack(
const AccountId& account_id) {
LOG(WARNING) << "Requesting backward migration from session_manager";
bool success =
SessionManagerClient::Get()->BlockingRequestBrowserDataBackwardMigration(
cryptohome::CreateAccountIdentifierFromAccountId(account_id));
if (!success) {
LOG(ERROR) << "SessionManagerClient::"
"BlockingRequestBrowserDataBackwardMigration() failed.";
return false;
}
AttemptRestart();
return true;
}
// static
bool BrowserDataBackMigrator::MaybeRestartToMigrateBack(
const AccountId& account_id,
const std::string& user_id_hash,
ash::standalone_browser::migrator_util::PolicyInitState policy_init_state) {
if (!ShouldMigrateBack(account_id, user_id_hash, policy_init_state)) {
return false;
}
return RestartToMigrateBack(account_id);
}
// static
BrowserDataBackMigratorBase::Result BrowserDataBackMigrator::ToResult(
TaskResult result) {
switch (result.status) {
case TaskStatus::kSucceeded:
return Result::kSucceeded;
case TaskStatus::kPreMigrationCleanUpDeleteTmpDirFailed:
case TaskStatus::kMergeSplitItemsCreateTmpDirFailed:
case TaskStatus::kMergeSplitItemsCopyExtensionsFailed:
case TaskStatus::kMergeSplitItemsCopyExtensionStorageFailed:
case TaskStatus::kMergeSplitItemsCreateDirFailed:
case TaskStatus::kMergeSplitItemsMergeIndexedDBFailed:
case TaskStatus::kMergeSplitItemsMergePrefsFailed:
case TaskStatus::kMergeSplitItemsMergeLocalStorageLevelDBFailed:
case TaskStatus::kMergeSplitItemsMergeStateStoreLevelDBFailed:
case TaskStatus::kMergeSplitItemsMergeSyncDataFailed:
case TaskStatus::kDeleteAshItemsDeleteExtensionsFailed:
case TaskStatus::kDeleteAshItemsDeleteLacrosItemFailed:
case TaskStatus::kDeleteLacrosDirDeleteFailed:
case TaskStatus::kDeleteTmpDirDeleteFailed:
case TaskStatus::kMoveLacrosItemsToAshDirFailed:
case TaskStatus::kMoveMergedItemsBackToAshCopyDirectoryFailed:
case TaskStatus::kMoveMergedItemsBackToAshMoveFileFailed:
return Result::kFailed;
}
}
void BrowserDataBackMigrator::InvokeCallback(TaskResult result) {
browser_data_back_migrator_metrics::RecordFinalStatus(result);
browser_data_back_migrator_metrics::RecordPosixErrnoIfAvailable(result);
browser_data_back_migrator_metrics::RecordMigrationTimeIfSuccessful(
result, migration_start_time_);
std::move(finished_callback_).Run(ToResult(result));
}
} // namespace ash