db.c
changeset 14 51eb85ae4de4
parent 13 23a242d7b7fa
child 15 2706fc514dea
--- 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;
 }