/** * store.c * * Copyright (C) 2003 - 2007 Bojan Smojver, Rexursive * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public Licence as published by the Free * Software Foundation; either version 2 of the Licence, or (at your option) * any later version. * * This program 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 General Public Licence for * more details. * * You should have received a copy of the GNU General Public Licence along * with this program; if not, write to the Free Software Foundation, Inc., 59 * Temple Place, Suite 330, Boston, MA 02111-1307 USA * * In addition, as two special exceptions, Bojan Smojver, Rexursive, gives * permission to: * * 1. Link, both statically and dynamically, the code of this program with * Apache HTTP Server, Apache Portable Runtime and Apache Portable Runtime * Utility Library from Apache Software Foundation (or with modified versions * of the above, that use the same licence - Apache Software Licence 1.1, 2.0 * or any later version), and distribute linked combinations including the * program and the above software from Apache Software Foundation. You must * obey the GNU General Public Licence in all respects for all of the code * used other than Apache HTTP Server, Apache Portable Runtime and Apache * Portable Runtime Utility Library. * * 2. Dynamically link any shared library with this program at run-time, * through the interface of SpinApplication or LoadModule run-time * configuration directives of Apache HTTP Server, as provided by this program * or Apache HTTP Server itself, regardless of licensing terms of those shared * libraries. You must obey the GNU General Public Licence in all respects for * all of the code used other than that of those shared libraries. * * If you modify this file, you may extend these exceptions to your version of * the file, but you are not obligated to do so. If you do not wish to do so, * delete one or both of these exception statements from your version. * */ #include "private.h" /* * Application and session API functions */ static void doc_parse(apr_pool_t *pool,xmlDoc *doc,apr_hash_t *hash){ xmlNode *root,*node,*text; xmlAttr *attr; root=xmlDocGetRootElement(doc); for(node=root->children;node;node=node->next){ if(xmlStrEqual(node->name,(xmlChar*)"prop") && (text=node->children) && xmlStrEqual(text->name,(xmlChar*)"text") && text->content && (attr=node->properties) && xmlStrEqual(attr->name,(xmlChar*)"name") && attr->children){ apr_hash_set(hash, apr_pstrdup(pool,(char *)attr->children->content), APR_HASH_KEY_STRING, rxv_spin_single(pool, apr_pstrdup(pool,(char *)text->content))); } } } static apr_status_t xctx_clean(void *data){ xmlFreeParserCtxt(((rxv_spin_ctx_t *)data)->xctx); ((rxv_spin_ctx_t *)data)->xctx=NULL; return APR_SUCCESS; } static void file_parse(rxv_spin_ctx_t *ctx,apr_file_t *file,apr_hash_t *hash){ xmlDoc *doc; apr_os_file_t fd; if(!ctx->xctx){ if((ctx->xctx=xmlNewParserCtxt())) apr_pool_cleanup_register(ctx->pool,ctx,xctx_clean,xctx_clean); else return; } if(apr_os_file_get(&fd,file)!=APR_SUCCESS) return; if((doc=xmlCtxtReadFd(ctx->xctx,fd,NULL,NULL,0))){ doc_parse(ctx->pool,doc,hash); xmlFreeDoc(doc); } } static void buffer_parse(rxv_spin_ctx_t *ctx,const char *buf,apr_size_t len, apr_hash_t *hash){ xmlDoc *doc; if(!ctx->xctx){ if((ctx->xctx=xmlNewParserCtxt())) apr_pool_cleanup_register(ctx->pool,ctx,xctx_clean,xctx_clean); else return; } if((doc=xmlCtxtReadMemory(ctx->xctx,buf,len,NULL,NULL,0))){ doc_parse(ctx->pool,doc,hash); xmlFreeDoc(doc); } } static xmlDoc *doc_store(apr_pool_t *pool,apr_hash_t *hash){ xmlDoc *doc; xmlNode *root,*node; apr_hash_index_t *hi; const void *k; void *v; apr_ssize_t len; if(!(doc=xmlNewDoc((const xmlChar *)"1.0"))) return NULL; if (!(root=xmlNewNode(NULL,(const xmlChar *)"spin"))){ xmlFreeDoc(doc); return NULL; } xmlDocSetRootElement(doc,root); for(hi=apr_hash_first(pool,hash);hi;hi=apr_hash_next(hi)){ apr_hash_this(hi,&k,&len,&v); if(!(node=xmlNewChild(root,NULL, (const xmlChar *)"prop", (const xmlChar *)(((rxv_spin_data_t *)v)->data)))){ xmlFreeDoc(doc); return NULL; } if(!xmlNewProp(node,(const xmlChar *)"name",(const xmlChar *)k)){ xmlFreeDoc(doc); return NULL; } } return doc; } static char *buffer_store(apr_pool_t *pool,apr_hash_t *hash){ char *data; xmlSaveCtxt *xctx; xmlBuffer *buf; xmlDoc *doc; if(!(buf=xmlBufferCreate())) return NULL; if(!(xctx=xmlSaveToBuffer(buf,NULL,XML_SAVE_NO_DECL))){ xmlBufferFree(buf); return NULL; } if(!(doc=doc_store(pool,hash))){ xmlSaveClose(xctx); xmlBufferFree(buf); return NULL; } if(xmlSaveDoc(xctx,doc)<0){ xmlSaveClose(xctx); xmlBufferFree(buf); xmlFreeDoc(doc); return NULL; } if(xmlSaveClose(xctx)<=0){ xmlBufferFree(buf); xmlFreeDoc(doc); return NULL; } data = apr_pstrdup(pool,(char *)buf->content); xmlBufferFree(buf); xmlFreeDoc(doc); return data; } static apr_status_t file_store(apr_pool_t *pool,apr_hash_t *hash, apr_file_t *file){ apr_os_file_t fd; xmlSaveCtxt *xctx; xmlDoc *doc; if(apr_os_file_get(&fd,file)!=APR_SUCCESS) return APR_EGENERAL; if(!(xctx=xmlSaveToFd(fd,NULL,XML_SAVE_NO_DECL))) return APR_EGENERAL; if(!(doc=doc_store(pool,hash))){ xmlSaveClose(xctx); return APR_EGENERAL; } if(xmlSaveDoc(xctx,doc)<0){ xmlSaveClose(xctx); xmlFreeDoc(doc); return APR_EGENERAL; } if(xmlSaveClose(xctx)<=0){ xmlFreeDoc(doc); return APR_EGENERAL; } xmlFreeDoc(doc); return APR_SUCCESS; } #define TXN_RETRIES 10 #define TXN_TIMEOUT 100000 static apr_status_t store_close_child(void *data){ ((rxv_spin_ctx_t *)data)->havest=RXV_SPIN_FALSE; return APR_SUCCESS; } static apr_status_t store_close(void *data){ rxv_spin_ctx_t *ctx=data; if(!ctx->havest || (!ctx->app && !ctx->ses)) return APR_SUCCESS; /* database backend */ if(ctx->dbstor){ apr_dbd_transaction_t *txn=NULL; int r; char *adata=NULL,*sdata=NULL,*astamp=NULL,*sstamp=NULL; const char *update="UPDATE %s SET tstamp=%%s,data=%%s WHERE id=%%s", *insert="INSERT INTO %s (id,tstamp,data) VALUES (%%s,%%s,%%s)", *ustamp="UPDATE %s SET tstamp=%%s WHERE id=%%s"; if(!ctx->store) return APR_SUCCESS; for(r=0;rstore->driver,ctx->pool, ctx->store->handle,&txn)){ apr_sleep(TXN_TIMEOUT); continue; } /* only update if application data modified */ if(ctx->app && ctx->adirty){ if(!astamp){ astamp=apr_psprintf(ctx->pool,"%" APR_TIME_T_FMT,ctx->ptime); adata=buffer_store(ctx->pool,ctx->app); } if(adata){ if(!ctx->ustore) ctx->ustore=apr_psprintf(ctx->pool,update,ctx->sttbl); switch(rxv_spin_db_pquery(ctx->pool,ctx->store,ctx->ustore, astamp,adata,"__application",NULL)){ case -1: apr_dbd_transaction_end(ctx->store->driver,ctx->pool,txn); apr_sleep(TXN_TIMEOUT); continue; case 0: if(!ctx->istore) ctx->istore=apr_psprintf(ctx->pool,insert,ctx->sttbl); if(rxv_spin_db_pquery(ctx->pool,ctx->store,ctx->istore, "__application",astamp,adata,NULL)<=0){ apr_dbd_transaction_end(ctx->store->driver,ctx->pool,txn); apr_sleep(TXN_TIMEOUT); continue; } break; default: break; } } } /* only update if session valid */ if(ctx->ses && ctx->sesid && ctx->valid){ if(!sstamp) sstamp=apr_psprintf(ctx->pool,"%" APR_TIME_T_FMT,ctx->atime); /* only update access time if session isn't dirty */ if(ctx->sdirty){ if(!sdata) sdata=buffer_store(ctx->pool,ctx->ses); if(sdata){ if(!ctx->ustore) ctx->ustore=apr_psprintf(ctx->pool,update,ctx->sttbl); switch(rxv_spin_db_pquery(ctx->pool,ctx->store, ctx->ustore,sstamp,sdata, ctx->sesid,NULL)){ case -1: apr_dbd_transaction_end(ctx->store->driver,ctx->pool,txn); apr_sleep(TXN_TIMEOUT); continue; case 0: if(!ctx->istore) ctx->istore=apr_psprintf(ctx->pool,insert,ctx->sttbl); if(rxv_spin_db_pquery(ctx->pool,ctx->store, ctx->istore, ctx->sesid,sstamp,sdata,NULL)<=0){ apr_dbd_transaction_end(ctx->store->driver,ctx->pool,txn); apr_sleep(TXN_TIMEOUT); continue; } break; default: break; } } } else{ if(!ctx->ustamp) ctx->ustamp=apr_psprintf(ctx->pool,ustamp,ctx->sttbl); switch(rxv_spin_db_pquery(ctx->pool,ctx->store, ctx->ustamp,sstamp,ctx->sesid,NULL)){ case -1: apr_dbd_transaction_end(ctx->store->driver,ctx->pool,txn); apr_sleep(TXN_TIMEOUT); continue; case 0: if(!ctx->istore) ctx->istore=apr_psprintf(ctx->pool,insert,ctx->sttbl); if(!sdata) sdata=buffer_store(ctx->pool,ctx->ses); if(rxv_spin_db_pquery(ctx->pool,ctx->store, ctx->istore,ctx->sesid,sstamp,sdata,NULL)<=0){ apr_dbd_transaction_end(ctx->store->driver,ctx->pool,txn); apr_sleep(TXN_TIMEOUT); continue; } break; default: break; } } } apr_dbd_transaction_end(ctx->store->driver,ctx->pool,txn); break; } } else{ /* file backend */ apr_file_t *file; char *filename; if(ctx->app && ctx->adirty){ filename=apr_pstrcat(ctx->pool,ctx->appfn,".XXXXXX",NULL); if(apr_file_mktemp(&file,filename,0,ctx->pool)==APR_SUCCESS){ if(file_store(ctx->pool,ctx->app,file)==APR_SUCCESS) apr_file_rename(filename,ctx->appfn,ctx->pool); apr_file_close(file); } } /* only update if session valid */ if(ctx->ses && ctx->sesid && ctx->valid){ /* only update access time if session isn't dirty */ if(ctx->sdirty){ filename=apr_pstrcat(ctx->pool,ctx->sesfn,".XXXXXX",NULL); if(apr_file_mktemp(&file,filename,0,ctx->pool)==APR_SUCCESS){ if(file_store(ctx->pool,ctx->ses,file)==APR_SUCCESS) apr_file_rename(filename,ctx->sesfn,ctx->pool); apr_file_close(file); } } else{ apr_file_mtime_set(ctx->sesfn,ctx->atime,ctx->pool); } } } return APR_SUCCESS; } static void store_open(rxv_spin_ctx_t *ctx){ apr_file_t *file; apr_finfo_t *fi=apr_pcalloc(ctx->pool,sizeof(*fi)); ctx->closed=RXV_SPIN_FALSE; /* nothing configured, just try app configuration */ if(!ctx->havest) goto appconf; /* database backend */ if(ctx->dbstor){ rxv_spin_data_t *res; apr_dbd_transaction_t *txn=NULL; int r; const char *select="SELECT tstamp,data FROM %s WHERE id=%%s"; if(!(ctx->store=rxv_spin_db_connect(ctx,ctx->cinfo))) goto appconf; for(r=0;rstore->driver,ctx->pool, ctx->store->handle,&txn)){ apr_sleep(TXN_TIMEOUT); continue; } if(!ctx->sstore) ctx->sstore=apr_psprintf(ctx->pool,select,ctx->sttbl); /* pick up previous change time and application store data */ ctx->app=apr_hash_make(ctx->pool); res=rxv_spin_db_pselect(ctx->pool,ctx->store, ctx->sstore,"__application",NULL); if(res && res->size>0){ rxv_spin_data_t *s; s=apr_hash_get(res->cols,"tstamp",APR_HASH_KEY_STRING); ctx->ptime=apr_atoi64(s->data); s=apr_hash_get(res->cols,"data",APR_HASH_KEY_STRING); buffer_parse(ctx,s->data,strlen(s->data),ctx->app); } if(ctx->sesid && ctx->valid){ /* pick up session store data */ ctx->ses=apr_hash_make(ctx->pool); res=rxv_spin_db_pselect(ctx->pool,ctx->store, ctx->sstore,ctx->sesid,NULL); if(res && res->size>0){ rxv_spin_data_t *s; /* pick up the session data only if session didn't time out */ if(!ctx->tmout || (s=apr_hash_get(res->cols,"tstamp",APR_HASH_KEY_STRING), apr_atoi64(s->data)+ctx->tmout>=ctx->atime)){ s=apr_hash_get(res->cols,"data",APR_HASH_KEY_STRING); buffer_parse(ctx,s->data,strlen(s->data),ctx->ses); } } } apr_dbd_transaction_end(ctx->store->driver,ctx->pool,txn); break; } } else{ /* file backend */ ctx->app=apr_hash_make(ctx->pool); if(apr_file_open(&file,ctx->appfn, APR_FOPEN_READ, APR_FPROT_OS_DEFAULT,ctx->pool)==APR_SUCCESS){ if(apr_file_info_get(fi,APR_FINFO_MTIME,file)==APR_SUCCESS){ ctx->ptime=fi->mtime; file_parse(ctx,file,ctx->app); } apr_file_close(file); } if(ctx->sesid && ctx->valid){ /* pick up session store data */ if(apr_filepath_merge(&ctx->sesfn,ctx->cinfo,ctx->sesid, APR_FILEPATH_SECUREROOT,ctx->pool)==APR_SUCCESS){ ctx->ses=apr_hash_make(ctx->pool); if(apr_file_open(&file,ctx->sesfn, APR_FOPEN_READ, APR_FPROT_OS_DEFAULT,ctx->pool)==APR_SUCCESS){ /* pick up the session data only if session didn't time out */ if(!ctx->tmout || (apr_file_info_get(fi,APR_FINFO_MTIME,file)==APR_SUCCESS && fi->mtime+ctx->tmout>=ctx->atime)){ file_parse(ctx,file,ctx->ses); } apr_file_close(file); } } } } appconf: if(ctx->cfgfn && apr_file_open(&file,ctx->cfgfn, APR_FOPEN_READ, APR_FPROT_OS_DEFAULT,ctx->pool)==APR_SUCCESS){ /* parse if application configuration file changed since last parse */ if(apr_file_info_get(fi,APR_FINFO_MTIME,file)==APR_SUCCESS && fi->mtime>=ctx->ptime){ ctx->adirty=RXV_SPIN_TRUE; ctx->ptime=ctx->atime; if(!ctx->app) ctx->app=apr_hash_make(ctx->pool); file_parse(ctx,file,ctx->app); } apr_file_close(file); } /* register store cleanup */ apr_pool_cleanup_register(ctx->pool,ctx,store_close,store_close_child); } /* application get/set/del */ rxv_spin_data_t *rxv_spin_app_get(rxv_spin_ctx_t *ctx,const char *key){ if(ctx && key){ if(ctx->closed) store_open(ctx); if(ctx->app) return apr_hash_get(ctx->app,key,APR_HASH_KEY_STRING); } return NULL; } rxv_spin_data_t *rxv_spin_app_set(rxv_spin_ctx_t *ctx, const char *key,rxv_spin_data_t *val){ if(ctx && key && val && val->type==RXV_SPIN_DATA_SGL){ if(ctx->closed) store_open(ctx); if(ctx->app){ apr_hash_set(ctx->app,key,APR_HASH_KEY_STRING,val); ctx->adirty=RXV_SPIN_TRUE; return val; } } return NULL; } apr_status_t rxv_spin_app_del(rxv_spin_ctx_t *ctx,const char *key){ if(ctx && key){ if(ctx->closed) store_open(ctx); if(ctx->app){ apr_hash_set(ctx->app,key,APR_HASH_KEY_STRING,NULL); ctx->adirty=RXV_SPIN_TRUE; return APR_SUCCESS; } } return APR_EGENERAL; } /* session get/set/del */ rxv_spin_data_t *rxv_spin_ses_get(rxv_spin_ctx_t *ctx,const char *key){ if(ctx && ctx->valid && ctx->sesid && key){ if(ctx->closed) store_open(ctx); if(ctx->ses) return apr_hash_get(ctx->ses,key,APR_HASH_KEY_STRING); } return NULL; } rxv_spin_data_t *rxv_spin_ses_set(rxv_spin_ctx_t *ctx, const char *key,rxv_spin_data_t *val){ if(ctx && ctx->valid && ctx->sesid && key && val && val->type==RXV_SPIN_DATA_SGL){ if(ctx->closed) store_open(ctx); if(ctx->ses){ apr_hash_set(ctx->ses,key,APR_HASH_KEY_STRING,val); ctx->sdirty=RXV_SPIN_TRUE; return val; } } return NULL; } apr_status_t rxv_spin_ses_del(rxv_spin_ctx_t *ctx,const char *key){ if(ctx && ctx->valid && ctx->sesid && key){ if(ctx->closed) store_open(ctx); if(ctx->ses){ apr_hash_set(ctx->ses,key,APR_HASH_KEY_STRING,NULL); ctx->sdirty=RXV_SPIN_TRUE; return APR_SUCCESS; } } return APR_EGENERAL; } /* session id and validity */ char *rxv_spin_ses_id(rxv_spin_ctx_t *ctx){ return ctx?ctx->sesid:NULL; } int rxv_spin_ses_valid(rxv_spin_ctx_t *ctx){ return ctx?(int)ctx->valid:0; } /* access time for application/session */ apr_time_t rxv_spin_ses_atime(rxv_spin_ctx_t *ctx){ if((ctx && ctx->valid && ctx->sesid)) return ctx->atime; return 0; }