// Copyright 2014 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. #include "components/user_prefs/tracked/tracked_preferences_migration.h" #include #include "base/bind.h" #include "base/callback.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/metrics/histogram.h" #include "base/values.h" #include "components/user_prefs/tracked/dictionary_hash_store_contents.h" #include "components/user_prefs/tracked/hash_store_contents.h" #include "components/user_prefs/tracked/interceptable_pref_filter.h" #include "components/user_prefs/tracked/pref_hash_store.h" #include "components/user_prefs/tracked/pref_hash_store_transaction.h" namespace { class TrackedPreferencesMigrator : public base::RefCounted { public: TrackedPreferencesMigrator( const std::set& unprotected_pref_names, const std::set& protected_pref_names, const base::Callback& unprotected_store_cleaner, const base::Callback& protected_store_cleaner, const base::Callback& register_on_successful_unprotected_store_write_callback, const base::Callback& register_on_successful_protected_store_write_callback, scoped_ptr unprotected_pref_hash_store, scoped_ptr protected_pref_hash_store, scoped_ptr legacy_pref_hash_store, InterceptablePrefFilter* unprotected_pref_filter, InterceptablePrefFilter* protected_pref_filter); private: friend class base::RefCounted; enum PrefFilterID { UNPROTECTED_PREF_FILTER, PROTECTED_PREF_FILTER }; ~TrackedPreferencesMigrator(); // Stores the data coming in from the filter identified by |id| into this // class and then calls MigrateIfReady(); void InterceptFilterOnLoad( PrefFilterID id, const InterceptablePrefFilter::FinalizeFilterOnLoadCallback& finalize_filter_on_load, scoped_ptr prefs); // Proceeds with migration if both |unprotected_prefs_| and |protected_prefs_| // have been set. void MigrateIfReady(); const std::set unprotected_pref_names_; const std::set protected_pref_names_; const base::Callback unprotected_store_cleaner_; const base::Callback protected_store_cleaner_; const base::Callback register_on_successful_unprotected_store_write_callback_; const base::Callback register_on_successful_protected_store_write_callback_; InterceptablePrefFilter::FinalizeFilterOnLoadCallback finalize_unprotected_filter_on_load_; InterceptablePrefFilter::FinalizeFilterOnLoadCallback finalize_protected_filter_on_load_; scoped_ptr unprotected_pref_hash_store_; scoped_ptr protected_pref_hash_store_; scoped_ptr legacy_pref_hash_store_; scoped_ptr unprotected_prefs_; scoped_ptr protected_prefs_; DISALLOW_COPY_AND_ASSIGN(TrackedPreferencesMigrator); }; // Invokes |store_cleaner| for every |keys_to_clean|. void CleanupPrefStore( const base::Callback& store_cleaner, const std::set& keys_to_clean) { for (std::set::const_iterator it = keys_to_clean.begin(); it != keys_to_clean.end(); ++it) { store_cleaner.Run(*it); } } // If |wait_for_commit_to_destination_store|: schedules (via // |register_on_successful_destination_store_write_callback|) a cleanup of the // |keys_to_clean| from the source pref store (through |source_store_cleaner|) // once the destination pref store they were migrated to was successfully // written to disk. Otherwise, executes the cleanup right away. void ScheduleSourcePrefStoreCleanup( const base::Callback& register_on_successful_destination_store_write_callback, const base::Callback& source_store_cleaner, const std::set& keys_to_clean, bool wait_for_commit_to_destination_store) { DCHECK(!keys_to_clean.empty()); if (wait_for_commit_to_destination_store) { register_on_successful_destination_store_write_callback.Run( base::Bind(&CleanupPrefStore, source_store_cleaner, keys_to_clean)); } else { CleanupPrefStore(source_store_cleaner, keys_to_clean); } } // Removes hashes for |migrated_pref_names| from |origin_pref_store| using // the configuration/implementation in |origin_pref_hash_store|. void CleanupMigratedHashes(const std::set& migrated_pref_names, PrefHashStore* origin_pref_hash_store, base::DictionaryValue* origin_pref_store) { scoped_ptr transaction( origin_pref_hash_store->BeginTransaction(scoped_ptr( new DictionaryHashStoreContents(origin_pref_store)))); for (std::set::const_iterator it = migrated_pref_names.begin(); it != migrated_pref_names.end(); ++it) { transaction->ClearHash(*it); } } // Copies the value of each pref in |pref_names| which is set in |old_store|, // but not in |new_store| into |new_store|. Sets |old_store_needs_cleanup| to // true if any old duplicates remain in |old_store| and sets |new_store_altered| // to true if any value was copied to |new_store|. void MigratePrefsFromOldToNewStore(const std::set& pref_names, base::DictionaryValue* old_store, base::DictionaryValue* new_store, PrefHashStore* new_hash_store, HashStoreContents* legacy_hash_store, bool* old_store_needs_cleanup, bool* new_store_altered, bool* used_legacy_pref_hashes) { const base::DictionaryValue* old_hash_store_contents = DictionaryHashStoreContents(old_store).GetContents(); const base::DictionaryValue* legacy_hash_store_contents = legacy_hash_store->GetContents(); scoped_ptr new_hash_store_transaction( new_hash_store->BeginTransaction(scoped_ptr( new DictionaryHashStoreContents(new_store)))); for (std::set::const_iterator it = pref_names.begin(); it != pref_names.end(); ++it) { const std::string& pref_name = *it; const base::Value* value_in_old_store = NULL; // If the destination does not have a hash for this pref we will // unconditionally attempt to move it. bool destination_hash_missing = !new_hash_store_transaction->HasHash(pref_name); // If we migrate the value we will also attempt to migrate the hash. bool migrated_value = false; if (old_store->Get(pref_name, &value_in_old_store)) { // Whether this value ends up being copied below or was left behind by a // previous incomplete migration, it should be cleaned up. *old_store_needs_cleanup = true; if (!new_store->Get(pref_name, NULL)) { // Copy the value from |old_store| to |new_store| rather than moving it // to avoid data loss should |old_store| be flushed to disk without // |new_store| having equivalently been successfully flushed to disk // (e.g., on crash or in cases where |new_store| is read-only following // a read error on startup). new_store->Set(pref_name, value_in_old_store->DeepCopy()); migrated_value = true; *new_store_altered = true; } } if (destination_hash_missing || migrated_value) { const base::Value* old_hash = NULL; if (old_hash_store_contents) old_hash_store_contents->Get(pref_name, &old_hash); if (!old_hash && legacy_hash_store_contents) { legacy_hash_store_contents->Get(pref_name, &old_hash); if (old_hash) *used_legacy_pref_hashes = true; } if (old_hash) { new_hash_store_transaction->ImportHash(pref_name, old_hash); *new_store_altered = true; } else if (!destination_hash_missing) { // Do not allow values to be migrated without MACs if the destination // already has a MAC (http://crbug.com/414554). Remove the migrated // value in order to provide the same no-op behaviour as if the pref was // added to the wrong file when there was already a value for // |pref_name| in |new_store|. new_store->Remove(pref_name, NULL); *new_store_altered = true; } } } } TrackedPreferencesMigrator::TrackedPreferencesMigrator( const std::set& unprotected_pref_names, const std::set& protected_pref_names, const base::Callback& unprotected_store_cleaner, const base::Callback& protected_store_cleaner, const base::Callback& register_on_successful_unprotected_store_write_callback, const base::Callback& register_on_successful_protected_store_write_callback, scoped_ptr unprotected_pref_hash_store, scoped_ptr protected_pref_hash_store, scoped_ptr legacy_pref_hash_store, InterceptablePrefFilter* unprotected_pref_filter, InterceptablePrefFilter* protected_pref_filter) : unprotected_pref_names_(unprotected_pref_names), protected_pref_names_(protected_pref_names), unprotected_store_cleaner_(unprotected_store_cleaner), protected_store_cleaner_(protected_store_cleaner), register_on_successful_unprotected_store_write_callback_( register_on_successful_unprotected_store_write_callback), register_on_successful_protected_store_write_callback_( register_on_successful_protected_store_write_callback), unprotected_pref_hash_store_(std::move(unprotected_pref_hash_store)), protected_pref_hash_store_(std::move(protected_pref_hash_store)), legacy_pref_hash_store_(std::move(legacy_pref_hash_store)) { // The callbacks bound below will own this TrackedPreferencesMigrator by // reference. unprotected_pref_filter->InterceptNextFilterOnLoad( base::Bind(&TrackedPreferencesMigrator::InterceptFilterOnLoad, this, UNPROTECTED_PREF_FILTER)); protected_pref_filter->InterceptNextFilterOnLoad( base::Bind(&TrackedPreferencesMigrator::InterceptFilterOnLoad, this, PROTECTED_PREF_FILTER)); } TrackedPreferencesMigrator::~TrackedPreferencesMigrator() {} void TrackedPreferencesMigrator::InterceptFilterOnLoad( PrefFilterID id, const InterceptablePrefFilter::FinalizeFilterOnLoadCallback& finalize_filter_on_load, scoped_ptr prefs) { switch (id) { case UNPROTECTED_PREF_FILTER: finalize_unprotected_filter_on_load_ = finalize_filter_on_load; unprotected_prefs_ = std::move(prefs); break; case PROTECTED_PREF_FILTER: finalize_protected_filter_on_load_ = finalize_filter_on_load; protected_prefs_ = std::move(prefs); break; } MigrateIfReady(); } void TrackedPreferencesMigrator::MigrateIfReady() { // Wait for both stores to have been read before proceeding. if (!protected_prefs_ || !unprotected_prefs_) return; bool used_legacy_pref_hashes = false; bool protected_prefs_need_cleanup = false; bool unprotected_prefs_altered = false; MigratePrefsFromOldToNewStore(unprotected_pref_names_, protected_prefs_.get(), unprotected_prefs_.get(), unprotected_pref_hash_store_.get(), legacy_pref_hash_store_.get(), &protected_prefs_need_cleanup, &unprotected_prefs_altered, &used_legacy_pref_hashes); bool unprotected_prefs_need_cleanup = false; bool protected_prefs_altered = false; MigratePrefsFromOldToNewStore(protected_pref_names_, unprotected_prefs_.get(), protected_prefs_.get(), protected_pref_hash_store_.get(), legacy_pref_hash_store_.get(), &unprotected_prefs_need_cleanup, &protected_prefs_altered, &used_legacy_pref_hashes); UMA_HISTOGRAM_BOOLEAN("Settings.MigratedHashesFromLocalState", used_legacy_pref_hashes); if (!unprotected_prefs_altered && !protected_prefs_altered) { // Clean up any MACs that might have been previously migrated from the // various stores. It's safe to leave them behind for a little while as they // will be ignored unless the corresponding value is _also_ present. The // cleanup must be deferred until the MACs have been written to their target // stores, and doing so in a subsequent launch is easier than within the // same process. CleanupMigratedHashes(unprotected_pref_names_, protected_pref_hash_store_.get(), protected_prefs_.get()); CleanupMigratedHashes(protected_pref_names_, unprotected_pref_hash_store_.get(), unprotected_prefs_.get()); legacy_pref_hash_store_->Reset(); } // Hand the processed prefs back to their respective filters. finalize_unprotected_filter_on_load_.Run(std::move(unprotected_prefs_), unprotected_prefs_altered); finalize_protected_filter_on_load_.Run(std::move(protected_prefs_), protected_prefs_altered); if (unprotected_prefs_need_cleanup) { // Schedule a cleanup of the |protected_pref_names_| from the unprotected // prefs once the protected prefs were successfully written to disk (or // do it immediately if |!protected_prefs_altered|). ScheduleSourcePrefStoreCleanup( register_on_successful_protected_store_write_callback_, unprotected_store_cleaner_, protected_pref_names_, protected_prefs_altered); } if (protected_prefs_need_cleanup) { // Schedule a cleanup of the |unprotected_pref_names_| from the protected // prefs once the unprotected prefs were successfully written to disk (or // do it immediately if |!unprotected_prefs_altered|). ScheduleSourcePrefStoreCleanup( register_on_successful_unprotected_store_write_callback_, protected_store_cleaner_, unprotected_pref_names_, unprotected_prefs_altered); } } } // namespace void SetupTrackedPreferencesMigration( const std::set& unprotected_pref_names, const std::set& protected_pref_names, const base::Callback& unprotected_store_cleaner, const base::Callback& protected_store_cleaner, const base::Callback& register_on_successful_unprotected_store_write_callback, const base::Callback& register_on_successful_protected_store_write_callback, scoped_ptr unprotected_pref_hash_store, scoped_ptr protected_pref_hash_store, scoped_ptr legacy_pref_hash_store, InterceptablePrefFilter* unprotected_pref_filter, InterceptablePrefFilter* protected_pref_filter) { scoped_refptr prefs_migrator( new TrackedPreferencesMigrator( unprotected_pref_names, protected_pref_names, unprotected_store_cleaner, protected_store_cleaner, register_on_successful_unprotected_store_write_callback, register_on_successful_protected_store_write_callback, std::move(unprotected_pref_hash_store), std::move(protected_pref_hash_store), std::move(legacy_pref_hash_store), unprotected_pref_filter, protected_pref_filter)); }