1/*
2** 2010 November 19
3**
4** The author disclaims copyright to this source code.  In place of
5** a legal notice, here is a blessing:
6**
7**    May you do good and not evil.
8**    May you find forgiveness for yourself and forgive others.
9**    May you share freely, never taking more than you give.
10**
11*************************************************************************
12** Example code for obtaining an exclusive lock on an SQLite database
13** file. This method is complicated, but works for both WAL and rollback
14** mode database files. The interface to the example code in this file
15** consists of the following two functions:
16**
17**   sqlite3demo_superlock()
18**   sqlite3demo_superunlock()
19*/
20
21#include <sqlite3.h>
22#include <string.h>               /* memset(), strlen() */
23#include <assert.h>               /* assert() */
24
25/*
26** A structure to collect a busy-handler callback and argument and a count
27** of the number of times it has been invoked.
28*/
29struct SuperlockBusy {
30  int (*xBusy)(void*,int);        /* Pointer to busy-handler function */
31  void *pBusyArg;                 /* First arg to pass to xBusy */
32  int nBusy;                      /* Number of times xBusy has been invoked */
33};
34typedef struct SuperlockBusy SuperlockBusy;
35
36/*
37** An instance of the following structure is allocated for each active
38** superlock. The opaque handle returned by sqlite3demo_superlock() is
39** actually a pointer to an instance of this structure.
40*/
41struct Superlock {
42  sqlite3 *db;                    /* Database handle used to lock db */
43  int bWal;                       /* True if db is a WAL database */
44};
45typedef struct Superlock Superlock;
46
47/*
48** The pCtx pointer passed to this function is actually a pointer to a
49** SuperlockBusy structure. Invoke the busy-handler function encapsulated
50** by the structure and return the result.
51*/
52static int superlockBusyHandler(void *pCtx, int UNUSED){
53  SuperlockBusy *pBusy = (SuperlockBusy *)pCtx;
54  if( pBusy->xBusy==0 ) return 0;
55  return pBusy->xBusy(pBusy->pBusyArg, pBusy->nBusy++);
56}
57
58/*
59** This function is used to determine if the main database file for
60** connection db is open in WAL mode or not. If no error occurs and the
61** database file is in WAL mode, set *pbWal to true and return SQLITE_OK.
62** If it is not in WAL mode, set *pbWal to false.
63**
64** If an error occurs, return an SQLite error code. The value of *pbWal
65** is undefined in this case.
66*/
67static int superlockIsWal(Superlock *pLock){
68  int rc;                         /* Return Code */
69  sqlite3_stmt *pStmt;            /* Compiled PRAGMA journal_mode statement */
70
71  rc = sqlite3_prepare(pLock->db, "PRAGMA main.journal_mode", -1, &pStmt, 0);
72  if( rc!=SQLITE_OK ) return rc;
73
74  pLock->bWal = 0;
75  if( SQLITE_ROW==sqlite3_step(pStmt) ){
76    const char *zMode = (const char *)sqlite3_column_text(pStmt, 0);
77    if( zMode && strlen(zMode)==3 && sqlite3_strnicmp("wal", zMode, 3)==0 ){
78      pLock->bWal = 1;
79    }
80  }
81
82  return sqlite3_finalize(pStmt);
83}
84
85/*
86** Obtain an exclusive shm-lock on nByte bytes starting at offset idx
87** of the file fd. If the lock cannot be obtained immediately, invoke
88** the busy-handler until either it is obtained or the busy-handler
89** callback returns 0.
90*/
91static int superlockShmLock(
92  sqlite3_file *fd,               /* Database file handle */
93  int idx,                        /* Offset of shm-lock to obtain */
94  int nByte,                      /* Number of consective bytes to lock */
95  SuperlockBusy *pBusy            /* Busy-handler wrapper object */
96){
97  int rc;
98  int (*xShmLock)(sqlite3_file*, int, int, int) = fd->pMethods->xShmLock;
99  do {
100    rc = xShmLock(fd, idx, nByte, SQLITE_SHM_LOCK|SQLITE_SHM_EXCLUSIVE);
101  }while( rc==SQLITE_BUSY && superlockBusyHandler((void *)pBusy, 0) );
102  return rc;
103}
104
105/*
106** Obtain the extra locks on the database file required for WAL databases.
107** Invoke the supplied busy-handler as required.
108*/
109static int superlockWalLock(
110  sqlite3 *db,                    /* Database handle open on WAL database */
111  SuperlockBusy *pBusy            /* Busy handler wrapper object */
112){
113  int rc;                         /* Return code */
114  sqlite3_file *fd = 0;           /* Main database file handle */
115  void volatile *p = 0;           /* Pointer to first page of shared memory */
116
117  /* Obtain a pointer to the sqlite3_file object open on the main db file. */
118  rc = sqlite3_file_control(db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);
119  if( rc!=SQLITE_OK ) return rc;
120
121  /* Obtain the "recovery" lock. Normally, this lock is only obtained by
122  ** clients running database recovery.
123  */
124  rc = superlockShmLock(fd, 2, 1, pBusy);
125  if( rc!=SQLITE_OK ) return rc;
126
127  /* Zero the start of the first shared-memory page. This means that any
128  ** clients that open read or write transactions from this point on will
129  ** have to run recovery before proceeding. Since they need the "recovery"
130  ** lock that this process is holding to do that, no new read or write
131  ** transactions may now be opened. Nor can a checkpoint be run, for the
132  ** same reason.
133  */
134  rc = fd->pMethods->xShmMap(fd, 0, 32*1024, 1, &p);
135  if( rc!=SQLITE_OK ) return rc;
136  memset((void *)p, 0, 32);
137
138  /* Obtain exclusive locks on all the "read-lock" slots. Once these locks
139  ** are held, it is guaranteed that there are no active reader, writer or
140  ** checkpointer clients.
141  */
142  rc = superlockShmLock(fd, 3, SQLITE_SHM_NLOCK-3, pBusy);
143  return rc;
144}
145
146/*
147** Release a superlock held on a database file. The argument passed to
148** this function must have been obtained from a successful call to
149** sqlite3demo_superlock().
150*/
151void sqlite3demo_superunlock(void *pLock){
152  Superlock *p = (Superlock *)pLock;
153  if( p->bWal ){
154    int rc;                         /* Return code */
155    int flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE;
156    sqlite3_file *fd = 0;
157    rc = sqlite3_file_control(p->db, "main", SQLITE_FCNTL_FILE_POINTER, (void *)&fd);
158    if( rc==SQLITE_OK ){
159      fd->pMethods->xShmLock(fd, 2, 1, flags);
160      fd->pMethods->xShmLock(fd, 3, SQLITE_SHM_NLOCK-3, flags);
161    }
162  }
163  sqlite3_close(p->db);
164  sqlite3_free(p);
165}
166
167/*
168** Obtain a superlock on the database file identified by zPath, using the
169** locking primitives provided by VFS zVfs. If successful, SQLITE_OK is
170** returned and output variable *ppLock is populated with an opaque handle
171** that may be used with sqlite3demo_superunlock() to release the lock.
172**
173** If an error occurs, *ppLock is set to 0 and an SQLite error code
174** (e.g. SQLITE_BUSY) is returned.
175**
176** If a required lock cannot be obtained immediately and the xBusy parameter
177** to this function is not NULL, then xBusy is invoked in the same way
178** as a busy-handler registered with SQLite (using sqlite3_busy_handler())
179** until either the lock can be obtained or the busy-handler function returns
180** 0 (indicating "give up").
181*/
182int sqlite3demo_superlock(
183  const char *zPath,              /* Path to database file to lock */
184  const char *zVfs,               /* VFS to use to access database file */
185  int (*xBusy)(void*,int),        /* Busy handler callback */
186  void *pBusyArg,                 /* Context arg for busy handler */
187  void **ppLock                   /* OUT: Context to pass to superunlock() */
188){
189  SuperlockBusy busy = {0, 0, 0}; /* Busy handler wrapper object */
190  int rc;                         /* Return code */
191  Superlock *pLock;
192
193  pLock = sqlite3_malloc(sizeof(Superlock));
194  if( !pLock ) return SQLITE_NOMEM;
195  memset(pLock, 0, sizeof(Superlock));
196
197  /* Open a database handle on the file to superlock. */
198  rc = sqlite3_open_v2(
199      zPath, &pLock->db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, zVfs
200  );
201
202  /* Install a busy-handler and execute a BEGIN EXCLUSIVE. If this is not
203  ** a WAL database, this is all we need to do.
204  **
205  ** A wrapper function is used to invoke the busy-handler instead of
206  ** registering the busy-handler function supplied by the user directly
207  ** with SQLite. This is because the same busy-handler function may be
208  ** invoked directly later on when attempting to obtain the extra locks
209  ** required in WAL mode. By using the wrapper, we are able to guarantee
210  ** that the "nBusy" integer parameter passed to the users busy-handler
211  ** represents the total number of busy-handler invocations made within
212  ** this call to sqlite3demo_superlock(), including any made during the
213  ** "BEGIN EXCLUSIVE".
214  */
215  if( rc==SQLITE_OK ){
216    busy.xBusy = xBusy;
217    busy.pBusyArg = pBusyArg;
218    sqlite3_busy_handler(pLock->db, superlockBusyHandler, (void *)&busy);
219    rc = sqlite3_exec(pLock->db, "BEGIN EXCLUSIVE", 0, 0, 0);
220  }
221
222  /* If the BEGIN EXCLUSIVE was executed successfully and this is a WAL
223  ** database, call superlockWalLock() to obtain the extra locks required
224  ** to prevent readers, writers and/or checkpointers from accessing the
225  ** db while this process is holding the superlock.
226  **
227  ** Before attempting any WAL locks, commit the transaction started above
228  ** to drop the WAL read and write locks currently held. Otherwise, the
229  ** new WAL locks may conflict with the old.
230  */
231  if( rc==SQLITE_OK ){
232    if( SQLITE_OK==(rc = superlockIsWal(pLock)) && pLock->bWal ){
233      rc = sqlite3_exec(pLock->db, "COMMIT", 0, 0, 0);
234      if( rc==SQLITE_OK ){
235        rc = superlockWalLock(pLock->db, &busy);
236      }
237    }
238  }
239
240  if( rc!=SQLITE_OK ){
241    sqlite3demo_superunlock(pLock);
242    *ppLock = 0;
243  }else{
244    *ppLock = pLock;
245  }
246
247  return rc;
248}
249
250/*
251** End of example code. Everything below here is the test harness.
252**************************************************************************
253**************************************************************************
254*************************************************************************/
255
256
257#ifdef SQLITE_TEST
258
259#include <tcl.h>
260
261struct InterpAndScript {
262  Tcl_Interp *interp;
263  Tcl_Obj *pScript;
264};
265typedef struct InterpAndScript InterpAndScript;
266
267static void superunlock_del(ClientData cd){
268  sqlite3demo_superunlock((void *)cd);
269}
270
271static int superunlock_cmd(
272  ClientData cd,
273  Tcl_Interp *interp,
274  int objc,
275  Tcl_Obj *CONST objv[]
276){
277  if( objc!=1 ){
278    Tcl_WrongNumArgs(interp, 1, objv, "");
279    return TCL_ERROR;
280  }
281  Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
282  return TCL_OK;
283}
284
285static int superlock_busy(void *pCtx, int nBusy){
286  InterpAndScript *p = (InterpAndScript *)pCtx;
287  Tcl_Obj *pEval;                 /* Script to evaluate */
288  int iVal = 0;                   /* Value to return */
289
290  pEval = Tcl_DuplicateObj(p->pScript);
291  Tcl_IncrRefCount(pEval);
292  Tcl_ListObjAppendElement(p->interp, pEval, Tcl_NewIntObj(nBusy));
293  Tcl_EvalObjEx(p->interp, pEval, TCL_EVAL_GLOBAL);
294  Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iVal);
295  Tcl_DecrRefCount(pEval);
296
297  return iVal;
298}
299
300/*
301** Tclcmd: sqlite3demo_superlock CMDNAME PATH VFS BUSY-HANDLER-SCRIPT
302*/
303static int superlock_cmd(
304  ClientData cd,
305  Tcl_Interp *interp,
306  int objc,
307  Tcl_Obj *CONST objv[]
308){
309  void *pLock;                    /* Lock context */
310  char *zPath;
311  char *zVfs = 0;
312  InterpAndScript busy = {0, 0};
313  int (*xBusy)(void*,int) = 0;    /* Busy handler callback */
314  int rc;                         /* Return code from sqlite3demo_superlock() */
315
316  if( objc<3 || objc>5 ){
317    Tcl_WrongNumArgs(
318        interp, 1, objv, "CMDNAME PATH ?VFS? ?BUSY-HANDLER-SCRIPT?");
319    return TCL_ERROR;
320  }
321
322  zPath = Tcl_GetString(objv[2]);
323
324  if( objc>3 ){
325    zVfs = Tcl_GetString(objv[3]);
326    if( strlen(zVfs)==0 ) zVfs = 0;
327  }
328  if( objc>4 ){
329    busy.interp = interp;
330    busy.pScript = objv[4];
331    xBusy = superlock_busy;
332  }
333
334  rc = sqlite3demo_superlock(zPath, zVfs, xBusy, &busy, &pLock);
335  assert( rc==SQLITE_OK || pLock==0 );
336  assert( rc!=SQLITE_OK || pLock!=0 );
337
338  if( rc!=SQLITE_OK ){
339    extern const char *sqlite3ErrStr(int);
340    Tcl_ResetResult(interp);
341    Tcl_AppendResult(interp, sqlite3ErrStr(rc), 0);
342    return TCL_ERROR;
343  }
344
345  Tcl_CreateObjCommand(
346      interp, Tcl_GetString(objv[1]), superunlock_cmd, pLock, superunlock_del
347  );
348  Tcl_SetObjResult(interp, objv[1]);
349  return TCL_OK;
350}
351
352int SqliteSuperlock_Init(Tcl_Interp *interp){
353  Tcl_CreateObjCommand(interp, "sqlite3demo_superlock", superlock_cmd, 0, 0);
354  return TCL_OK;
355}
356#endif
357