/* $Id: dbexp.c,v 1.5 2005/09/20 19:13:03 dm Exp $ */

/*
 *
 * Copyright (C) 2004 David Mazieres (dm@uun.org)
 *
 * 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, or (at
 * your option) any later version.
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

#include "avutil.h"
#include "dbexp.h"

#define RECOVLK "recov.lock"
static int
_dbenv_init_lockfd (dbenv *dbe, const char *path)
{
  struct stat sb;
  char *pb;
  mode_t m;

  if (!stat (path, &sb)) {
    if (!S_ISDIR (sb.st_mode)) {
      fprintf (stderr, "%s: not a directory\n", path);
      return ENOTDIR;
    }
  }
  else if (errno != ENOENT) {
    perror (path);
    return errno;
  }
  else if (mkdir (path, 0700)) {
    perror (path);
    return errno;
  }

  pb = xmalloc (strlen (path) + sizeof (RECOVLK) + 1);
  sprintf (pb, "%s/%s", path, RECOVLK);

  m = umask (0);
  umask (m);
  if (m & 02)
    m |= 04;
  if (m & 020)
    m |= 040;
  dbe->lockfd = open (pb, O_CREAT|O_RDWR, 0666 & ~m);
  if (dbe->lockfd < 0) {
    perror (pb);
    free (pb);
    return errno;
  }

  free (pb);
  return 0;
}

dbenv *
dbenv_alloc (const char *path)
{
  int err;
  int recover = DB_RECOVER;
  dbenv *dbe = xmalloc (sizeof (*dbe));
  bzero (dbe, sizeof (*dbe));

  err = db_env_create (&dbe->e, 0);
  if (err) {
    fprintf (stderr, "db_env_create: %s\n", db_strerror (err));
    return NULL;
  }

  if (!path) {
    err = dbe->e->open (dbe->e, NULL,
			DB_PRIVATE|DB_CREATE|DB_INIT_MPOOL, 0666);
    if (!err)
      return dbe;
    fprintf (stderr, "dbenv_open: %s\n", db_strerror (err));
    dbe->e->close (dbe->e, 0);
    free (dbe);
    return NULL;
  }
  
  if (_dbenv_init_lockfd (dbe, path)) {
    dbe->e->close (dbe->e, 0);
    free (dbe);
    return NULL;
  }

  if (flock (dbe->lockfd, LOCK_EX|LOCK_NB)) {
    recover = 0;
    if (flock (dbe->lockfd, LOCK_SH)) {
      fprintf (stderr, "%s/%s: %s\n", path, RECOVLK, strerror (errno));
      close (dbe->lockfd);
      dbe->e->close (dbe->e, 0);
      free (dbe);
      return NULL;
    }
  }

#if 0
  /* For debugging: */
  err = dbe->e->set_lg_bsize (dbe->e, 25 * 1024);
  if (err)
    fprintf (stderr, "set_lg_bsize: %s\n", db_strerror (err));
  dbe->e->set_lg_max (dbe->e, 100 * 1024);
  if (err)
    fprintf (stderr, "set_lg_size: %s\n", db_strerror (err));
#endif

  err = dbe->e->open (dbe->e, path,
		      (DB_INIT_MPOOL|DB_INIT_LOG|DB_INIT_LOCK|DB_INIT_TXN
		       |recover|DB_CREATE), 0666);
  if (err) {
    fprintf (stderr, "%s: %s\n", path, db_strerror (err));
    close (dbe->lockfd);
    dbe->e->close (dbe->e, 0);
    free (dbe);
    return NULL;
  }

  if (recover)
    flock (dbe->lockfd, LOCK_SH);

  dbe->txnflag = DB_AUTO_COMMIT;
  dbe->home = xmalloc (strlen (path) + 1);
  strcpy (dbe->home, path);

  return dbe;
}

void
dbenv_free (dbenv *dbe)
{
  dbe->e->close (dbe->e, 0);
  close (dbe->lockfd);
  free (dbe);
}

int
dbenv_txn (DB_TXN **tid, dbenv *dbe)
{
  if (dbe->txnflag)
    return dbe->e->txn_begin (dbe->e, NULL, tid, 0);
  *tid = NULL;
  return 0;
}

int
dbenv_commit (DB_TXN *tid)
{
  if (tid)
    return tid->commit (tid, 0);
  return 0;
}

int
dbexp_clean (dbexp *dbx)
{
  u_int32_t now = time (NULL);
  DB_TXN *tid;
  int i;
  int err = 0;
  DBC *c;
  DBT k, v;

  while (!err) {
    if ((err = dbenv_txn (&tid, dbx->dbe))) {
      fprintf (stderr, "%s: %s\n", dbx->path, db_strerror (err));
      return err;
    }
    if ((err = dbx->exp->cursor (dbx->exp, tid, &c, 0))) {
      fprintf (stderr, "%s: %s\n", dbx->path, db_strerror (err));
      dbenv_commit (tid);
      return err;
    }

    bzero (&k, sizeof (k));
    bzero (&v, sizeof (v));

    err = c->c_get (c, &k, &v, DB_FIRST);
    for (i = 0; !err && i < 20; i++) {
      if (k.size >= 4) {
	if (getint (k.data) > now)
	  err = DB_NOTFOUND;
	else
	  err = c->c_del (c, 0);
      }
      if (!err)
	err = c->c_get (c, &k, &v, DB_NEXT);
    }

    if (err && err != DB_NOTFOUND)
      fprintf (stderr, "%s: %s\n", dbx->path, db_strerror (err));

    c->c_close (c);
    if (!err)
      err = dbenv_commit (tid);
    else
      dbenv_commit (tid);
  }

  if (dbx->dbe->home) {
#ifdef DB_ARCH_REMOVE
    dbx->dbe->e->log_archive (dbx->dbe->e, NULL, DB_ARCH_REMOVE);
#else /* !DB_ARCH_REMOVE */
    int i;
    char **paths = NULL;
    int err = dbx->dbe->e->log_archive (dbx->dbe->e, &paths, DB_ARCH_ABS);
    if (err)
      fprintf (stderr, "%s: log_archive: %s\n", dbx->path, db_strerror (err));
    else if (paths) {
      for (i = 0; paths[i]; i++)
	unlink (paths[i]);
      free (paths);
    }
#endif /* !DB_ARCH_REMOVE */
  }

  if (err == DB_NOTFOUND)
    return 0;
  return err;
}

int
dbexp_free (dbexp *dbx, int sync)
{
  int ret = 0;
  int err;

  if (dbx->exp) {
    err = dbx->exp->close (dbx->exp, 0);
    if (err) {
      fprintf (stderr, "%s: %s\n", dbx->path, db_strerror (err));
      ret = err;
    }
  }

  if (dbx->db) {
    if (sync && !ret && !dbx->dbe->txnflag) {
      err = dbx->db->sync (dbx->db, 0);
      if (!ret && err) {
	fprintf (stderr, "%s; %s\n", dbx->path, db_strerror (err));
	ret = err;
      }
    }
    err = dbx->db->close (dbx->db, 0);
    if (!ret && err) {
      fprintf (stderr, "%s: %s\n", dbx->path, db_strerror (err));
      ret = err;
    }
  }

  free (dbx->path);
  free (dbx);
  return ret;
}

static int
_dbexp_getexp (DB *exp, const DBT *pkey, const DBT *pdata, DBT *skey)
{
  if (pdata->size < 4)
    return EINVAL;
  skey->data = pdata->data;
  skey->size = 4;
  return 0;
}

dbexp *
dbexp_alloc (dbenv *dbe, const char *path, int rdonly)
{
  int err;
  int fd;
  dbexp *dbx = malloc (sizeof (*dbx));

  bzero (dbx, sizeof (*dbx));
  dbx->dbe = dbe;
  dbx->path = xmalloc (strlen (path) + 1);
  strcpy (dbx->path, path);

  err = db_create (&dbx->db, dbx->dbe->e, 0);
  if (!err)
    err = dbx->db->set_flags(dbx->db, DB_CHKSUM);
  if (!rdonly) {
    if (!err)
      err = db_create (&dbx->exp, dbx->dbe->e, 0);
    if (!err)
      err = dbx->exp->set_flags(dbx->exp, DB_DUPSORT);
  }
  if (err) {
    fprintf (stderr, "db_create: %s\n", db_strerror (err));
    dbexp_free (dbx, 0);
    return NULL;
  }

  err = dbx->db->open (dbx->db, NULL, dbx->path, "db", DB_BTREE,
		       rdonly ? DB_RDONLY : dbx->dbe->txnflag|DB_CREATE,
		       0666);
  if (!err)
    err = dbx->db->fd (dbx->db, &fd);
  if (err) {
    fprintf (stderr, "%s: %s\n", dbx->path, db_strerror (err));
    dbexp_free (dbx, 0);
    return NULL;
  }
  if (flock (fd, rdonly ? LOCK_SH : LOCK_EX) < 0) {
    fprintf (stderr, "flock (%s): %s\n", dbx->path, strerror (errno));
    dbexp_free (dbx, 0);
    return NULL;
  }

  if (!rdonly) {
    err = dbx->exp->open (dbx->exp, NULL, dbx->path, "exp", DB_BTREE,
			  dbx->dbe->txnflag|DB_CREATE, 0666);
    if (!err)
      err = dbx->db->associate (dbx->db, NULL, dbx->exp,
				_dbexp_getexp, dbx->dbe->txnflag);
    if (err) {
      fprintf (stderr, "%s: %s\n", dbx->path, db_strerror (err));
      dbexp_free (dbx, 0);
      return NULL;
    }
  }

  return dbx;
}



syntax highlighted by Code2HTML, v. 0.9.1