trema/trema-edge

View on GitHub
src/lib/persistent_storage.c

Summary

Maintainability
Test Coverage
/*
 * Author: Yasunobu Chiba
 *
 * Copyright (C) 2008-2013 NEC Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2, as
 * published by the Free Software Foundation.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */


#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <sqlite3.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "checks.h"
#include "log.h"
#include "persistent_storage.h"
#include "trema_private.h"
#include "trema_wrapper.h"
#include "wrapper.h"


static const size_t MAX_KEY_LENGTH = 256;
static const size_t MAX_VALUE_LENGTH = 256;
static const char DEFAULT_DB_FILE[] = ".trema.db";
static char *db_file = NULL;
static sqlite3 *db_handle = NULL;
static bool backend_initialized = false;


static bool
finalize_backend( bool delete_file ) {
  assert( db_handle != NULL );
  assert( db_file != NULL );

  int ret = trema_sqlite3_close( db_handle );
  if ( ret != SQLITE_OK ) {
    error( "Failed to destroy a sqlite3 object ( %s ).", trema_sqlite3_errmsg( db_handle ) );
    goto error;
  }
  db_handle = NULL;

  if ( delete_file ) {
    int ret = trema_unlink( db_file );
    if ( ret < 0 ) {
      char buf[ 256 ];
      error( "Failed to delete database file. ( db_file = %s, errno = %s [%d] ).",
             db_file, strerror_r( errno, buf, sizeof( buf ) ), errno );
      goto error;
    }
  }

  xfree( db_file );
  db_file = NULL;

  return true;

error:
  db_handle = NULL;
  if ( db_file != NULL ) {
    xfree( db_file );
    db_file = NULL;
  }
  return false;
}


static bool
init_backend() {
  assert( db_file == NULL );
  assert( db_handle == NULL );

  db_file = xmalloc( PATH_MAX );
  memset( db_file, '\0', PATH_MAX );
  sprintf( db_file, "%s/%s", get_trema_tmp(), DEFAULT_DB_FILE );

  int ret = trema_sqlite3_open( db_file, &db_handle );
  if ( ret != SQLITE_OK ) {
    error( "Failed to open backend database ( %s ).", trema_sqlite3_errmsg( db_handle ) );
    finalize_backend( false );
    return false;
  }

  char *err = NULL;
  char *statement = sqlite3_mprintf( "CREATE TABLE IF NOT EXISTS trema ( key TEXT, value TEXT, CONSTRAINT key_unique UNIQUE (key) ON CONFLICT FAIL )" );
  ret = trema_sqlite3_exec( db_handle, statement, NULL, NULL, &err );
  if ( ret != SQLITE_OK ) {
    error( "Failed to create a table ( error = %s ).", err );
    xfree( db_file );
    db_file = NULL;
    trema_sqlite3_close( db_handle );
    db_handle = NULL;
    trema_sqlite3_free( err );
    trema_sqlite3_free( statement );
    return false;
  }
  trema_sqlite3_free( statement );

  return true;
}


static int
save_value( void *value, int n_columns, char **columns, char **column_names ) {
  UNUSED( column_names );

  assert( n_columns == 1 );
  assert( columns != NULL );
  assert( columns[ 0 ] != NULL );
  assert( value != NULL );

  size_t length = strlen( columns[ 0 ] ) + 1;
  *( char ** ) value = xmalloc( length );
  strncpy( *( char ** ) value, columns[ 0 ], length );

  return 0;
}


static char*
get_value_from_backend( const char *key ) {
  assert( db_handle != NULL );
  assert( key != NULL );

  char *value = NULL;
  char *err = NULL;
  char *statement= sqlite3_mprintf( "SELECT value FROM trema WHERE key = '%q'", key );
  int ret = trema_sqlite3_exec( db_handle, statement, save_value, &value, &err );
  if ( ret != SQLITE_OK ) {
    error( "Failed to execute a SQL statement ( statement = %s, error = %s ).", statement, err );
    trema_sqlite3_free( statement );
    trema_sqlite3_free( err );
    return NULL;
  }
  trema_sqlite3_free( statement );

  return value;
}


static bool
save_value_to_backend( const char *key, const char *value ) {
  assert( db_handle != NULL );
  assert( key != NULL );

  char *err = NULL;
  char *statement = sqlite3_mprintf( "INSERT INTO trema (key,value) VALUES ('%q','%q')", key, value );
  int ret = trema_sqlite3_exec( db_handle, statement, NULL, NULL, &err );
  if ( ret != SQLITE_OK ) {
    error( "Failed to execute a SQL statement ( statement = %s, error = %s ).", statement, err );
    trema_sqlite3_free( statement );
    trema_sqlite3_free( err );
    return false;
  }
  trema_sqlite3_free( statement );

  int n_changes = trema_sqlite3_changes( db_handle );
  if ( n_changes != 1 ) {
    if ( n_changes > 1 ) {
      error( "Multiple entries are created ( n_changes = %d ).", n_changes );
    }
    return false;
  }

  return true;
}


static bool
update_value_on_backend( const char *key, const char *value ) {
  assert( db_handle != NULL );
  assert( key != NULL );

  char *err = NULL;
  char *statement = sqlite3_mprintf( "UPDATE trema SET value = '%q' WHERE key = '%q'", value, key );
  int ret = trema_sqlite3_exec( db_handle, statement, NULL, NULL, &err );
  if ( ret != SQLITE_OK ) {
    error( "Failed to execute a SQL statement ( statement = %s, error = %s ).", statement, err );
    trema_sqlite3_free( statement );
    trema_sqlite3_free( err );
    return false;
  }
  trema_sqlite3_free( statement );

  int n_changes = trema_sqlite3_changes( db_handle );
  if ( n_changes != 1 ) {
    if ( n_changes > 1 ) {
      error( "Multiple entries are updated ( n_changes = %d ).", n_changes );
    }
    return false;
  }

  return true;
}


static bool
delete_key_value_from_backend( const char *key ) {
  assert( db_handle != NULL );
  assert( key != NULL );

  char *err = NULL;
  char *statement = sqlite3_mprintf( "DELETE FROM trema WHERE key = '%q'", key );
  int ret = trema_sqlite3_exec( db_handle, statement, NULL, NULL, &err );
  if ( ret != SQLITE_OK ) {
    error( "Failed to execute a SQL statement ( statement = %s, error = %s ).", statement, err );
    trema_sqlite3_free( statement );
    trema_sqlite3_free( err );
    return false;
  }
  trema_sqlite3_free( statement );

  int n_changes = trema_sqlite3_changes( db_handle );
  if ( n_changes != 1 ) {
    if ( n_changes > 1 ) {
      error( "Multiple entries are deleted ( n_changes = %d ).", n_changes );
    }
    return false;
  }

  return true;
}


bool
init_persistent_storage() {
  if ( backend_initialized ) {
    error( "Backend is already initialized." );
    return false;
  }

  backend_initialized = init_backend();

  return backend_initialized;
}


bool
finalize_persistent_storage() {
  if ( !backend_initialized ) {
    error( "Backend is not initialized yet." );
    return false;
  }

  backend_initialized = false;

  return finalize_backend( false );
}


bool
clear_persistent_storage() {
  if ( !backend_initialized ) {
    error( "Backend is not initialized yet." );
    return false;
  }

  bool ret = finalize_backend( true );
  if ( ret == false ) {
    backend_initialized = false;
    return false;
  }

  backend_initialized = init_backend();

  return backend_initialized;
}


static bool
key_exists( const char *key ) {
  assert( key != NULL );

  char *retrieved = get_value_from_backend( key );
  if ( retrieved == NULL ) {
    return false;
  }

  xfree( retrieved );

  return true;
}


static bool
backend_ready( void ) {
  if ( !backend_initialized ) {
    error( "Backend is not initialized yet." );
  }

  return backend_initialized;
}


static bool
ascii_string( const char *string ) {
  assert( string != NULL );

  int i = 0;
  while ( string[ i ] != '\0' ) {
    if ( isprint( string[ i ] ) == 0 ) {
      return false;
    }
    i++;
  }

  return true;
}


static bool
valid_key( const char *key ) {
  if ( key == NULL ) {
    error( "'key' must be specified." );
    return false;
  }

  if ( !ascii_string( key ) ) {
    error( "'key' must only have printable ASCII characters." );
    return false;
  }

  size_t length = strlen( key );
  if ( length > MAX_KEY_LENGTH ) {
    error( "Too long key ( length = %u ) specified. Maximum length is %u.", length, MAX_KEY_LENGTH );
    return false;
  }

  return true;
}


static bool
valid_value( const char *value ) {
  if ( value == NULL ) {
    error( "'value' must be specified." );
    return false;
  }

  if ( !ascii_string( value ) ) {
    error( "'value' must only have printable ASCII characters." );
    return false;
  }

  size_t length = strlen( value );
  if ( length > MAX_KEY_LENGTH ) {
    error( "Too long value ( length = %u ) specified. Maximum length is %u.", length, MAX_VALUE_LENGTH );
    return false;
  }

  return true;
}



bool
set_value( const char *key, const char *value ) {
  if ( !backend_ready() ) {
    return false;
  }
  if ( !valid_key( key ) ) {
    return false;
  }
  if ( !valid_value( value ) ) {
    return false;
  }

  if ( key_exists( key ) ) {
    return update_value_on_backend( key, value );
  }

  return save_value_to_backend( key, value );
}


bool
get_value( const char *key, char *value, const size_t length ) {
  if ( !backend_ready() ) {
    return false;
  }
  if ( !valid_key( key ) ) {
    return false;
  }
  if ( value == NULL ) {
    error( "'value' must be specified." );
    return false;
  }
  if ( length == 0 ) {
    error( "'length' must be greater than zero." );
    return false;
  }

  char *retrieved = get_value_from_backend( key );
  if ( retrieved == NULL ) {
    error( "Failed to retrieve a value for '%s'.", key );
    return false;
  }

  size_t required_length = strlen( retrieved ) + 1;
  if ( required_length > length ) {
    error( "Insufficient buffer space ( %u [bytes] > %u [bytes] ).",
           required_length, length );
    return false;
  }

  strncpy( value, retrieved, required_length );
  xfree( retrieved );

  return true;
}


bool
delete_key_value( const char *key ) {
  if ( !backend_ready() ) {
    return false;
  }
  if ( !valid_key( key ) ) {
    return false;
  }

  if ( !key_exists( key ) ) {
    error( "An entry for '%s' does not exist.", key );
    return false;
  }

  return delete_key_value_from_backend( key );
}


const char*
_get_db_file( void ) {
  return db_file;
}


const sqlite3*
_get_db_handle( void ) {
  return db_handle;
}


bool
_get_backend_initialized( void ) {
  return backend_initialized;
}


size_t
_get_max_key_length( void ) {
  return MAX_KEY_LENGTH;
}


size_t
_get_max_value_length( void ) {
  return MAX_VALUE_LENGTH;
}


/*
 * Local variables:
 * c-basic-offset: 2
 * indent-tabs-mode: nil
 * End:
 */