blob: 6eefa2cb5780686c9b2fa51e08b112d1457d076c [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/webdata/common/web_database.h"
#include <algorithm>
#include "base/debug/crash_logging.h"
#include "base/debug/dump_without_crashing.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "sql/transaction.h"
// Current version number. Note: when changing the current version number,
// corresponding changes must happen in the unit tests, and new migration test
// added. See `WebDatabaseMigrationTest::kCurrentTestedVersionNumber`.
// static
const int WebDatabase::kCurrentVersionNumber = 112;
const int WebDatabase::kDeprecatedVersionNumber = 82;
const base::FilePath::CharType WebDatabase::kInMemoryPath[] =
FILE_PATH_LITERAL(":memory");
namespace {
BASE_FEATURE(kWebDatabaseDumpWithoutCrashingOnInitProblems,
"WebDatabaseDumpWithoutCrashingOnInitProblems",
base::FEATURE_ENABLED_BY_DEFAULT);
std::string GetDiagnostics(const sql::Database& db) {
if (!db.is_open()) {
return "Database is not open";
}
return base::StringPrintf("ErrorCode: %d, LastErrorno: %d, Error: %s",
db.GetErrorCode(), db.GetLastErrno(),
db.GetErrorMessage());
}
// TODO(crbug.com/1430313): Remove when bug is fixed.
NOINLINE void LogDiagnostics(sql::Database& db) {
if (!base::FeatureList::IsEnabled(
kWebDatabaseDumpWithoutCrashingOnInitProblems)) {
return;
}
SCOPED_CRASH_KEY_STRING1024("db_init_error", "diagnostics",
GetDiagnostics(db));
base::debug::DumpWithoutCrashing();
}
const int kCompatibleVersionNumber = 106;
// Change the version number and possibly the compatibility version of
// |meta_table_|.
[[nodiscard]] bool ChangeVersion(sql::MetaTable* meta_table,
int version_num,
bool update_compatible_version_num) {
return meta_table->SetVersionNumber(version_num) &&
(!update_compatible_version_num ||
meta_table->SetCompatibleVersionNumber(
std::min(version_num, kCompatibleVersionNumber)));
}
// Outputs the failed version number as a warning and always returns
// |sql::INIT_FAILURE|.
sql::InitStatus FailedMigrationTo(int version_num) {
LOG(WARNING) << "Unable to update web database to version " << version_num
<< ".";
return sql::INIT_FAILURE;
}
} // namespace
WebDatabase::WebDatabase()
: db_({// Run the database in exclusive mode. Nobody else should be
// accessing the database while we're running, and this will give
// somewhat improved perf.
.exclusive_locking = true,
// We don't store that much data in the tables so use a small page
// size. This provides a large benefit for empty tables (which is
// very likely with the tables we create).
.page_size = 2048,
// We shouldn't have much data and what access we currently have is
// quite infrequent. So we go with a small cache size.
.cache_size = 32}) {}
WebDatabase::~WebDatabase() = default;
void WebDatabase::AddTable(WebDatabaseTable* table) {
tables_[table->GetTypeKey()] = table;
}
WebDatabaseTable* WebDatabase::GetTable(WebDatabaseTable::TypeKey key) {
return tables_[key];
}
void WebDatabase::BeginTransaction() {
db_.BeginTransaction();
}
void WebDatabase::CommitTransaction() {
db_.CommitTransaction();
}
std::string WebDatabase::GetDiagnosticInfo(int extended_error,
sql::Statement* statement) {
return db_.GetDiagnosticInfo(extended_error, statement);
}
sql::Database* WebDatabase::GetSQLConnection() {
return &db_;
}
sql::InitStatus WebDatabase::Init(const base::FilePath& db_name) {
db_.set_histogram_tag("Web");
if ((db_name.value() == kInMemoryPath) ? !db_.OpenInMemory()
: !db_.Open(db_name)) {
LogDiagnostics(db_);
return sql::INIT_FAILURE;
}
// Check whether we have write access at the earliest possible time.
// While failures can happen later as well, this gives us some clarity
// that we could at least use the database when we opened it.
// TODO(crbug.com/1430313): Remove when bug is fixed.
{
if (!db_.Execute("BEGIN EXCLUSIVE")) {
LogDiagnostics(db_);
return sql::INIT_FAILURE;
}
if (!db_.Execute("COMMIT")) {
LogDiagnostics(db_);
return sql::INIT_FAILURE;
}
}
// Clobber really old databases.
static_assert(kDeprecatedVersionNumber < kCurrentVersionNumber,
"Deprecation version must be less than current");
if (!sql::MetaTable::RazeIfIncompatible(
&db_, /*lowest_supported_version=*/kDeprecatedVersionNumber + 1,
kCurrentVersionNumber)) {
LogDiagnostics(db_);
return sql::INIT_FAILURE;
}
// TODO(crbug.com/1430313): Remove when bug is fixed.
if (!db_.is_open()) {
LogDiagnostics(db_);
}
// Scope initialization in a transaction so we can't be partially
// initialized.
sql::Transaction transaction(&db_);
if (!transaction.Begin()) {
LogDiagnostics(db_);
return sql::INIT_FAILURE;
}
// Version check.
if (!meta_table_.Init(&db_, kCurrentVersionNumber,
kCompatibleVersionNumber)) {
LogDiagnostics(db_);
return sql::INIT_FAILURE;
}
if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
LOG(WARNING) << "Web database is too new.";
return sql::INIT_TOO_NEW;
}
// Initialize the tables.
for (const auto& table : tables_) {
table.second->Init(&db_, &meta_table_);
// TODO(crbug.com/1430313): Remove when bug is fixed.
if (!db_.is_open()) {
LogDiagnostics(db_);
}
}
// If the file on disk is an older database version, bring it up to date.
// If the migration fails we return an error to caller and do not commit
// the migration.
sql::InitStatus migration_status = MigrateOldVersionsAsNeeded();
if (migration_status != sql::INIT_OK) {
LogDiagnostics(db_);
return migration_status;
}
// Create the desired SQL tables if they do not already exist.
// It's important that this happen *after* the migration code runs.
// Otherwise, the migration code would have to explicitly check for empty
// tables created in the new format, and skip the migration in that case.
for (const auto& table : tables_) {
if (!table.second->CreateTablesIfNecessary()) {
LogDiagnostics(db_);
LOG(WARNING) << "Unable to initialize the web database.";
return sql::INIT_FAILURE;
}
}
bool result = transaction.Commit();
if (!result) {
LogDiagnostics(db_);
}
return result ? sql::INIT_OK : sql::INIT_FAILURE;
}
sql::InitStatus WebDatabase::MigrateOldVersionsAsNeeded() {
// Some malware used to lower the version number, causing migration to
// fail. Ensure the version number is at least as high as the compatible
// version number.
int current_version = std::max(meta_table_.GetVersionNumber(),
meta_table_.GetCompatibleVersionNumber());
if (current_version > meta_table_.GetVersionNumber() &&
!ChangeVersion(&meta_table_, current_version, false)) {
LogDiagnostics(db_);
return FailedMigrationTo(current_version);
}
DCHECK_GT(current_version, kDeprecatedVersionNumber);
for (int next_version = current_version + 1;
next_version <= kCurrentVersionNumber; ++next_version) {
// Do any database-wide migrations.
bool update_compatible_version = false;
if (!MigrateToVersion(next_version, &update_compatible_version) ||
!ChangeVersion(&meta_table_, next_version, update_compatible_version)) {
LogDiagnostics(db_);
return FailedMigrationTo(next_version);
}
// Give each table a chance to migrate to this version.
for (const auto& table : tables_) {
// Any of the tables may set this to true, but by default it is false.
update_compatible_version = false;
if (!table.second->MigrateToVersion(next_version,
&update_compatible_version) ||
!ChangeVersion(&meta_table_, next_version,
update_compatible_version)) {
LogDiagnostics(db_);
return FailedMigrationTo(next_version);
}
}
}
return sql::INIT_OK;
}
bool WebDatabase::MigrateToVersion(int version,
bool* update_compatible_version) {
// Migrate if necessary.
switch (version) {
case 58:
*update_compatible_version = true;
return MigrateToVersion58DropWebAppsAndIntents();
case 79:
*update_compatible_version = true;
return MigrateToVersion79DropLoginsTable();
case 105:
*update_compatible_version = true;
return MigrateToVersion105DropIbansTable();
}
return true;
}
bool WebDatabase::MigrateToVersion58DropWebAppsAndIntents() {
sql::Transaction transaction(&db_);
return transaction.Begin() && db_.Execute("DROP TABLE IF EXISTS web_apps") &&
db_.Execute("DROP TABLE IF EXISTS web_app_icons") &&
db_.Execute("DROP TABLE IF EXISTS web_intents") &&
db_.Execute("DROP TABLE IF EXISTS web_intents_defaults") &&
transaction.Commit();
}
bool WebDatabase::MigrateToVersion79DropLoginsTable() {
sql::Transaction transaction(&db_);
return transaction.Begin() &&
db_.Execute("DROP TABLE IF EXISTS ie7_logins") &&
db_.Execute("DROP TABLE IF EXISTS logins") && transaction.Commit();
}
bool WebDatabase::MigrateToVersion105DropIbansTable() {
return db_.Execute("DROP TABLE IF EXISTS ibans");
}