Index: src/global.c ================================================================== --- src/global.c +++ src/global.c @@ -145,10 +145,11 @@ SQLITE_DEFAULT_MEMSTATUS, /* bMemstat */ 1, /* bCoreMutex */ SQLITE_THREADSAFE==1, /* bFullMutex */ SQLITE_USE_URI, /* bOpenUri */ SQLITE_ALLOW_COVERING_INDEX_SCAN, /* bUseCis */ + 0, /* bReadOnly */ 0x7ffffffe, /* mxStrlen */ 128, /* szLookaside */ 500, /* nLookaside */ {0,0,0,0,0,0,0,0}, /* m */ {0,0,0,0,0,0,0,0,0}, /* mutex */ Index: src/main.c ================================================================== --- src/main.c +++ src/main.c @@ -493,10 +493,49 @@ sqlite3GlobalConfig.xSqllog = va_arg(ap, SQLLOGFUNC_t); sqlite3GlobalConfig.pSqllogArg = va_arg(ap, void *); break; } #endif + + case SQLITE_CONFIG_READONLY: { + sqlite3GlobalConfig.bReadOnly = va_arg(ap, int); + break; + } + + default: { + rc = SQLITE_ERROR; + break; + } + } + va_end(ap); + return rc; +} + +/* +** This API allows applications to modify the global configuration of +** the SQLite library at run-time. +** +** This routine differs from sqlite3_config() in that it may be called when +** there are outstanding database connections and/or memory allocations. +** This routine is threadsafe. +*/ +int sqlite3_reconfig(int op, ...){ + va_list ap; + int rc = SQLITE_OK; + + va_start(ap, op); + switch( op ){ + case SQLITE_CONFIG_READONLY: { + /* + ** On platforms where assignment of an integer value is atomic, there + ** is no need for a mutex here. On other platforms, there could be a + ** subtle race condition here; however, the effect would simply be that + ** a call to open a database would fail with SQLITE_READONLY. + */ + sqlite3GlobalConfig.bReadOnly = va_arg(ap, int); + break; + } default: { rc = SQLITE_ERROR; break; } @@ -2223,11 +2262,12 @@ */ static int openDatabase( const char *zFilename, /* Database filename UTF-8 encoded */ sqlite3 **ppDb, /* OUT: Returned database handle */ unsigned int flags, /* Operational flags */ - const char *zVfs /* Name of the VFS to use */ + const char *zVfs, /* Name of the VFS to use */ + int defaultFlags /* Zero if opening via sqlite3_open_v2 */ ){ sqlite3 *db; /* Store allocated handle here */ int rc; /* Return code */ int isThreadsafe; /* True for threadsafe connections */ char *zOpen = 0; /* Filename argument to pass to BtreeOpen() */ @@ -2292,10 +2332,20 @@ SQLITE_OPEN_MASTER_JOURNAL | SQLITE_OPEN_NOMUTEX | SQLITE_OPEN_FULLMUTEX | SQLITE_OPEN_WAL ); + + /* Check for global read-only mode */ + if( sqlite3GlobalConfig.bReadOnly ){ + if( defaultFlags ){ + flags &= ~(SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE); + flags |= SQLITE_OPEN_READONLY; + }else if( flags & SQLITE_OPEN_READWRITE ){ + return SQLITE_READONLY; + } + } /* Allocate the sqlite data structure */ db = sqlite3MallocZero( sizeof(sqlite3) ); if( db==0 ) goto opendb_out; if( isThreadsafe ){ @@ -2490,19 +2540,19 @@ int sqlite3_open( const char *zFilename, sqlite3 **ppDb ){ return openDatabase(zFilename, ppDb, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0, 1); } int sqlite3_open_v2( const char *filename, /* Database filename (UTF-8) */ sqlite3 **ppDb, /* OUT: SQLite db handle */ int flags, /* Flags */ const char *zVfs /* Name of VFS module to use */ ){ - return openDatabase(filename, ppDb, (unsigned int)flags, zVfs); + return openDatabase(filename, ppDb, (unsigned int)flags, zVfs, 0); } #ifndef SQLITE_OMIT_UTF16 /* ** Open a new database handle. @@ -2525,11 +2575,11 @@ pVal = sqlite3ValueNew(0); sqlite3ValueSetStr(pVal, -1, zFilename, SQLITE_UTF16NATIVE, SQLITE_STATIC); zFilename8 = sqlite3ValueText(pVal, SQLITE_UTF8); if( zFilename8 ){ rc = openDatabase(zFilename8, ppDb, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0); + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0, 1); assert( *ppDb || rc==SQLITE_NOMEM ); if( rc==SQLITE_OK && !DbHasProperty(*ppDb, 0, DB_SchemaLoaded) ){ ENC(*ppDb) = SQLITE_UTF16NATIVE; } }else{ Index: src/sqlite.h.in ================================================================== --- src/sqlite.h.in +++ src/sqlite.h.in @@ -1244,15 +1244,15 @@ int sqlite3_os_end(void); /* ** CAPI3REF: Configuring The SQLite Library ** -** The sqlite3_config() interface is used to make global configuration -** changes to SQLite in order to tune SQLite to the specific needs of -** the application. The default configuration is recommended for most -** applications and so this routine is usually not necessary. It is -** provided to support rare applications with unusual needs. +** The sqlite3_config() and sqlite3_reconfig() interfaces are used to make +** global configuration changes to SQLite in order to tune SQLite to the +** specific needs of the application. The default configuration is recommended +** for most applications and so this routine is usually not necessary. They +** are provided to support rare applications with unusual needs. ** ** The sqlite3_config() interface is not threadsafe. The application ** must insure that no other SQLite interfaces are invoked by other ** threads while sqlite3_config() is running. Furthermore, sqlite3_config() ** may only be invoked prior to library initialization using @@ -1260,21 +1260,27 @@ ** ^If sqlite3_config() is called after [sqlite3_initialize()] and before ** [sqlite3_shutdown()] then it will return SQLITE_MISUSE. ** Note, however, that ^sqlite3_config() can be called as part of the ** implementation of an application-defined [sqlite3_os_init()]. ** -** The first argument to sqlite3_config() is an integer -** [configuration option] that determines +** The sqlite3_reconfig() interface is threadsafe and may be called at any +** time. However, it supports only a small subset of the configuration +** options available for use with sqlite3_config(). +** +** The first argument to both sqlite3_config() and sqlite3_reconfig() is an +** integer [configuration option] that determines ** what property of SQLite is to be configured. Subsequent arguments ** vary depending on the [configuration option] ** in the first argument. ** -** ^When a configuration option is set, sqlite3_config() returns [SQLITE_OK]. +** ^When a configuration option is set, both sqlite3_config() and +** sqlite3_reconfig() return [SQLITE_OK]. ** ^If the option is unknown or SQLite is unable to set the option -** then this routine returns a non-zero [error code]. +** then these routines returns a non-zero [error code]. */ int sqlite3_config(int, ...); +int sqlite3_reconfig(int, ...); /* ** CAPI3REF: Configure database connections ** ** The sqlite3_db_config() interface is used to make configuration @@ -1595,10 +1601,23 @@ ** The ability to disable the use of covering indices for full table scans ** is because some incorrectly coded legacy applications might malfunction ** malfunction when the optimization is enabled. Providing the ability to ** disable the optimization allows the older, buggy application code to work ** without change even with newer versions of SQLite. +** +** [[SQLITE_CONFIG_READONLY]]
SQLITE_CONFIG_READONLY +**
This option takes a single argument of type int. If non-zero, then +** read-only mode for opening databases is globally enabled. If the parameter +** is zero, then read-only mode for opening databases is globally disabled. If +** read-only mode for opening databases is globally enabled, all databases +** opened by [sqlite3_open()], [sqlite3_open16()], or specified as part of +** [ATTACH] commands will be opened in read-only mode. Additionally, all calls +** to [sqlite3_open_v2()] must have the [SQLITE_OPEN_READONLY] flag set in the +** third argument; otherwise, a [SQLITE_READONLY] error will be returned. If it +** is globally disabled, [sqlite3_open()], [sqlite3_open16()], +** [sqlite3_open_v2()], and [ATTACH] commands will function normally. By +** default, read-only mode is globally disabled. ** ** [[SQLITE_CONFIG_PCACHE]] [[SQLITE_CONFIG_GETPCACHE]] **
SQLITE_CONFIG_PCACHE and SQLITE_CONFIG_GETPCACHE **
These options are obsolete and should not be used by new code. ** They are retained for backwards compatibility but are now no-ops. @@ -1639,10 +1658,11 @@ #define SQLITE_CONFIG_URI 17 /* int */ #define SQLITE_CONFIG_PCACHE2 18 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_GETPCACHE2 19 /* sqlite3_pcache_methods2* */ #define SQLITE_CONFIG_COVERING_INDEX_SCAN 20 /* int */ #define SQLITE_CONFIG_SQLLOG 21 /* xSqllog, void* */ +#define SQLITE_CONFIG_READONLY 22 /* int */ /* ** CAPI3REF: Database Connection Configuration Options ** ** These constants are the available integer configuration options that Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -2482,10 +2482,11 @@ int bMemstat; /* True to enable memory status */ int bCoreMutex; /* True to enable core mutexing */ int bFullMutex; /* True to enable full mutexing */ int bOpenUri; /* True to interpret filenames as URIs */ int bUseCis; /* Use covering indices for full-scans */ + int bReadOnly; /* True to force read-only mode */ int mxStrlen; /* Maximum string length */ int szLookaside; /* Default lookaside buffer size */ int nLookaside; /* Default lookaside buffer count */ sqlite3_mem_methods m; /* Low-level memory allocation interface */ sqlite3_mutex_methods mutex; /* Low-level mutex interface */ Index: src/test_malloc.c ================================================================== --- src/test_malloc.c +++ src/test_malloc.c @@ -1223,10 +1223,52 @@ rc = sqlite3_config(SQLITE_CONFIG_COVERING_INDEX_SCAN, bUseCis); Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_VOLATILE); return TCL_OK; } + +/* +** Usage: sqlite3_config_readonly BOOLEAN +** sqlite3_reconfig_readonly BOOLEAN +** +** Enables or disables global read-only mode using SQLITE_CONFIG_READONLY. +*/ +static int test_config_readonly( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ + int rc; + int bReadOnly; + + if( objc!=2 ){ + Tcl_WrongNumArgs(interp, 1, objv, "BOOL"); + return TCL_ERROR; + } + if( Tcl_GetBooleanFromObj(interp, objv[1], &bReadOnly) ){ + return TCL_ERROR; + } + + switch( SQLITE_PTR_TO_INT(clientData) ){ + case 0: { + rc = sqlite3_config(SQLITE_CONFIG_READONLY, bReadOnly); + break; + } + case 1: { + rc = sqlite3_reconfig(SQLITE_CONFIG_READONLY, bReadOnly); + break; + } + default: { + rc = SQLITE_ERROR; + break; + } + } + Tcl_SetResult(interp, (char *)sqlite3TestErrorName(rc), TCL_VOLATILE); + + return TCL_OK; +} /* ** Usage: sqlite3_dump_memsys3 FILENAME ** sqlite3_dump_memsys5 FILENAME ** @@ -1475,10 +1517,12 @@ { "sqlite3_config_memstatus", test_config_memstatus ,0 }, { "sqlite3_config_lookaside", test_config_lookaside ,0 }, { "sqlite3_config_error", test_config_error ,0 }, { "sqlite3_config_uri", test_config_uri ,0 }, { "sqlite3_config_cis", test_config_cis ,0 }, + { "sqlite3_config_readonly", test_config_readonly ,0 }, + { "sqlite3_reconfig_readonly", test_config_readonly ,1 }, { "sqlite3_db_config_lookaside",test_db_config_lookaside ,0 }, { "sqlite3_dump_memsys3", test_dump_memsys3 ,3 }, { "sqlite3_dump_memsys5", test_dump_memsys3 ,5 }, { "sqlite3_install_memsys3", test_install_memsys3 ,0 }, { "sqlite3_memdebug_vfs_oom_test", test_vfs_oom_test ,0 }, Index: test/openv2.test ================================================================== --- test/openv2.test +++ test/openv2.test @@ -50,6 +50,38 @@ do_test openv2-2.2 { catchsql {CREATE TABLE t1(x)} } {1 {attempt to write a readonly database}} +# Attempt to open a database with SQLITE_OPEN_READWRITE when the +# SQLITE_CONFIG_READONLY flag is enabled. +# +db close +sqlite3_reconfig_readonly 1 + +do_test openv2-3.1 { + list [catch {sqlite3 db :memory:} msg] $msg +} {1 {attempt to write a readonly database}} +catch {db close} +do_test openv2-3.2 { + list [catch {sqlite3 db test.db} msg] $msg +} {1 {attempt to write a readonly database}} +catch {db close} +do_test openv2-3.3 { + list [catch {sqlite3 db :memory: -readonly 1} msg] $msg +} {0 {}} +catch {db close} +do_test openv2-3.4 { + list [catch {sqlite3 db test.db -readonly 1} msg] $msg +} {0 {}} +catch {db close} +do_test openv2-3.5 { + list [catch {sqlite3 db :memory: -readonly 0} msg] $msg +} {1 {attempt to write a readonly database}} +catch {db close} +do_test openv2-3.6 { + list [catch {sqlite3 db test.db -readonly 0} msg] $msg +} {1 {attempt to write a readonly database}} +catch {db close} + +sqlite3_reconfig_readonly 0 finish_test