| // 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 |