/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Copyright (C) 1999-2011 Novell, Inc. (www.novell.com) * * This library is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . * */ #include #include #include #include #include #include #include #include #include #include "e-sqlite3-vfs.h" #define SYNC_TIMEOUT_SECONDS 5 static sqlite3_vfs *old_vfs = NULL; static GThreadPool *sync_pool = NULL; typedef struct { sqlite3_file parent; sqlite3_file *old_vfs_file; /* pointer to old_vfs' file */ GRecMutex sync_mutex; guint timeout_id; gint flags; /* Do know how many syncs are pending, to not close the file before the last sync is over */ guint pending_syncs; GMutex pending_syncs_lock; GCond pending_syncs_cond; } ESqlite3File; static gint call_old_file_Sync (ESqlite3File *cFile, gint flags) { g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); g_return_val_if_fail (cFile != NULL, SQLITE_ERROR); g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR); return cFile->old_vfs_file->pMethods->xSync (cFile->old_vfs_file, flags); } struct SyncRequestData { ESqlite3File *cFile; guint32 flags; EFlag *sync_op; /* not NULL when waiting for a finish; will be freed by the caller */ }; static void sync_request_thread_cb (gpointer task_data, gpointer null_data) { struct SyncRequestData *sync_data = task_data; EFlag *sync_op; g_return_if_fail (sync_data != NULL); g_return_if_fail (sync_data->cFile != NULL); call_old_file_Sync (sync_data->cFile, sync_data->flags); g_mutex_lock (&sync_data->cFile->pending_syncs_lock); g_warn_if_fail (sync_data->cFile->pending_syncs > 0); sync_data->cFile->pending_syncs--; if (!sync_data->cFile->pending_syncs) g_cond_signal (&sync_data->cFile->pending_syncs_cond); g_mutex_unlock (&sync_data->cFile->pending_syncs_lock); sync_op = sync_data->sync_op; g_free (sync_data); if (sync_op) e_flag_set (sync_op); } static void sync_push_request (ESqlite3File *cFile, gboolean wait_for_finish) { struct SyncRequestData *data; EFlag *sync_op = NULL; GError *error = NULL; g_return_if_fail (cFile != NULL); g_return_if_fail (sync_pool != NULL); g_rec_mutex_lock (&cFile->sync_mutex); if (!cFile->flags) { /* nothing to sync, might be when xClose is called without any pending xSync request */ g_rec_mutex_unlock (&cFile->sync_mutex); return; } if (wait_for_finish) sync_op = e_flag_new (); data = g_new0 (struct SyncRequestData, 1); data->cFile = cFile; data->flags = cFile->flags; data->sync_op = sync_op; cFile->flags = 0; g_mutex_lock (&cFile->pending_syncs_lock); cFile->pending_syncs++; g_mutex_unlock (&cFile->pending_syncs_lock); g_rec_mutex_unlock (&cFile->sync_mutex); g_thread_pool_push (sync_pool, data, &error); if (error) { g_warning ("%s: Failed to push to thread pool: %s\n", G_STRFUNC, error->message); g_error_free (error); if (sync_op) e_flag_free (sync_op); return; } if (sync_op) { e_flag_wait (sync_op); e_flag_free (sync_op); } } static gboolean sync_push_request_timeout (gpointer user_data) { ESqlite3File *cFile = user_data; g_rec_mutex_lock (&cFile->sync_mutex); if (cFile->timeout_id != 0) { sync_push_request (cFile, FALSE); cFile->timeout_id = 0; } g_rec_mutex_unlock (&cFile->sync_mutex); return FALSE; } #define def_subclassed(_nm, _params, _call) \ static gint \ e_sqlite3_file_ ## _nm _params \ { \ ESqlite3File *cFile; \ \ g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); \ g_return_val_if_fail (pFile != NULL, SQLITE_ERROR); \ \ cFile = (ESqlite3File *) pFile; \ g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR); \ return cFile->old_vfs_file->pMethods->_nm _call; \ } #define def_subclassed_void(_nm, _params, _call) \ static void \ e_sqlite3_file_ ## _nm _params \ { \ ESqlite3File *cFile; \ \ g_return_if_fail (old_vfs != NULL); \ g_return_if_fail (pFile != NULL); \ \ cFile = (ESqlite3File *) pFile; \ g_return_if_fail (cFile->old_vfs_file->pMethods != NULL); \ cFile->old_vfs_file->pMethods->_nm _call; \ } def_subclassed (xRead, (sqlite3_file *pFile, gpointer pBuf, gint iAmt, sqlite3_int64 iOfst), (cFile->old_vfs_file, pBuf, iAmt, iOfst)) def_subclassed (xWrite, (sqlite3_file *pFile, gconstpointer pBuf, gint iAmt, sqlite3_int64 iOfst), (cFile->old_vfs_file, pBuf, iAmt, iOfst)) def_subclassed (xTruncate, (sqlite3_file *pFile, sqlite3_int64 size), (cFile->old_vfs_file, size)) def_subclassed (xFileSize, (sqlite3_file *pFile, sqlite3_int64 *pSize), (cFile->old_vfs_file, pSize)) def_subclassed (xLock, (sqlite3_file *pFile, gint lockType), (cFile->old_vfs_file, lockType)) def_subclassed (xUnlock, (sqlite3_file *pFile, gint lockType), (cFile->old_vfs_file, lockType)) def_subclassed (xFileControl, (sqlite3_file *pFile, gint op, gpointer pArg), (cFile->old_vfs_file, op, pArg)) def_subclassed (xSectorSize, (sqlite3_file *pFile), (cFile->old_vfs_file)) def_subclassed (xDeviceCharacteristics, (sqlite3_file *pFile), (cFile->old_vfs_file)) def_subclassed (xShmMap, (sqlite3_file *pFile, gint iPg, gint pgsz, gint n, void volatile **arr), (cFile->old_vfs_file, iPg, pgsz, n, arr)) def_subclassed (xShmLock, (sqlite3_file *pFile, gint offset, gint n, gint flags), (cFile->old_vfs_file, offset, n, flags)) def_subclassed_void (xShmBarrier, (sqlite3_file *pFile), (cFile->old_vfs_file)) def_subclassed (xShmUnmap, (sqlite3_file *pFile, gint deleteFlag), (cFile->old_vfs_file, deleteFlag)) def_subclassed (xFetch, (sqlite3_file *pFile, sqlite3_int64 iOfst, gint iAmt, void **pp), (cFile->old_vfs_file, iOfst, iAmt, pp)) def_subclassed (xUnfetch, (sqlite3_file *pFile, sqlite3_int64 iOfst, void *p), (cFile->old_vfs_file, iOfst, p)) #undef def_subclassed static gint e_sqlite3_file_xCheckReservedLock (sqlite3_file *pFile, gint *pResOut) { ESqlite3File *cFile; g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); g_return_val_if_fail (pFile != NULL, SQLITE_ERROR); cFile = (ESqlite3File *) pFile; g_return_val_if_fail (cFile->old_vfs_file->pMethods != NULL, SQLITE_ERROR); /* check version in runtime */ if (sqlite3_libversion_number () < 3006000) return ((gint (*)(sqlite3_file *)) (cFile->old_vfs_file->pMethods->xCheckReservedLock)) (cFile->old_vfs_file); else return ((gint (*)(sqlite3_file *, gint *)) (cFile->old_vfs_file->pMethods->xCheckReservedLock)) (cFile->old_vfs_file, pResOut); } static gint e_sqlite3_file_xClose (sqlite3_file *pFile) { ESqlite3File *cFile; gint res; g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); g_return_val_if_fail (pFile != NULL, SQLITE_ERROR); cFile = (ESqlite3File *) pFile; g_rec_mutex_lock (&cFile->sync_mutex); /* Cancel any pending sync requests. */ if (cFile->timeout_id > 0) { g_source_remove (cFile->timeout_id); cFile->timeout_id = 0; } g_rec_mutex_unlock (&cFile->sync_mutex); /* Make the last sync. */ sync_push_request (cFile, TRUE); g_mutex_lock (&cFile->pending_syncs_lock); while (cFile->pending_syncs > 0) { g_cond_wait (&cFile->pending_syncs_cond, &cFile->pending_syncs_lock); } g_mutex_unlock (&cFile->pending_syncs_lock); if (cFile->old_vfs_file->pMethods) res = cFile->old_vfs_file->pMethods->xClose (cFile->old_vfs_file); else res = SQLITE_OK; g_free (cFile->old_vfs_file); cFile->old_vfs_file = NULL; g_rec_mutex_clear (&cFile->sync_mutex); g_mutex_clear (&cFile->pending_syncs_lock); g_cond_clear (&cFile->pending_syncs_cond); return res; } static gint e_sqlite3_file_xSync (sqlite3_file *pFile, gint flags) { ESqlite3File *cFile; g_return_val_if_fail (old_vfs != NULL, SQLITE_ERROR); g_return_val_if_fail (pFile != NULL, SQLITE_ERROR); cFile = (ESqlite3File *) pFile; g_rec_mutex_lock (&cFile->sync_mutex); /* If a sync request is already scheduled, accumulate flags. */ cFile->flags |= flags; /* Cancel any pending sync requests. */ if (cFile->timeout_id > 0) g_source_remove (cFile->timeout_id); /* Wait SYNC_TIMEOUT_SECONDS before we actually sync. */ cFile->timeout_id = e_named_timeout_add_seconds ( SYNC_TIMEOUT_SECONDS, sync_push_request_timeout, cFile); g_rec_mutex_unlock (&cFile->sync_mutex); return SQLITE_OK; } static gint e_sqlite3_vfs_xOpen (sqlite3_vfs *pVfs, const gchar *zPath, sqlite3_file *pFile, gint flags, gint *pOutFlags) { static GRecMutex only_once_lock; static sqlite3_io_methods io_methods = {0}; ESqlite3File *cFile; gint res; g_return_val_if_fail (old_vfs != NULL, -1); g_return_val_if_fail (pFile != NULL, -1); cFile = (ESqlite3File *) pFile; cFile->old_vfs_file = g_malloc0 (old_vfs->szOsFile); res = old_vfs->xOpen (old_vfs, zPath, cFile->old_vfs_file, flags, pOutFlags); if (res != SQLITE_OK) { g_free (cFile->old_vfs_file); return res; } g_rec_mutex_init (&cFile->sync_mutex); g_mutex_init (&cFile->pending_syncs_lock); g_cond_init (&cFile->pending_syncs_cond); cFile->pending_syncs = 0; g_rec_mutex_lock (&only_once_lock); if (!sync_pool) sync_pool = g_thread_pool_new (sync_request_thread_cb, NULL, 2, FALSE, NULL); /* cFile->old_vfs_file->pMethods is NULL when open failed for some reason, * thus do not initialize our structure when do not know the version */ if (io_methods.xClose == NULL && cFile->old_vfs_file->pMethods) { /* initialize our subclass function only once */ io_methods.iVersion = cFile->old_vfs_file->pMethods->iVersion; /* check version in compile time */ #if SQLITE_VERSION_NUMBER < 3006000 io_methods.xCheckReservedLock = (gint (*)(sqlite3_file *)) e_sqlite3_file_xCheckReservedLock; #else io_methods.xCheckReservedLock = e_sqlite3_file_xCheckReservedLock; #endif #define use_subclassed(x) io_methods.x = e_sqlite3_file_ ## x use_subclassed (xClose); use_subclassed (xRead); use_subclassed (xWrite); use_subclassed (xTruncate); use_subclassed (xSync); use_subclassed (xFileSize); use_subclassed (xLock); use_subclassed (xUnlock); use_subclassed (xFileControl); use_subclassed (xSectorSize); use_subclassed (xDeviceCharacteristics); if (io_methods.iVersion > 1) { use_subclassed (xShmMap); use_subclassed (xShmLock); use_subclassed (xShmBarrier); use_subclassed (xShmUnmap); } if (io_methods.iVersion > 2) { use_subclassed (xFetch); use_subclassed (xUnfetch); } if (io_methods.iVersion > 3) { g_warning ("%s: Unchecked IOMethods version %d, downgrading to version 3", G_STRFUNC, io_methods.iVersion); io_methods.iVersion = 3; } #undef use_subclassed } g_rec_mutex_unlock (&only_once_lock); cFile->parent.pMethods = &io_methods; return res; } static gpointer init_sqlite_vfs (void) { static sqlite3_vfs vfs = { 0 }; old_vfs = sqlite3_vfs_find (NULL); g_return_val_if_fail (old_vfs != NULL, NULL); memcpy (&vfs, old_vfs, sizeof (sqlite3_vfs)); vfs.szOsFile = sizeof (ESqlite3File); vfs.zName = "e_sqlite3_vfs"; vfs.xOpen = e_sqlite3_vfs_xOpen; sqlite3_vfs_register (&vfs, 1); return NULL; } /** * e_sqlite3_vfs_init: * * Since: 3.2 **/ void e_sqlite3_vfs_init (void) { static GOnce vfs_once = G_ONCE_INIT; g_once (&vfs_once, (GThreadFunc) init_sqlite_vfs, NULL); return; }