/** * 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 Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * */ #include "private.h" /* * Application and session API functions */ static void doc_parse(apr_pool_t *pool,xmlTextReader *rdr,apr_hash_t *hash){ const xmlChar *n,*v,*a; while(xmlTextReaderRead(rdr)==1){ if((n=xmlTextReaderConstName(rdr))){ if(xmlTextReaderNodeType(rdr)==1 && xmlTextReaderDepth(rdr)==1 && xmlStrEqual(n,BAD_CAST "prop")){ if((a=xmlTextReaderGetAttribute(rdr,BAD_CAST "name"))){ if(xmlTextReaderRead(rdr)==1 && xmlStrEqual(xmlTextReaderConstName(rdr),BAD_CAST "#text") && (v=xmlTextReaderValue(rdr))){ apr_hash_set(hash,apr_pstrdup(pool,(char *)a),APR_HASH_KEY_STRING, rxv_spin_single(pool,apr_pstrdup(pool,(char *)v))); xmlFree(BAD_CAST v); } xmlFree(BAD_CAST a); } } } } } static void file_parse(rxv_spin_ctx_t *ctx,apr_file_t *file,apr_hash_t *hash){ xmlTextReader *rdr; apr_os_file_t fd; if(apr_os_file_get(&fd,file)!=APR_SUCCESS) return; if((rdr=xmlReaderForFd(fd,NULL,NULL,0))){ doc_parse(ctx->pool,rdr,hash); xmlFreeTextReader(rdr); } } static void buffer_parse(rxv_spin_ctx_t *ctx,const char *buf,apr_size_t len, apr_hash_t *hash){ xmlTextReader *rdr; if((rdr=xmlReaderForMemory(buf,len,NULL,NULL,0))){ doc_parse(ctx->pool,rdr,hash); xmlFreeTextReader(rdr); } } static char *buffer_store(apr_pool_t *pool,apr_hash_t *hash,apr_size_t *size){ char *data; xmlBuffer *buf; xmlTextWriter *wtr; apr_hash_index_t *hi; const void *k; void *v; apr_ssize_t len; if(!(buf=xmlBufferCreate())) return NULL; if(!(wtr=xmlNewTextWriterMemory(buf,0))){ xmlBufferFree(buf); return NULL; } if(xmlTextWriterStartElement(wtr,BAD_CAST "spin")<0){ xmlFreeTextWriter(wtr); xmlBufferFree(buf); return NULL; } for(hi=apr_hash_first(pool,hash);hi;hi=apr_hash_next(hi)){ apr_hash_this(hi,&k,&len,&v); if(xmlTextWriterStartElement(wtr,BAD_CAST "prop")<0 || xmlTextWriterWriteAttribute(wtr,BAD_CAST "name", BAD_CAST k)<0 || xmlTextWriterWriteString(wtr,BAD_CAST ((rxv_spin_data_t *)v)->data)<0 || xmlTextWriterEndElement(wtr)<0){ xmlFreeTextWriter(wtr); xmlBufferFree(buf); return NULL; } } if(xmlTextWriterEndDocument(wtr)<0 || xmlTextWriterFlush(wtr)<0){ xmlFreeTextWriter(wtr); xmlBufferFree(buf); return NULL; } xmlFreeTextWriter(wtr); data=apr_pstrmemdup(pool,(char *)buf->content,buf->use); *size=buf->use; xmlBufferFree(buf); return data; } #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; apr_size_t len; 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,&len); } 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,&len); 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,&len); 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,*buf; 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((buf=buffer_store(ctx->pool,ctx->app,&len)) && apr_file_write_full(file,buf,len,&len)==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((buf=buffer_store(ctx->pool,ctx->ses,&len)) && apr_file_write_full(file,buf,len,&len)==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; }