Groundwork for automatic database initialization and schema upgrades.
--- 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
--- 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*
--- 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 <mahlon@martini.nu>
-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 );
-}
-
--- /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 <mahlon@martini.nu>
+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;
+}
+
--- /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 <mahlon@martini.nu>
+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 <sqlite3.h>
+
+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
+
--- 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();
--- /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;
--- 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 <level>] [-a <init>]\n", prg );
+ printf( "%s [-vh] [-f <filename>] [-d <level>]\n", prg );
printf( " -v Display version\n" );
printf( " -d <level> 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 <filename> 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 );
+}
+
+
+
--- 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 <string.h>
#include <unistd.h>
#include <time.h>
+#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
-#include <sqlite3.h>
-
#ifdef DEBUG
#include <google/profiler.h>
#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 <SP> client_ip "/" fqdn <SP> user <SP> method [<SP> kvpairs]<NL> */
@@ -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 );