[go: nahoru, domu]

blob: 110a875577205c04e410055a65da70be89684dea [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/first_party_sets/database/first_party_sets_database.h"
#include <inttypes.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/sequence_checker.h"
#include "base/version.h"
#include "content/browser/first_party_sets/first_party_set_parser.h"
#include "net/base/schemeful_site.h"
#include "net/first_party_sets/first_party_set_entry.h"
#include "net/first_party_sets/first_party_sets_cache_filter.h"
#include "net/first_party_sets/first_party_sets_context_config.h"
#include "net/first_party_sets/global_first_party_sets.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/recovery.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace content {
namespace {
// Version number of the database.
const int kCurrentVersionNumber = 3;
// Earliest version which can use a |kCurrentVersionNumber| database
// without failing.
const int kCompatibleVersionNumber = 2;
// Latest version of the database that cannot be upgraded to
// |kCurrentVersionNumber| without razing the database.
const int kDeprecatedVersionNumber = 1;
const char kRunCountKey[] = "run_count";
[[nodiscard]] bool InitSchema(sql::Database& db) {
static constexpr char kPublicSetsSql[] =
"CREATE TABLE IF NOT EXISTS public_sets("
"version TEXT NOT NULL,"
"site TEXT NOT NULL,"
"primary_site TEXT NOT NULL,"
"site_type INTEGER NOT NULL,"
"PRIMARY KEY(version,site)"
")WITHOUT ROWID";
if (!db.Execute(kPublicSetsSql))
return false;
static constexpr char kBrowserContextSetsVersionSql[] =
"CREATE TABLE IF NOT EXISTS browser_context_sets_version("
"browser_context_id TEXT PRIMARY KEY NOT NULL,"
"public_sets_version TEXT NOT NULL"
")WITHOUT ROWID";
if (!db.Execute(kBrowserContextSetsVersionSql))
return false;
static constexpr char kPublicSetsVersionBrowserContextsSql[] =
"CREATE INDEX IF NOT EXISTS idx_public_sets_version_browser_contexts "
"ON browser_context_sets_version(public_sets_version)";
if (!db.Execute(kPublicSetsVersionBrowserContextsSql))
return false;
static constexpr char kBrowserContextSitesToClearSql[] =
"CREATE TABLE IF NOT EXISTS browser_context_sites_to_clear("
"browser_context_id TEXT NOT NULL,"
"site TEXT NOT NULL,"
"marked_at_run INTEGER NOT NULL,"
"PRIMARY KEY(browser_context_id,site)"
")WITHOUT ROWID";
if (!db.Execute(kBrowserContextSitesToClearSql))
return false;
static constexpr char kMarkedAtRunSitesSql[] =
"CREATE INDEX IF NOT EXISTS idx_marked_at_run_sites "
"ON browser_context_sites_to_clear(marked_at_run)";
if (!db.Execute(kMarkedAtRunSitesSql))
return false;
static constexpr char kBrowserContextsClearedSql[] =
"CREATE TABLE IF NOT EXISTS browser_contexts_cleared("
"browser_context_id TEXT PRIMARY KEY NOT NULL,"
"cleared_at_run INTEGER NOT NULL"
")WITHOUT ROWID";
if (!db.Execute(kBrowserContextsClearedSql))
return false;
static constexpr char kClearedAtRunBrowserContextsSql[] =
"CREATE INDEX IF NOT EXISTS idx_cleared_at_run_browser_contexts "
"ON browser_contexts_cleared(cleared_at_run)";
if (!db.Execute(kClearedAtRunBrowserContextsSql))
return false;
static constexpr char kPolicyConfigurationsSql[] =
"CREATE TABLE IF NOT EXISTS policy_configurations("
"browser_context_id TEXT NOT NULL,"
"site TEXT NOT NULL,"
"primary_site TEXT," // May be NULL if this row represents a deletion.
"PRIMARY KEY(browser_context_id,site)"
")WITHOUT ROWID";
if (!db.Execute(kPolicyConfigurationsSql))
return false;
static constexpr char kManualSetsSql[] =
"CREATE TABLE IF NOT EXISTS manual_sets("
"browser_context_id TEXT NOT NULL,"
"site TEXT NOT NULL,"
"primary_site TEXT NOT NULL,"
"site_type INTEGER NOT NULL,"
"PRIMARY KEY(browser_context_id,site)"
")WITHOUT ROWID";
if (!db.Execute(kManualSetsSql))
return false;
return true;
}
void RecordInitializationStatus(FirstPartySetsDatabase::InitStatus status) {
base::UmaHistogramEnumeration("FirstPartySets.Database.InitStatus", status);
}
} // namespace
FirstPartySetsDatabase::FirstPartySetsDatabase(base::FilePath db_path)
: db_path_(std::move(db_path)) {
DCHECK(db_path_.IsAbsolute());
}
FirstPartySetsDatabase::~FirstPartySetsDatabase() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
bool FirstPartySetsDatabase::PersistSets(
const std::string& browser_context_id,
const net::GlobalFirstPartySets& sets,
const net::FirstPartySetsContextConfig& config) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!LazyInit())
return false;
sql::Transaction transaction(db_.get());
if (!transaction.Begin())
return false;
// Only persist public sets if the version is valid.
if (sets.public_sets_version().IsValid() &&
!SetPublicSets(browser_context_id, sets)) {
return false;
}
if (!InsertManualSets(browser_context_id, sets.manual_sets()))
return false;
if (!InsertPolicyConfigurations(browser_context_id, config))
return false;
return transaction.Commit();
}
bool FirstPartySetsDatabase::SetPublicSets(
const std::string& browser_context_id,
const net::GlobalFirstPartySets& sets) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(db_status_, InitStatus::kSuccess);
DCHECK(sets.public_sets_version().IsValid());
const std::string& version = sets.public_sets_version().GetString();
// Checks if the version of the current public sets is referenced by *any*
// browser context in the public_sets_version table. If so, that means the
// sets already exist in public_sets table and we don't need to write them to
// public_sets table again.
static constexpr char kCheckSql[] =
"SELECT 1 FROM browser_context_sets_version WHERE public_sets_version=?"
"LIMIT 1";
sql::Statement check_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kCheckSql));
check_statement.BindString(0, version);
const bool has_matching_version = check_statement.Step();
if (!check_statement.Succeeded())
return false;
if (!has_matching_version) {
if (!sets.ForEachPublicSetEntry(
[&](const net::SchemefulSite& site,
const net::FirstPartySetEntry& entry) -> bool {
DCHECK(!site.opaque());
DCHECK(!entry.primary().opaque());
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
static constexpr char kInsertSql[] =
"INSERT INTO public_sets(version,site,primary_site,site_type)"
"VALUES(?,?,?,?)";
sql::Statement insert_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kInsertSql));
insert_statement.BindString(0, version);
insert_statement.BindString(1, site.Serialize());
insert_statement.BindString(2, entry.primary().Serialize());
insert_statement.BindInt(3, static_cast<int>(entry.site_type()));
return insert_statement.Run();
})) {
return false;
}
}
// Keeps track of the version used by the given `browser_context_id` in
// browser_context_sets_version table.
static constexpr char kInsertSql[] =
"INSERT OR REPLACE INTO browser_context_sets_version"
"(browser_context_id,public_sets_version)VALUES(?,?)";
sql::Statement insert_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kInsertSql));
insert_statement.BindString(0, browser_context_id);
insert_statement.BindString(1, version);
if (!insert_statement.Run())
return false;
// TODO(shuuran): Garbage collect the public sets no longer used by any
// browser_context_id.
return true;
}
bool FirstPartySetsDatabase::InsertSitesToClear(
const std::string& browser_context_id,
const base::flat_set<net::SchemefulSite>& sites) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!LazyInit())
return false;
sql::Transaction transaction(db_.get());
if (!transaction.Begin())
return false;
for (const auto& site : sites) {
DCHECK(!site.opaque());
static constexpr char kInsertSql[] =
// clang-format off
"INSERT OR REPLACE INTO browser_context_sites_to_clear"
"(browser_context_id,site,marked_at_run)"
"VALUES(?,?,?)";
// clang-format on
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE, kInsertSql));
statement.BindString(0, browser_context_id);
statement.BindString(1, site.Serialize());
statement.BindInt64(2, run_count_);
if (!statement.Run())
return false;
}
return transaction.Commit();
}
bool FirstPartySetsDatabase::InsertBrowserContextCleared(
const std::string& browser_context_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!browser_context_id.empty());
if (!LazyInit())
return false;
static constexpr char kInsertBrowserContextsClearedSql[] =
// clang-format off
"INSERT OR REPLACE INTO browser_contexts_cleared(browser_context_id,cleared_at_run)"
"VALUES(?,?)";
// clang-format on
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE, kInsertBrowserContextsClearedSql));
statement.BindString(0, browser_context_id);
statement.BindInt64(1, run_count_);
return statement.Run();
}
bool FirstPartySetsDatabase::InsertPolicyConfigurations(
const std::string& browser_context_id,
const net::FirstPartySetsContextConfig& config) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(db_status_, InitStatus::kSuccess);
static constexpr char kDeleteSql[] =
"DELETE FROM policy_configurations WHERE browser_context_id=?";
sql::Statement delete_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kDeleteSql));
delete_statement.BindString(0, browser_context_id);
if (!delete_statement.Run())
return false;
return config.ForEachCustomizationEntry(
[&](const net::SchemefulSite& site,
const absl::optional<net::FirstPartySetEntry>& entry) -> bool {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!site.opaque());
static constexpr char kInsertSql[] =
"INSERT INTO "
"policy_configurations(browser_context_id,site,primary_site)"
"VALUES(?,?,?)";
sql::Statement insert_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kInsertSql));
insert_statement.BindString(0, browser_context_id);
insert_statement.BindString(1, site.Serialize());
if (entry.has_value()) {
insert_statement.BindString(2, entry.value().primary().Serialize());
} else {
insert_statement.BindNull(2);
}
return insert_statement.Run();
});
}
bool FirstPartySetsDatabase::InsertManualSets(
const std::string& browser_context_id,
const base::flat_map<net::SchemefulSite, net::FirstPartySetEntry>&
manual_sets) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(db_status_, InitStatus::kSuccess);
static constexpr char kDeleteSql[] =
"DELETE FROM manual_sets WHERE browser_context_id=?";
sql::Statement delete_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kDeleteSql));
delete_statement.BindString(0, browser_context_id);
if (!delete_statement.Run())
return false;
for (const auto& [site, entry] : manual_sets) {
DCHECK(!site.opaque());
static constexpr char kInsertSql[] =
"INSERT INTO "
"manual_sets(browser_context_id,site,primary_site,site_type)"
"VALUES(?,?,?,?)";
sql::Statement insert_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kInsertSql));
insert_statement.BindString(0, browser_context_id);
insert_statement.BindString(1, site.Serialize());
insert_statement.BindString(2, entry.primary().Serialize());
insert_statement.BindInt(3, static_cast<int>(entry.site_type()));
if (!insert_statement.Run())
return false;
}
return true;
}
net::GlobalFirstPartySets FirstPartySetsDatabase::GetGlobalSets(
const std::string& browser_context_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!LazyInit())
return {};
// Query public sets entries.
std::vector<std::pair<net::SchemefulSite, net::FirstPartySetEntry>> entries;
static constexpr char kVersionSql[] =
"SELECT public_sets_version FROM browser_context_sets_version "
"WHERE browser_context_id=?";
sql::Statement version_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kVersionSql));
version_statement.BindString(0, browser_context_id);
std::string version;
if (version_statement.Step()) {
version = version_statement.ColumnString(0);
static constexpr char kSelectSql[] =
"SELECT site,primary_site,site_type FROM public_sets WHERE version=?";
sql::Statement statement(
db_->GetCachedStatement(SQL_FROM_HERE, kSelectSql));
statement.BindString(0, version);
while (statement.Step()) {
absl::optional<net::SchemefulSite> site =
FirstPartySetParser::CanonicalizeRegisteredDomain(
statement.ColumnString(0), /*emit_errors=*/false);
absl::optional<net::SchemefulSite> primary =
FirstPartySetParser::CanonicalizeRegisteredDomain(
statement.ColumnString(1), /*emit_errors=*/false);
absl::optional<net::SiteType> site_type =
net::FirstPartySetEntry::DeserializeSiteType(statement.ColumnInt(2));
// TODO(crbug.com/1314039): Invalid entries should be rare case but
// possible. Consider deleting them from DB.
if (site.has_value() && primary.has_value() && site_type.has_value()) {
entries.emplace_back(
std::move(site.value()),
net::FirstPartySetEntry(primary.value(), site_type.value(),
/*site_index=*/absl::nullopt));
}
}
if (!statement.Succeeded())
return {};
}
if (!version_statement.Succeeded())
return {};
// Aliases are merged with entries inside of the public sets table so it is
// sufficient to declare the global sets object with only the entries field.
net::GlobalFirstPartySets global_sets(base::Version(version), entries,
/*aliases=*/{});
// Query & apply manual set.
global_sets.ApplyManuallySpecifiedSet(FetchManualSets(browser_context_id));
return global_sets;
}
std::pair<std::vector<net::SchemefulSite>, net::FirstPartySetsCacheFilter>
FirstPartySetsDatabase::GetSitesToClearFilters(
const std::string& browser_context_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!browser_context_id.empty());
if (!LazyInit())
return {};
DCHECK_GT(run_count_, 0);
sql::Transaction transaction(db_.get());
if (!transaction.Begin())
return {};
std::vector<net::SchemefulSite> sites_to_clear =
FetchSitesToClear(browser_context_id);
base::flat_map<net::SchemefulSite, int64_t> all_sites_to_clear =
FetchAllSitesToClearFilter(browser_context_id);
net::FirstPartySetsCacheFilter cache_filter =
all_sites_to_clear.empty()
? net::FirstPartySetsCacheFilter()
: net::FirstPartySetsCacheFilter(std::move(all_sites_to_clear),
run_count_);
if (!transaction.Commit())
return {};
return std::make_pair(std::move(sites_to_clear), std::move(cache_filter));
}
std::vector<net::SchemefulSite> FirstPartySetsDatabase::FetchSitesToClear(
const std::string& browser_context_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!browser_context_id.empty());
DCHECK_EQ(db_status_, InitStatus::kSuccess);
// Gets the sites that were marked to clear but haven't been cleared yet for
// the given `browser_context_id`. Use 0 as the default
// `browser_contexts_cleared.cleared_at_run` value if the `browser_context_id`
// does not exist in the browser_contexts_cleared table.
std::vector<net::SchemefulSite> results;
static constexpr char kSelectSql[] =
// clang-format off
"SELECT p.site FROM browser_context_sites_to_clear p "
"LEFT JOIN browser_contexts_cleared c ON p.browser_context_id=c.browser_context_id "
"WHERE p.marked_at_run>COALESCE(c.cleared_at_run,0)"
"AND p.browser_context_id=?";
// clang-format on
sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSelectSql));
statement.BindString(0, browser_context_id);
while (statement.Step()) {
absl::optional<net::SchemefulSite> site =
FirstPartySetParser::CanonicalizeRegisteredDomain(
statement.ColumnString(0), /*emit_errors=*/false);
// TODO(crbug/1314039): Invalid sites should be rare case but possible.
// Consider deleting them from DB.
if (site.has_value()) {
results.push_back(std::move(site.value()));
}
}
if (!statement.Succeeded())
return {};
return results;
}
base::flat_map<net::SchemefulSite, int64_t>
FirstPartySetsDatabase::FetchAllSitesToClearFilter(
const std::string& browser_context_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(db_status_, InitStatus::kSuccess);
std::vector<std::pair<net::SchemefulSite, int64_t>> results;
static constexpr char kSelectSql[] =
// clang-format off
"SELECT site,marked_at_run FROM browser_context_sites_to_clear "
"WHERE browser_context_id=?";
// clang-format on
sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSelectSql));
statement.BindString(0, browser_context_id);
while (statement.Step()) {
absl::optional<net::SchemefulSite> site =
FirstPartySetParser::CanonicalizeRegisteredDomain(
statement.ColumnString(0), /*emit_errors=*/false);
// TODO(crbug/1314039): Invalid sites should be rare case but possible.
// Consider deleting them from DB.
if (site.has_value()) {
results.emplace_back(std::move(site.value()), statement.ColumnInt(1));
}
}
if (!statement.Succeeded())
return {};
return results;
}
net::FirstPartySetsContextConfig
FirstPartySetsDatabase::FetchPolicyConfigurations(
const std::string& browser_context_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!LazyInit())
return {};
std::vector<
std::pair<net::SchemefulSite, absl::optional<net::FirstPartySetEntry>>>
results;
static constexpr char kSelectSql[] =
// clang-format off
"SELECT site,primary_site FROM policy_configurations "
"WHERE browser_context_id=?";
// clang-format on
sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSelectSql));
statement.BindString(0, browser_context_id);
while (statement.Step()) {
absl::optional<net::SchemefulSite> site =
FirstPartySetParser::CanonicalizeRegisteredDomain(
statement.ColumnString(0), /*emit_errors=*/false);
absl::optional<net::SchemefulSite> maybe_primary_site;
if (std::string primary_site = statement.ColumnString(1);
!primary_site.empty()) {
maybe_primary_site = FirstPartySetParser::CanonicalizeRegisteredDomain(
primary_site, /*emit_errors=*/false);
}
// TODO(crbug/1314039): Invalid sites should be rare case but possible.
// Consider deleting them from DB.
if (site.has_value()) {
results.emplace_back(
std::move(site.value()),
maybe_primary_site.has_value()
? absl::make_optional(net::FirstPartySetEntry(
maybe_primary_site.value(),
// TODO(https://crbug.com/1219656): May change to use the
// real site_type and site_index in the future, depending on
// the design details. Use kAssociated as default site type
// and null site index for now.
net::SiteType::kAssociated, absl::nullopt))
: absl::nullopt);
}
}
if (!statement.Succeeded())
return {};
return net::FirstPartySetsContextConfig(std::move(results));
}
bool FirstPartySetsDatabase::HasEntryInBrowserContextsClearedForTesting(
const std::string& browser_context_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!browser_context_id.empty());
if (!LazyInit())
return {};
static constexpr char kSelectSql[] =
// clang-format off
"SELECT 1 FROM browser_contexts_cleared "
"WHERE browser_context_id=? LIMIT 1";
// clang-format on
sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSelectSql));
statement.BindString(0, browser_context_id);
return statement.Step() && statement.Succeeded();
}
base::flat_map<net::SchemefulSite, net::FirstPartySetEntry>
FirstPartySetsDatabase::FetchManualSets(const std::string& browser_context_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!LazyInit())
return {};
std::vector<std::pair<net::SchemefulSite, net::FirstPartySetEntry>> results;
static constexpr char kSelectSql[] =
// clang-format off
"SELECT site,primary_site,site_type FROM manual_sets "
"WHERE browser_context_id=?";
// clang-format on
sql::Statement statement(db_->GetCachedStatement(SQL_FROM_HERE, kSelectSql));
statement.BindString(0, browser_context_id);
while (statement.Step()) {
absl::optional<net::SchemefulSite> site =
FirstPartySetParser::CanonicalizeRegisteredDomain(
statement.ColumnString(0), /*emit_errors=*/false);
absl::optional<net::SchemefulSite> primary =
FirstPartySetParser::CanonicalizeRegisteredDomain(
statement.ColumnString(1), /*emit_errors=*/false);
absl::optional<net::SiteType> site_type =
net::FirstPartySetEntry::DeserializeSiteType(statement.ColumnInt(2));
// TODO(crbug.com/1314039): Invalid entries should be rare case but
// possible. Consider deleting them from DB.
if (site.has_value() && primary.has_value() && site_type.has_value()) {
results.emplace_back(
std::move(site.value()),
net::FirstPartySetEntry(primary.value(), site_type.value(),
/*site_index=*/absl::nullopt));
}
}
if (!statement.Succeeded())
return {};
return results;
}
bool FirstPartySetsDatabase::LazyInit() {
// Early return in case of previous failure, to prevent an unbounded
// number of re-attempts.
if (db_status_ != InitStatus::kUnattempted)
return db_status_ == InitStatus::kSuccess;
DCHECK_EQ(db_.get(), nullptr);
db_ = std::make_unique<sql::Database>(sql::DatabaseOptions{
.exclusive_locking = true, .page_size = 4096, .cache_size = 32});
db_->set_histogram_tag("FirstPartySets");
// base::Unretained is safe here because this FirstPartySetsDatabase owns
// the sql::Database instance that stores and uses the callback. So,
// `this` is guaranteed to outlive the callback.
db_->set_error_callback(base::BindRepeating(
&FirstPartySetsDatabase::DatabaseErrorCallback, base::Unretained(this)));
db_status_ = InitializeTables();
if (db_status_ != InitStatus::kSuccess) {
db_.reset();
meta_table_.Reset();
} else {
IncreaseRunCount();
}
RecordInitializationStatus(db_status_);
return db_status_ == InitStatus::kSuccess;
}
bool FirstPartySetsDatabase::OpenDatabase() {
DCHECK(db_);
if (db_->is_open() || db_->Open(db_path_)) {
db_->Preload();
return true;
}
return false;
}
void FirstPartySetsDatabase::DatabaseErrorCallback(int extended_error,
sql::Statement* stmt) {
DCHECK(db_);
// Attempt to recover a corrupt database.
if (sql::Recovery::ShouldRecover(extended_error)) {
// Prevent reentrant calls.
db_->reset_error_callback();
// After this call, the |db_| handle is poisoned so that future calls will
// return errors until the handle is re-opened.
sql::Recovery::RecoverDatabaseWithMetaVersion(db_.get(), db_path_);
// The DLOG(FATAL) below is intended to draw immediate attention to errors
// in newly-written code. Database corruption is generally a result of OS or
// hardware issues, not coding errors at the client level, so displaying the
// error would probably lead to confusion. The ignored call signals the
// test-expectation framework that the error was handled.
std::ignore = sql::Database::IsExpectedSqliteError(extended_error);
return;
}
// The default handling is to assert on debug and to ignore on release.
if (!sql::Database::IsExpectedSqliteError(extended_error))
DLOG(FATAL) << db_->GetErrorMessage();
// Consider the database closed if we did not attempt to recover so we did not
// produce further errors.
db_status_ = InitStatus::kError;
}
FirstPartySetsDatabase::InitStatus FirstPartySetsDatabase::InitializeTables() {
if (!OpenDatabase())
return InitStatus::kError;
// Database should now be open.
DCHECK(db_->is_open());
// Razes the DB if the version is deprecated or too new to get the feature
// working.
//
// TODO(crbug.com/1372445): Re-enable track DB init status kTooNew and kTooOld
// after the bug is resolved and migration is implemented.
DCHECK_LT(kDeprecatedVersionNumber, kCurrentVersionNumber);
sql::MetaTable::RazeIfIncompatible(
db_.get(), /*lowest_supported_version=*/kDeprecatedVersionNumber + 1,
kCurrentVersionNumber);
// db could have been razed due to version being deprecated or too new.
bool db_empty = !sql::MetaTable::DoesTableExist(db_.get());
// Scope initialization in a transaction so we can't be partially initialized.
sql::Transaction transaction(db_.get());
if (!transaction.Begin()) {
LOG(WARNING) << "First-Party Sets database begin initialization failed.";
db_->RazeAndClose();
return InitStatus::kError;
}
if (!meta_table_.Init(db_.get(), kCurrentVersionNumber,
kCompatibleVersionNumber)) {
return InitStatus::kError;
}
// Create the tables if db not already exists.
if (db_empty) {
if (!InitSchema(*db_))
return InitStatus::kError;
} else {
if (!UpgradeSchema())
return InitStatus::kError;
}
if (!transaction.Commit()) {
LOG(WARNING) << "First-Party Sets database initialization commit failed.";
return InitStatus::kError;
}
return InitStatus::kSuccess;
}
bool FirstPartySetsDatabase::UpgradeSchema() {
if (meta_table_.GetVersionNumber() == 2) {
if (!MigrateToVersion3())
return false;
}
// Add similar if () blocks for new versions here.
return true;
}
bool FirstPartySetsDatabase::MigrateToVersion3() {
DCHECK(db_->HasActiveTransactions());
// Rename the policy_modifications table with policy_configurations.
static constexpr char kRenamePolicyConfigurationsTableSql[] =
"ALTER TABLE policy_modifications RENAME TO policy_configurations";
if (!db_->Execute(kRenamePolicyConfigurationsTableSql))
return false;
meta_table_.SetVersionNumber(3);
return true;
}
void FirstPartySetsDatabase::IncreaseRunCount() {
DCHECK_EQ(db_status_, InitStatus::kSuccess);
// 0 is the default value, `run_count_` should only be set once.
DCHECK_EQ(run_count_, 0);
int64_t count = 0;
// `count` should be positive if the value exists in the meta table. Consider
// db data is corrupted and delete db file if that's not the case.
if (meta_table_.GetValue(kRunCountKey, &count) && count <= 0) {
db_status_ = InitStatus::kCorrupted;
// TODO(crbug/1316090): Need to resolve how the restarted `run_count_` could
// affect cache clearing.
if (!Destroy()) {
LOG(ERROR) << "First-Party Sets database destruction failed.";
}
return;
}
run_count_ = count + 1;
// TODO(crbug/1314039): Figure out how to handle run_count update failure.
if (!meta_table_.SetValue(kRunCountKey, run_count_)) {
LOG(ERROR) << "First-Party Sets database updating run_count failed.";
}
}
bool FirstPartySetsDatabase::Destroy() {
// Reset the value.
run_count_ = 0;
if (db_ && db_->is_open() && !db_->RazeAndClose())
return false;
// The file already doesn't exist.
if (db_path_.empty())
return true;
return base::DeleteFile(db_path_);
}
} // namespace content