# HG changeset patch # User Mahlon E. Smith # Date 1316044168 25200 # Node ID 5701b7859a3146c3f347f12ae6a4502cde93bce2 # Parent 97f767832c520e035c51c5f6f2117846418ef57e Groundwork for automatic database initialization and schema upgrades. diff -r 97f767832c52 -r 5701b7859a31 .hgignore --- a/.hgignore Tue Sep 13 22:16:11 2011 -0700 +++ b/.hgignore Wed Sep 14 16:49:28 2011 -0700 @@ -1,4 +1,5 @@ ^volta$ +^volta.db$ ^parser_graph.* .*debug .*.o diff -r 97f767832c52 -r 5701b7859a31 Makefile --- a/Makefile Tue Sep 13 22:16:11 2011 -0700 +++ b/Makefile Wed Sep 14 16:49:28 2011 -0700 @@ -1,10 +1,10 @@ CFLAGS = -O2 -CFLAGS_DEBUG = -Wall -L /usr/lib -L /opt/local/lib -DDEBUG -DPROG='"volta (debugmode)"' +CFLAGS_DEBUG = -Wall -L /opt/local/lib -I /opt/local/include -DDEBUG -DPROG='"volta (debugmode)"' LIBS = -lsqlite3 LIBS_DEBUG = -lprofiler # requires proftools #OBJS = $(patsubst %.c,%.o,$(wildcard *.c)) parser.o -OBJS = accept_loop.o database.o main.o parser.o util.o +OBJS = accept_loop.o db.o main.o parser.o util.o ######################################################################## ### P R O D U C T I O N @@ -52,7 +52,7 @@ .PHONY : clean cleanall cleanall: clean - rm -f parser.c + rm -f parser.c volta.db clean: -rm -f volta volta_debug* parser_graph.* *.o *.prof* diff -r 97f767832c52 -r 5701b7859a31 database.c --- a/database.c Tue Sep 13 22:16:11 2011 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,67 +0,0 @@ -/* vim: set noet nosta sw=4 ts=4 ft=c : */ -/* -Copyright (c) 2011, Mahlon E. Smith -All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - * Neither the name of Mahlon E. Smith nor the names of his - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#include "volta.h" - - -/* - * Parse command line options, perform actions, and enter accept loop. - * - */ -int -db_initialize( void ) -{ - debug( 1, LOC, "init! init! init!\n" ); - - struct sqlite3 *db; - struct sqlite3_stmt *stmt; - - if ( sqlite3_open( "testing.db", &db ) != SQLITE_OK ) { - debug( 1, LOC, "Error when initializing database: %s\n", sqlite3_errmsg(db) ); - return( sqlite3_errcode(db) ); - } - - if ( sqlite3_prepare( db, "create table foo(bar int);", -1, &stmt, NULL ) != SQLITE_OK ) { - debug( 1, LOC, "Error preparing statement: %s\n", sqlite3_errmsg(db) ); - return( sqlite3_errcode(db) ); - } - - if ( sqlite3_step( stmt ) != SQLITE_DONE ) { - debug( 1, LOC, "Error executing statement: %s\n", sqlite3_errmsg(db) ); - return( sqlite3_errcode(db) ); - } - - sqlite3_finalize( stmt ); - sqlite3_close( db ); - - debug( 1, LOC, "okay! okay! okay!\n" ); - return( SQLITE_OK ); -} - diff -r 97f767832c52 -r 5701b7859a31 db.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db.c Wed Sep 14 16:49:28 2011 -0700 @@ -0,0 +1,146 @@ +/* vim: set noet nosta sw=4 ts=4 ft=c : */ +/* +Copyright (c) 2011, Mahlon E. Smith +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Mahlon E. Smith nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#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. + * + */ +int +db_attach( void ) +{ + if ( v.db != NULL ) return( SQLITE_OK ); /* already attached */ + if ( strlen(v.dbname) == 0 ) return( SQLITE_ERROR ); /* name 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(); + if ( version != DB_VERSION ) { + debug( 1, LOC, "Database version mismatch: expected %hu, got %d.\n", + DB_VERSION, 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) ); + } + + /* Something else is wack. */ + else { + 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( 1, LOC, "Initializing new database.\n" ); + } + else { + debug( 1, 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 ); + + /* 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( 1, LOC, "Error upgrading database: %s\n", 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( 1, LOC, "Error upgrading database: %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( 1, LOC, "Error finding DB version: %s\n", sqlite3_errmsg(v.db) ); + return( -1 ); + } + + if ( sqlite3_step( stmt ) == SQLITE_ROW ) + version = sqlite3_column_int( stmt, 0 ); + + sqlite3_finalize( stmt ); + return version; +} + diff -r 97f767832c52 -r 5701b7859a31 db.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/db.h Wed Sep 14 16:49:28 2011 -0700 @@ -0,0 +1,49 @@ +/* vim: set noet nosta sw=4 ts=4 ft=c : */ +/* +Copyright (c) 2011, Mahlon E. Smith +All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of Mahlon E. Smith nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef _DB_H +#define _DB_H + +#include + +extern const unsigned short int DB_VERSION; + +/* + * + * Function prototypes + * + */ + +int db_attach( void ); +int db_upgrade( unsigned short int current_version ); +short int db_version( void ); + +#endif + diff -r 97f767832c52 -r 5701b7859a31 main.c --- a/main.c Tue Sep 13 22:16:11 2011 -0700 +++ b/main.c Wed Sep 14 16:49:28 2011 -0700 @@ -31,16 +31,15 @@ /* * TODO * - * empty struct not necessary? * inet_pton( AF_INET, *char src, dest ) * an option to run the DB out of memory? - * PRAGMA user_version = 1; * */ #include "volta.h" -unsigned short int debugmode; +#include "db.h" +struct v_globals v; /* * Parse command line options, perform actions, and enter accept loop. @@ -49,41 +48,38 @@ int main( int argc, char *argv[] ) { - /* opt action flags */ - struct { - unsigned char init; - unsigned char dump; - } actions = {0}; - #ifdef DEBUG /* debugmode set at compile time, * default to display everything */ - debugmode = 99; + v.debugmode = 99; #else - debugmode = 0; + v.debugmode = 0; #endif + /* default database file name */ + v.db = NULL; + strcpy( v.dbname, "volta.db" ); + /* get_opt vars */ int opt = 0; opterr = 0; /* parse options */ - while ( (opt = getopt( argc, argv, "a:d:hv" )) != -1 ) { + while ( (opt = getopt( argc, argv, "d:f:hv" )) != -1 ) { switch ( opt ) { - /* action */ - case 'a': - if ( strcmp( optarg, "init" ) == 0 ) actions.init++; - if ( strcmp( optarg, "dump" ) == 0 ) actions.dump++; + /* database filename */ + case 'f': + strncpy( v.dbname, optarg, sizeof(v.dbname) ); break; /* debug */ case 'd': if ( optarg[2] == '-' ) { argc++; argv -= 1; - debugmode = 1; + v.debugmode = 1; } - sscanf( optarg, "%hu", &debugmode ); + sscanf( optarg, "%hu", &v.debugmode ); break; /* help */ @@ -100,7 +96,7 @@ case '?': switch( optopt ) { case 'd': /* no debug argument, default to level 1 */ - debugmode = 1; + v.debugmode = 1; break; default: usage( argv[0] ); @@ -114,15 +110,8 @@ argc -= optind; argv += optind; - /* perform any requested actions */ - if ( actions.init ) { - db_initialize(); - return( 0 ); - } - if ( actions.dump ) { - debug( 1, LOC, "dump.\n" ); - return( 0 ); - } + /* get the initial database handle or bomb immediately. */ + if ( db_attach() != SQLITE_OK ) exit( 1 ); /* enter stdin parsing loop */ return accept_loop(); diff -r 97f767832c52 -r 5701b7859a31 sql/1.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sql/1.sql Wed Sep 14 16:49:28 2011 -0700 @@ -0,0 +1,6 @@ +BEGIN; + +DROP TABLE IF EXISTS init; +CREATE TABLE init ( bar INT ); + +COMMIT; diff -r 97f767832c52 -r 5701b7859a31 util.c --- a/util.c Tue Sep 13 22:16:11 2011 -0700 +++ b/util.c Wed Sep 14 16:49:28 2011 -0700 @@ -37,13 +37,11 @@ void usage( char *prg ) { - printf( "%s [-vh] [-d ] [-a ]\n", prg ); + printf( "%s [-vh] [-f ] [-d ]\n", prg ); printf( " -v Display version\n" ); printf( " -d Show debug information on stderr\n" ); printf( " -h Usage (you're lookin' at it)\n" ); - printf( " -a Perform an action, being one of:\n" ); - printf( " init: Initialize a new, empty database\n" ); - printf( " dump: DUMP IT\n" ); + printf( " -f Specify the database to use (default is 'volta.db' in the cwd.)\n"); printf( "\n" ); return; } @@ -62,7 +60,7 @@ void debug( int level, char *file, int line, const char *fmt, ... ) { - if ( debugmode < level ) return; + if ( v.debugmode < level ) return; char timestamp[20]; time_t t = time( NULL ); @@ -114,9 +112,48 @@ } else { line = line_realloc; - memcpy( line + offset, buf, strlen(buf) ); + memcpy( line + offset, buf, LINE_BUFSIZE - 1 ); } return( line ); } + +/* + * Read an entire file into memory, returning a pointer the contents. + * Returns NULL on error. + * + */ +char * +slurp_file( char *file ) +{ + FILE *fh = NULL; + char *contents = NULL; + struct stat sb; + + if ( stat( file, &sb ) != 0 ) { + debug( 1, LOC, "Unable to stat() file '%s': %s\n", + file, strerror(errno) ); + return( NULL ); + } + + if ( (contents = malloc( sb.st_size + 1 )) == NULL ) { + debug( 1, LOC, "Unable to allocate memory for file '%s': %s\n", + file, strerror(errno) ); + return( NULL ); + } + + if ( (fh = fopen( file, "r" )) == NULL ) { + debug( 1, LOC, "Could not open file for reading '%s': %s\n", + file, strerror(errno) ); + return( NULL ); + } + + fread( contents, sizeof(char), sb.st_size, fh ); + fclose( fh ); + + return( contents ); +} + + + diff -r 97f767832c52 -r 5701b7859a31 volta.h --- a/volta.h Tue Sep 13 22:16:11 2011 -0700 +++ b/volta.h Wed Sep 14 16:49:28 2011 -0700 @@ -43,14 +43,13 @@ #include #include #include +#include #include #include #include #include -#include - #ifdef DEBUG #include #endif @@ -60,8 +59,14 @@ /* Aid debugging */ #define LOC __FILE__, __LINE__ -/* Global debug toggle */ -extern unsigned short int debugmode; + +/* a global struct for easy access to common vars */ +struct v_globals { + unsigned short int debugmode; /* debug level */ + char dbname[128]; /* path to database file */ + struct sqlite3 *db; /* database handle */ +}; +extern struct v_globals v; /* defined in main.c */ /* The parsed attributes from the request line, as given to us by squid. * URL client_ip "/" fqdn user method [ kvpairs] */ @@ -75,9 +80,6 @@ char *kvpairs; } request; -/* FIXME: An "empty" request struct used for re-assignment */ -static const struct request reset_request; - /* * * Function prototypes @@ -86,10 +88,9 @@ void usage( char *prg ); void debug( int level, char *file, int line, const char *fmt, ... ); +char *slurp_file( char *file ); char *extend_line( char *line, const char *buf ); -int db_initialize( void ); - int accept_loop( void ); struct request *parse( char *p );