Fix return values. Check text for qoutes.
[shibboleth/sp.git] / odbc-store / odbc-store.cpp
1 /*
2  *  Copyright 2001-2007 Internet2
3  * 
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /*
18  * odbc-store.cpp - Storage service using ODBC
19  *
20  * $Id$
21  */
22
23 // eventually we might be able to support autoconf via cygwin...
24 #if defined (_MSC_VER) || defined(__BORLANDC__)
25 # include "config_win32.h"
26 #else
27 # include "config.h"
28 #endif
29
30 #ifdef WIN32
31 # define _CRT_NONSTDC_NO_DEPRECATE 1
32 # define _CRT_SECURE_NO_DEPRECATE 1
33 # define NOMINMAX
34 # define SHIBODBC_EXPORTS __declspec(dllexport)
35 #else
36 # define SHIBODBC_EXPORTS
37 #endif
38
39 #include <shib-target/shib-target.h>
40 #include <shibsp/exceptions.h>
41 #include <log4cpp/Category.hh>
42 #include <xmltooling/util/NDC.h>
43 #include <xmltooling/util/Threads.h>
44
45 #include <ctime>
46 #include <algorithm>
47 #include <sstream>
48
49 #include <sql.h>
50 #include <sqlext.h>
51
52 #ifdef HAVE_LIBDMALLOCXX
53 #include <dmalloc.h>
54 #endif
55
56 using namespace shibsp;
57 using namespace shibtarget;
58 using namespace opensaml::saml2md;
59 using namespace saml;
60 using namespace xmltooling;
61 using namespace log4cpp;
62 using namespace std;
63
64 #define PLUGIN_VER_MAJOR 3
65 #define PLUGIN_VER_MINOR 0
66
67 #define COLSIZE_KEY 64
68 #define COLSIZE_CONTEXT 256
69 #define COLSIZE_STRING_VALUE 256
70
71
72 /* tables definitions - not used here */
73
74 #define STRING_TABLE "STRING_STORE"
75
76 #define STRING_TABLE \
77   "CREATE TABLE STRING_TABLE ( "\
78   "context VARCHAR( COLSIZE_CONTEXT ), " \
79   "key VARCHAR( COLSIZE_KEY ), " \
80   "value VARCHAR( COLSIZE_STRING_VALUE ), " \
81   "expires TIMESTAMP, "
82   "PRIMARY KEY (context, key), "
83   "INDEX (context))"
84
85
86 #define TEXT_TABLE "TEXT_STORE"
87
88 #define TEXT_TABLE \
89   "CREATE TABLE TEXT_TABLE ( "\
90   "context VARCHAR( COLSIZE_CONTEXT ), " \
91   "key VARCHAR( COLSIZE_KEY ), " \
92   "value LONG TEXT, " \
93   "expires TIMESTAMP, "
94   "PRIMARY KEY (context, key), "
95   "INDEX (context))"
96
97
98
99
100 static const XMLCh ConnectionString[] =
101 { chLatin_C, chLatin_o, chLatin_n, chLatin_n, chLatin_e, chLatin_c, chLatin_t, chLatin_i, chLatin_o, chLatin_n,
102   chLatin_S, chLatin_t, chLatin_r, chLatin_i, chLatin_n, chLatin_g, chNull
103 };
104 static const XMLCh cleanupInterval[] =
105 { chLatin_c, chLatin_l, chLatin_e, chLatin_a, chLatin_n, chLatin_u, chLatin_p,
106   chLatin_I, chLatin_n, chLatin_t, chLatin_e, chLatin_r, chLatin_v, chLatin_a, chLatin_l, chNull
107 };
108 static const XMLCh cacheTimeout[] =
109 { chLatin_c, chLatin_a, chLatin_c, chLatin_h, chLatin_e, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
110 static const XMLCh odbcTimeout[] =
111 { chLatin_o, chLatin_d, chLatin_b, chLatin_c, chLatin_T, chLatin_i, chLatin_m, chLatin_e, chLatin_o, chLatin_u, chLatin_t, chNull };
112 static const XMLCh storeAttributes[] =
113 { chLatin_s, chLatin_t, chLatin_o, chLatin_r, chLatin_e, chLatin_A, chLatin_t, chLatin_t, chLatin_r, chLatin_i, chLatin_b, chLatin_u, chLatin_t, chLatin_e, chLatin_s, chNull };
114
115 static const XMLCh cleanupInterval[] = UNICODE_LITERAL_15(c,l,e,a,n,u,p,I,n,t,e,r,v,a,l);
116
117
118 // ODBC tools
119
120 struct ODBCConn {
121     ODBCConn(SQLHDBC conn) : handle(conn) {}
122     ~ODBCConn() {SQLFreeHandle(SQL_HANDLE_DBC,handle);}
123     operator SQLHDBC() {return handle;}
124     SQLHDBC handle;
125 };
126
127 class ODBCBase : public virtual saml::IPlugIn
128 {
129 public:
130     ODBCBase(const DOMElement* e);
131     virtual ~ODBCBase();
132
133     SQLHDBC getHDBC();
134
135     Category* log;
136
137 protected:
138     const DOMElement* m_root; // can only use this during initialization
139     string m_connstring;
140
141     static SQLHENV m_henv;          // single handle for both plugins
142     bool m_bInitializedODBC;        // tracks which class handled the process
143     static const char* p_connstring;
144
145     pair<int,int> getVersion(SQLHDBC);
146     void log_error(SQLHANDLE handle, SQLSMALLINT htype);
147 };
148
149 SQLHENV ODBCBase::m_henv = SQL_NULL_HANDLE;
150 const char* ODBCBase::p_connstring = NULL;
151
152 ODBCBase::ODBCBase(const DOMElement* e) : m_root(e), m_bInitializedODBC(false)
153 {
154 #ifdef _DEBUG
155     xmltooling::NDC ndc("ODBCBase");
156 #endif
157     log = &(Category::getInstance("shibtarget.ODBC"));
158
159     if (m_henv == SQL_NULL_HANDLE) {
160         // Enable connection pooling.
161         SQLSetEnvAttr(SQL_NULL_HANDLE, SQL_ATTR_CONNECTION_POOLING, (void*)SQL_CP_ONE_PER_HENV, 0);
162
163         // Allocate the environment.
164         if (!SQL_SUCCEEDED(SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv)))
165             throw ConfigurationException("ODBC failed to initialize.");
166
167         // Specify ODBC 3.x
168         SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
169
170         log->info("ODBC initialized");
171         m_bInitializedODBC = true;
172     }
173
174     // Grab connection string from the configuration.
175     e=XMLHelper::getFirstChildElement(e,ConnectionString);
176     if (!e || !e->hasChildNodes()) {
177         if (!p_connstring) {
178             this->~ODBCBase();
179             throw ConfigurationException("ODBC cache requires ConnectionString element in configuration.");
180         }
181         m_connstring=p_connstring;
182     }
183     else {
184         xmltooling::auto_ptr_char arg(e->getFirstChild()->getNodeValue());
185         m_connstring=arg.get();
186         p_connstring=m_connstring.c_str();
187     }
188
189     // Connect and check version.
190     SQLHDBC conn=getHDBC();
191     pair<int,int> v=getVersion(conn);
192     SQLFreeHandle(SQL_HANDLE_DBC,conn);
193
194     // Make sure we've got the right version.
195     if (v.first != PLUGIN_VER_MAJOR) {
196         this->~ODBCBase();
197         log->crit("unknown database version: %d.%d", v.first, v.second);
198         throw SAMLException("Unknown cache database version.");
199     }
200 }
201
202 ODBCBase::~ODBCBase()
203 {
204     //delete m_mysql;
205     if (m_bInitializedODBC)
206         SQLFreeHandle(SQL_HANDLE_ENV,m_henv);
207     m_bInitializedODBC=false;
208     m_henv = SQL_NULL_HANDLE;
209     p_connstring=NULL;
210 }
211
212 void ODBCBase::log_error(SQLHANDLE handle, SQLSMALLINT htype)
213 {
214     SQLSMALLINT  i = 0;
215     SQLINTEGER   native;
216     SQLCHAR      state[7];
217     SQLCHAR      text[256];
218     SQLSMALLINT  len;
219     SQLRETURN    ret;
220
221     do {
222         ret = SQLGetDiagRec(htype, handle, ++i, state, &native, text, sizeof(text), &len);
223         if (SQL_SUCCEEDED(ret))
224             log->error("ODBC Error: %s:%ld:%ld:%s", state, i, native, text);
225     } while(SQL_SUCCEEDED(ret));
226 }
227
228 SQLHDBC ODBCBase::getHDBC()
229 {
230 #ifdef _DEBUG
231     xmltooling::NDC ndc("getMYSQL");
232 #endif
233
234     // Get a handle.
235     SQLHDBC handle;
236     SQLRETURN sr=SQLAllocHandle(SQL_HANDLE_DBC, m_henv, &handle);
237     if (!SQL_SUCCEEDED(sr)) {
238         log->error("failed to allocate connection handle");
239         log_error(m_henv, SQL_HANDLE_ENV);
240         throw SAMLException("ODBCBase::getHDBC failed to allocate connection handle");
241     }
242
243     sr=SQLDriverConnect(handle,NULL,(SQLCHAR*)m_connstring.c_str(),m_connstring.length(),NULL,0,NULL,SQL_DRIVER_NOPROMPT);
244     if (!SQL_SUCCEEDED(sr)) {
245         log->error("failed to connect to database");
246         log_error(handle, SQL_HANDLE_DBC);
247         throw SAMLException("ODBCBase::getHDBC failed to connect to database");
248     }
249
250     return handle;
251 }
252
253 pair<int,int> ODBCBase::getVersion(SQLHDBC conn)
254 {
255     // Grab the version number from the database.
256     SQLHSTMT hstmt;
257     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
258     
259     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)"SELECT major,minor FROM version", SQL_NTS);
260     if (!SQL_SUCCEEDED(sr)) {
261         log->error("failed to read version from database");
262         log_error(hstmt, SQL_HANDLE_STMT);
263         throw SAMLException("ODBCBase::getVersion failed to read version from database");
264     }
265
266     SQLINTEGER major;
267     SQLINTEGER minor;
268     SQLBindCol(hstmt,1,SQL_C_SLONG,&major,0,NULL);
269     SQLBindCol(hstmt,2,SQL_C_SLONG,&minor,0,NULL);
270
271     if ((sr=SQLFetch(hstmt)) != SQL_NO_DATA) {
272         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
273         return pair<int,int>(major,minor);
274     }
275
276     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
277     log->error("no rows returned in version query");
278     throw SAMLException("ODBCBase::getVersion failed to read version from database");
279 }
280
281
282 // ------------------------------------------------------------
283
284 // ODBC Storage Service class
285
286 class ODBCStorageService : public ODBCBase, public StorageService
287 {
288     string stringTable = STRING_TABLE;
289     string textTable = TEXT_TABLE;
290
291 public:
292     ODBCStorageService(const DOMElement* e);
293     virtual ~ODBCStorageService();
294
295     void createString(const char* context, const char* key, const char* value, time_t expiration) {
296         return createRow(string_table, context, key, value, expiration);
297     }
298     bool readString(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL) {
299         return readRow(string_table, context, key, value, expiration, COLSIZE_STRING_VALUE);
300     }
301     bool updateString(const char* context, const char* key, const char* value=NULL, time_t expiration=0) {
302         return updateRow(string_table, context, key, value, expiration);
303     }
304     bool deleteString(const char* context, const char* key) {
305         return deleteRow(string_table, context, key, value, expiration);
306     }
307
308     void createText(const char* context, const char* key, const char* value, time_t expiration) {
309         return createRow(text_table, context, key, value, expiration);
310     }
311     bool readText(const char* context, const char* key, string* pvalue=NULL, time_t* pexpiration=NULL) {
312         return readRow(text_table, context, key, value, expiration, 0);
313     }
314     bool updateText(const char* context, const char* key, const char* value=NULL, time_t expiration=0) {
315         return updateRow(text_table, context, key, value, expiration);
316     }
317     bool deleteText(const char* context, const char* key) {
318         return deleteRow(text_table, context, key, value, expiration);
319     }
320
321     void reap(const char* context) {
322         reap(string_table, context);
323         reap(text_table, context);
324     }
325     void deleteContext(const char* context) {
326         deleteCtx(string_table, context);
327         deleteCtx(text_table, context);
328     }
329      
330
331 private:
332
333     void createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
334     bool readRow(const char *table, const char* context, const char* key, string* pvalue, time_t* pexpiration, int maxsize);
335     bool updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration);
336     bool deleteRow(const char *table, const char* context, const char* key);
337
338     void reapRows(const char* table, const char* context);
339     void deleteCtx(const char* table, const char* context);
340
341     xmltooling::CondWait* shutdown_wait;
342     bool shutdown;
343     xmltooling::Thread* cleanup_thread;
344
345     static void* cleanup_fcn(void*); 
346     void cleanup();
347
348     CondWait* shutdown_wait;
349     Thread* cleanup_thread;
350     bool shutdown;
351     int m_cleanupInterval;
352     Category& log;
353
354     StorageService* ODBCStorageServiceFactory(const DOMElement* const & e)
355     {
356         return new ODBCStorageService(e);
357     }
358
359     // convert SQL timestamp to time_t 
360     time_t timeFromTimestamp(SQL_TIMESTAMP_STRUCT expires)
361     {
362         time_t ret;
363         struct tm t;
364         t.tm_sec=expires.second;
365         t.tm_min=expires.minute;
366         t.tm_hour=expires.hour;
367         t.tm_mday=expires.day;
368         t.tm_mon=expires.month-1;
369         t.tm_year=expires.year-1900;
370         t.tm_isdst=0;
371 #if defined(HAVE_TIMEGM)
372         ret = timegm(&t);
373 #else
374         ret = mktime(&t) - timezone;
375 #endif
376         return (ret);
377     }
378
379     // conver time_t to SQL string
380     void timestampFromTime(time_t t, char &ret)
381     {
382 #ifdef HAVE_GMTIME_R
383         struct tm res;
384         struct tm* ptime=gmtime_r(&created,&res);
385 #else
386         struct tm* ptime=gmtime(&created);
387 #endif
388         strftime(ret,32,"{ts '%Y-%m-%d %H:%M:%S'}",ptime);
389     }
390
391     // make a string safe for SQL command
392     // result to be free'd only if it isn't the input
393     char *makeSafeSQL(const char *src)
394     {
395        int ns = 0;
396        int nc = 0;
397        char *s;
398     
399        // see if any conversion needed
400        for (s=(char*)src; *s; nc++,s++) if (*s=='\''||*s=='\\') ns++;
401        if (ns==0) return ((char*)src);
402     
403        char *safe = (char*) malloc(nc+2*ns+1);
404        for (s=safe; *src; src++) {
405            if (*src=='\''||*src=='\\') *s++ = '\\';
406            *s++ = (char)*src;
407        }
408        *s = '\0';
409        return (safe);
410     }
411
412     void freeSafeSQL(char *safe, const char *src)
413     {
414         if (safe!=src) free(safe);
415     }
416
417 };
418
419 // class constructor
420
421 ODBCStorageService::ODBCStorageService(const DOMElement* e):
422    ODBCBase(e),
423    shutdown(false),
424    m_cleanupInterval(0)
425
426 {
427 #ifdef _DEBUG
428     xmltooling::NDC ndc("ODBCStorageService");
429 #endif
430     log = &(Category::getInstance("shibtarget.StorageService.ODBC"));
431
432     const XMLCh* tag=e ? e->getAttributeNS(NULL,cleanupInterval) : NULL;
433     if (tag && *tag) {
434         m_cleanupInterval = XMLString::parseInt(tag);
435     }
436     if (!m_cleanupInterval) m_cleanupInterval=300;
437
438     contextLock = Mutex::create();
439     shutdown_wait = CondWait::create();
440
441     // Initialize the cleanup thread
442     cleanup_thread = Thread::create(&cleanup_fcn, (void*)this);
443 }
444
445 ODBCStorageService::~ODBCStorageService()
446 {
447     shutdown = true;
448     shutdown_wait->signal();
449     cleanup_thread->join(NULL);
450
451     delete shutdown_wait;
452 }
453
454
455 // create 
456
457 void ODBCStorageService::createRow(const char *table, const char* context, const char* key, const char* value, time_t expiration)
458 {
459 #ifdef _DEBUG
460     xmltooling::NDC ndc("createRow");
461 #endif
462
463     char timebuf[32];
464     timestampFromTime(expiration, timebuf);
465
466     // Get statement handle.
467     SQLHSTMT hstmt;
468     ODBCConn conn(getHDBC());
469     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
470
471     // Prepare and exectute insert statement.
472     char *scontext = makeSafeSQL(context);
473     char *svalue = makeSafeSQL(value);
474     string q  = string("INSERT ") + table + " VALUES ('" + scontext + "','" + key + "','" + svalue + "'," + timebuf + "')";
475     freeSafeSQL(scontext, context)
476     freeSafeSQL(svalue, value)
477     log->debug("SQL: %s", q.str());
478
479     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
480     if (!SQL_SUCCEEDED(sr)) {
481         log->error("insert record failed (t=%s, c=%s, k=%s", table, context, key);
482         log_error(hstmt, SQL_HANDLE_STMT);
483     }
484
485     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
486 }
487
488 // read
489
490 bool ODBCStorageService::readRow(const char *table, const char* context, const char* key, string& pvalue, time_t& pexpiration, int maxsize)
491 {
492 #ifdef _DEBUG
493     xmltooling::NDC ndc("readRow");
494 #endif
495
496     SQLCHAR *tvalue = NULL;
497     SQL_TIMESTAMP_STRUCT expires;
498     time_t exp;
499
500     // Get statement handle.
501     SQLHSTMT hstmt;
502     ODBCConn conn(getHDBC());
503     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
504
505     // Prepare and exectute select statement.
506     char *scontext = makeSafeSQL(context);
507     string q = string("SELECT expires,value FROM ") + table +
508                " WHERE context='" + scontext + "' AND key='" + key + "'";
509     freeSafeSQL(scontext, context)
510     log->debug("SQL: %s", q.str());
511
512     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
513     if (!SQL_SUCCEEDED(sr)) {
514         log->error("error searching for (t=%s, c=%s, k=%s)", table, context, key);
515         log_error(hstmt, SQL_HANDLE_STMT);
516         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
517         return false;
518     }
519
520     // retrieve data 
521     SQLBindCol(hstmt,1,SQL_C_TYPE_TIMESTAMP,&expires,0,NULL);
522
523     if ((sr=SQLFetch(hstmt)) == SQL_NO_DATA) {
524         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
525         return false;
526     }
527
528     // expire time from bound col
529     exp = timeFromTimestamp(expires);
530     if (time(NULL)>ezp) {
531         log->debug(".. expired");
532         SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
533         return false;
534     }
535     if (pexpiration) pexpiration = exp;
536
537     // value by getdata
538
539     // see how much text there is
540     if (maxsize==0) {
541          SQLINTEGER nch;
542          SQLCHAR tv[12];
543          sr = SQLGetData(hstmt, 2, SQL_C_CHAR, tvp, BUFSIZE_TEXT_BLOCK, &nch);
544          if (sr==SQL_SUCCESS || sr==SQL_SUCCESS_WITH_INFO) {
545              maxsize = nch;
546          }
547     }
548
549     tvalue = (SQLCHAR*) malloc(maxsize+1);
550     sr = SQLGetData(hstmt, 2, SQL_C_CHAR, tvalue, maxsize, &nch);
551         if (sr!=SQL_SUCCESS) {
552             log->error("error retriving value for (t=%s, c=%s, k=%s)", table, context, key);
553             log_error(hstmt, SQL_HANDLE_STMT);
554             SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
555             return false;
556         }
557     }
558     pvalue = string(tvalue);
559     free(tvalue);
560
561     log->debug(".. value found");
562
563     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
564     return true;
565 }
566
567
568 // update 
569
570 bool ODBCStorageService::updateRow(const char *table, const char* context, const char* key, const char* value, time_t expiration)
571 {
572 #ifdef _DEBUG
573     xmltooling::NDC ndc("updateRow");
574 #endif
575
576     bool ret = true;
577
578     char timebuf[32];
579     timestampFromTime(expiration, timebuf);
580
581     // Get statement handle.
582     SQLHSTMT hstmt;
583     ODBCConn conn(getHDBC());
584     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
585
586     // Prepare and exectute update statement.
587
588     string expstr = "";
589     if (expiration) expstr = string(",expires = '") + timebuf + "' ";
590
591     char *scontext = makeSafeSQL(context);
592     char *svalue = makeSafeSQL(value);
593     string q  = string("UPDATE ") + table + " SET value='" + svalue + "'" + expstr + 
594                " WHERE context='" + scontext + "' AND key='" + key + "' AND expires > NOW()";
595     freeSafeSQL(scontext, context)
596     freeSafeSQL(svalue, value)
597     log->debug("SQL: %s", q.str());
598
599     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.str().c_str(), SQL_NTS);
600     if (!SQL_SUCCEEDED(sr)) {
601         log->error("update record failed (t=%s, c=%s, k=%s", table, context, key);
602         log_error(hstmt, SQL_HANDLE_STMT);
603         ret = false;
604     }
605
606     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
607     return ret;
608 }
609
610
611 // delete
612
613 bool ODBCStorageService::deleteRow(const char *table, const char *context, const char* key)
614 {
615 #ifdef _DEBUG
616     xmltooling::NDC ndc("deleteRow");
617 #endif
618
619     bool ret = true;
620
621     // Get statement handle.
622     SQLHSTMT hstmt;
623     ODBCConn conn(getHDBC());
624     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
625
626     // Prepare and execute delete statement.
627     char *scontext = makeSafeSQL(context);
628     string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND key='" + key + "'";
629     freeSafeSQL(scontext, context)
630     log->debug("SQL: %s", q.str());
631
632     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
633  
634     if (sr==SQL_NO_DATA) {
635         ret = false;
636     } else if (!SQL_SUCCEEDED(sr)) {
637         log->error("error deleting record (t=%s, c=%s, k=%s)", table, context, key);
638         log_error(hstmt, SQL_HANDLE_STMT);
639         ret = false;
640     }
641
642     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
643     return ret;
644 }
645
646
647 // cleanup - delete expired entries
648
649 void ODBCStorageService::cleanup()
650 {
651 #ifdef _DEBUG
652     xmltooling::NDC ndc("cleanup");
653 #endif
654
655     Mutex* mutex = xmltooling::Mutex::create();
656
657     int rerun_timer = 0;
658     int timeout_life = 0;
659
660     mutex->lock();
661
662     log->info("cleanup thread started... running every %d secs", m_cleanupInterval);
663
664     while (!shutdown) {
665         shutdown_wait->timedwait(mutex, m_cleanupInterval);
666
667         if (shutdown) break;
668
669         reap(NULL);
670     }
671
672     log->info("cleanup thread exiting...");
673
674     mutex->unlock();
675     delete mutex;
676     xmltooling::Thread::exit(NULL);
677 }
678
679 void* ODBCStorageService::cleanup_fcn(void* cache_p)
680 {
681   ODBCStorageService* cache = (ODBCStorageService*)cache_p;
682
683 #ifndef WIN32
684   // First, let's block all signals
685   Thread::mask_all_signals();
686 #endif
687
688   // Now run the cleanup process.
689   cache->cleanup();
690   return NULL;
691 }
692
693
694 // remove expired entries for a context
695
696 void ODBCStorageService::reapRows(const char *table, const char* context)
697 {
698 #ifdef _DEBUG
699     xmltooling::NDC ndc("reapRows");
700 #endif
701
702     // Get statement handle.
703     SQLHSTMT hstmt;
704     ODBCConn conn(getHDBC());
705     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
706
707     // Prepare and execute delete statement.
708     string q;
709     if (context) {
710         char *scontext = makeSafeSQL(context);
711         q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "' AND expires<NOW()";
712         freeSafeSQL(scontext, context)
713     } else {
714         q = string("DELETE FROM ") + table + " WHERE expires<NOW()";
715     }
716     log->debug("SQL: %s", q.str());
717
718     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
719  
720     if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
721         log->error("error expiring records (t=%s, c=%s)", table, context?context:"null");
722         log_error(hstmt, SQL_HANDLE_STMT);
723     }
724
725     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
726 }
727
728
729
730 // remove all entries for a context
731
732 void ODBCStorageService::deleteCtx(const char *table, const char* context)
733 {
734 #ifdef _DEBUG
735     xmltooling::NDC ndc("deleteCtx");
736 #endif
737
738     // Get statement handle.
739     SQLHSTMT hstmt;
740     ODBCConn conn(getHDBC());
741     SQLAllocHandle(SQL_HANDLE_STMT,conn,&hstmt);
742
743     // Prepare and execute delete statement.
744     char *scontext = makeSafeSQL(context);
745     string q = string("DELETE FROM ") + table + " WHERE context='" + scontext + "'";
746     freeSafeSQL(scontext, context)
747     log->debug("SQL: %s", q.str());
748
749     SQLRETURN sr=SQLExecDirect(hstmt, (SQLCHAR*)q.c_str(), SQL_NTS);
750  
751     if ((sr!=SQL_NO_DATA) && !SQL_SUCCEEDED(sr)) {
752         log->error("error deleting context (t=%s, c=%s)", table, context);
753         log_error(hstmt, SQL_HANDLE_STMT);
754     }
755
756     SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
757 }