/**
* 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;
}