blob: 315665ff5b4434f5e79833cd333b82322e0be3bd [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_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",
"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",
"Shortcuts",
"Storage",
"Sync App Settings",
"Sync Extension Settings",
"Top Sites",
"Visited Links",
"Web Applications",
"Web Data"};
// 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", "Policy", "shared_proto_db"};
// The same as `kNeedCopyDataPathsForMove` + "Preferences".
constexpr const char* const kNeedCopyForCopyDataPaths[]{
"DNR Extension Rules", "Extension Cookies", "Policy", "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";
// State Store paths.
constexpr const char* const kStateStorePaths[] = {
"Extension Rules",
"Extension Scripts",
"Extension State",
};
// `Storage` path.
constexpr char kStorageFilePath[] = "Storage";
constexpr char kStorageExtFilePath[] = "ext";
// 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(40 == 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 MigrateSyncData(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_