| // 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/browser_data_back_migrator.h" |
| |
| #include <errno.h> |
| #include <memory> |
| #include <string> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_switches.h" |
| #include "base/callback.h" |
| #include "base/command_line.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.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/path_service.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "chrome/browser/ash/profiles/profile_helper.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 "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"; |
| |
| // 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 |
| |
| 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; |
| |
| void BrowserDataBackMigrator::Migrate( |
| BackMigrationProgressCallback progress_callback, |
| BackMigrationFinishedCallback finished_callback) { |
| LOG(WARNING) << "BrowserDataBackMigrator::Migrate() is called."; |
| |
| DCHECK(!running_); |
| DCHECK(IsBackMigrationEnabled( |
| crosapi::browser_util::PolicyInitState::kBeforeInit)); |
| |
| running_ = true; |
| |
| const base::FilePath lacros_profile_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_profile_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_profile_dir) { |
| LOG(WARNING) << "Running PreMigrationCleanUp()"; |
| |
| 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_profile_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(); |
| } |
| } |
| |
| return {TaskStatus::kSucceeded}; |
| } |
| |
| void BrowserDataBackMigrator::OnPreMigrationCleanUp( |
| BrowserDataBackMigrator::TaskResult result) { |
| if (result.status != TaskStatus::kSucceeded) { |
| LOG(ERROR) << "PreMigrationCleanup() failed."; |
| std::move(finished_callback_).Run(ToResult(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()"; |
| |
| 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_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_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_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 char* extension_id : |
| browser_data_migrator_util::kExtensionsBothChromes) { |
| if (!MergeCommonIndexedDB(ash_profile_dir, lacros_profile_dir, |
| extension_id)) { |
| 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_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_profile_dir.Append(path))) { |
| if (!CopyLevelDBBase(lacros_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_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_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}; |
| } |
| |
| return {TaskStatus::kSucceeded}; |
| } |
| |
| void BrowserDataBackMigrator::OnMergeSplitItems( |
| BrowserDataBackMigrator::TaskResult result) { |
| if (result.status != TaskStatus::kSucceeded) { |
| LOG(ERROR) << "MergeSplitItems() failed."; |
| std::move(finished_callback_).Run(ToResult(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()"; |
| |
| // 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(1) << "Deleting " << item.path.value() << " with permissions " |
| << permissions; |
| } |
| |
| if (!base::DeletePathRecursively(item.path)) { |
| PLOG(ERROR) << "Failed to delete " << item.path.value(); |
| return {TaskStatus::kDeleteAshItemsDeleteLacrosItemFailed, errno}; |
| } |
| } |
| |
| return {TaskStatus::kSucceeded}; |
| } |
| |
| void BrowserDataBackMigrator::OnDeleteAshItems(TaskResult result) { |
| if (result.status != TaskStatus::kSucceeded) { |
| LOG(ERROR) << "DeleteAshItems() failed."; |
| std::move(finished_callback_).Run(ToResult(result)); |
| return; |
| } |
| |
| SetProgress(MigrationStep::kMoveLacrosItemsToTmpDir); |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::USER_VISIBLE, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}, |
| base::BindOnce(&BrowserDataBackMigrator::MoveLacrosItemsToTmpDir, |
| ash_profile_dir_), |
| base::BindOnce(&BrowserDataBackMigrator::OnMoveLacrosItemsToTmpDir, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| // static |
| BrowserDataBackMigrator::TaskResult |
| BrowserDataBackMigrator::MoveLacrosItemsToTmpDir( |
| const base::FilePath& ash_profile_dir) { |
| LOG(WARNING) << "Running MoveLacrosItemsToTmpDir()"; |
| |
| const base::FilePath lacros_profile_dir = |
| ash_profile_dir.Append(browser_data_migrator_util::kLacrosDir) |
| .Append(browser_data_migrator_util::kLacrosProfilePath); |
| |
| const base::FilePath tmp_profile_dir = |
| ash_profile_dir.Append(browser_data_back_migrator::kTmpDir); |
| |
| browser_data_migrator_util::TargetItems lacros_items = |
| browser_data_migrator_util::GetTargetItems( |
| lacros_profile_dir, browser_data_migrator_util::ItemType::kLacros); |
| |
| for (const auto& item : lacros_items.items) { |
| // The corresponding items in Ash will be deleted in `DeleteAshItems` before |
| // they are overwritten by the Lacros items from the tmp directory. |
| 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::kMoveLacrosItemsToTmpDirMoveFailed, errno}; |
| } |
| } |
| |
| return {TaskStatus::kSucceeded}; |
| } |
| |
| void BrowserDataBackMigrator::OnMoveLacrosItemsToTmpDir( |
| BrowserDataBackMigrator::TaskResult result) { |
| if (result.status != TaskStatus::kSucceeded) { |
| LOG(ERROR) << "MoveLacrosItemsToTmpDir() failed."; |
| std::move(finished_callback_).Run(ToResult(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()"; |
| |
| 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}; |
| } |
| |
| return {TaskStatus::kSucceeded}; |
| } |
| |
| // static |
| bool BrowserDataBackMigrator::MoveFilesToAshDirectory( |
| const base::FilePath& source_dir, |
| const base::FilePath& dest_dir, |
| unsigned int recursion_depth) { |
| LOG(WARNING) << "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(1) << "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."; |
| std::move(finished_callback_).Run(ToResult(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()"; |
| |
| const base::FilePath lacros_profile_dir = |
| ash_profile_dir.Append(browser_data_migrator_util::kLacrosDir); |
| |
| if (base::PathExists(lacros_profile_dir)) { |
| if (!base::DeletePathRecursively(lacros_profile_dir)) { |
| PLOG(ERROR) << "Deleting " << lacros_profile_dir.value() << " failed: "; |
| return {TaskStatus::kDeleteLacrosDirDeleteFailed, errno}; |
| } |
| } |
| |
| return {TaskStatus::kSucceeded}; |
| } |
| |
| void BrowserDataBackMigrator::OnDeleteLacrosDir( |
| BrowserDataBackMigrator::TaskResult result) { |
| if (result.status != TaskStatus::kSucceeded) { |
| LOG(ERROR) << "DeleteLacrosDir() failed."; |
| std::move(finished_callback_).Run(ToResult(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()"; |
| |
| 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}; |
| } |
| } |
| |
| return {TaskStatus::kSucceeded}; |
| } |
| |
| void BrowserDataBackMigrator::OnDeleteTmpDir( |
| BrowserDataBackMigrator::TaskResult result) { |
| if (result.status != TaskStatus::kSucceeded) { |
| LOG(ERROR) << "DeleteTmpDir() failed."; |
| std::move(finished_callback_).Run(ToResult(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); |
| std::move(finished_callback_).Run(ToResult({TaskStatus::kSucceeded})); |
| } |
| |
| // static |
| bool BrowserDataBackMigrator::MergeCommonExtensionsDataFiles( |
| const base::FilePath& ash_profile_dir, |
| const base::FilePath& lacros_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_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 char* extension_id : |
| browser_data_migrator_util::kExtensionsBothChromes) { |
| 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 char* extension_id : |
| browser_data_migrator_util::kExtensionsBothChromes) { |
| 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_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_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. |
| absl::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. |
| absl::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; |
| } |
| |
| // For preferences that were moved to Lacros, and deleted in Ash, copy them |
| // back to Ash. |
| for (const char* key : |
| browser_data_migrator_util::kLacrosOnlyPreferencesKeys) { |
| base::Value* lacros_value = lacros_root_dict->FindByDottedPath(key); |
| if (lacros_value) |
| ash_root_dict->SetByDottedPath(key, lacros_value->Clone()); |
| } |
| |
| // 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 base::StringPiece 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 base::StringPiece 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::IsLacrosOnlyExtension( |
| const base::StringPiece extension_id) { |
| return !base::Contains(browser_data_migrator_util::kExtensionsAshOnly, |
| extension_id) && |
| !base::Contains(browser_data_migrator_util::kExtensionsBothChromes, |
| 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(); |
| std::string value; |
| status = ash_db->Get(leveldb::ReadOptions(), key, &value); |
| if (!status.ok()) { |
| PLOG(ERROR) << "Failure while reading from Ash Sync Data LevelDB: " |
| << ash_db_path; |
| return false; |
| } |
| |
| if (browser_data_migrator_util::IsAshOnlySyncDataType(key)) |
| ash_write_batch.Put(key, value); |
| } |
| } |
| |
| // 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(); |
| std::string value; |
| |
| status = lacros_db->Get(leveldb::ReadOptions(), key, &value); |
| if (!status.ok()) { |
| PLOG(ERROR) << "Failure while reading from Lacros Sync Data LevelDB: " |
| << lacros_db_path; |
| return false; |
| } |
| |
| if (!browser_data_migrator_util::IsAshOnlySyncDataType(key)) |
| lacros_write_batch.Put(key, value); |
| } |
| } |
| |
| // 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 |
| BrowserDataBackMigrator::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::kMoveLacrosItemsToTmpDirMoveFailed: |
| case TaskStatus::kMoveMergedItemsBackToAshCopyDirectoryFailed: |
| case TaskStatus::kMoveMergedItemsBackToAshMoveFileFailed: |
| return Result::kFailed; |
| } |
| } |
| |
| // 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( |
| crosapi::browser_util::PolicyInitState policy_init_state) { |
| if (IsBackMigrationForceEnabled()) { |
| 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) { |
| return false; |
| } |
| |
| crosapi::browser_util::LacrosDataBackwardMigrationMode migration_mode = |
| crosapi::browser_util::LacrosDataBackwardMigrationMode::kNone; |
| if (policy_init_state == |
| crosapi::browser_util::PolicyInitState::kBeforeInit) { |
| auto 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, |
| crosapi::browser_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) |
| 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) |
| return false; |
| |
| return base::FeatureList::IsEnabled( |
| ash::features::kLacrosProfileBackwardMigration); |
| } |
| |
| // static |
| bool BrowserDataBackMigrator::ShouldMigrateBack( |
| const AccountId& account_id, |
| const std::string& user_id_hash, |
| crosapi::browser_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; |
| } |
| |
| 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_profile_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_profile_dir)) { |
| VLOG(1) << "Lacros folder not found at '" << lacros_profile_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; |
| } |
| |
| // TODO(b/253621578): Add g_attempt_restart helper for testing |
| chrome::AttemptRestart(); |
| return true; |
| } |
| |
| // static |
| bool BrowserDataBackMigrator::MaybeRestartToMigrateBack( |
| const AccountId& account_id, |
| const std::string& user_id_hash, |
| crosapi::browser_util::PolicyInitState policy_init_state) { |
| if (!ShouldMigrateBack(account_id, user_id_hash, policy_init_state)) { |
| return false; |
| } |
| |
| return RestartToMigrateBack(account_id); |
| } |
| |
| } // namespace ash |