blob: ddb2fffece70da204421516d75e76af420031704 [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_ASH_CROSAPI_BROWSER_DATA_MIGRATOR_H_
#define CHROME_BROWSER_ASH_CROSAPI_BROWSER_DATA_MIGRATOR_H_
#include <atomic>
#include <memory>
#include <vector>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/gtest_prod_util.h"
#include "base/sequence_checker.h"
#include "base/strings/string_piece.h"
#include "base/synchronization/atomic_flag.h"
#include "base/timer/elapsed_timer.h"
#include "base/version.h"
#include "chrome/browser/ash/crosapi/migration_progress_tracker.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_registry_simple.h"
class PrefService;
namespace ash {
// User data directory name for lacros.
constexpr char kLacrosDir[] = "lacros";
// Profile data directory name for lacros.
constexpr char kLacrosProfilePath[] = "Default";
// The name of temporary directory that will store copies of files from the
// original user data directory. At the end of the migration, it will be moved
// to the appropriate destination.
constexpr char kTmpDir[] = "browser_data_migrator";
// The following are UMA names.
constexpr char kFinalStatus[] = "Ash.BrowserDataMigrator.FinalStatus";
constexpr char kCopiedDataSize[] = "Ash.BrowserDataMigrator.CopiedDataSizeMB";
constexpr char kNoCopyDataSize[] = "Ash.BrowserDataMigrator.NoCopyDataSizeMB";
constexpr char kAshDataSize[] = "Ash.BrowserDataMigrator.AshDataSizeMB";
constexpr char kLacrosDataSize[] = "Ash.BrowserDataMigrator.LacrosDataSizeMB";
constexpr char kCommonDataSize[] = "Ash.BrowserDataMigrator.CommonDataSizeMB";
constexpr char kTotalTime[] = "Ash.BrowserDataMigrator.TotalTimeTakenMS";
constexpr char kLacrosDataTime[] =
"Ash.BrowserDataMigrator.LacrosDataTimeTakenMS";
constexpr char kCommonDataTime[] =
"Ash.BrowserDataMigrator.CommonDataTimeTakenMS";
constexpr char kCreateDirectoryFail[] =
"Ash.BrowserDataMigrator.CreateDirectoryFailure";
// The following UMAs are recorded from
// `BrowserDataMigratorImpl::DryRunToCollectUMA()`.
constexpr char kDryRunNoCopyDataSize[] =
"Ash.BrowserDataMigrator.DryRunNoCopyDataSizeMB";
constexpr char kDryRunAshDataSize[] =
"Ash.BrowserDataMigrator.DryRunAshDataSizeMB";
constexpr char kDryRunLacrosDataSize[] =
"Ash.BrowserDataMigrator.DryRunLacrosDataSizeMB";
constexpr char kDryRunCommonDataSize[] =
"Ash.BrowserDataMigrator.DryRunCommonDataSizeMB";
constexpr char kDryRunCopyMigrationHasEnoughDiskSpace[] =
"Ash.BrowserDataMigrator.DryRunHasEnoughDiskSpace.Copy";
constexpr char kDryRunMoveMigrationHasEnoughDiskSpace[] =
"Ash.BrowserDataMigrator.DryRunHasEnoughDiskSpace.Move";
constexpr char kDryRunDeleteAndCopyMigrationHasEnoughDiskSpace[] =
"Ash.BrowserDataMigrator.DryRunHasEnoughDiskSpace.DeleteAndCopy";
constexpr char kDryRunDeleteAndMoveMigrationHasEnoughDiskSpace[] =
"Ash.BrowserDataMigrator.DryRunHasEnoughDiskSpace.DeleteAndMove";
// Local state pref name, which is used to keep track of what step migration is
// at. This ensures that ash does not get repeatedly for migration.
// 1. The user logs in and restarts ash if necessary to apply flags.
// 2. Migration check runs.
// 3. Restart ash to run migration.
// 4. Restart ash again to show the home screen.
constexpr char kMigrationStep[] = "ash.browser_data_migrator.migration_step";
// Local state pref name to keep track of the number of migration attempts a
// user has gone through before. It is a dictionary of the form
// `{<user_id_hash>: <count>}`.
constexpr char kMigrationAttemptCountPref[] =
"ash.browser_data_migrator.migration_attempt_count";
// Maximum number of migration attempts. Migration will be skipped for the user
// after
constexpr int kMaxMigrationAttemptCount = 3;
// CancelFlag
class CancelFlag : public base::RefCountedThreadSafe<CancelFlag> {
public:
CancelFlag();
CancelFlag(const CancelFlag&) = delete;
CancelFlag& operator=(const CancelFlag&) = delete;
void Set() { cancelled_ = true; }
bool IsSet() const { return cancelled_; }
private:
friend base::RefCountedThreadSafe<CancelFlag>;
~CancelFlag();
std::atomic_bool cancelled_;
};
// The interface is exposed to be inherited by fakes in tests.
class BrowserDataMigrator {
public:
virtual ~BrowserDataMigrator() = default;
// Carries out the migration. It needs to be called on UI thread.
virtual void Migrate() = 0;
// Cancels the migration.
virtual void Cancel() = 0;
};
// BrowserDataMigratorImpl is responsible for one time browser data migration
// from ash-chrome to lacros-chrome.
class BrowserDataMigratorImpl : public BrowserDataMigrator {
public:
// Used to describe a file/dir that has to be migrated.
struct TargetItem {
enum class ItemType { kFile, kDirectory };
TargetItem(base::FilePath path, int64_t size, ItemType item_type);
~TargetItem() = default;
bool operator==(const TargetItem& rhs) const;
base::FilePath path;
// The size of the TargetItem. If TargetItem is a directory, it is the sum
// of all files under the directory.
int64_t size;
bool is_directory;
};
// Used to describe what files/dirs have to be migrated to the new location
// and the total byte size of those files.
struct TargetInfo {
TargetInfo();
~TargetInfo();
TargetInfo(TargetInfo&&);
// Items that should stay in ash data dir.
std::vector<TargetItem> ash_data_items;
// Items that should be moved to lacros data dir.
std::vector<TargetItem> lacros_data_items;
// Items that will be duplicated in both ash and lacros data dir.
std::vector<TargetItem> common_data_items;
// Items that can be deleted from both ash and lacros data dir.
std::vector<TargetItem> no_copy_data_items;
// The total size of `ash_data_items`.
int64_t ash_data_size;
// The total size of items that can be deleted during the migration.
int64_t no_copy_data_size;
// The total size of `lacros_data_items`.
int64_t lacros_data_size;
// The total size of `common_data_items`.
int64_t common_data_size;
// The size of data that are duplicated. Equivalent to the extra space
// needed for the migration. Currently this is equal to `lacros_data_size +
// common_data_size` since we are copying lacros data rather than moving
// them.
int64_t TotalCopySize() const;
// The total size of the profile data directory. It is the sum of ash,
// no_copy, lacros and common sizes.
int64_t TotalDirSize() const;
};
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
//
// This enum corresponds to BrowserDataMigratorFinalStatus in hisograms.xml
// and enums.xml.
enum class FinalStatus {
kSkipped = 0, // No longer in use.
kSuccess = 1,
kGetPathFailed = 2,
kDeleteTmpDirFailed = 3,
kNotEnoughSpace = 4,
kCopyFailed = 5,
kMoveFailed = 6,
kDataWipeFailed = 7,
kSizeLimitExceeded = 8, // No longer in use.
kCancelled = 9,
kMaxValue = kCancelled
};
// The value for `kMigrationStep`.
enum class MigrationStep {
kCheckStep = 0, // Migration check should run.
kRestartCalled = 1, // `MaybeRestartToMigrate()` called restart.
kStarted = 2, // `Migrate()` was called.
kEnded = 3 // Migration ended. It was either skipped, failed or succeeded.
};
enum class ResultValue { kSkipped, kSucceeded, kFailed, kCancelled };
// Return value of `MigrateInternal()`.
struct MigrationResult {
// Describes the end result of user data wipe.
ResultValue data_wipe;
// Describes the end result of data migration.
ResultValue data_migration;
};
// Specifies the mode of migration.
enum class Mode {
kCopy = 0, // Copies browser related files to lacros.
kMove = 1, // Moves browser related files to lacros while copying files
// that are needed by both ash and lacros.
kDeleteAndCopy = 2, // Similar to kCopy but deletes
// TargetInfo::no_copy_items to make extra space.
kDeleteAndMove = 3 // Similar to kMove but deletes
// TargetInfo::no_copy_items to make extra space.
};
// `BrowserDataMigratorImpl` migrates browser data from `original_profile_dir`
// to a new profile location for lacros chrome. `progress_callback` is called
// to update the progress bar on the screen. `completion_callback` passed as
// an argument will be called on the UI thread where `Migrate()` is called
// once migration has completed or failed.
BrowserDataMigratorImpl(const base::FilePath& original_profile_dir,
const std::string& user_id_hash,
const ProgressCallback& progress_callback,
base::OnceClosure completion_callback,
PrefService* local_state);
BrowserDataMigratorImpl(const BrowserDataMigratorImpl&) = delete;
BrowserDataMigratorImpl& operator=(const BrowserDataMigratorImpl&) = delete;
~BrowserDataMigratorImpl() override;
// Checks if migration is required for the user identified by `user_id_hash`
// and if it is required, calls a D-Bus method to session_manager and
// terminates ash-chrome. It returns true if the D-Bus call to the
// session_manager is made and successful. The return value of true means that
// `chrome::AttemptRestart()` has been called.
static bool MaybeRestartToMigrate(const AccountId& account_id,
const std::string& user_id_hash);
// `BrowserDataMigrator` methods.
void Migrate() override;
void Cancel() override;
// Registers boolean pref `kCheckForMigrationOnRestart` with default as false.
static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
// Clears the value of `kMigrationStep` in Local State.
static void ClearMigrationStep(PrefService* local_state);
ResultValue GetFinalStatus();
// Collects migration specific UMAs without actually running the migration. It
// does not check if lacros is enabled.
static void DryRunToCollectUMA(const base::FilePath& profile_data_dir);
private:
FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest,
ManipulateMigrationAttemptCount);
FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, GetTargetInfo);
FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, CopyDirectory);
FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, SetupTmpDir);
FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, CancelSetupTmpDir);
FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, RecordStatus);
FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, MigrateInternal);
FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, Migrate);
FRIEND_TEST_ALL_PREFIXES(BrowserDataMigratorTest, MigrateCancelled);
// Sets the value of `kMigrationStep` in Local State.
static void SetMigrationStep(PrefService* local_state, MigrationStep step);
// Gets the value of `kMigrationStep` in Local State.
static MigrationStep GetMigrationStep(PrefService* local_state);
// Increments the migration attempt count stored in
// `kMigrationAttemptCountPref` by 1 for the user identified by
// `user_id_hash`.
static void UpdateMigrationAttemptCountForUser(
PrefService* local_state,
const std::string& user_id_hash);
// Gets the number of migration attempts for the user stored in
// `kMigrationAttemptCountPref.
static int GetMigrationAttemptCountForUser(PrefService* local_state,
const std::string& user_id_hash);
// Resets the number of migration attempts for the user stored in
// `kMigrationAttemptCountPref.
static void ClearMigrationAttemptCountForUser(
PrefService* local_state,
const std::string& user_id_hash);
// Handles the migration on a worker thread. Returns the end status of data
// wipe and migration. `progress_callback` gets posted on UI thread whenever
// an update to the UI is required
static MigrationResult MigrateInternal(
const base::FilePath& original_user_dir,
std::unique_ptr<MigrationProgressTracker> progress_tracker,
scoped_refptr<CancelFlag> cancel_flag);
// Called from `MaybeRestartToMigrate()` to proceed with restarting to start
// the migration. It returns true if D-Bus call was successful.
static bool RestartToMigrate(const AccountId& account_id,
const std::string& user_id_hash);
// Called on UI thread once migration is finished.
void MigrateInternalFinishedUIThread(MigrationResult result);
// Records to UMA histograms. Note that if `target_info` is nullptr, timer
// will be ignored.
static void RecordStatus(const FinalStatus& final_status,
const TargetInfo* target_info = nullptr,
const base::ElapsedTimer* timer = nullptr);
// Create `TargetInfo` from `original_user_dir`. `TargetInfo` will include
// paths of files/dirs that needs to be migrated.
static TargetInfo GetTargetInfo(const base::FilePath& original_user_dir);
// Compares space available for `to_dir` against total byte size that
// needs to be copied.
static bool HasEnoughDiskSpace(const TargetInfo& target_info,
const base::FilePath& original_user_dir,
Mode mode);
// Set up the temporary directory `tmp_dir` by copying items into it.
static bool SetupTmpDir(const TargetInfo& target_info,
const base::FilePath& from_dir,
const base::FilePath& tmp_dir,
CancelFlag* cancel_flag,
MigrationProgressTracker* progress_tracker);
// Copies `items` to `to_dir`. `items_size` and `category_name` are used for
// logging.
static bool CopyTargetItems(const base::FilePath& to_dir,
const std::vector<TargetItem>& items,
CancelFlag* cancel_flag,
int64_t items_size,
base::StringPiece category_name,
MigrationProgressTracker* progress_tracker);
// Copies `item` to location pointed by `dest`. Returns true on success and
// false on failure.
static bool CopyTargetItem(const BrowserDataMigratorImpl::TargetItem& item,
const base::FilePath& dest,
CancelFlag* cancel_flag,
MigrationProgressTracker* progress_tracker);
// Copies the contents of `from_path` to `to_path` recursively. Unlike
// `base::CopyDirectory()` it skips symlinks.
static bool CopyDirectory(const base::FilePath& from_path,
const base::FilePath& to_path,
CancelFlag* cancel_flag,
MigrationProgressTracker* progress_tracker);
// Records the sizes of `TargetItem`s.
static void RecordTargetItemSizes(const std::vector<TargetItem>& items);
// 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<MigrationProgressTracker> progress_tracker_;
// Callback to be called once migration is done. It is called regardless of
// whether migration succeeded or not.
base::OnceClosure completion_callback_;
// `cancel_flag_` gets set by `Cancel()` and tasks posted to worker threads
// can check if migration is cancelled or not.
scoped_refptr<CancelFlag> cancel_flag_;
// Local state prefs, not owned.
PrefService* local_state_ = nullptr;
// Final status of the migration.
ResultValue final_status_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<BrowserDataMigratorImpl> weak_factory_{this};
};
} // namespace ash
#endif // CHROME_BROWSER_ASH_CROSAPI_BROWSER_DATA_MIGRATOR_H_