| // 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_ |