1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266
|
// 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 "sql/error_delegate_util.h"
#include <ostream> // Needed to compile NOTREACHED() with operator <<.
#include <string>
#include "base/files/file_path.h"
#include "base/notreached.h"
#include "third_party/sqlite/sqlite3.h"
namespace sql {
bool IsErrorCatastrophic(int sqlite_error_code) {
// SQLite result codes are documented at https://www.sqlite.org/rescode.html
int primary_error_code = sqlite_error_code & 0xff;
// Within each group, error codes are sorted by their numerical values. This
// matches the order used by the SQLite documentation describing them.
switch (primary_error_code) {
// Group of error codes that should never be returned by SQLite.
//
// If we do get these, our database schema / query pattern / data managed to
// trigger a bug in SQLite. In development, we DCHECK to flag this SQLite
// bug. In production, we [[fallback]] to corruption handling, because the
// bug may be persistent, and corruption recovery will get the user unstuck.
case SQLITE_INTERNAL: // Bug in SQLite.
case SQLITE_EMPTY: // Marked for SQLite internal use.
case SQLITE_FORMAT: // Not currently used, according to SQLite docs.
case SQLITE_NOTICE: // Only used as an argument to sqlite3_log().
case SQLITE_WARNING: // Only used as an argument to sqlite3_log().
NOTREACHED() << "SQLite returned result code marked for internal use: "
<< sqlite_error_code;
[[fallthrough]];
// Group of error codes that may only be returned by SQLite (given Chrome's
// usage patterns) if a database is corrupted. DCHECK would not be
// appropriate, since these can occur in production. Silently [[fallback]]
// to corruption handling.
case SQLITE_ERROR:
// Generic/fallback error code.
//
// In production, database corruption leads our SQL statements being
// flagged as invalid. For example, a SQL statement may reference a table
// or column whose name got corrupted.
//
// In development, this error code shows up most often when passing
// invalid SQL statements to SQLite. We have DCHECKs in sql::Statement and
// sql::Database::Execute() that catch obvious SQL syntax errors. We can't
// DCHECK when a SQL statement uses incorrect table/index/row names,
// because that can legitimately happen in production, due to corruption.
//
// In 2022 we considered these errors as non-catastrophic, and we didn't
// find ANY invalid SQL statements, and only found failed transactions
// and schemas that didn't match the reported schema version, which both
// suggest corruption. See https://crbug.com/1321483 for context.
[[fallthrough]];
case SQLITE_PERM:
// Failed to get the requested access mode for a newly created database.
// The database was just created, so error recovery will not cause data
// loss. Error recovery steps, such as re-creating database files, may
// fix the permission problems.
[[fallthrough]];
case SQLITE_CORRUPT:
// Some form of database corruption was detected. The sql::Recovery code
// may be able to recover some of the data.
[[fallthrough]];
case SQLITE_CANTOPEN:
// Failed to open the database, for a variety of reasons. All the reasons
// come down to some form of corruption. Here are some known reasons:
// * One of the file names (database, journal, WAL, etc.) points to a
// directory, not a file. This indicates filesystem corruption. Most
// likely, some app messed with the user's Chrome file. It's also
// possible that the inode was corrupted and the is_dir bit flipped.
// * One of the file names is a symlink, and SQLite was instructed not to
// follow symlinks. This should not occur in Chrome, we let SQLite use
// its default symlink handling.
// * The WAL file has a format version that SQLite can't understand. This
// should not occur in Chrome, as we don't use WAL yet.
[[fallthrough]];
case SQLITE_MISMATCH:
// SQLite was forced to perform an operation that involves incompatible
// data types. An example is attempting to store a non-integer value in a
// ROWID primary key.
//
// In production, database corruption can lead to this. For example, it's
// possible that a schema is corrupted in such a way that the ROWID
// primary key column's name is swapped with another column's name.
[[fallthrough]];
case SQLITE_NOLFS:
// The database failed to grow past the filesystem size limit. This is
// unlikely to happen in Chrome, but it is theoretically possible.
[[fallthrough]];
case SQLITE_NOTADB:
// The database header is corrupted. The sql::Recovery code will not be
// able to recovery any data, as SQLite will refuse to open the database.
return true;
// Group of result codes that are not error codes. These should never make
// it to error handling code. In development, we DCHECK to flag this Chrome
// bug. In production, we hope this is a transient error, such as a race
// condition.
case SQLITE_OK: // Most used success code.
case SQLITE_ROW: // The statement produced a row of output.
case SQLITE_DONE: // A step has completed in a multi-step operation.
NOTREACHED() << "Called with non-error result code " << sqlite_error_code;
[[fallthrough]];
// Group of error codes that should not be returned by SQLite given Chrome's
// usage patterns, even if the database gets corrupted. In development, we
// DCHECK to flag this Chrome bug. In production, we hope the errors have
// transient causes, such as race conditions.
case SQLITE_LOCKED:
// Conflict between two concurrently executing statements in the same
// database connection.
//
// In theory, SQLITE_LOCKED could also signal a conflict between different
// connections (in the same process) sharing a page cache, but Chrome only
// uses private page caches.
NOTREACHED() << "Conflict between concurrently executing SQL statements";
[[fallthrough]];
case SQLITE_NOMEM:
// Out of memory. This is most likely a transient error.
//
// There's a small chance that the error is caused by trying to exchange
// too much data with SQLite. Most such errors result in SQLITE_TOOBIG.
NOTREACHED() << "SQLite reported out-of-memory: " << sqlite_error_code;
[[fallthrough]];
case SQLITE_INTERRUPT:
// Chrome features don't use sqlite3_interrupt().
NOTREACHED() << "SQLite returned INTERRUPT code: " << sqlite_error_code;
[[fallthrough]];
case SQLITE_NOTFOUND:
// Unknown opcode in sqlite3_file_control(). Chrome's features only use a
// few built-in opcodes.
NOTREACHED() << "SQLite returned NOTFOUND code: " << sqlite_error_code;
[[fallthrough]];
case SQLITE_MISUSE:
// SQLite API misuse, such as trying to use a prepared statement after it
// was finalized. In development, we DCHECK to flag this Chrome bug. In
// production, we hope this is a race condition, and therefore transient.
NOTREACHED() << "SQLite returned MISUSE code: " << sqlite_error_code;
[[fallthrough]];
case SQLITE_AUTH:
// Chrome features don't install an authorizer callback. Only WebSQL does.
NOTREACHED() << "SQLite returned AUTH code: " << sqlite_error_code;
[[fallthrough]];
case SQLITE_RANGE:
// Chrome uses DCHECKs to ensure the validity of column indexes passed to
// sqlite3_bind() and sqlite3_column().
NOTREACHED() << "SQLite returned RANGE code: " << sqlite_error_code;
[[fallthrough]];
// Group of error codes that should may be returned by SQLite given Chrome's
// usage patterns, even without database corruption. In development, we
// DCHECK to flag this Chrome bug. In production, we hope the errors have
// transient causes, such as race conditions.
case SQLITE_ABORT:
// SQLITE_ABORT may be returned when a ROLLBACK statement is executed
// concurrently with a pending read or write, and Chrome features are
// allowed to execute concurrent statements in the same transaction, under
// some conditions.
//
// It may be worth noting that Chrome features don't use callback routines
// that may abort SQL statements, such as passing a callback to
// sqlite3_exec().
[[fallthrough]];
case SQLITE_BUSY:
// Failed to grab a lock on the database. Another database connection
// (most likely in another process) is holding the database lock. This
// should not be a problem for exclusive databases, which are strongly
// recommended for Chrome features.
[[fallthrough]];
case SQLITE_READONLY:
// SQLite either failed to write to the database file or its associated
// files (journal, WAL, etc.), or considers it unsafe to do so.
//
// Most error codes (SQLITE_READONLY_DIRECTORY, SQLITE_READONLY_RECOVERY,
// SQLITE_READONLY_ROLLBACK, SQLITE_READONLY_CANTLOCK) mean that SQLite
// failed to write to some file, or to create a file (which entails
// writing to the directory containing the database).
//
// SQLITE_READONLY_CANTLOCK should never happen in Chrome, because we will
// only allow enabling WAL on databases that use exclusive locking.
//
// Unlike all other codes, SQLITE_READONLY_DBMOVED signals that a file was
// deleted or renamed. It is returned when SQLite realizes that the
// database file was moved or unlinked from the filesystem after it was
// opened, so the associated files (journal, WAL, etc.) would not be found
// by another SQLite instance in the event of a crash. This was observed
// on the iOS try bots.
[[fallthrough]];
case SQLITE_IOERR:
// Catch-all for many errors reported by the VFS. Some of the errors
// indicate media failure (SQLITE_IOERR_READ), while others indicate
// transient problems (SQLITE_IOERR_LOCK). In the future, we may invest in
// distinguishing between them. For now, since all the codes are bundled
// up, we must assume that the error is transient.
[[fallthrough]];
case SQLITE_FULL:
// The disk is full. This is definitely a transient error, and does not
// indicate any database corruption. While it's true that the user will be
// stuck in this state until some action is taken, we're unlikely to help
// the user if we run our recovery code or delete our databases.
[[fallthrough]];
case SQLITE_PROTOCOL:
// Gave up while attempting to grab a lock on a WAL database at the
// beginning of a transaction. In theory, this should not be a problem in
// Chrome, because we'll only allow enabling WAL on databases with
// exclusive locking. However, other software on the user's system may
// lock our databases in a way that triggers this error.
[[fallthrough]];
case SQLITE_SCHEMA:
// The database schema was changed between the time when a prepared
// statement was compiled, and when it was executing.
//
// This can happen in production. Databases that don't use exclusive
// locking (recommended but not yet required for Chrome features) may be
// changed from another process via legitimate use of SQLite APIs.
// Databases that do use exclusive locks may still be mutated on-disk, on
// operating systems where exclusive locks are only enforced via advisory
// locking.
//
// When we mandate exclusive locks for all features in Chrome, we may
// classify this error as database corruption, because it is an indicator
// that another process is interfering with Chrome's schemas.
[[fallthrough]];
case SQLITE_TOOBIG:
// SQLite encountered a string or blob whose length exceeds
// SQLITE_MAX_LENGTH, or it was asked to execute a SQL statement whose
// length exceeds SQLITE_MAX_SQL_LENGTH or SQLITE_LIMIT_SQL_LENGTH.
//
// A corrupted database could cause this in the following ways:
// * SQLite could encounter an overly large string or blob because its
// size field got corrupted.
// * SQLite could attempt to execute an overly large SQL statement while
// operating on a corrupted schema. (Some of SQLite's DDL statements
// involve executing SQL that includes schema content.)
//
// However, this could also occur due to a Chrome bug where we ask SQLite
// to bind an overly large string or blob. So, we currently don't classify
// this as definitely induced by corruption.
[[fallthrough]];
case SQLITE_CONSTRAINT:
// This can happen in production, when executing SQL statements with the
// semantics of "create a record if it doesn't exist, otherwise do
// nothing".
return false;
}
NOTREACHED() << "SQLite returned unknown result code: " << sqlite_error_code;
return false;
}
std::string GetCorruptFileDiagnosticsInfo(
const base::FilePath& corrupted_file_path) {
std::string corrupted_file_info("Corrupted file: ");
corrupted_file_info +=
corrupted_file_path.DirName().BaseName().AsUTF8Unsafe() + "/" +
corrupted_file_path.BaseName().AsUTF8Unsafe() + "\n";
return corrupted_file_info;
}
} // namespace sql
|