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