--- a/db.c Mon Oct 31 17:17:07 2011 -0700
+++ b/db.c Fri Nov 04 20:34:28 2011 -0700
@@ -31,259 +31,195 @@
#include "volta.h"
#include "db.h"
-const unsigned short int DB_VERSION = 1;
-
/*
- * Connect to the database specified in the 'v' global struct,
- * and populate v.db with a handle to it.
- *
- * If required, automatically handle initializing/upgrading a DB.
+ * Open the database specified in the 'v' global struct,
+ * setting the file descriptor. Returns 0 on success.
*
*/
-int
+short int
db_attach( void )
{
- if ( v.db != NULL ) return( SQLITE_OK ); /* already attached */
- if ( strlen(v.dbname) == 0 ) return( SQLITE_ERROR ); /* db filename not set? */
-
- debug( 2, LOC, "Attaching to database '%s'\n", v.dbname );
- if ( sqlite3_open( v.dbname, &v.db ) != SQLITE_OK ) {
- debug( 1, LOC, "Error when attaching to database: %s\n", sqlite3_errmsg(v.db) );
- return( sqlite3_errcode(v.db) );
- }
-
- /* check DB version */
- unsigned short int version = db_version();
- debug( 2, LOC, "Database version: %d\n", version );
- if ( version != DB_VERSION ) {
- debug( 2, LOC, "Database version mismatch: expected %hu\n", DB_VERSION );
-
- /* We're in need of a DB initialization, or just behind.
- * Attempt to "stair step" upgrade to the current version. */
- if ( version < DB_VERSION ) {
- return( db_upgrade(version) );
+ /* only re-open the db at most every 10 seconds */
+ time_t now = time( NULL );
+ if ( v.timer.db_lastcheck > 0 ) {
+ if ( now - v.timer.db_lastcheck >= 10 ) {
+ close( v.db_fd );
}
-
- /* Something else is wack. */
else {
- return( SQLITE_ERROR );
+ return( 0 );
}
}
- /* initialize prepared statements */
- if ( prepare_statements() != 0 ) return SQLITE_ERROR;
-
- return( SQLITE_OK );
-}
-
-
-/*
- * Perform automatic stair-step upgrades of DB versions
- * to get up to the current version expected from code.
- *
- */
-int
-db_upgrade( unsigned short int current_version )
-{
- unsigned short int i = 0;
- char user_pragma[30];
- char sql_file[30];
- char *upgrade_sql = NULL;
-
- for ( i = current_version + 1; i <= DB_VERSION; i++ ) {
- if ( i == 1 ) {
- debug( 2, LOC, "Initializing new database.\n" );
- }
- else {
- debug( 2, LOC, "Upgrading database version from %hu to %hu\n", current_version, i );
- }
-
- sprintf( sql_file, "sql/%d.sql", i );
- upgrade_sql = slurp_file( sql_file );
- if ( upgrade_sql == NULL ) return( SQLITE_ERROR );
+ debug( 2, LOC, "(Re)attaching to database '%s'\n", v.dbname );
- /* If there is SQL to execute, do so and then reset for more */
- if ( sqlite3_exec( v.db, upgrade_sql, NULL, NULL, NULL ) != SQLITE_OK ) {
- debug( 2, LOC, "Error %s database: %s\n",
- (i == 1 ? "initalizing" : "upgrading"), sqlite3_errmsg(v.db) );
- return( sqlite3_errcode(v.db) );
- }
- free( upgrade_sql ), upgrade_sql = NULL;
-
- /* update version metadata in DB if update was successful */
- current_version = i;
- sprintf( user_pragma, "PRAGMA user_version = %hu;", current_version );
- if ( sqlite3_exec( v.db, user_pragma, NULL, NULL, NULL ) != SQLITE_OK ) {
- debug( 2, LOC, "Error setting version: %s\n", sqlite3_errmsg(v.db) );
- return( sqlite3_errcode(v.db) );
- }
- }
-
- return( SQLITE_OK );
-}
-
-
-/*
- * Fetch and return the database's current version. or -1 on error.
- * The database should already be attached before calling this function.
- *
- */
-short int
-db_version( void )
-{
- struct sqlite3_stmt *stmt;
- int version = -1;
-
- if ( sqlite3_prepare_v2( v.db, "PRAGMA user_version", -1, &stmt, NULL ) != SQLITE_OK ) {
- debug( 2, LOC, "Error finding DB version: %s\n", sqlite3_errmsg(v.db) );
+ /* db filename not set? */
+ if ( strlen(v.dbname) == 0 ) {
+ debug( 1, LOC, "Error when attaching to database: DB filename unset?\n" );
return( -1 );
}
- if ( sqlite3_step( stmt ) == SQLITE_ROW )
- version = sqlite3_column_int( stmt, 0 );
+ if ( (v.db_fd = open( v.dbname, O_RDONLY )) == -1 ) {
+ debug( 1, LOC, "Error when attaching to database: %s\n", strerror(errno) );
+ return( -1 );
+ }
- sqlite3_finalize( stmt );
- return( version );
+ v.timer.db_lastcheck = now;
+ return( 0 );
}
/*
- * Initialize the DB statements, returning 0 on success.
+ * Given a rule file in ascii specified by the -c command line arg,
+ * convert it to a cdb after checking rules for validity.
*
*/
unsigned short int
-prepare_statements( void )
+db_create_new( char *txt )
{
- unsigned short int rv = 0;
+ struct cdb_make cdbm;
- rv = rv + sqlite3_prepare_v2( v.db, DBSQL_GET_REWRITE_RULE, -1, &v.db_stmt.get_rewrite_rule, NULL );
- if ( rv != 0 )
- debug( 2, LOC, "Error preparing DB statement \"%s\": %s\n",
- DBSQL_GET_REWRITE_RULE, sqlite3_errmsg(v.db) );
-
- rv = rv + sqlite3_prepare_v2( v.db, DBSQL_MATCH_REQUEST, -1, &v.db_stmt.match_request, NULL );
- if ( rv != 0 )
- debug( 2, LOC, "Error preparing DB statement \"%s\": %s\n",
- DBSQL_MATCH_REQUEST, sqlite3_errmsg(v.db) );
+ char buf[ LINE_BUFSIZE*10 ];
+ char tmpfile[25];
+ int tmp_fd;
+ FILE *txt_f = NULL;
+ int linenum = 0, parsed = 0;
+ struct db_input *dbline;
- return( rv );
-}
-
+ /* open temporary file */
+ debug( 0, LOC, "Creating/updating database (%s) using rules in \"%s\"\n", v.dbname, txt );
+ sprintf( tmpfile, "/tmp/volta-db-%d.tmp", getpid() );
+ if ( (tmp_fd = open( tmpfile,
+ O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH )) == -1 ) {
+ debug( 0, LOC, "Error writing temporary file: %s\n", strerror(errno) );
+ return( 1 );
+ }
-/*
- * Initialize and return a pointer to a new rewrite object.
- *
- */
-rewrite *
-init_rewrite( void )
-{
- rewrite *p_rewrite = NULL;
- if ( (p_rewrite = malloc( sizeof(rewrite) )) == NULL ) {
- debug( 5, LOC, "Unable to allocate memory for rewrite struct: %s\n", strerror(errno) );
- return( NULL );
+ /* open rules file */
+ if ( (txt_f = fopen( txt, "r" )) == NULL ) {
+ debug( 0, LOC, "Error reading rules file: %s\n", strerror(errno) );
+ return( 1 );
}
- p_rewrite->scheme = NULL;
- p_rewrite->host = NULL;
- p_rewrite->path = NULL;
- p_rewrite->port = 0;
- p_rewrite->redir = 0;
+ /* init struct and start parsing lines */
+ cdb_make_start( &cdbm, tmp_fd );
+ while ( fgets( buf, LINE_BUFSIZE*10, txt_f ) != NULL ) {
+ linenum++;
+
+ /* skip blank lines and comments */
+ if ( strlen(buf) == 1 || buf[0] == '#' ) continue;
+
+ /* validate and add! */
+ dbline = parse_dbinput( buf );
+ if ( dbline == NULL ) {
+ debug( 0, LOC, "Invalid rule (line %d), skipping: %s", linenum, buf );
+ continue;
+ }
+
+ cdb_make_add( &cdbm, dbline->key, dbline->klen, dbline->val, dbline->vlen );
+ parsed++;
- return( p_rewrite );
+ free( dbline->key );
+ free( dbline->val );
+ free( dbline );
+ }
+
+ /* write indexes */
+ fclose( txt_f );
+ cdb_make_finish( &cdbm );
+ close( tmp_fd );
+
+ /* move cdb into place */
+ if ( (rename( tmpfile, v.dbname )) == -1 ) {
+ debug( 1, LOC, "Unable to move temp cdb into place: %s", strerror(errno) );
+ return( 1 );
+ }
+
+ debug( 0, LOC, "Added %d rules to %s.\n", parsed, v.dbname );
+ return( 0 );
}
-#define COPY_REWRITE_ROW( INDEX ) copy_string_token( \
- (char *)sqlite3_column_text( v.db_stmt.get_rewrite_rule, INDEX ),\
- sqlite3_column_bytes( v.db_stmt.get_rewrite_rule, INDEX ))
-/*
- * Given a request struct pointer, try and find the best matching
- * rewrite rule, returning a pointer to a rewrite struct.
+/* Fast single record lookup.
+ * Returns a pointer to the found value or NULL if there is no match.
+ *
+ * The returned pointer should be freed after use.
*
*/
-rewrite *
-prepare_rewrite( request *p_request )
+char *
+find_record( char *key )
{
- if ( p_request == NULL ) return( NULL );
+ if ( key == NULL ) return( NULL );
- unsigned short int rewrite_id = 0;
- rewrite *p_rewrite = init_rewrite();
+ char *val = NULL;
+ cdbi_t vlen;
- sqlite3_bind_text( v.db_stmt.match_request, 3, p_request->tld, -1, SQLITE_STATIC );
- sqlite3_bind_text( v.db_stmt.match_request, 1, p_request->scheme, -1, SQLITE_STATIC );
- sqlite3_bind_text( v.db_stmt.match_request, 2, p_request->host, -1, SQLITE_STATIC );
- sqlite3_bind_text( v.db_stmt.match_request, 3, p_request->tld, -1, SQLITE_STATIC );
- sqlite3_bind_text( v.db_stmt.match_request, 4, p_request->path, -1, SQLITE_STATIC );
- sqlite3_bind_int( v.db_stmt.match_request, 5, p_request->port );
- /*
- sqlite3_bind_text( v.db_stmt.match_request, 6, NULL, -1, SQLITE_STATIC );
- sqlite3_bind_text( v.db_stmt.match_request, 6, p_request->client_ip, -1, SQLITE_STATIC );
- */
- sqlite3_bind_text( v.db_stmt.match_request, 7, p_request->user, -1, SQLITE_STATIC );
- sqlite3_bind_text( v.db_stmt.match_request, 8, p_request->method, -1, SQLITE_STATIC );
+ if ( cdb_seek( v.db_fd, key, (int)strlen(key), &vlen) > 0 ) {
- switch ( sqlite3_step( v.db_stmt.match_request )) {
- case SQLITE_ROW:
- rewrite_id = sqlite3_column_int( v.db_stmt.match_request, 0 );
- break;
+ if ( (val = malloc( vlen + 1 )) == NULL ) {
+ debug( 5, LOC, "Unable to allocate memory for value storage: %s\n", strerror(errno) );
+ return( NULL );
+ }
- case SQLITE_DONE:
- break;
-
- default:
- return( NULL );
+ cdb_bread( v.db_fd, val, vlen );
+ val[vlen] = '\0';
+ debug( 4, LOC, "Match for key '%s': %s\n", key, val );
}
- /* FIXME: CHECK for rewrite_rule being NULL on successful match, emit warning, continue */
-
- /* return early if we didn't get a matching request */
- if ( rewrite_id == 0 ) return( NULL );
-
- /* pull the rewrite data, populate the struct. only one
- * row should ever be returned for this. */
- sqlite3_bind_int( v.db_stmt.get_rewrite_rule, 1, rewrite_id );
- switch ( sqlite3_step( v.db_stmt.get_rewrite_rule )) {
- case SQLITE_ROW:
- p_rewrite->scheme = COPY_REWRITE_ROW( 1 );
- p_rewrite->host = COPY_REWRITE_ROW( 2 );
- p_rewrite->path = COPY_REWRITE_ROW( 3 );
- p_rewrite->port = sqlite3_column_int( v.db_stmt.get_rewrite_rule, 4 );
- p_rewrite->redir = sqlite3_column_int( v.db_stmt.get_rewrite_rule, 5 );
- break;
-
- case SQLITE_DONE:
- break;
-
- default:
- return( NULL );
- }
-
- return( p_rewrite );
+ return val;
}
-/*
- * Release memory used by the rewrite struct and
- * reset prepared statements.
+/*
+ * Search the CDB for all occurences of the given +key+,
+ * populating the +results+ array with pointers to parsed rule structs.
+ *
+ * Returns the number of successful matches. reset_results()
+ * should be called after the result set is examined.
*
*/
-void
-finish_rewrite( rewrite *p_rewrite )
+unsigned int
+find_records( char *key, parsed **results )
{
- sqlite3_reset( v.db_stmt.get_rewrite_rule );
- sqlite3_reset( v.db_stmt.match_request );
- sqlite3_clear_bindings( v.db_stmt.get_rewrite_rule );
- sqlite3_clear_bindings( v.db_stmt.match_request );
+ if ( key == NULL ) return( 0 );
+
+ struct cdb cdb;
+ struct cdb_find cdbf; /* structure to hold current find position */
+
+ unsigned int match = 0;
+ parsed *result = NULL;
+ char *val = NULL;
+ unsigned int vlen, vpos;
+
+ /* initialize search structs */
+ if ( db_attach() == -1 ) return( 0 );
+ cdb_init( &cdb, v.db_fd );
+ cdb_findinit( &cdbf, &cdb, key, (int)strlen(key) );
+
+ while ( cdb_findnext( &cdbf ) > 0 && match < DB_RESULTS_MAX ) {
+ vpos = cdb_datapos( &cdb );
+ vlen = cdb_datalen( &cdb );
- if ( p_rewrite == NULL ) return;
+ /* pull the value from the db */
+ if ( (val = calloc( vlen, sizeof(char) )) == NULL ) {
+ debug( 5, LOC, "Unable to allocate memory for DB value storage: %s\n",
+ strerror(errno) );
+ return( 0 );
+ }
+ cdb_read( &cdb, val, vlen, vpos );
- free( p_rewrite->scheme );
- free( p_rewrite->host );
- free( p_rewrite->path );
+ /* if it parses properly, add it to the result set. */
+ result = parse_rule( val );
+ if ( result != NULL ) {
+ results[match] = result;
+ debug( 4, LOC, "DB match %d for key '%s': %s\n", match+1, key, val );
+ }
- free( p_rewrite ), p_rewrite = NULL;
+ match++;
+ free( val );
+ }
- return;
+ cdb_free( &cdb );
+ return match;
}