pike.git / src / modules / Gdbm / gdbmmod.c

version» Context lines:

pike.git/src/modules/Gdbm/gdbmmod.c:1:   /*   || This file is part of Pike. For copyright information see COPYRIGHT.   || Pike is distributed under GPL, LGPL and MPL. See the file COPYING   || for more information. - || $Id$ +    */      #include "global.h"   #include "gdbm_machine.h"   #include "threads.h"      /* Todo: make sure only one thread accesses the same gdbmmod */      #include "interpret.h"   #include "svalue.h"   #include "stralloc.h"   #include "module.h" -  + #include "array.h"   #include "program.h"   #include "module_support.h"      #if defined(HAVE_GDBM_H) && defined(HAVE_LIBGDBM)      #include <gdbm.h>      #ifdef _REENTRANT   static MUTEX_T gdbm_lock STATIC_MUTEX_INIT;   #endif      #define sp Pike_sp -  + static struct program *iterator;   struct gdbm_glue   {    GDBM_FILE dbf; -  +  struct pike_string *iter;   };      #define THIS ((struct gdbm_glue *)(Pike_fp->current_storage))      static void do_free(void)   {    if(THIS->dbf)    {    GDBM_FILE dbf;    dbf=THIS->dbf;    THIS->dbf=0;       THREADS_ALLOW();    mt_lock(& gdbm_lock);    gdbm_close(dbf);    mt_unlock(& gdbm_lock);    THREADS_DISALLOW();    } -  +  if(THIS->iter) +  { +  free_string(THIS->iter); +  THIS->iter=0;    } -  + }      /* Compat with old gdbm. */   #ifndef GDBM_SYNC   #define GDBM_SYNC 0   #endif   #ifndef GDBM_NOLOCK   #define GDBM_NOLOCK 0   #endif      static int fixmods(char *mods)
pike.git/src/modules/Gdbm/gdbmmod.c:99:    case 's': case 'S': flags |= GDBM_SYNC; break;    /* NOTE: This flag is inverted! */    case 'l': case 'L': flags &= ~GDBM_NOLOCK; break;       default:    Pike_error("Bad mode flag '%c' in gdbm->open.\n", mods[-1]);    }    }   }    - void gdbmmod_fatal(char *err) + void gdbmmod_fatal(const char *err)   {    Pike_error("GDBM: %s\n",err);   }      /*! @module Gdbm -  +  *! +  *! This module provides an interface to the GNU dbm database. +  *! +  *! The basic use of GDBM is to store key/data pairs in a data +  *! file. Each key must be unique and each key is paired with only one +  *! data item. +  *! +  *! The library provides primitives for storing key/data pairs, +  *! searching and retrieving the data by its key and deleting a key +  *! along with its data. It also support sequential iteration over all +  *! key/data pairs in a database. +  *! +  *! The @[DB] class also overloads enough operators to make it behave +  *! a lot like a @tt{mapping(string(8bit):string(8bit))@}, you can index it, +  *! assign indices and loop over it using foreach. +  *!    */    - /*! @class gdbm + /*! @class DB    */    - /*! @decl void create(void|string file, void|string mode) + /*! @decl protected void create(void|string file, void|string(99..119) mode)    *!    *! Without arguments, this function does nothing. With one argument it    *! opens the given file as a gdbm database, if this fails for some    *! reason, an error will be generated. If a second argument is present,    *! it specifies how to open the database using one or more of the follow    *! flags in a string:    *!    *! @string    *! @value r    *! Open database for reading
pike.git/src/modules/Gdbm/gdbmmod.c:146:    *! in the database immediately. This is dangerous because the database    *! can be left in an unusable state if Pike is terminated abnormally.    *!    *! The default mode is @expr{"rwc"@}.    *!    *! @note    *! The gdbm manual states that it is important that the database is    *! closed properly. Unfortunately this will not be the case if Pike    *! calls exit() or returns from main(). You should therefore make sure    *! you call close or destruct your gdbm objects when exiting your -  *! program. This will probably be done automatically in the future. +  *! program. +  *! +  *! @[atexit] might be useful. +  *! +  *! This is very important if the database is used with the 'l' flag.    */      static void gdbmmod_create(INT32 args)   {    struct gdbm_glue *this=THIS;    do_free(); -  +  if(!args) +  Pike_error("Need at least one argument to Gdbm.DB, the filename\n");    if(args)    {    GDBM_FILE tmp;    struct pike_string *tmp2;    int rwmode = GDBM_WRCREAT|GDBM_NOLOCK;       if(TYPEOF(sp[-args]) != T_STRING)    Pike_error("Bad argument 1 to gdbm->create()\n");       if(args>1)
pike.git/src/modules/Gdbm/gdbmmod.c:185:       THREADS_ALLOW();    mt_lock(& gdbm_lock);    tmp=gdbm_open(tmp2->str, 512, rwmode, 00666, gdbmmod_fatal);    mt_unlock(& gdbm_lock);    THREADS_DISALLOW();       if(!Pike_fp->current_object->prog)    {    if(tmp) gdbm_close(tmp); -  Pike_error("Object destructed in gdbm->open()n"); +  Pike_error("Object destructed in gdbm->create()\n");    }    this->dbf=tmp;    -  pop_n_elems(args); +     if(!this->dbf)    Pike_error("Failed to open GDBM database: %d: %s.\n",    gdbm_errno, gdbm_strerror(gdbm_errno));    }   }      #define STRING_TO_DATUM(dat, st) dat.dptr=st->str,dat.dsize=st->len;   #define DATUM_TO_STRING(dat) make_shared_binary_string(dat.dptr, dat.dsize)    - /*! @decl string fetch(string key) -  *! @decl string `[](string key) + /*! @decl string(8bit) fetch(string(8bit) key) +  *! @decl string(8bit) `[](string(8bit) key)    *!    *! Return the data associated with the key 'key' in the database.    *! If there was no such key in the database, zero is returned.    */ -  +    static void gdbmmod_fetch(INT32 args)   {    struct gdbm_glue *this=THIS;    datum key,ret;       if(!args)    Pike_error("Too few arguments to gdbm->fetch()\n");       if(TYPEOF(sp[-args]) != T_STRING)    Pike_error("Bad argument 1 to gdbm->fetch()\n");
pike.git/src/modules/Gdbm/gdbmmod.c:234:    ret=gdbm_fetch(this->dbf, key);    mt_unlock(& gdbm_lock);    THREADS_DISALLOW();       pop_n_elems(args);    if(ret.dptr)    {    push_string(DATUM_TO_STRING(ret));    free(ret.dptr);    }else{ -  push_int(0); +  push_undefined();    }   }      /*! @decl int(0..1) delete(string key)    *!    *! Remove a key from the database. Returns 1 if successful,    *! otherwise 0, e.g. when the item is not present or the    *! database is read only.    */ -  +    static void gdbmmod_delete(INT32 args)   {    struct gdbm_glue *this=THIS;    datum key;    int ret;    if(!args)    Pike_error("Too few arguments to gdbm->delete()\n");       if(TYPEOF(sp[-args]) != T_STRING)    Pike_error("Bad argument 1 to gdbm->delete()\n");
pike.git/src/modules/Gdbm/gdbmmod.c:271:    THREADS_ALLOW();    mt_lock(& gdbm_lock);    ret=gdbm_delete(this->dbf, key);    mt_unlock(& gdbm_lock);    THREADS_DISALLOW();       pop_n_elems(args);    push_int( ret==0 );   }    + /*! @decl string(8bit) _m_delete( string(8bit) key ) +  *! +  *! Provides overloading of the @[m_delete] function. +  *! +  *! Will return the value the key had before it was removed, if any +  *! +  *! If the key exists but deletion fails (usually due to a read only +  *! database) this function will throw an error. +  */ + static void gdbmmod_m_delete(INT32 args) + { +  struct gdbm_glue *this=THIS; +  datum key, ret; +  +  if(TYPEOF(sp[-args]) != T_STRING) +  { +  push_undefined(); +  return; +  } +  +  if(!this->dbf) +  Pike_error("GDBM database not open.\n"); +  +  STRING_TO_DATUM(key, sp[-args].u.string); +  +  THREADS_ALLOW(); +  mt_lock(& gdbm_lock); +  ret=gdbm_fetch(this->dbf, key); +  if( ret.dptr ) +  if( gdbm_delete(this->dbf, key) ) +  Pike_error("Failed to delete key from database.\n"); +  mt_unlock(& gdbm_lock); +  THREADS_DISALLOW(); +  if(ret.dptr) +  { +  push_string(DATUM_TO_STRING(ret)); +  free(ret.dptr); +  }else{ +  push_undefined(); +  } + } +    /*! @decl string firstkey()    *!    *! Return the first key in the database, this can be any key in the    *! database. -  +  *! +  *! Used together with @[nextkey] the databse can be iterated. +  *! +  *! @note +  *! The database also works as an @[Iterator], and can be used as the +  *! first argument to @[foreach]. +  *! +  *! Adding or removing keys will change the iteration order, +  *! this can cause keys to be skipped while iterating. +  *! +  *! +  *! @example +  *! @code +  *! // Write the contents of the database +  *! for(key=gdbm->firstkey(); k; k=gdbm->nextkey(k)) +  *! write(k+":"+gdbm->fetch(k)+"\n"); +  *! @endcode +  *! +  *! Or, using foreach +  *! @code +  *! // Write the contents of the database +  *! foreach( db; string key; string value ) +  *! write(key+":"+value+"\n"); +  *! @endcode    */      static void gdbmmod_firstkey(INT32 args)   {    struct gdbm_glue *this=THIS;    datum ret;    pop_n_elems(args);       if(!this->dbf) Pike_error("GDBM database not open.\n");   
pike.git/src/modules/Gdbm/gdbmmod.c:300:       if(ret.dptr)    {    push_string(DATUM_TO_STRING(ret));    free(ret.dptr);    }else{    push_int(0);    }   }    - /*! @decl string nextkey(string key) + /*! @decl string(8bit) nextkey(string(8bit) key)    *!    *! This returns the key in database that follows the key 'key' key.    *! This is of course used to iterate over all keys in the database.    *! -  +  *! @note +  *! Changing (adding or removing keys) the database while iterating +  *! can cause keys to be skipped. +  *! +  *! The database also works as an @[Iterator], and can be used as the +  *! first argument to @[foreach]. +  *!    *! @example -  +  *! @code    *! // Write the contents of the database    *! for(key=gdbm->firstkey(); k; k=gdbm->nextkey(k))    *! write(k+":"+gdbm->fetch(k)+"\n"); -  +  *! @endcode +  *! +  *! Or, using foreach +  *! @code +  *! // Write the contents of the database +  *! foreach( db; string key; string value ) +  *! write(key+":"+value+"\n"); +  *! @endcode +  *!    */      static void gdbmmod_nextkey(INT32 args)   {    struct gdbm_glue *this=THIS;    datum key,ret;    if(!args)    Pike_error("Too few arguments to gdbm->nextkey()\n");       if(TYPEOF(sp[-args]) != T_STRING)
pike.git/src/modules/Gdbm/gdbmmod.c:342:    pop_n_elems(args);    if(ret.dptr)    {    push_string(DATUM_TO_STRING(ret));    free(ret.dptr);    }else{    push_int(0);    }   }    - /*! @decl string `[]= (string key, string data) + /*! @decl string `[]= (string(8bit) key, string(8bit) data)    *!    *! Associate the contents of 'data' with the key 'key'. If the key 'key'    *! already exists in the database the data for that key will be replaced.    *! If it does not exist it will be added. An error will be generated if    *! the database was not open for writing.    *!    *! @example    *! gdbm[key] = data;    *!    *! @returns
pike.git/src/modules/Gdbm/gdbmmod.c:437:    gdbmmod_store(args);    pop_stack();    push_int(1);   }      /*! @decl int reorganize()    *!    *! Deletions and insertions into the database can cause fragmentation    *! which will make the database bigger. This routine reorganizes the    *! contents to get rid of fragmentation. Note however that this function -  *! can take a LOT of time to run. +  *! can take a LOT of time to run if the database is big.    */      static void gdbmmod_reorganize(INT32 args)   {    struct gdbm_glue *this=THIS;    int ret;    pop_n_elems(args);       if(!THIS->dbf) Pike_error("GDBM database not open.\n");    THREADS_ALLOW();
pike.git/src/modules/Gdbm/gdbmmod.c:464:   }      /*! @decl void sync()    *!    *! When opening the database with the 'f' flag writings to the database    *! can be cached in memory for a long time. Calling sync will write    *! all such caches to disk and not return until everything is stored    *! on the disk.    */    - static void gdbmmod_sync(INT32 args) + static void gdbmmod_sync(INT32 UNUSED(args))   {    struct gdbm_glue *this=THIS; -  pop_n_elems(args); -  +     if(!THIS->dbf) Pike_error("GDBM database not open.\n");    THREADS_ALLOW();    mt_lock(& gdbm_lock);    gdbm_sync(this->dbf);    mt_unlock(& gdbm_lock);    THREADS_DISALLOW(); -  push_int(0); +    }    - /*! @decl void close() +  + static void gdbmmod_iter_first(INT32 UNUSED(args)) + { +  struct gdbm_glue *this=THIS; +  gdbmmod_firstkey(0); +  if( Pike_sp[-1].u.string ) +  this->iter = Pike_sp[-1].u.string; +  Pike_sp--; +  push_int( !!this->iter ); + } +  + static void gdbmmod_iter_next(INT32 UNUSED(args)) + { +  struct gdbm_glue *this=THIS; +  if(!this->iter) +  { +  push_undefined(); +  return; +  } +  push_string( this->iter ); +  gdbmmod_nextkey(1); +  if( TYPEOF(Pike_sp[-1]) != PIKE_T_STRING ) +  { +  this->iter = 0; +  push_undefined(); +  return; +  } +  this->iter = Pike_sp[-1].u.string; +  push_int(1); +  return; +  + } +  + static void gdbmmod_iter_index(INT32 UNUSED(args)) + { +  struct gdbm_glue *this=THIS; +  if( this->iter ) +  ref_push_string( this->iter ); +  else +  push_undefined(); + } +  + static void gdbmmod_iter_no_value(INT32 UNUSED(args)) + { +  push_int( !THIS->iter ); + } +  + static void gdbmmod_iter_value(INT32 UNUSED(args)) + { +  struct gdbm_glue *this=THIS; +  if( this->iter ) +  { +  ref_push_string( this->iter ); +  gdbmmod_fetch(1); +  } +  else +  push_undefined(); + } +  + /*! @decl array(string(8bit)) _indices()    *! -  *! Closes the database. +  *! Provides overloading of @[indices]. +  *! +  *! @note +  *! Mainly useful when debugging, the returned list might not fit in +  *! memory for large databases.    */ -  + static void gdbmmod_indices(INT32 UNUSED(args)) + { +  struct gdbm_glue *this=THIS; +  struct svalue *start = Pike_sp; +  gdbmmod_iter_first(0); +  pop_stack(); +  while( this->iter ) +  { +  ref_push_string( this->iter ); +  gdbmmod_iter_next(0); +  pop_stack(); +  } +  push_array(aggregate_array( Pike_sp-start )); + }    - static void gdbmmod_close(INT32 args) + /*! @decl array(string(8bit)) _values() +  *! +  *! Provides overloading of @[values]. +  *! +  *! @note +  *! Mainly useful when debugging, the returned list might not fit in +  *! memory for large databases. +  */ + static void gdbmmod_values(INT32 UNUSED(args))   { -  pop_n_elems(args); +  struct gdbm_glue *this=THIS; +  struct svalue *start = Pike_sp; +  gdbmmod_iter_first(0); +  pop_stack();    -  +  while( this->iter ) +  { +  ref_push_string( this->iter ); +  gdbmmod_fetch(1); +  gdbmmod_iter_next(0); +  pop_stack(); +  } +  push_array(aggregate_array( Pike_sp-start )); + } +  + static void gdbmmod_get_iterator(INT32 UNUSED(args)) + { +  push_object( clone_object( iterator, 0 ) ); +  *((struct gdbm_glue *)Pike_sp[-1].u.object->storage) = *THIS; +  apply(Pike_sp[-1].u.object, "first", 0); +  pop_stack(); + } +  +  + /*! @decl void close() +  *! +  *! Closes the database. The object is no longer usable after this function has been called. +  *! +  *! This is also done automatically when the object is destructed for +  *! any reason (running out of references or explicit destruct, as an +  *! example) +  */ +  + static void gdbmmod_close(INT32 UNUSED(args)) + {    do_free(); -  push_int(0); +    }    - static void init_gdbm_glue(struct object *o) + static void init_gdbm_glue(struct object *UNUSED(o))   {    THIS->dbf=0; -  +  THIS->iter=0;   }    - static void exit_gdbm_glue(struct object *o) + static void init_gdbm_iterator(struct object *UNUSED(o))   { -  +  THIS->dbf=0; +  THIS->iter=0; + } +  + static void exit_gdbm_glue(struct object *UNUSED(o)) + {    do_free();   }    -  + static void exit_gdbm_iterator(struct object *UNUSED(o)) + { +  if( THIS->iter ) +  free_string( THIS->iter ); + } +    /*! @endclass    */    -  + /*! @class Iterator +  *! @inherit predef::Iterator +  *! +  *! Object keeping track of an iteration over a @[DB] +  *! +  *! @note +  *! Can not be usefully constructed manually, instead use the database +  *! as the first argument to @[foreach] or @[predef::get_iterator] +  *! +  */ +  +  /*! @endclass +  */ +    /*! @endmodule    */      #endif /* defined(HAVE_GDBM_H) && defined(HAVE_LIBGDBM) */    - PIKE_MODULE_EXIT {} + PIKE_MODULE_EXIT { + }      PIKE_MODULE_INIT   { -  +  struct program *db;   #if defined(HAVE_GDBM_H) && defined(HAVE_LIBGDBM)    start_new_program();    ADD_STORAGE(struct gdbm_glue);       /* function(void|string,void|string:void) */    ADD_FUNCTION("create", gdbmmod_create, -  tFunc(tOr(tVoid,tStr) tOr(tVoid,tStr), tVoid), 0 /*ID_PROTECTED*/); +  tFunc(tOr(tVoid,tStr) tOr(tVoid,tStr), tVoid), ID_PROTECTED);       /* function(:void) */    ADD_FUNCTION("close",gdbmmod_close,tFunc(tNone,tVoid),0);    /* function(string, string, int(0..1)|void: int) */    ADD_FUNCTION("store", gdbmmod_store_compat, -  tFunc(tStr tStr tOr(tInt01, tVoid), tInt), 0); +  tFunc(tStr8 tStr8 tOr(tInt01, tVoid), tInt), 0);    /* function(string, string, int(0..1)|void: string) */    ADD_FUNCTION("`[]=", gdbmmod_store, -  tFunc(tStr tSetvar(0, tStr) tOr(tInt01, tVoid), tVar(0)), 0); +  tFunc(tStr8 tSetvar(0, tStr8) tOr(tInt01, tVoid), tVar(0)), 0);    /* function(string:string) */ -  ADD_FUNCTION("fetch",gdbmmod_fetch,tFunc(tStr,tStr),0); +  ADD_FUNCTION("fetch",gdbmmod_fetch,tFunc(tStr,tStr8),0);    /* function(string:string) */ -  ADD_FUNCTION("`[]",gdbmmod_fetch,tFunc(tStr,tStr),0); +  ADD_FUNCTION("`[]",gdbmmod_fetch,tFunc(tStr,tStr8),0);    /* function(string:int) */    ADD_FUNCTION("delete",gdbmmod_delete,tFunc(tStr,tInt01),0);    /* function(:string) */ -  ADD_FUNCTION("firstkey",gdbmmod_firstkey,tFunc(tNone,tStr),0); +  ADD_FUNCTION("firstkey",gdbmmod_firstkey,tFunc(tNone,tStr8),0);    /* function(string:string) */ -  ADD_FUNCTION("nextkey",gdbmmod_nextkey,tFunc(tStr,tStr),0); +  ADD_FUNCTION("nextkey",gdbmmod_nextkey,tFunc(tStr,tStr8),0);    /* function(:int) */    ADD_FUNCTION("reorganize",gdbmmod_reorganize,tFunc(tNone,tInt),0);    /* function(:void) */    ADD_FUNCTION("sync",gdbmmod_sync,tFunc(tNone,tVoid),0);    -  +  /* iterator API.*/ +  ADD_FUNCTION("_get_iterator", gdbmmod_get_iterator,tFunc(tNone,tObj),0); +  +  /* some mapping operator overloading */ +  ADD_FUNCTION("_m_delete",gdbmmod_m_delete,tFunc(tStr,tStr8),0); +  ADD_FUNCTION("_values",gdbmmod_values,tFunc(tNone,tArr(tStr8)),0); +  ADD_FUNCTION("_indices",gdbmmod_indices,tFunc(tNone,tArr(tStr8)),0); +     set_init_callback(init_gdbm_glue);    set_exit_callback(exit_gdbm_glue); -  end_class("gdbm",0); +  db = end_program(); +  add_program_constant( "DB", db, 0 ); +  add_program_constant( "gdbm", db, 0 ); /* compat (...-7.8). */ +  free_program(db); +  +  start_new_program(); +  ADD_STORAGE(struct gdbm_glue); +  ADD_FUNCTION("first", gdbmmod_iter_first,tFunc(tNone,tInt01),0); +  ADD_FUNCTION("next", gdbmmod_iter_next, tFunc(tNone,tInt01),0); +  ADD_FUNCTION("index", gdbmmod_iter_index,tFunc(tNone,tStr8),0); +  ADD_FUNCTION("value", gdbmmod_iter_value,tFunc(tNone,tStr8),0); +  ADD_FUNCTION("`!", gdbmmod_iter_no_value,tFunc(tNone,tInt01),0); +  set_init_callback(init_gdbm_iterator); +  set_exit_callback(exit_gdbm_iterator); +  iterator = end_program(); +  add_program_constant( "Iterator", iterator, 0 ); +  free_program(iterator);   #else -  if(!TEST_COMPAT(7,6)) +     HIDE_MODULE();   #endif   }