commit 78760d2acab48a8705a0861914f427d32192ca83
Author: ak User <ak@bereshit.home>
Date:   Tue Aug 8 11:43:59 2006 +0200

    dietlibc implementation of ndbm interface as specified by SuSv3.

diff --git a/include/ndbm.h b/include/ndbm.h
new file mode 100644
index 0000000..d487fcd
--- /dev/null
+++ b/include/ndbm.h
@@ -0,0 +1,41 @@
+#ifndef _NDBM_H
+#define _NDBM_H
+
+/*
+ * dietlibc implementation of DBM interface as specified by SuSv3.
+ * (c) 2006 Andreas Krennmair <ak@synflood.at> 
+ * Licensed under the same conditions as dietlibc.
+ */
+
+#include <stddef.h>
+#include <sys/types.h>
+#include <fcntl.h>
+
+typedef struct {
+  size_t dsize;
+  void * dptr;
+} datum;
+
+typedef struct {
+  int fd;
+  char * mptr; /* memory-mapped pointer */
+  size_t msize; /* size of mapped memory */
+  char * dptr; /* data pointer */
+  char * fknk_ptr; /* used for dbm_firstkey/dbm_nextkey function */
+  int errcond;
+} DBM;
+
+
+enum { DBM_INSERT, DBM_REPLACE };
+
+int     dbm_clearerr(DBM *);
+void    dbm_close(DBM *);
+int     dbm_delete(DBM *, datum);
+int     dbm_error(DBM *);
+datum   dbm_fetch(DBM *, datum);
+datum   dbm_firstkey(DBM *);
+datum   dbm_nextkey(DBM *);
+DBM    *dbm_open(const char *, int, mode_t);
+int     dbm_store(DBM *, datum, datum, int);
+
+#endif
diff --git a/lib/ndbm.c b/lib/ndbm.c
new file mode 100644
index 0000000..5844cf7
--- /dev/null
+++ b/lib/ndbm.c
@@ -0,0 +1,267 @@
+#include <ndbm.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <netinet/in.h> /* for htons/ntohs */
+
+#define MAGIC_CODE "DIETLIBC-NDBMv1"
+
+/**
+ * description of file format:
+ * - file starts with magic code "DIETLIBC-NDBMv1"
+ * - then follow 0 or more records of the following format:
+ *  - uint16_t record_len
+ *    - uint16_t key_len
+ *    - char key_data[key_len]
+ *    - uint16_t value_len
+ *    - char value_data[value_len]
+ *
+ * for portability reasons (especially for the data files), all uint16_t values are stored in network byte order.
+ **/
+
+DBM * dbm_open(const char * filename, int open_flags, mode_t file_mode) {
+  DBM * db = calloc(1,sizeof(DBM));
+  if (!db) return NULL;
+  if (open_flags | O_APPEND) /* O_APPEND has no meaning */
+    open_flags &= (~O_APPEND);
+  if (open_flags | O_WRONLY) {
+    open_flags &= (~O_WRONLY);
+    open_flags |= O_RDWR;
+  }
+  db->fd = open(filename,open_flags,file_mode);
+  if (db->fd < 0) {
+    free(db);
+    return NULL;
+  }
+  db->msize = lseek(db->fd, 0, SEEK_END);
+  lseek(db->fd, 0, SEEK_SET);
+
+  /* empty files must be initialized with the magic code first */
+  if (db->msize==0) {
+    write(db->fd,MAGIC_CODE,strlen(MAGIC_CODE));
+    db->msize = strlen(MAGIC_CODE);
+  }
+
+  db->mptr = mmap(NULL, db->msize, PROT_READ | PROT_WRITE, MAP_SHARED, db->fd, 0);
+  if (MAP_FAILED == db->mptr) {
+    close(db->fd);
+    free(db);
+    return NULL;
+  }
+
+  /* check magic code */
+  if (db->mptr && db->msize >= strlen(MAGIC_CODE) && memcmp(db->mptr,MAGIC_CODE,strlen(MAGIC_CODE))==0) {
+    db->dptr = db->mptr + strlen(MAGIC_CODE);
+    return db;
+  }
+
+  /* all checks failed, not a valid file */
+  return NULL;
+}
+
+int dbm_store(DBM * db, datum key, datum value, int store_mode) {
+  uint16_t record_len, key_len, value_len, x;
+  void * new_mptr;
+
+  if (DBM_REPLACE == store_mode) {
+    dbm_delete(db,key);
+  } else if (DBM_INSERT == store_mode) {
+    datum tmp = dbm_fetch(db,key);
+    if (tmp.dptr != NULL) {
+      return 1;
+    }
+  }
+
+
+  lseek(db->fd, 0, SEEK_END);
+
+  record_len = 3*sizeof(uint16_t) + key.dsize + value.dsize;
+  key_len = key.dsize;
+  value_len = value.dsize;
+
+#if NDBM_DEBUG
+  fprintf(stderr,"dbm_store: record_len = %u\n",record_len);
+#endif
+
+  x = htons(record_len);
+  write(db->fd, &x, sizeof(x));
+
+  x = htons(key_len);
+  write(db->fd, &x, sizeof(x));
+
+  write(db->fd, key.dptr, key_len);
+
+  x = htons(value_len);
+  write(db->fd, &x, sizeof(x));
+
+  write(db->fd, value.dptr,value_len);
+
+  new_mptr = mremap(db->mptr,db->msize,db->msize + record_len, MREMAP_MAYMOVE);
+  if (new_mptr == MAP_FAILED) {
+    db->errcond = 1;
+    return -1;
+  }
+
+  db->mptr = new_mptr;
+  db->dptr = db->mptr + strlen(MAGIC_CODE);
+  db->msize += record_len;
+
+  msync(db->mptr, db->msize, MS_SYNC); /* wo we really need this? */
+
+  return 0;
+}
+
+void dbm_close(DBM * db) {
+  if (db) {
+    munmap(db->mptr,db->msize);
+    close(db->fd);
+    free(db);
+  }
+}
+
+int dbm_delete(DBM * db, datum key) {
+  int rc = -1;
+  if (db) {
+    uint16_t record_len = 0;
+    char * ptr;
+    for (ptr = db->dptr;ptr < db->mptr+db->msize;ptr += record_len) {
+      uint16_t key_len, value_len;
+
+      memcpy(&record_len,ptr,sizeof(record_len));
+      record_len = ntohs(record_len);
+
+      memcpy(&key_len,ptr+sizeof(record_len),sizeof(key_len));
+      key_len = ntohs(key_len);
+
+      if (key.dsize != key_len) {
+        continue;
+      } else {
+        if (memcmp(key.dptr,ptr+sizeof(record_len)+sizeof(key_len),key.dsize)==0) {
+#if NDBM_DEBUG
+          fprintf(stderr,"dbm_delete: record_len = %u\n",record_len);
+#endif
+          /* found -> delete */
+          memmove(ptr,ptr+record_len,(db->mptr + db->msize) - (ptr + record_len));
+          ftruncate(db->fd,db->msize - record_len);
+          char * new_mptr = mremap(db->mptr,db->msize,db->msize - record_len, MREMAP_MAYMOVE);
+          if (!new_mptr) {
+            db->errcond = 1;
+            return -1;
+          }
+          db->mptr = new_mptr;
+          db->dptr = db->mptr + strlen(MAGIC_CODE);
+          db->msize -= record_len;
+          msync(db->mptr,db->msize,MS_SYNC); /* do we really need this? */
+          rc = 0;
+          continue; /* try to find next entry w/ same key; should never be, but you never know... */
+        } else {
+          /* not found, move to next */
+          continue;
+        }
+      }
+    }
+  } else {
+    db->errcond = 1;
+    return -1;
+  }
+  return rc;
+}
+
+datum dbm_fetch(DBM * db, datum key) {
+  datum d = { 0, NULL};
+#if NDBM_DEBUG
+  fprintf(stderr,"dbm_fetch called; db = %08X key.dptr = %08X\n",db,key.dptr);
+#endif
+  if (db && key.dptr) {
+    uint16_t record_len = 0;
+    char * ptr;
+#if NDBM_DEBUG
+    fprintf(stderr,"inside if; ptr = %08x mptr = %08X msize = %d\n",ptr,db->mptr,db->msize);
+#endif
+    for (ptr = db->dptr;ptr < db->mptr+db->msize;ptr += record_len) {
+      uint16_t key_len, value_len;
+      memcpy(&record_len,ptr,sizeof(record_len));
+      record_len = ntohs(record_len);
+
+#if NDBM_DEBUG
+      fprintf(stderr,"record_len = %d ptr = %08x end = %08x\n",record_len,ptr,db->mptr+db->msize);
+#endif
+
+      memcpy(&key_len,ptr+sizeof(record_len),sizeof(key_len));
+      key_len = ntohs(key_len);
+
+      if (key.dsize != key_len) {
+        continue;
+      } else {
+        if (memcmp(key.dptr,ptr+sizeof(record_len)+sizeof(key_len),key.dsize)==0) {
+          /* found -> unpack and return */
+          uint16_t value_len;
+
+          memcpy(&value_len,ptr+sizeof(record_len)+sizeof(key_len)+key_len,sizeof(value_len));
+          value_len = ntohs(value_len);
+
+          d.dsize = value_len;
+          d.dptr = ptr+sizeof(record_len)+sizeof(key_len)+key_len+sizeof(value_len);
+
+          return d;
+        } else {
+          /* not found, move to next */
+          continue;
+        }
+      }
+    }
+  }
+  return d;
+}
+
+static datum _nextkey(DBM * db) {
+  datum d = { 0, NULL };
+  if (db->fknk_ptr < db->mptr+db->msize) {
+    uint16_t record_len;
+    uint16_t key_len;
+
+    memcpy(&record_len,db->fknk_ptr,sizeof(record_len));
+    record_len = ntohs(record_len);
+
+    memcpy(&key_len,db->fknk_ptr+sizeof(record_len),sizeof(key_len));
+    key_len = ntohs(key_len);
+
+    d.dsize = key_len;
+    d.dptr = db->fknk_ptr+sizeof(record_len)+sizeof(key_len);
+
+    db->fknk_ptr += record_len;
+  }
+  return d;
+}
+
+static datum eof_datum = { 0, NULL };
+
+datum dbm_nextkey(DBM * db) {
+  if (!db) return eof_datum;
+  return _nextkey(db);
+}
+
+datum dbm_firstkey(DBM * db) {
+  if (!db) return eof_datum;
+  db->fknk_ptr = db->dptr;
+  return _nextkey(db);
+}
+
+int dbm_error(DBM * db) {
+  if (db) {
+    return db->errcond;
+  }
+  return 0; /* where there is no db, there can be no error (i.e. it's unspecified) */
+}
+
+int dbm_clearerr(DBM * db) {
+  if (db) {
+    db->errcond = 0;
+  }
+  return 0; /* quote: "The return value of dbm_clearerr() is unspecified." */
+}
diff --git a/test/ndbm1.c b/test/ndbm1.c
new file mode 100644
index 0000000..f926ef8
--- /dev/null
+++ b/test/ndbm1.c
@@ -0,0 +1,85 @@
+#include <ndbm.h>
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+
+int main(void) {
+  DBM * db;
+  system("rm -f testdb.dbm");
+  db = dbm_open("testdb.dbm",O_RDWR|O_CREAT,0644);
+  if (db) {
+    datum k, v, x, y;
+
+    /* first, add some test entries */
+
+    k.dptr = "asdf";
+    k.dsize = 4;
+    v.dptr = "qwert";
+    v.dsize = 5;
+
+    dbm_store(db,k,v,DBM_INSERT);
+
+    k.dptr = "yxcv";
+    v.dptr = "12345";
+
+    dbm_store(db,k,v,DBM_INSERT);
+
+    /* test dbm_firstkey/dbm_nextkey */
+
+    x = dbm_firstkey(db);
+    assert(x.dptr != NULL);
+    assert(memcmp(x.dptr,"asdf",x.dsize)==0);
+    y = dbm_fetch(db,x);
+    assert(y.dptr != NULL);
+    assert(memcmp(y.dptr,"qwert",y.dsize)==0);
+
+    x = dbm_nextkey(db);
+    assert(x.dptr != NULL);
+    write(1,x.dptr,x.dsize);
+    write(1,"\n",1);
+    assert(memcmp(x.dptr,"yxcv",x.dsize)==0);
+    y = dbm_fetch(db,x);
+    assert(y.dptr != NULL);
+    assert(memcmp(y.dptr,"12345",y.dsize)==0);
+
+    x = dbm_nextkey(db);
+    assert(x.dptr == NULL);
+
+    /* test end of iteration a second time so that subsequent dbm_nextkey calls don't produce any crash */
+    x = dbm_nextkey(db);
+    assert(x.dptr == NULL);
+ 
+
+    /* test dbm_fetch further */
+
+    k.dptr = "asdf";
+
+    x = dbm_fetch(db,k);
+    assert(x.dptr != NULL);
+    assert(memcmp(x.dptr,"qwert",5)==0);
+
+    k.dptr = "xxx";
+    x = dbm_fetch(db,k);
+    assert(x.dptr == NULL);
+
+    /* test dbm_delete */
+
+    k.dptr = "asdf";
+
+    assert(0==dbm_delete(db,k));
+
+    x = dbm_fetch(db,k);
+    assert(x.dptr == NULL);
+
+    k.dptr = "yxcv";
+    assert(0==dbm_delete(db,k));
+
+    /* close db */
+
+    dbm_close(db);
+
+  } else {
+    puts("error: dbm_open failed");
+  }
+  return 0;
+}
diff --git a/test/ndbm2.c b/test/ndbm2.c
new file mode 100644
index 0000000..ebe76cd
--- /dev/null
+++ b/test/ndbm2.c
@@ -0,0 +1,86 @@
+#include <ndbm.h>
+#include <stdio.h>
+#include <assert.h>
+#include <stdlib.h>
+
+int main(void) {
+  DBM * db;
+
+  FILE * f1, * f2, * f3;
+
+  f1 = fopen("output1.txt","w");
+  f2 = fopen("output2.txt","w");
+  f3 = fopen("output3.txt","w");
+
+//  system("rm -f testdb.dbm");
+
+  srand(getpid());
+
+  db = dbm_open("testdb.dbm",O_RDWR|O_CREAT,0644);
+  if (db) {
+
+    datum k, v;
+    int i;
+
+    /* first, insert 1000 entries */
+    puts("=======");
+    for (i=0;i<1000;++i) {
+      char keydata[100];
+      char valuedata[1001];
+      int j, valuelen = rand()%1000;
+      snprintf(keydata,sizeof(keydata),"test%u",i);
+      k.dptr = keydata;
+      k.dsize = strlen(keydata)+1;
+      for (j=0;j<valuelen;++j) {
+        valuedata[j] = 'a' + rand()%26;
+      }
+      valuedata[j] = '\0';
+      ++valuelen;
+      v.dptr = valuedata;
+      v.dsize = valuelen;
+      fprintf(f1,"%s=%s\n",keydata,valuedata);
+      dbm_store(db,k,v,DBM_REPLACE);
+    }
+    puts("=======");
+
+    for(k = dbm_firstkey(db); k.dptr != NULL; k = dbm_nextkey(db)) {
+      assert(k.dptr != NULL);
+      v = dbm_fetch(db,k);
+      assert(v.dptr != NULL);
+      fprintf(f2,"%s=%s\n",k.dptr,v.dptr);
+    }
+
+    puts("=======");
+
+
+    k.dptr = "test23";
+    k.dsize = 7; /* [sic] */
+
+    assert(0==dbm_delete(db,k));
+
+    k.dptr = "test666";
+    k.dsize = 8;
+    assert(0==dbm_delete(db,k));
+
+    for(i=0,k = dbm_firstkey(db); k.dptr != NULL; k = dbm_nextkey(db),++i) {
+      assert(k.dptr != NULL);
+      v = dbm_fetch(db,k);
+      assert(v.dptr != NULL);
+      fprintf(f3,"%s=%s\n",k.dptr,v.dptr);
+    }
+
+    assert(998==i);
+
+    fclose(f1);
+    fclose(f2);
+    fclose(f3);
+
+    /* close db */
+
+    dbm_close(db);
+
+  } else {
+    puts("error: dbm_open failed");
+  }
+  return 0;
+}
