/*****************************************************************************
 *
 * Generic engine database.
 * Copyright (C) 2001  Bart Trojanowski <bart@jukie.net>.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.  See <http://www.fsf.org/copyleft/gpl.txt>.
 * 
 * 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.
 *
 *****************************************************************************/

#include <linux/module.h>
#include <linux/init.h>
#include <linux/string.h>
#include "engine.h"
#include "engine-debug.h"

int ge_debug_message_mask = GE_DBG_ALL;

static int check_engine_name( const char * );
static int find_engine( const char *, struct generic_engine** );
static int init_engine( struct generic_engine* );
static int engine_db_append( struct generic_engine* );
static int engine_db_remove( struct generic_engine* );
static int con_ops_process_not_implemented( struct generic_job* );

/* -------------------------------------------------------------------------
 * DATABASE
 * ------------------------------------------------------------------------- */

static LIST_HEAD(engine_db);
rwlock_t db_lock = RW_LOCK_UNLOCKED;

/* -------------------------------------------------------------------------
 * DATABASE FUNCTIONS
 * ------------------------------------------------------------------------- */

/* add engine to the database */
int 
register_generic_engine(struct generic_engine* ge)
{
  int ret;
  struct generic_engine* eng = NULL;
  
  FIN();
  
  ret = -ENODEV;		/* XXX is there a better return? */
  if( check_engine_name( ge->name ) != 3 )
    goto done;
  
  ret = -EEXIST;		/* XXX is there a better return? */
  if( find_engine( ge->name, &eng ) == 0 )
    goto done;
  
  if( (ret = init_engine( ge )) )
    goto done;
    
  write_lock_bh( &db_lock );

  ret = engine_db_append( ge );
  
  write_unlock_bh( &db_lock );

 done:
  FEXIT(ret==0);
  return ret;
}

/* add engine to the database */
int 
unregister_generic_engine(struct generic_engine* ge)
{
  int ret;

  FIN();
  
  write_lock_bh( &db_lock );

  ret = engine_db_remove( ge );

  write_unlock_bh( &db_lock );
  
  FEXIT(ret==0);
  
  return ret;
}

/* get an engine given a name, null if not present; note that the engine 
 * use count is incremented by this function, use put_engine to undo it */
struct generic_engine* 
get_engine_by_name(const char* desc)
{
  struct generic_engine *ge = NULL;
  int rc;
  
  FIN();
  
  read_lock_bh( &db_lock );
  
  rc = find_engine( desc, &ge );
  if( !rc ) 
    get_engine( ge );
  
  read_unlock_bh( &db_lock );
  
  FEXIT(ge);
  return ge;
}

/* -------------------------------------------------------------------------
 * HELPER FUNCTIONS - note of these functions lock the database
 * ------------------------------------------------------------------------- */

/* engine name is composed of "class-algoritym-implementation", this function
 * returns 1 if only the class is supplied, 2 if the class and the algorithm 
 * are supplied and 3 if all are supplied. a negative is an error */
static int 
check_engine_name( const char * name )
{
  int ret = -1;
  const char *ptr;
  unsigned parts = 0;
  unsigned part_len = 0;
  
  FIN();
  
  if ( !name || !*name ) 
    goto bail;
  
  ptr = name;
  while ( *ptr ) {
    register char ch;
    if ( (ptr - name) >= GENERIC_ENGINE_NAME_MAX_LEN )
      goto bail;
    
    ch = *ptr;
    if ( ch == '-' ) {
      if ( !part_len )
	goto bail;
      part_len = 0;
      parts ++;
      
    } else if ( (ch >= 'a' && ch <= 'z') ||
		(ch >= 'A' && ch <= 'Z') ||
		(ch >= '0' && ch <= '9') ) {
      part_len++;
    }
    
    ptr++;
  }
  
  if ( part_len ) {
    parts++;
    if ( parts<=3 )
      ret = parts;
  }
  
 bail:
  FEXIT(ret>0);
  return ret;
}

/* attempts to locate the engine in teh database - caller must already 
 * have acquired lock on the db; returns 0 on success */
static int 
find_engine( const char * name, struct generic_engine** peng )
{
  int depth, ret;
  struct list_head *elem;
  const char *str;
  char buffer[ GENERIC_ENGINE_NAME_MAX_LEN ];
  int slen;
  
  FIN();
  
  ret = -ENOENT;
  depth = check_engine_name( name );
  switch( depth ) {
  case 1:
  case 2:
    /* partial match, match only to last '-' */
    strcpy(buffer,name);
    strcat(buffer,"-");
    str = buffer;
    slen = 0;
    break;
  case 3:
    /* exact match, compare even the \0 char */
    str = name;
    slen = 1;
    break;
  default:
    goto done;
  }
  
  slen += strlen(str);
  
  ret = 0;
  list_for_each(elem, &engine_db) {
    struct generic_engine *ge = (void*)elem;

    if( ge->name
        && (strncmp(str, ge->name, slen) == 0) 
	&& !atomic_read(&ge->del_flag) ) {
      *peng = ge;
      goto done;
    }
  }
  
  ret = -ENOENT;
  
 done:
  FEXIT(ret==0);
  return ret;
}

static int 
init_engine( struct generic_engine* eng )
{
  int ret;
  
  FIN();
  
  ret = -EFAULT;		/* XXX is there a better return? */
  if( !eng->owner )
    goto done;
  
  if( !( eng->name &&
	 *(eng->name) &&
	 eng->ops.context_size &&
	 eng->con_ops.init &&
	 eng->con_ops.cleanup &&
	 eng->con_ops.job_size &&
	 eng->job_ops.cancel_async ) )
    goto done;
  
  INIT_LIST_HEAD( &eng->eng_elem );
  INIT_LIST_HEAD( &eng->con_list );
  rwlock_init( &eng->lock );
  atomic_set( &eng->del_flag, 0 );

  eng->con_ops.process = con_ops_process_not_implemented;

  ret = 0;
  
 done:
  FEXIT(ret==0);
  return ret;
}

static int engine_db_append( struct generic_engine* eng )
{
  FIN();
  
  list_add_tail( &eng->eng_elem, &engine_db );

  FOUT();
  return 0;
}

static int engine_db_remove( struct generic_engine* eng )
{
  FIN();
  
  list_del( &eng->eng_elem );
  
  FOUT();
  return 0;
}

/* this function is here to prevent a client from calling a NULL pointer */
static int 
con_ops_process_not_implemented( struct generic_job *job )
{
  GE_ERR("engine %s->ops.process not implemented\n",
	 job->con->eng->name);
  return ENGINE_CONTEXT_FAULT;
}

/* -------------------------------------------------------------------------
 * MODULE FUNCTIONS
 * ------------------------------------------------------------------------- */

static int __init
engine_init(void)
{
  int ret = 0;
  FIN();
  
  printk(KERN_INFO "engine database init\n");
  
  FEXIT(ret==0);
  return ret;
}

static void __init
engine_cleanup(void)
{
  FIN();
  FOUT();
}

module_init(engine_init);
module_exit(engine_cleanup);

EXPORT_SYMBOL(ge_debug_message_mask);
EXPORT_SYMBOL(register_generic_engine);
EXPORT_SYMBOL(unregister_generic_engine);
EXPORT_SYMBOL(get_engine_by_name);

/*****************************************************************************
 *
 * $Log: engine.c,v $
 * Revision 1.9  2001/06/04 02:14:02  bart
 * merged forward/reverse into one call 'process' which is a function pointer
 * to a specific processor decided in create_context (i.e. con_init)
 *
 * Revision 1.8  2001/06/03 13:16:21  bart
 * fixed list traversal in find_engine
 *
 * Revision 1.7  2001/06/03 02:15:18  bart
 * fixed various minor bugs
 *
 * Revision 1.6  2001/06/02 22:51:50  bart
 * improved debug logging
 *
 * Revision 1.5  2001/06/02 22:24:59  bart
 * added debug messages to generic engine.
 *
 * Revision 1.4  2001/05/27 21:44:04  bart
 * cleaned up the activation and cleanup of contexts by using init/cleanup
 * functions.
 *
 * Revision 1.3  2001/05/25 23:52:36  bart
 * implemented all functions - first cut
 *
 * Revision 1.2  2001/05/15 11:39:38  bart
 * added some documentation
 *
 * Revision 1.1  2001/05/15 01:10:00  bart
 * initial compiling state
 *
 *
 *****************************************************************************/

