blob: bd7c285618733235bbd34a24d5814d66151dc4e8 [file] [log] [blame]
// Copyright 2015 The Chromium OS 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 "settingsd/simple_settings_map.h"
#include <algorithm>
#include <base/logging.h>
#include "settingsd/identifier_utils.h"
#include "settingsd/settings_document.h"
namespace settingsd {
SimpleSettingsMap::SimpleSettingsMap() {}
SimpleSettingsMap::~SimpleSettingsMap() {}
BlobRef SimpleSettingsMap::GetValue(const Key& key) const {
const auto it = value_map_.find(key);
return it == value_map_.end() ? BlobRef() : it->second->GetValue(key);
}
std::set<Key> SimpleSettingsMap::GetKeys(const Key& prefix) const {
std::set<Key> keys;
for (const auto& entry : utils::GetRange(prefix, value_map_))
keys.insert(entry.first);
return keys;
}
bool SimpleSettingsMap::HasLaterValueAssignment(
const Key& key,
const VersionStamp& lower_bound) {
const auto entry = value_map_.find(key);
return entry != value_map_.end() &&
entry->second->GetVersionStamp().IsAfter(lower_bound);
}
bool SimpleSettingsMap::HasLaterSubtreeDeletion(
const Key& prefix,
const VersionStamp& lower_bound) const {
Key current_prefix = prefix;
do {
const auto deletion = deletion_map_.find(current_prefix);
if (deletion != deletion_map_.end() &&
deletion->second->GetVersionStamp().IsAfter(lower_bound))
return true;
current_prefix = current_prefix.GetParent();
} while (!current_prefix.IsRootKey());
return false;
}
SimpleSettingsMap::DocumentWeakPtrList::iterator
SimpleSettingsMap::FindDocumentInSortedList(
const SettingsDocument* document_ptr) {
return std::find_if(
documents_.begin(), documents_.end(),
[document_ptr](const std::weak_ptr<const SettingsDocument> doc_ptr) {
return doc_ptr.lock().get() == document_ptr;
});
}
void SimpleSettingsMap::InsertDocumentIntoSortedList(
std::shared_ptr<const SettingsDocument> document) {
const auto pos = std::find_if(
documents_.begin(), documents_.end(),
[&document](const std::weak_ptr<const SettingsDocument>& document_ptr) {
std::shared_ptr<const SettingsDocument> doc = document_ptr.lock();
return doc->GetVersionStamp().IsAfter(document->GetVersionStamp());
});
documents_.insert(pos, document);
}
void SimpleSettingsMap::DeleteSubtree(const Key& prefix,
const VersionStamp& upper_limit,
std::set<Key>* modified_keys) {
// Remove deletions.
const auto deletion_range = utils::GetRange(prefix, deletion_map_);
for (auto it = deletion_range.begin(); it != deletion_range.end();) {
if (it->second->GetVersionStamp().IsBefore(upper_limit))
deletion_map_.erase(it++);
else
++it;
}
// Remove value assignments.
const auto value_range = utils::GetRange(prefix, value_map_);
for (auto it = value_range.begin(); it != value_range.end();) {
if (it->second->GetVersionStamp().IsBefore(upper_limit)) {
if (modified_keys)
modified_keys->insert(it->first);
value_map_.erase(it++);
} else {
++it;
}
}
}
void SimpleSettingsMap::InsertDocumentSubset(
std::shared_ptr<const SettingsDocument> document,
const std::set<Key>& prefixes,
std::set<Key>* modified_keys) {
// Convenience shortcuts.
const VersionStamp& version_stamp = document->GetVersionStamp();
for (const auto& prefix : prefixes) {
// Deletions are always processed first, so that value assignments in key
// prefixes affected by a deletion in the same document are not clobbered by
// those deletions.
for (const auto& prefix_deletion : document->GetDeletions(prefix)) {
// Check if there is a later deletion in place higher up in the
// ancestorial key hierarchy which renders this deletion obsolete.
if (!HasLaterSubtreeDeletion(prefix_deletion, version_stamp)) {
DeleteSubtree(prefix_deletion, version_stamp, modified_keys);
deletion_map_[prefix_deletion] = document;
}
}
// After having processed all pending deletions, insert the new values into
// |value_map_|.
for (const auto& key : document->GetKeys(prefix)) {
// Check if there is a later deletion in place higher up in the
// ancestorial key hierarchy or whether there is a more recent value
// assignment which would render this value assignment obsolete.
if (!HasLaterSubtreeDeletion(key, version_stamp) &&
!HasLaterValueAssignment(key, version_stamp)) {
if (modified_keys)
modified_keys->insert(key);
value_map_[key] = document;
}
}
}
}
bool SimpleSettingsMap::InsertDocument(
const SettingsDocument* document,
std::set<Key>* modified_keys,
std::vector<const SettingsDocument*>* unreferenced_documents) {
// |unreferenced_documents_| should be empty.
DCHECK(unreferenced_documents_.empty());
// Check if |document| has a prefix collision with a previously inserted,
// concurrent document. In that case, abort.
for (const auto& doc_ptr : documents_) {
std::shared_ptr<const SettingsDocument> doc = doc_ptr.lock();
if (doc->GetVersionStamp().IsConcurrent(document->GetVersionStamp()) &&
SettingsDocument::HasOverlap(*document, *doc))
return false;
}
// NOTE: The vector |documents_| actually has ownership of the
// SettingsDocuments. Here we use the shared_ptr merely as a notification
// system to inform SimpleSettingsMap of SettingsDocuments whose reference
// count has dropped to zero. To accomplish this, we pass
// OnDocumentUnreferenced as a deleter for
// std::shared_ptr<const SettingsDocument>.
std::shared_ptr<const SettingsDocument> document_ptr(
document, std::bind(&SimpleSettingsMap::OnDocumentUnreferenced, this,
std::placeholders::_1));
// Insert the whole document into |value_map_| and |deletion_map_|.
std::set<Key> root_set;
root_set.insert(Key());
InsertDocumentSubset(document_ptr, root_set, modified_keys);
// Insert the document into the sorted vector of active documents only if it
// is currently providing a setting.
if (!document_ptr.unique())
InsertDocumentIntoSortedList(document_ptr);
// If the document to be inserted has not become active, discarding the last
// shared_ptr referring to it will add it to the list of unreferenced
// documents.
document_ptr.reset();
// If the out parameter |unreferenced_documents| is non-null, output the list
// of unreferenced documents.
if (unreferenced_documents)
std::swap(*unreferenced_documents, unreferenced_documents_);
unreferenced_documents_.clear();
return true;
}
void SimpleSettingsMap::RemoveDocument(
const SettingsDocument* document_ptr,
std::set<Key>* modified_keys,
std::vector<const SettingsDocument*>* unreferenced_documents) {
// |unreferenced_documents_| should be empty.
DCHECK(unreferenced_documents_.empty());
auto position = FindDocumentInSortedList(document_ptr);
// If the SettingsDocument |document_ptr| is not in the list of active
// documents, there is nothing to do.
if (position == documents_.end())
return;
// Acquire a shared_ptr.
std::shared_ptr<const SettingsDocument> document(*position);
// Get a list of all keys from |document| currently providing values and
// remove them from the |value_map_|.
std::set<Key> prefixes_to_restore;
for (auto it = value_map_.begin(); it != value_map_.end();) {
if (it->second == document) {
prefixes_to_restore.insert(it->first);
if (modified_keys)
modified_keys->insert(it->first);
value_map_.erase(it++);
} else {
++it;
}
}
// Add all keys identifying prefixes which have been deleted by |document| and
// remove them from the |deletion_map_|.
for (auto it = deletion_map_.begin(); it != deletion_map_.end();) {
if (it->second == document) {
prefixes_to_restore.insert(it->first);
deletion_map_.erase(it++);
} else {
++it;
}
}
// |document| should be the only remaining shared_ptr to the settings
// document at this point.
DCHECK_EQ(1, document.use_count());
// Walk the sorted list of documents from the earliest document up to (but not
// including) the current document and reinstall links in the |value_map_| and
// |deletion_map_| which are now shining through.
DocumentWeakPtrList::reverse_iterator current_document_ptr(position);
for (; current_document_ptr != documents_.rend(); ++current_document_ptr) {
std::shared_ptr<const SettingsDocument> current_document =
current_document_ptr->lock();
// Install the relevant prefixes into |value_map_| and |deletion_map_|.
InsertDocumentSubset(current_document, prefixes_to_restore, modified_keys);
}
// Trigger clean-up of the document to be removed and addition to the list of
// unreferenced documents by discarding the last shared_ptr referring to it.
document.reset();
// This should have triggered the clean-up of |document|.
DCHECK(documents_.end() == FindDocumentInSortedList(document_ptr));
// If the out parameter |unreferenced_documents| is non-null, output the list
// of unreferenced documents.
if (unreferenced_documents)
std::swap(*unreferenced_documents, unreferenced_documents_);
unreferenced_documents_.clear();
}
void SimpleSettingsMap::OnDocumentUnreferenced(
const SettingsDocument* document) {
DocumentWeakPtrList::iterator position =
std::remove_if(documents_.begin(), documents_.end(),
[](const std::weak_ptr<const SettingsDocument>& doc) {
return doc.expired();
});
documents_.erase(position, documents_.end());
unreferenced_documents_.push_back(document);
}
void SimpleSettingsMap::Clear() {
deletion_map_.clear();
value_map_.clear();
}
} // namespace settingsd