blob: f8808eef3700cf3f88fad9edd45333210eafbd68 [file] [log] [blame]
// Copyright 2021 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_BROWSER_DATA_MIGRATOR_UTIL_H_
#define CHROME_BROWSER_ASH_CROSAPI_BROWSER_DATA_MIGRATOR_UTIL_H_
#include <atomic>
#include <map>
#include <string>
#include "base/files/file_path.h"
#include "base/synchronization/atomic_flag.h"
#include "base/values.h"
#include "chrome/browser/ash/crosapi/migration_progress_tracker.h"
#include "components/sync/base/model_type.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/leveldatabase/env_chromium.h"
namespace base {
class FilePath;
}
namespace ash::browser_data_migrator_util {
// 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";
// `MoveMigrator` migrates user data to this directory first then moves it to
// the correct location as its final step.
constexpr char kMoveTmpDir[] = "move_migrator";
// `MoveMigrator` splits user data that needs to remain Ash into this directory
// first, then moves it to the correct location as its final step.
constexpr char kSplitTmpDir[] = "move_migrator_split";
// Directory for `MoveMigrator` to move hard links for lacros file/dirs in ash
// directory so that they become inaccessible from ash. This directory should be
// cleaned up after the migraton.
constexpr char kRemoveDir[] = "move_migrator_trash";
// The following UMAs are recorded from
// `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 kDryRunCopyMigrationTotalCopySize[] =
"Ash.BrowserDataMigrator.DryRunTotalCopySizeMB.Copy";
constexpr char kDryRunMoveMigrationTotalCopySize[] =
"Ash.BrowserDataMigrator.DryRunTotalCopySizeMB.Move";
constexpr char kDryRunMoveMigrationExtraSpaceReserved[] =
"Ash.BrowserDataMigrator.DryRunExtraSizeReservedMB.Move";
constexpr char kDryRunMoveMigrationExtraSpaceRequired[] =
"Ash.BrowserDataMigrator.DryRunExtraSizeRequiredMB.Move";
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";
// The base names of files/dirs directly under the original profile
// data directory that can be deleted if needed because they are temporary
// storages.
constexpr const char* const kDeletablePaths[] = {
kTmpDir,
kMoveTmpDir,
"blob_storage",
"Cache",
"Code Cache",
"coupon_db",
"crash",
"Download Service",
"GCache",
"GPUCache",
"heavy_ad_intervention_opt_out.db",
"merchant_signal_db",
"Network Action Predictor",
"Network Persistent State",
"previews_opt_out.db",
"Reporting and NEL",
"Site Characteristics Database",
"Translate Ranker Model",
"TransportSecurity"};
// The base names of files/dirs that should remain in ash data
// directory.
constexpr const char* const kRemainInAshDataPaths[] = {
"AccountManagerTokens.bin",
"Accounts",
"app_ranker.pb",
"arc.apps",
"autobrightness",
"BudgetDatabase",
"crostini.icons",
"data_reduction_proxy_leveldb",
"Downloads",
"extension_install_log",
"Feature Engagement Tracker",
"FullRestoreData",
"GCM Store",
"google-assistant-library",
"input_methods",
"launcher_ranking",
"LOCK",
"LOG",
"LOG.old",
"login-times",
"logout-times",
"MyFiles",
"NearbySharePublicCertificateDatabase",
"PPDCache",
"PreferredApps",
"PrintJobDatabase",
"README",
"RLZ Data",
"smartcharging",
"structured_metrics",
"Sync Data",
"Trusted Vault",
"trusted_vault.pb",
"WebRTC Logs",
"webrtc_event_logs",
"zero_state_group_ranker.pb",
"zero_state_local_files.pb"};
// The base names of files/dirs that are required for browsing and should be
// moved to lacros data dir.
constexpr const char* const kLacrosDataPaths[]{
"Affiliation Database",
"AutofillStrikeDatabase",
"Bookmarks",
"chrome_cart_db",
"commerce_subscription_db",
"Cookies",
"databases",
"Extension Rules",
"Extension Scripts",
"Extension State",
"Extensions",
"Favicons",
"File System",
"History",
"IndexedDB",
"Local App Settings",
"Local Extension Settings",
"Local Storage",
"Login Data",
"Login Data For Account",
"optimization_guide_hint_cache_store",
"optimization_guide_model_and_features_store",
"Managed Extension Settings",
"persisted_state_db",
"Platform Notifications",
"QuotaManager",
"Safe Browsing Cookies",
"Safe Browsing Network",
"Service Worker",
"Session Storage",
"Sessions",
"SharedStorage",
"Shortcuts",
"Storage",
"Sync App Settings",
"Sync Extension Settings",
"Top Sites",
"Visited Links",
"Web Applications",
"Web Data",
"WebStorage"};
// The base names of files/dirs that are required by both ash and lacros and
// thus should be copied to lacros while keeping the original files/dirs in ash
// data dir.
constexpr const char* const kNeedCopyForMoveDataPaths[]{
"DNR Extension Rules", "Extension Cookies", "shared_proto_db"};
// The same as `kNeedCopyDataPathsForMove` + "Preferences".
constexpr const char* const kNeedCopyForCopyDataPaths[]{
"DNR Extension Rules", "Extension Cookies", "Preferences",
"shared_proto_db"};
// List of extension ids to be kept in Ash.
// TODO(crbug.com/1302613): make sure this is the complete list.
constexpr const char* const kExtensionsAshOnly[] = {
"gjjabgpgjpampikjhjpfhneeoapjbjaf", // Google Speech Synthesis Ext. (patts)
"dakbfdmgjiabojdgbiljlhgjbokobjpg", // ESpeak Speech Synthesis Extension
"jacnkoglebceckolkoapelihnglgaicd", // Enhanced Network Tts Extension
"klbcgckkldhdhonijdbnhhaiedfkllef", // Select to Speak Extension
"egfdjlfmgnehecnclamagfafdccgfndp", // Accessibility Common Extension
"mndnfokpggljbaajbnioimlmbfngpief", // Chrome Vox Extension
"pmehocpgjmkenlokgjfkaichfjdhpeol", // Switch Access Extension
"jddehjeebkoimngcbdkaahpobgicbffp", // Braille IME (in IME allowlist)
"mppnpdlheglhdfmldimlhpnegondlapf", // Keyboard App Extension
"mecfefiddjlmabpeilblgegnbioikfmp", // sign in profile testing extension
"behllobkkfkfnphdnhnkndlbkcpglgmj", // guest mode test extension
"honijodknafkokifofgiaalefdiedpko", // Help App
"pmfjbimdmchhbnneeidfognadeopoehp", // Image Loader Extension
"cnbgggchhmkkdmeppjobngjoejnihlei", // Arc Support (Play Store)
};
// List of extension ids to be kept in both Ash and Lacros.
constexpr const char* const kExtensionsBothChromes[] = {
"cfmgaohenjcikllcgjpepfadgbflcjof", // GCSE (Google Corp SSH Extension)
"lfboplenmmjcmpbkeemecobbadnmpfhi", // gnubbyd-v3 (new Gnubby extension)
"beknehfpfkghjoafdifaflglpjkojoco", // gnubbyd
};
// Extensions path.
constexpr char kExtensionsFilePath[] = "Extensions";
// IndexedDB path.
constexpr char kIndexedDBFilePath[] = "IndexedDB";
// `Local Storage` paths.
constexpr char kLocalStorageFilePath[] = "Local Storage";
constexpr char kLocalStorageLeveldbName[] = "leveldb";
// `Sync Data` path.
constexpr char kSyncDataFilePath[] = "Sync Data";
constexpr char kSyncDataLeveldbName[] = "LevelDB";
constexpr char kSyncDataNigoriFileName[] = "Nigori.bin";
// State Store paths.
constexpr const char* const kStateStorePaths[] = {
"Extension Rules",
"Extension Scripts",
"Extension State",
};
// `Storage` path.
constexpr char kStorageFilePath[] = "Storage";
constexpr char kStorageExtFilePath[] = "ext";
// Values used for the kBrowserDataMigrationMode flag.
constexpr char kCopySwitchValue[] = "copy"; // Corresponds to kCopy.
constexpr char kMoveSwitchValue[] = "move"; // Corresponds to KMove.
// The type of LevelDB schema.
enum class LevelDBType {
kLocalStorage = 0,
kStateStore = 1,
};
// Map from ExtensionID -> { leveldb keys..}.
using ExtensionKeys = std::map<std::string, std::vector<std::string>>;
// Structure containing both IndexedDB paths for an extension.
struct IndexedDBPaths {
base::FilePath blob_path;
base::FilePath leveldb_path;
};
// Structure containing Ash and Lacros's version of Preferences.
struct PreferencesContents {
std::string ash;
std::string lacros;
};
// Chrome instance type (Ash or Lacros).
enum class ChromeType {
kAsh,
kLacros,
};
// Preferences's keys that have to be split between Ash and Lacros
// based on extension id.
constexpr const char* kSplitPreferencesKeys[] = {
"app_list.local_state", "extensions.pinned_extensions",
"extensions.settings", "extensions.toolbar",
"updateclientdata.apps", "web_apps.web_app_ids",
};
// Preferences's keys that should not be migrated to Lacros.
constexpr const char* kAshOnlyPreferencesKeys[] = {
"fcm.invalidation.client_id_cache",
"invalidation.active_registration_token",
"invalidation.per_sender_active_registration_tokens",
"invalidation.per_sender_client_id_cache",
"invalidation.per_sender_registered_for_invalidation",
"invalidation.per_sender_topics_to_handler",
"invalidation.registered_for_invalidation",
"invalidation.topics_to_handler",
};
// Preferences's key that has to be moved to Lacros, and cleared in Ash.
constexpr const char* kLacrosOnlyPreferencesKeys[] = {
"sync.cache_guid",
};
// List of data types in Sync Data that have to stay in Ash and Ash only.
static_assert(43 == syncer::GetNumModelTypes(),
"If adding a new sync data type, update the lists below if"
" you want to keep the new data type in Ash only.");
constexpr syncer::ModelType kAshOnlySyncDataTypes[] = {
syncer::ModelType::APP_LIST,
syncer::ModelType::ARC_PACKAGE,
syncer::ModelType::OS_PREFERENCES,
syncer::ModelType::OS_PRIORITY_PREFERENCES,
syncer::ModelType::PRINTERS,
syncer::ModelType::PRINTERS_AUTHORIZATION_SERVERS,
syncer::ModelType::WIFI_CONFIGURATIONS,
syncer::ModelType::WORKSPACE_DESK,
};
constexpr char kTotalSize[] = "Ash.UserDataStatsRecorder.DataSize.TotalSize";
// UMA name prefix to record sizes of files/dirs in profile data directory. The
// name unique to each file/dir is appended to the end to create a full UMA name
// as follows `Ash.UserDataStatsRecorder.DataSize.{ItemName}`.
constexpr char kUserDataStatsRecorderDataSize[] =
"Ash.UserDataStatsRecorder.DataSize.";
// Files/dirs that is not assigned a unique uma name is given this name.
constexpr char kUnknownUMAName[] = "Unknown";
constexpr int64_t kBytesInOneMB = 1024 * 1024;
// The size of disk space that should be kept free after migration. This is
// important since crypotohome conducts an aggressive disk cleanup if free disk
// space becomes less than 768MB. The buffer is rounded up to 1GB.
constexpr uint64_t kBuffer = 1024LL * 1024 * 1024;
// 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_;
};
// This is used to describe top level entries inside ash-chrome's profile data
// directory.
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;
};
// `TargetItems` should hold `TargetItem`s of the same `ItemType`.
struct TargetItems {
TargetItems();
~TargetItems();
TargetItems(TargetItems&&);
std::vector<TargetItem> items;
// The sum of the sizes of `TargetItem`s in `items`.
int64_t total_size = 0;
};
// Specifies the type of `TargetItem`
enum class ItemType {
kLacros = 0, // Item that should be moved to lacros profile directory.
kRemainInAsh = 1, // Item that should remain in ash.
kNeedCopyForMove =
2, // Item that should be copied to lacros during move migration.
kNeedCopyForCopy = 3, // Item that should be copied to lacros during copy
// migration. This is kNeedCopyForMove + "Preferences".
kDeletable = 4, // Item that can be deleted to free up space i.e. cache.
};
// It enumerates the file/dirs in the given directory and returns items of
// `type`. E.g. `GetTargetItems(path, ItemType::kLacros)` will get all items
// that should be moved to lacros.
TargetItems GetTargetItems(const base::FilePath& original_profile_dir,
ItemType type);
// Checks if there is enough disk space to migration to be carried out safely.
// that needs to be copied.
bool HasEnoughDiskSpace(int64_t total_copy_size,
const base::FilePath& original_profile_dir);
// Returns extra bytes that has to be freed for the migration to be carried out
// if there are `total_copy_size` bytes of copying to be done. Returns 0 if no
// extra space needs to be freed.
uint64_t ExtraBytesRequiredToBeFreed(
int64_t total_copy_size,
const base::FilePath& original_profile_dir);
// Injects the bytes to be returned by ExtraBytesRequiredToBeFreed above
// in RAII manner.
class ScopedExtraBytesRequiredToBeFreedForTesting {
public:
explicit ScopedExtraBytesRequiredToBeFreedForTesting(uint64_t bytes);
~ScopedExtraBytesRequiredToBeFreedForTesting();
};
// Copies `items` to `to_dir`.
bool CopyTargetItems(const base::FilePath& to_dir,
const TargetItems& items,
CancelFlag* cancel_flag,
MigrationProgressTracker* progress_tracker);
// Copies `item` to location pointed by `dest`. Returns true on success and
// false on failure.
bool CopyTargetItem(const 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.
bool CopyDirectory(const base::FilePath& from_path,
const base::FilePath& to_path,
CancelFlag* cancel_flag,
MigrationProgressTracker* progress_tracker);
// Creates a hard link from `from_file` to `to_file`. Use it on a file and not a
// directory. Any parent directory of `to_file` should already exist. This will
// fail if `to_dir` already exists.
bool CreateHardLink(const base::FilePath& from_file,
const base::FilePath& to_file);
// Copies the content of `from_dir` to `to_dir` recursively similar to
// `CopyDirectory` while skipping symlinks. Unlike `CopyDirectory` it creates
// hard links for the files from `from_dir` to `to_dir`. If `to_dir`
// already exists, then this will fail.
bool CopyDirectoryByHardLinks(const base::FilePath& from_dir,
const base::FilePath& to_dir);
// Copies `items` to `to_dir` by calling `CreateHardLink()` for files and
// `CopyDirectoryBeHardLinks()` for directories.
bool CopyTargetItemsByHardLinks(const base::FilePath& to_dir,
const TargetItems& items,
CancelFlag* cancel_flag);
// Records the sizes of `TargetItem`s.
void RecordTargetItemSizes(const std::vector<TargetItem>& items);
// Records `size` of the file/dir pointed by `path`. If it is a directory, the
// size is the recursively accumulated sizes of contents inside.
void RecordUserDataSize(const base::FilePath& path, int64_t size);
// Collects migration specific UMAs without actually running the migration. It
// does not check if lacros is enabled.
void DryRunToCollectUMA(const base::FilePath& profile_data_dir);
// Given a leveldb instance and its type, output a map from
// ExtensionID -> { keys associated with the extension... }.
leveldb::Status GetExtensionKeys(leveldb::DB* db,
LevelDBType leveldb_type,
ExtensionKeys* result);
// Returns UMA name for `path`. Returns `kUnknownUMAName` if `path` is not in
// `kPathNamePairs`.
std::string GetUMAItemName(const base::FilePath& path);
// Similar to `base::ComputeDirectorySize()` this computes the sum of all files
// under `dir_path` recursively while skipping symlinks.
int64_t ComputeDirectorySizeWithoutLinks(const base::FilePath& dir_path);
// Record the total size of the user's profile data directory in MB.
void RecordTotalSize(int64_t size);
// Given an extension id, return the paths of the associated blob
// and leveldb directories inside IndexedDB.
IndexedDBPaths GetIndexedDBPaths(const base::FilePath& profile_path,
const char* extension_id);
// Migrate the LevelDB instance at `original_path` to `target_path`,
// Filter out all the extensions that are not in `kExtensionsAshOnly`.
// `leveldb_type` determines the schema type.
bool MigrateLevelDB(const base::FilePath& original_path,
const base::FilePath& target_path,
const LevelDBType leveldb_type);
// Migrate Sync Data's LevelDB instance at `original_path` to Ash and Lacros.
// For Ash, filter out the data types that are not meant to be ported to Lacros.
// For Lacros, filter out the data types that are meant to stay in Ash.
bool MigrateSyncDataLevelDB(const base::FilePath& original_path,
const base::FilePath& ash_target_path,
const base::FilePath& lacros_target_path);
// Manipulates the given representation of Preferences (`root_dict`)
// so that the given key only contains values relevant to Ash or
// Lacros, depending on `chrome_type`.
//
// If the entry in `root_dict` at `key` is a dict in the format
// { <AppId> : { ... }, ... }, it will change the dict to contain only
// AppIds of extensions meant to be in `chrome_type` (Ash or Lacros).
//
// If the entry is a list in the format [ <AppId>, ... ], it will
// change the list to contain only AppIds of extensions meant to be
// in `chrome_type` (Ash or Lacros).
//
// If the entry is a list in any other format, if it doesn't exist,
// or if it's not container type, no changes will be performed.
void UpdatePreferencesKeyByType(base::Value::Dict* root_dict,
const base::StringPiece key,
ChromeType chrome_type);
// Given a `original_contents` string containing the original Preferences
// file, return the migrated Ash and Lacros versions of Preferences.
absl::optional<PreferencesContents> MigratePreferencesContents(
const base::StringPiece original_contents);
// Migrate Preferences to Ash and Lacros.
bool MigratePreferences(const base::FilePath& original_path,
const base::FilePath& ash_target_path,
const base::FilePath& lacros_target_path);
// Copy or move IndexedDB objects to Ash's profile directory.
bool MigrateAshIndexedDB(const base::FilePath& src_profile_dir,
const base::FilePath& target_indexed_db_dir,
const char* extension_id,
bool copy);
} // namespace ash::browser_data_migrator_util
#endif // CHROME_BROWSER_ASH_CROSAPI_BROWSER_DATA_MIGRATOR_UTIL_H_