// Copyright (c) 2012 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 "base/prefs/json_pref_store.h" #include #include "base/bind.h" #include "base/callback.h" #include "base/files/file_path.h" #include "base/files/file_util.h" #include "base/json/json_file_value_serializer.h" #include "base/json/json_string_value_serializer.h" #include "base/memory/ref_counted.h" #include "base/metrics/histogram.h" #include "base/prefs/pref_filter.h" #include "base/sequenced_task_runner.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/task_runner_util.h" #include "base/threading/sequenced_worker_pool.h" #include "base/time/default_clock.h" #include "base/values.h" // Result returned from internal read tasks. struct JsonPrefStore::ReadResult { public: ReadResult(); ~ReadResult(); scoped_ptr value; PrefReadError error; bool no_dir; private: DISALLOW_COPY_AND_ASSIGN(ReadResult); }; JsonPrefStore::ReadResult::ReadResult() : error(PersistentPrefStore::PREF_READ_ERROR_NONE), no_dir(false) { } JsonPrefStore::ReadResult::~ReadResult() { } namespace { // Some extensions we'll tack on to copies of the Preferences files. const base::FilePath::CharType kBadExtension[] = FILE_PATH_LITERAL("bad"); PersistentPrefStore::PrefReadError HandleReadErrors( const base::Value* value, const base::FilePath& path, int error_code, const std::string& error_msg) { if (!value) { DVLOG(1) << "Error while loading JSON file: " << error_msg << ", file: " << path.value(); switch (error_code) { case JSONFileValueDeserializer::JSON_ACCESS_DENIED: return PersistentPrefStore::PREF_READ_ERROR_ACCESS_DENIED; break; case JSONFileValueDeserializer::JSON_CANNOT_READ_FILE: return PersistentPrefStore::PREF_READ_ERROR_FILE_OTHER; break; case JSONFileValueDeserializer::JSON_FILE_LOCKED: return PersistentPrefStore::PREF_READ_ERROR_FILE_LOCKED; break; case JSONFileValueDeserializer::JSON_NO_SUCH_FILE: return PersistentPrefStore::PREF_READ_ERROR_NO_FILE; break; default: // JSON errors indicate file corruption of some sort. // Since the file is corrupt, move it to the side and continue with // empty preferences. This will result in them losing their settings. // We keep the old file for possible support and debugging assistance // as well as to detect if they're seeing these errors repeatedly. // TODO(erikkay) Instead, use the last known good file. base::FilePath bad = path.ReplaceExtension(kBadExtension); // If they've ever had a parse error before, put them in another bucket. // TODO(erikkay) if we keep this error checking for very long, we may // want to differentiate between recent and long ago errors. bool bad_existed = base::PathExists(bad); base::Move(path, bad); return bad_existed ? PersistentPrefStore::PREF_READ_ERROR_JSON_REPEAT : PersistentPrefStore::PREF_READ_ERROR_JSON_PARSE; } } else if (!value->IsType(base::Value::TYPE_DICTIONARY)) { return PersistentPrefStore::PREF_READ_ERROR_JSON_TYPE; } return PersistentPrefStore::PREF_READ_ERROR_NONE; } // Records a sample for |size| in the Settings.JsonDataReadSizeKilobytes // histogram suffixed with the base name of the JSON file under |path|. void RecordJsonDataSizeHistogram(const base::FilePath& path, size_t size) { std::string spaceless_basename; base::ReplaceChars(path.BaseName().MaybeAsASCII(), " ", "_", &spaceless_basename); // The histogram below is an expansion of the UMA_HISTOGRAM_CUSTOM_COUNTS // macro adapted to allow for a dynamically suffixed histogram name. // Note: The factory creates and owns the histogram. base::HistogramBase* histogram = base::Histogram::FactoryGet( "Settings.JsonDataReadSizeKilobytes." + spaceless_basename, 1, 10000, 50, base::HistogramBase::kUmaTargetedHistogramFlag); histogram->Add(static_cast(size) / 1024); } scoped_ptr ReadPrefsFromDisk( const base::FilePath& path, const base::FilePath& alternate_path) { if (!base::PathExists(path) && !alternate_path.empty() && base::PathExists(alternate_path)) { base::Move(alternate_path, path); } int error_code; std::string error_msg; scoped_ptr read_result( new JsonPrefStore::ReadResult); JSONFileValueDeserializer deserializer(path); read_result->value.reset(deserializer.Deserialize(&error_code, &error_msg)); read_result->error = HandleReadErrors(read_result->value.get(), path, error_code, error_msg); read_result->no_dir = !base::PathExists(path.DirName()); if (read_result->error == PersistentPrefStore::PREF_READ_ERROR_NONE) RecordJsonDataSizeHistogram(path, deserializer.get_last_read_size()); return read_result.Pass(); } } // namespace // static scoped_refptr JsonPrefStore::GetTaskRunnerForFile( const base::FilePath& filename, base::SequencedWorkerPool* worker_pool) { std::string token("json_pref_store-"); token.append(filename.AsUTF8Unsafe()); return worker_pool->GetSequencedTaskRunnerWithShutdownBehavior( worker_pool->GetNamedSequenceToken(token), base::SequencedWorkerPool::BLOCK_SHUTDOWN); } JsonPrefStore::JsonPrefStore( const base::FilePath& filename, const scoped_refptr& sequenced_task_runner, scoped_ptr pref_filter) : JsonPrefStore(filename, base::FilePath(), sequenced_task_runner, pref_filter.Pass()) { } JsonPrefStore::JsonPrefStore( const base::FilePath& filename, const base::FilePath& alternate_filename, const scoped_refptr& sequenced_task_runner, scoped_ptr pref_filter) : path_(filename), alternate_path_(alternate_filename), sequenced_task_runner_(sequenced_task_runner), prefs_(new base::DictionaryValue()), read_only_(false), writer_(filename, sequenced_task_runner), pref_filter_(pref_filter.Pass()), initialized_(false), filtering_in_progress_(false), pending_lossy_write_(false), read_error_(PREF_READ_ERROR_NONE), write_count_histogram_(writer_.commit_interval(), path_) { DCHECK(!path_.empty()); } bool JsonPrefStore::GetValue(const std::string& key, const base::Value** result) const { DCHECK(CalledOnValidThread()); base::Value* tmp = NULL; if (!prefs_->Get(key, &tmp)) return false; if (result) *result = tmp; return true; } void JsonPrefStore::AddObserver(PrefStore::Observer* observer) { DCHECK(CalledOnValidThread()); observers_.AddObserver(observer); } void JsonPrefStore::RemoveObserver(PrefStore::Observer* observer) { DCHECK(CalledOnValidThread()); observers_.RemoveObserver(observer); } bool JsonPrefStore::HasObservers() const { DCHECK(CalledOnValidThread()); return observers_.might_have_observers(); } bool JsonPrefStore::IsInitializationComplete() const { DCHECK(CalledOnValidThread()); return initialized_; } bool JsonPrefStore::GetMutableValue(const std::string& key, base::Value** result) { DCHECK(CalledOnValidThread()); return prefs_->Get(key, result); } void JsonPrefStore::SetValue(const std::string& key, base::Value* value, uint32 flags) { DCHECK(CalledOnValidThread()); DCHECK(value); scoped_ptr new_value(value); base::Value* old_value = NULL; prefs_->Get(key, &old_value); if (!old_value || !value->Equals(old_value)) { prefs_->Set(key, new_value.Pass()); ReportValueChanged(key, flags); } } void JsonPrefStore::SetValueSilently(const std::string& key, base::Value* value, uint32 flags) { DCHECK(CalledOnValidThread()); DCHECK(value); scoped_ptr new_value(value); base::Value* old_value = NULL; prefs_->Get(key, &old_value); if (!old_value || !value->Equals(old_value)) { prefs_->Set(key, new_value.Pass()); ScheduleWrite(flags); } } void JsonPrefStore::RemoveValue(const std::string& key, uint32 flags) { DCHECK(CalledOnValidThread()); if (prefs_->RemovePath(key, NULL)) ReportValueChanged(key, flags); } void JsonPrefStore::RemoveValueSilently(const std::string& key, uint32 flags) { DCHECK(CalledOnValidThread()); prefs_->RemovePath(key, NULL); ScheduleWrite(flags); } bool JsonPrefStore::ReadOnly() const { DCHECK(CalledOnValidThread()); return read_only_; } PersistentPrefStore::PrefReadError JsonPrefStore::GetReadError() const { DCHECK(CalledOnValidThread()); return read_error_; } PersistentPrefStore::PrefReadError JsonPrefStore::ReadPrefs() { DCHECK(CalledOnValidThread()); OnFileRead(ReadPrefsFromDisk(path_, alternate_path_)); return filtering_in_progress_ ? PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE : read_error_; } void JsonPrefStore::ReadPrefsAsync(ReadErrorDelegate* error_delegate) { DCHECK(CalledOnValidThread()); initialized_ = false; error_delegate_.reset(error_delegate); // Weakly binds the read task so that it doesn't kick in during shutdown. base::PostTaskAndReplyWithResult( sequenced_task_runner_.get(), FROM_HERE, base::Bind(&ReadPrefsFromDisk, path_, alternate_path_), base::Bind(&JsonPrefStore::OnFileRead, AsWeakPtr())); } void JsonPrefStore::CommitPendingWrite() { DCHECK(CalledOnValidThread()); // Schedule a write for any lossy writes that are outstanding to ensure that // they get flushed when this function is called. SchedulePendingLossyWrites(); if (writer_.HasPendingWrite() && !read_only_) writer_.DoScheduledWrite(); } void JsonPrefStore::SchedulePendingLossyWrites() { if (pending_lossy_write_) writer_.ScheduleWrite(this); } void JsonPrefStore::ReportValueChanged(const std::string& key, uint32 flags) { DCHECK(CalledOnValidThread()); if (pref_filter_) pref_filter_->FilterUpdate(key); FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnPrefValueChanged(key)); ScheduleWrite(flags); } void JsonPrefStore::RegisterOnNextSuccessfulWriteCallback( const base::Closure& on_next_successful_write) { DCHECK(CalledOnValidThread()); writer_.RegisterOnNextSuccessfulWriteCallback(on_next_successful_write); } void JsonPrefStore::OnFileRead(scoped_ptr read_result) { DCHECK(CalledOnValidThread()); DCHECK(read_result); scoped_ptr unfiltered_prefs(new base::DictionaryValue); read_error_ = read_result->error; bool initialization_successful = !read_result->no_dir; if (initialization_successful) { switch (read_error_) { case PREF_READ_ERROR_ACCESS_DENIED: case PREF_READ_ERROR_FILE_OTHER: case PREF_READ_ERROR_FILE_LOCKED: case PREF_READ_ERROR_JSON_TYPE: case PREF_READ_ERROR_FILE_NOT_SPECIFIED: read_only_ = true; break; case PREF_READ_ERROR_NONE: DCHECK(read_result->value.get()); unfiltered_prefs.reset( static_cast(read_result->value.release())); break; case PREF_READ_ERROR_NO_FILE: // If the file just doesn't exist, maybe this is first run. In any case // there's no harm in writing out default prefs in this case. break; case PREF_READ_ERROR_JSON_PARSE: case PREF_READ_ERROR_JSON_REPEAT: break; case PREF_READ_ERROR_ASYNCHRONOUS_TASK_INCOMPLETE: // This is a special error code to be returned by ReadPrefs when it // can't complete synchronously, it should never be returned by the read // operation itself. NOTREACHED(); break; case PREF_READ_ERROR_LEVELDB_IO: case PREF_READ_ERROR_LEVELDB_CORRUPTION_READ_ONLY: case PREF_READ_ERROR_LEVELDB_CORRUPTION: // These are specific to LevelDBPrefStore. NOTREACHED(); case PREF_READ_ERROR_MAX_ENUM: NOTREACHED(); break; } } if (pref_filter_) { filtering_in_progress_ = true; const PrefFilter::PostFilterOnLoadCallback post_filter_on_load_callback( base::Bind( &JsonPrefStore::FinalizeFileRead, AsWeakPtr(), initialization_successful)); pref_filter_->FilterOnLoad(post_filter_on_load_callback, unfiltered_prefs.Pass()); } else { FinalizeFileRead(initialization_successful, unfiltered_prefs.Pass(), false); } } JsonPrefStore::~JsonPrefStore() { CommitPendingWrite(); } bool JsonPrefStore::SerializeData(std::string* output) { DCHECK(CalledOnValidThread()); pending_lossy_write_ = false; write_count_histogram_.RecordWriteOccured(); if (pref_filter_) pref_filter_->FilterSerializeData(prefs_.get()); JSONStringValueSerializer serializer(output); // Not pretty-printing prefs shrinks pref file size by ~30%. To obtain // readable prefs for debugging purposes, you can dump your prefs into any // command-line or online JSON pretty printing tool. serializer.set_pretty_print(false); return serializer.Serialize(*prefs_); } void JsonPrefStore::FinalizeFileRead(bool initialization_successful, scoped_ptr prefs, bool schedule_write) { DCHECK(CalledOnValidThread()); filtering_in_progress_ = false; if (!initialization_successful) { FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnInitializationCompleted(false)); return; } prefs_ = prefs.Pass(); initialized_ = true; if (schedule_write) ScheduleWrite(DEFAULT_PREF_WRITE_FLAGS); if (error_delegate_ && read_error_ != PREF_READ_ERROR_NONE) error_delegate_->OnError(read_error_); FOR_EACH_OBSERVER(PrefStore::Observer, observers_, OnInitializationCompleted(true)); return; } void JsonPrefStore::ScheduleWrite(uint32 flags) { if (read_only_) return; if (flags & LOSSY_PREF_WRITE_FLAG) pending_lossy_write_ = true; else writer_.ScheduleWrite(this); } // NOTE: This value should NOT be changed without renaming the histogram // otherwise it will create incompatible buckets. const int32_t JsonPrefStore::WriteCountHistogram::kHistogramWriteReportIntervalMins = 5; JsonPrefStore::WriteCountHistogram::WriteCountHistogram( const base::TimeDelta& commit_interval, const base::FilePath& path) : WriteCountHistogram(commit_interval, path, scoped_ptr(new base::DefaultClock)) { } JsonPrefStore::WriteCountHistogram::WriteCountHistogram( const base::TimeDelta& commit_interval, const base::FilePath& path, scoped_ptr clock) : commit_interval_(commit_interval), path_(path), clock_(clock.release()), report_interval_( base::TimeDelta::FromMinutes(kHistogramWriteReportIntervalMins)), last_report_time_(clock_->Now()), writes_since_last_report_(0) { } JsonPrefStore::WriteCountHistogram::~WriteCountHistogram() { ReportOutstandingWrites(); } void JsonPrefStore::WriteCountHistogram::RecordWriteOccured() { ReportOutstandingWrites(); ++writes_since_last_report_; } void JsonPrefStore::WriteCountHistogram::ReportOutstandingWrites() { base::Time current_time = clock_->Now(); base::TimeDelta time_since_last_report = current_time - last_report_time_; if (time_since_last_report <= report_interval_) return; // If the time since the last report exceeds the report interval, report all // the writes since the last report. They must have all occurred in the same // report interval. base::HistogramBase* histogram = GetHistogram(); histogram->Add(writes_since_last_report_); // There may be several report intervals that elapsed that don't have any // writes in them. Report these too. int64 total_num_intervals_elapsed = (time_since_last_report / report_interval_); for (int64 i = 0; i < total_num_intervals_elapsed - 1; ++i) histogram->Add(0); writes_since_last_report_ = 0; last_report_time_ += total_num_intervals_elapsed * report_interval_; } base::HistogramBase* JsonPrefStore::WriteCountHistogram::GetHistogram() { std::string spaceless_basename; base::ReplaceChars(path_.BaseName().MaybeAsASCII(), " ", "_", &spaceless_basename); std::string histogram_name = "Settings.JsonDataWriteCount." + spaceless_basename; // The min value for a histogram is 1. The max value is the maximum number of // writes that can occur in the window being recorded. The number of buckets // used is the max value (plus the underflow/overflow buckets). int32_t min_value = 1; int32_t max_value = report_interval_ / commit_interval_; int32_t num_buckets = max_value + 1; // NOTE: These values should NOT be changed without renaming the histogram // otherwise it will create incompatible buckets. DCHECK_EQ(30, max_value); DCHECK_EQ(31, num_buckets); // The histogram below is an expansion of the UMA_HISTOGRAM_CUSTOM_COUNTS // macro adapted to allow for a dynamically suffixed histogram name. // Note: The factory creates and owns the histogram. base::HistogramBase* histogram = base::Histogram::FactoryGet( histogram_name, min_value, max_value, num_buckets, base::HistogramBase::kUmaTargetedHistogramFlag); return histogram; }