/** * store.c * * Copyright (C) 2003 - 2008 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 "p")){ if((a=xmlTextReaderGetAttribute(rdr,BAD_CAST "n"))){ 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_datum(pool,apr_pstrdup(pool,(char *)v),NULL)); 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->r->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->r->pool,rdr,hash); xmlFreeTextReader(rdr); } } static char *buffer_store(apr_pool_t *pool,apr_hash_t *hash,apr_size_t *size){ char *data,*k; xmlBuffer *buf; xmlTextWriter *wtr; apr_hash_index_t *hi; rxv_spin_data_t *v; if(!(buf=xmlBufferCreate())) return NULL; if(!(wtr=xmlNewTextWriterMemory(buf,0))){ xmlBufferFree(buf); return NULL; } if(xmlTextWriterStartElement(wtr,BAD_CAST "s")<0){ xmlFreeTextWriter(wtr); xmlBufferFree(buf); return NULL; } for(hi=apr_hash_first(pool,hash);hi;hi=apr_hash_next(hi)){ apr_hash_this(hi,(const void **)&k,NULL,(void **)&v); if(xmlTextWriterStartElement(wtr,BAD_CAST "p")<0 || xmlTextWriterWriteAttribute(wtr,BAD_CAST "n",BAD_CAST k)<0 || xmlTextWriterWriteString(wtr,BAD_CAST 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 #define LOAD_TMOUT (10*1000000) 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->adirty) && !(ctx->ses && ctx->rcnf->sesid && ctx->rcnf->valid))) 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", *remove="DELETE FROM %s WHERE id=%%s"; if(!ctx->store) return APR_SUCCESS; for(r=0;rstore->cp->driver,ctx->r->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->r->pool,"%" APR_TIME_T_FMT,ctx->ptime); adata=buffer_store(ctx->r->pool,ctx->app,&len); } if(adata){ if(!ctx->ustore) ctx->ustore=apr_psprintf(ctx->r->pool,update,ctx->conf->storetbl); switch(rxv_spin_db_pquery(ctx->r->pool,ctx->store,ctx->ustore, astamp,adata,"__application",NULL)){ case -1: goto endtxn; case 0: if(!ctx->istore) ctx->istore=apr_psprintf(ctx->r->pool, insert,ctx->conf->storetbl); if(rxv_spin_db_pquery(ctx->r->pool,ctx->store,ctx->istore, "__application",astamp,adata,NULL)<=0) goto endtxn; break; default: break; } } } /* only update if session valid */ if(ctx->ses && ctx->rcnf->sesid && ctx->rcnf->valid){ /* session has been killed, delete from store */ if(ctx->killed){ if(!ctx->remove) ctx->remove=apr_psprintf(ctx->r->pool,remove,ctx->conf->storetbl); switch(rxv_spin_db_pquery(ctx->r->pool,ctx->store, ctx->remove,ctx->rcnf->sesid,NULL)){ case -1: goto endtxn; default: break; } } else{ if(!sstamp) sstamp=apr_psprintf(ctx->r->pool, "%" APR_TIME_T_FMT,ctx->r->request_time); /* only update access time if session isn't dirty */ if(ctx->sdirty){ if(!sdata) sdata=buffer_store(ctx->r->pool,ctx->ses,&len); if(sdata){ if(!ctx->ustore) ctx->ustore=apr_psprintf(ctx->r->pool, update,ctx->conf->storetbl); switch(rxv_spin_db_pquery(ctx->r->pool,ctx->store, ctx->ustore,sstamp,sdata, ctx->rcnf->sesid,NULL)){ case -1: goto endtxn; case 0: if(!ctx->istore) ctx->istore=apr_psprintf(ctx->r->pool, insert,ctx->conf->storetbl); if(rxv_spin_db_pquery(ctx->r->pool,ctx->store, ctx->istore, ctx->rcnf->sesid,sstamp,sdata,NULL)<=0) goto endtxn; break; default: break; } } } else{ if(!ctx->ustamp) ctx->ustamp=apr_psprintf(ctx->r->pool,ustamp,ctx->conf->storetbl); switch(rxv_spin_db_pquery(ctx->r->pool,ctx->store, ctx->ustamp,sstamp, ctx->rcnf->sesid,NULL)){ case -1: goto endtxn; case 0: if(!ctx->istore) ctx->istore=apr_psprintf(ctx->r->pool, insert,ctx->conf->storetbl); if(!sdata) sdata=buffer_store(ctx->r->pool,ctx->ses,&len); if(rxv_spin_db_pquery(ctx->r->pool,ctx->store, ctx->istore, ctx->rcnf->sesid,sstamp,sdata,NULL)<=0) goto endtxn; break; default: break; } } } } endtxn: apr_dbd_transaction_end(ctx->store->cp->driver,ctx->r->pool,txn); break; } } else{ /* file backend */ apr_file_t *file; char *filename,*buf; if(ctx->app && ctx->adirty){ filename=apr_pstrcat(ctx->r->pool,ctx->conf->appfn,".XXXXXX",NULL); if(apr_file_mktemp(&file,filename,0,ctx->r->pool)==APR_SUCCESS){ if((buf=buffer_store(ctx->r->pool,ctx->app,&len)) && apr_file_write_full(file,buf,len,&len)==APR_SUCCESS){ apr_file_mtime_set(filename,ctx->ptime,ctx->r->pool); apr_file_rename(filename,ctx->conf->appfn,ctx->r->pool); } apr_file_close(file); } } /* only update if session valid */ if(ctx->ses && ctx->rcnf->sesid && ctx->rcnf->valid){ /* session has been killed, delete from store */ if(ctx->killed){ apr_file_remove(ctx->sesfn,ctx->r->pool); } else{ /* only update access time if session isn't dirty */ if(ctx->sdirty){ filename=apr_pstrcat(ctx->r->pool,ctx->sesfn,".XXXXXX",NULL); if(apr_file_mktemp(&file,filename,0,ctx->r->pool)==APR_SUCCESS){ if((buf=buffer_store(ctx->r->pool,ctx->ses,&len)) && apr_file_write_full(file,buf,len,&len)==APR_SUCCESS) apr_file_rename(filename,ctx->sesfn,ctx->r->pool); apr_file_close(file); } } else{ apr_file_mtime_set(ctx->sesfn,ctx->r->request_time,ctx->r->pool); } } } } return APR_SUCCESS; } static void store_open(rxv_spin_ctx_t *ctx){ apr_file_t *file; apr_finfo_t *fi=apr_pcalloc(ctx->r->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_open(ctx,ctx->conf->store))) goto appconf; for(r=0;rstore->cp->driver,ctx->r->pool, ctx->store->handle,&txn)){ apr_sleep(TXN_TIMEOUT); continue; } if(!ctx->sstore) ctx->sstore=apr_psprintf(ctx->r->pool,select,ctx->conf->storetbl); /* pick up previous change time and application store data */ ctx->app=apr_hash_make(ctx->r->pool); res=rxv_spin_db_pselect(ctx->r->pool,ctx->store, ctx->sstore,"__application",NULL); if(res && rxv_spin_size(res)>0){ apr_array_header_t *a; rxv_spin_data_t *s; a=apr_hash_get(res->cols,"tstamp",APR_HASH_KEY_STRING); s=(rxv_spin_data_t *)a->elts; ctx->ptime=apr_atoi64(s->data); a=apr_hash_get(res->cols,"data",APR_HASH_KEY_STRING); s=(rxv_spin_data_t *)a->elts; buffer_parse(ctx,s->data,strlen(s->data),ctx->app); } if(ctx->rcnf->sesid && ctx->rcnf->valid){ /* pick up session store data */ ctx->ses=apr_hash_make(ctx->r->pool); res=rxv_spin_db_pselect(ctx->r->pool,ctx->store, ctx->sstore,ctx->rcnf->sesid,NULL); if(res && res->size>0){ apr_array_header_t *a; rxv_spin_data_t *s; /* pick up the session data only if session didn't time out */ if(!ctx->conf->timeout || (a=apr_hash_get(res->cols,"tstamp",APR_HASH_KEY_STRING), s=(rxv_spin_data_t *)a->elts, apr_atoi64(s->data)+ctx->conf->timeout>=ctx->r->request_time)){ a=apr_hash_get(res->cols,"data",APR_HASH_KEY_STRING); s=(rxv_spin_data_t *)a->elts; buffer_parse(ctx,s->data,strlen(s->data),ctx->ses); } } } apr_dbd_transaction_end(ctx->store->cp->driver,ctx->r->pool,txn); break; } } else{ /* file backend */ ctx->app=apr_hash_make(ctx->r->pool); if(apr_file_open(&file,ctx->conf->appfn, APR_FOPEN_READ, APR_FPROT_OS_DEFAULT,ctx->r->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->rcnf->sesid && ctx->rcnf->valid){ /* pick up session store data */ if(apr_filepath_merge(&ctx->sesfn, ctx->conf->store,ctx->rcnf->sesid, APR_FILEPATH_SECUREROOT,ctx->r->pool)==APR_SUCCESS){ ctx->ses=apr_hash_make(ctx->r->pool); if(apr_file_open(&file,ctx->sesfn, APR_FOPEN_READ, APR_FPROT_OS_DEFAULT,ctx->r->pool)==APR_SUCCESS){ /* pick up the session data only if session didn't time out */ if(!ctx->conf->timeout || (apr_file_info_get(fi,APR_FINFO_MTIME,file)==APR_SUCCESS && fi->mtime+ctx->conf->timeout>=ctx->r->request_time)){ file_parse(ctx,file,ctx->ses); } apr_file_close(file); } } } } appconf: /* parse if application configuration file changed since last parse */ if(ctx->conf->cfgfn && (!ctx->app || ctx->ptime+LOAD_TMOUTr->request_time) && apr_stat(fi,ctx->conf->cfgfn,APR_FINFO_MTIME,ctx->r->pool)==APR_SUCCESS && fi->mtime>ctx->ptime){ if(apr_file_open(&file,ctx->conf->cfgfn, APR_FOPEN_READ, APR_FPROT_OS_DEFAULT,ctx->r->pool)==APR_SUCCESS){ if(!ctx->app) ctx->app=apr_hash_make(ctx->r->pool); ctx->ptime=fi->mtime; file_parse(ctx,file,ctx->app); ctx->adirty=RXV_SPIN_TRUE; apr_file_close(file); } } /* register store cleanup */ apr_pool_cleanup_register(ctx->r->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->size!=RXV_SPIN_ROWS){ 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->rcnf->valid && ctx->rcnf->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->rcnf->valid && ctx->rcnf->sesid && key && val && val->size!=RXV_SPIN_ROWS){ 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->rcnf->valid && ctx->rcnf->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 destruction */ void rxv_spin_ses_kill(rxv_spin_ctx_t *ctx){ ctx->killed=RXV_SPIN_TRUE; } /* session id and validity */ char *rxv_spin_ses_id(rxv_spin_ctx_t *ctx){ return ctx?ctx->rcnf->sesid:NULL; } int rxv_spin_ses_valid(rxv_spin_ctx_t *ctx){ return ctx?(int)ctx->rcnf->valid:0; } /* access time for application/session */ apr_time_t rxv_spin_ses_atime(rxv_spin_ctx_t *ctx){ if((ctx && ctx->rcnf->valid && ctx->rcnf->sesid)) return ctx->r->request_time; return 0; }