// 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.
#ifndef CHROME_BROWSER_ASH_CROSAPI_MOVE_MIGRATOR_H_
#define CHROME_BROWSER_ASH_CROSAPI_MOVE_MIGRATOR_H_
#include <memory>
#include <optional>
#include <string>
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/elapsed_timer.h"
#include "chrome/browser/ash/crosapi/browser_data_migrator.h"
#include "chrome/browser/ash/crosapi/browser_data_migrator_util.h"
class PrefService;
class PrefRegistrySimple;
namespace ash {
namespace standalone_browser {
class MigrationProgressTracker;
} // namespace standalone_browser
using MigrationFinishedCallback =
base::OnceCallback<void(BrowserDataMigratorImpl::MigrationResult)>;
// Dictionary pref storing user id hash as key and `ResumeStep` as value.
constexpr char kMoveMigrationResumeStepPref[] =
"ash.browser_data_migrator.move_migration_resume_step";
// Dictionary pref storing user id hash as key and number of resumes in int as
// value.
constexpr char kMoveMigrationResumeCountPref[] =
"ash.browser_data_migrator.move_migration_resume_count";
// The number of maximum resume tries for `MoveMigrator`. If the limit is
// reached then move migration is marked as completed without actually
// completing the migration.
constexpr int kMoveMigrationResumeCountLimit = 5;
// The following are UMA names.
constexpr char kMoveMigratorResumeCount[] =
"Ash.BrowserDataMigrator.MoveMigrator.ResumeCount";
constexpr char kMoveMigratorResumeStepUMA[] =
"Ash.BrowserDataMigrator.MoveMigrator.ResumeStep";
constexpr char kMoveMigratorMaxResumeReached[] =
"Ash.BrowserDataMigrator.MoveMigrator.MaxResumeReached";
constexpr char kMoveMigratorTaskStatusUMA[] =
"Ash.BrowserDataMigrator.MoveMigrator.TaskStatus";
constexpr char kMoveMigratorExtraSpaceRequiredMB[] =
"Ash.BrowserDataMigrator.MoveMigrator.ExtraSpaceRequiredMB";
constexpr char kMoveMigratorPreMigrationCleanUpTimeUMA[] =
"Ash.BrowserDataMigrator.MoveMigrator.PreMigrationCleanUpTime";
constexpr char kMoveMigratorSetupLacrosDirCopyTargetItemsTimeUMA[] =
"Ash.BrowserDataMigrator.MoveMigrator.SetupLacrosDirCopyTargetItemsTime";
constexpr char kMoveMigratorCancelledMigrationTimeUMA[] =
"Ash.BrowserDataMigrator.MoveMigrator.CancelledMigrationTime";
constexpr char kMoveMigratorSuccessfulMigrationTimeUMA[] =
"Ash.BrowserDataMigrator.MoveMigrator.SuccessfulMigrationTime";
constexpr char kMoveMigratorMoveLacrosItemsTimeUMA[] =
"Ash.BrowserDataMigrator.MoveMigrator.MoveLacrosItemsTime";
constexpr char kMoveMigratorPosixErrnoUMA[] =
"Ash.BrowserDataMigrator.MoveMigrator.PosixErrno.";
constexpr char kMoveMigratorTmpProfileDirSize[] =
"Ash.BrowserDataMigrator.MoveMigrator.TmpProfileDirSize";
constexpr char kMoveMigratorTmpSplitDirSize[] =
"Ash.BrowserDataMigrator.MoveMigrator.TmpSplitDirSize";
constexpr char kMoveMigratorExtraDiskSpaceOccupied[] =
"Ash.BrowserDataMigrator.MoveMigrator.ExtraDiskSpaceOccupied";
constexpr char kMoveMigratorExtraDiskSpaceOccupiedDiffWithEst[] =
"Ash.BrowserDataMigrator.MoveMigrator.ExtraDiskSpaceOccupied."
"DiffWithEstimate";
// This class "moves" Lacros data from Ash to Lacros. It migrates user data from
// `original_profile_dir` (/home/user/<hash>/), denoted as <Ash PDD> from here
// forward, to the new profile data directory
// (/<Ash PDD>/lacros/Default/) with the steps described below. The renaming of
// <kMoveTmpDir> is the last step of the migration so that the existence of
// <Ash PDD>/lacros/ is equivalent to having completed the migration.
// 1) Delete any `ItemType::kDeletable` items in <Ash PDD>.
// 2) Setup <Ash PDD>/<kMoveTmpDir> by copying `ItemType::kNeedCopy`
// items into it.
// 3) Setup <Ash PDD>/<kSplitTmpDir> by generating split data that will have to
// remain in Ash.
// 4) Move `ItemType::kLacros` in <Ash PDD> to <lacros PDD>.
// 5) Move split items in <Ash PDD>/<kSplitTmpDir> to <Ash PDD>.
// 6) Rename <Ash PDD>/<kMoveTmpDir>/ as <Ash PDD>/lacros/.
class MoveMigrator : public BrowserDataMigratorImpl::MigratorDelegate {
public:
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// Indicate which step the migration should be resumed from if left unfinished
// in the previous attempt.
enum class ResumeStep {
kStart = 0,
kMoveLacrosItems = 1,
kMoveSplitItems = 2,
kMoveTmpDir = 3,
kCompleted = 4,
kMaxValue = kCompleted,
};
MoveMigrator(
const base::FilePath& original_profile_dir,
const std::string& user_id_hash,
std::unique_ptr<standalone_browser::MigrationProgressTracker>
progress_tracker,
scoped_refptr<browser_data_migrator_util::CancelFlag> cancel_flag,
PrefService* local_state,
MigrationFinishedCallback finished_callback);
MoveMigrator(const MoveMigrator&) = delete;
MoveMigrator& operator=(const MoveMigrator&) = delete;
~MoveMigrator() override;
// BrowserDataMigratorImpl::MigratorDelegate override.
void Migrate() override;
// Gets the `ResumeStep` for the user stored in `local_state` and checks if
// move migration has to be resumed by calling `IsResumeStep()`.
static bool ResumeRequired(PrefService* local_state,
const std::string& user_id_hash);
// Resets the number of resume attempts for the user stored in
// `kMoveMigrationResumeCountPref.
static void ClearResumeAttemptCountForUser(PrefService* local_state,
const std::string& user_id_hash);
// Clears `ResumeStep` for user stored in `kMoveMigrationResumeStepPref`.
static void ClearResumeStepForUser(PrefService* local_state,
const std::string& user_id_hash);
static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
private:
FRIEND_TEST_ALL_PREFIXES(MoveMigratorTest, ResumeRequired);
FRIEND_TEST_ALL_PREFIXES(MoveMigratorTest, PreMigrationCleanUp);
FRIEND_TEST_ALL_PREFIXES(MoveMigratorTest, SetupLacrosDir);
FRIEND_TEST_ALL_PREFIXES(MoveMigratorTest, SetupAshSplitDir);
FRIEND_TEST_ALL_PREFIXES(
MoveMigratorTest,
MoveLacrosItemsToNewDirFailIfNoWritePermForLacrosItem);
FRIEND_TEST_ALL_PREFIXES(MoveMigratorTest, MoveLacrosItemsToNewDir);
FRIEND_TEST_ALL_PREFIXES(MoveMigratorTest, RecordPosixErrnoUMA);
FRIEND_TEST_ALL_PREFIXES(MoveMigratorMigrateTest,
MigrateResumeFromMoveLacrosItems);
FRIEND_TEST_ALL_PREFIXES(MoveMigratorMigrateTest,
MigrateResumeFromMoveSplitItems);
FRIEND_TEST_ALL_PREFIXES(MoveMigratorMigrateTest,
MigrateResumeFromMoveTmpDir);
friend class BrowserDataMigratorResumeOnSignIn;
friend class BrowserDataMigratorResumeRestartInSession;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// This enum corresponds to MoveMigratorTaskStatus in histograms.xml
// and enums.xml.
enum class TaskStatus {
kSucceeded = 0,
kCancelled = 1,
kPreMigrationCleanUpDeleteLacrosDirFailed = 2,
kPreMigrationCleanUpDeleteTmpDirFailed = 3,
kPreMigrationCleanUpDeleteTmpSplitDirFailed = 4,
kPreMigrationCleanUpNotEnoughSpace = 5,
kSetupLacrosDirCreateTmpDirFailed = 6,
kSetupLacrosDirCreateTmpProfileDirFailed = 7,
kSetupLacrosDirCopyTargetItemsFailed = 8,
kSetupLacrosDirWriteFirstRunSentinelFileFailed = 9,
kSetupAshDirCreateSplitDirFailed = 10,
kSetupAshDirMigrateLevelDBForLocalStateFailed = 11,
kSetupAshDirMigrateLevelDBForStateFailed = 12,
kSetupAshDirMigratePreferencesFailed = 13,
kMoveLacrosItemsToNewDirNoWritePerm = 14,
kMoveLacrosItemsToNewDirMoveFailed = 15,
kMoveSplitItemsToOriginalDirMoveSplitItemsFailed = 16,
kMoveSplitItemsToOriginalDirCreateDirFailed = 17,
kMoveSplitItemsToOriginalDirMoveExtensionsFailed = 18,
kMoveSplitItemsToOriginalDirMoveIndexedDBFailed = 19,
kMoveTmpDirToLacrosDirMoveFailed = 20,
kSetupAshDirCreateDirFailed = 21,
kSetupAshDirCopyExtensionsFailed = 22,
kSetupAshDirCopyIndexedDBFailed = 23,
kSetupAshDirMigrateSyncDataLevelDBFailed = 24,
kSetupAshDirCopyStorageFailed = 25,
kMoveSplitItemsToOriginalDirMoveStorageFailed = 26,
kMoveLacrosItemsCreateDirFailed = 27,
kMaxValue = kMoveLacrosItemsCreateDirFailed,
};
struct TaskResult {
TaskStatus status;
// Value of `errno` set after a task has failed.
std::optional<int> posix_errno;
// Extra bytes required to be freed if the migrator requires more space to
// be carried out. Only set if `status` is
// `kPreMigrationCleanUpNotEnoughSpace`.
std::optional<uint64_t> extra_bytes_required_to_be_freed;
// Extra bytes that are estimated to be created due to the migration. It
// will later be used to calculate the diff between this estimate and the
// actual value.
std::optional<int64_t> estimated_extra_bytes_created;
};
// Called to determine where to start the migration. Returns
// `ResumeStep::kStart` unless there is a step recorded in `Local State` from
// the previous migration i.e. the previous migration did not complete and
// crashed halfway.
static ResumeStep GetResumeStep(PrefService* local_state,
const std::string& user_id_hash);
// Sets `kMoveMigrationResumeStepPref` in `Local State` for `user_id_hash`.
static void SetResumeStep(PrefService* local_state,
const std::string& user_id_hash,
ResumeStep step);
// Returns true if `resume_step` indicates that the migration had been left
// unfinished in the previous attempt and that it must be resumed before user
// profile is created.
static bool IsResumeStep(ResumeStep resume_step);
// Increments the resume attempt count stored in
// `kMoveMigrationResumeCountPref` by 1 for the user identified by
// `user_id_hash`. Returns the updated resume count.
int UpdateResumeAttemptCountForUser(PrefService* local_state,
const std::string& user_id_hash);
// Deletes lacros user directory and `kMoveTmpDir` if they exist. Set
// `PreMigrationCleanUpResult::success` to true if the deletion of those
// directories are successful. If the deletion is successful then it also
// checks if there is enough disk space available and set
// `PreMigrationCleanUpResult::extra_bytes_required_to_be_freed`. It also
// deletes `ItemType::kDeletable` items to free up extra space but this does
// not affect `PreMigrationCleanUpResult::success`.
static TaskResult PreMigrationCleanUp(
const base::FilePath& original_profile_dir);
// Called as a reply to `PreMigrationCleanUp()`. Posts
// `SetupLacrosRemoveHardLinksFromAshDir()` as the next step.
void OnPreMigrationCleanUp(TaskResult);
// Set up lacros user directory by copying `ItemType::kNeedCopy` items
// and also creating `First Run` file in Lacros user data dir.
static TaskResult SetupLacrosDir(
const base::FilePath& original_profile_dir,
std::unique_ptr<standalone_browser::MigrationProgressTracker>
progress_tracker,
scoped_refptr<browser_data_migrator_util::CancelFlag> cancel_flag);
// Called as a reply to `SetupLacrosDir()`. Posts
// `SetupAshSplitDir()` as the next step.
void OnSetupLacrosDir(TaskResult);
// Set up a temporary directory to hold items that need to be split between
// ash and lacros. This folder will hold ash's version of the items.
static TaskResult SetupAshSplitDir(
const base::FilePath& original_profile_dir,
const int64_t estimated_extra_bytes_created);
// Called as a reply to `SetupAshSplitDir()`. Posts `MoveLacrosItemsToNewDir`
// as the next step.
void OnSetupAshSplitDir(TaskResult);
// Move `ItemType::kLacros` in the original profile
// directory to the temp dir.
static TaskResult MoveLacrosItemsToNewDir(
const base::FilePath& original_profile_dir);
// Called as a reply to `MoveLacrosItemsToNewDir()`.
void OnMoveLacrosItemsToNewDir(TaskResult);
// Moves newly created split items to the original profile directory.
static TaskResult MoveSplitItemsToOriginalDir(
const base::FilePath& original_profile_dir);
// Called as a reply to `MoveSplitItemsToOriginalDir`.
void OnMoveSplitItemsToOriginalDir(TaskResult);
// Moves newly created `kMoveTmpDir` to `kLacrosDir`.
// Completes the migration.
static TaskResult MoveTmpDirToLacrosDir(
const base::FilePath& original_profile_dir);
// Called as a reply to `MoveTmpDirToLacrosDir()`.
void OnMoveTmpDirToLacrosDir(TaskResult);
// Selectively copy `target_dir` from `original_profile_dir` to
// `tmp_split_dir`. Only copy the subdirectories belonging to extensions
// that have to stay in both Ash and Lacros.
// If copying fails, return a TaskResult with `copy_fail_status` TaskStatus.
static TaskResult CopyBothChromesSubdirs(
const base::FilePath& original_profile_dir,
const base::FilePath& tmp_split_dir,
const std::string& target_dir,
TaskStatus copy_fail_status);
// Selectively move `target_dir` from `tmp_profile_dir` to
// `original_profile_dir`. Only move the subdirectories belonging to
// extensions that have to be in Ash only.
// If moving fails, return a TaskResult with `move_fail_status` TaskStatus.
static TaskResult MoveAshSubdirs(const base::FilePath& tmp_profile_dir,
const base::FilePath& original_profile_dir,
const std::string& target_dir,
TaskStatus move_fail_status);
// Records the final status of the migration in `kMoveMigratorTaskStatusUMA`
// and calls `finished_callback_`. This function gets called once regardless
// of whether the migration succeeded or not.
void InvokeCallback(TaskResult);
// Converts `TaskResult` to `BrowserDataMigratorImpl::MigrationResult`.
BrowserDataMigratorImpl::MigrationResult ToBrowserDataMigratorMigrationResult(
TaskResult result);
// Record UMA of the form
// "Ash.BrowserDataMigrator.MoveMigrator.PosixErrno.{task_status}" with the
// value of `errno`.
static void RecordPosixErrnoUMA(TaskStatus task_status,
const int posix_errno);
// Convert `TaskStatus` to string.
static std::string TaskStatusToString(TaskStatus task_status);
// Path to the original profile data directory, which is directly under the
// user data directory.
const base::FilePath original_profile_dir_;
// A hash string of the profile user ID.
const std::string user_id_hash_;
// `progress_tracker_` is used to report progress status to the screen.
std::unique_ptr<standalone_browser::MigrationProgressTracker>
progress_tracker_;
// `cancel_flag_` gets set by `BrowserDataMigratorImpl::Cancel()` and tasks
// posted to worker threads can check if migration is cancelled or not.
scoped_refptr<browser_data_migrator_util::CancelFlag> cancel_flag_;
// Local state prefs, not owned.
const raw_ptr<PrefService> local_state_;
// `finished_callback_` should be called once migration is completed/failed.
// Call this on UI thread.
MigrationFinishedCallback finished_callback_;
// Timer to count time since the initialization of the class. Used to get UMA
// data on how long the migration takes.
const base::ElapsedTimer timer_;
// Extra bytes that are estimated to be created due to the migration.
std::optional<int64_t> estimated_extra_bytes_created_;
base::WeakPtrFactory<MoveMigrator> weak_factory_{this};
};
} // namespace ash
#endif // CHROME_BROWSER_ASH_CROSAPI_MOVE_MIGRATOR_H_