/* tdbio.c - trust database I/O operations
 * Copyright (C) 1998-2002, 2012 Free Software Foundation, Inc.
 * Copyright (C) 1998-2015 Werner Koch
 *
 * This file is part of GnuPG.
 *
 * GnuPG 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 3 of the License, or
 * (at your option) any later version.
 *
 * GnuPG 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, see <https://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include "gpg.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "../common/util.h"
#include "options.h"
#include "main.h"
#include "../common/i18n.h"
#include "trustdb.h"
#include "tdbio.h"

#if defined(HAVE_DOSISH_SYSTEM) && !defined(ftruncate)
#define ftruncate chsize
#endif

#if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__)
#define MY_O_BINARY  O_BINARY
#else
#define MY_O_BINARY  0
#endif

/* We use ERRNO despite that the cegcc provided open/read/write
   functions don't set ERRNO - at least show that ERRNO does not make
   sense.  */
#ifdef HAVE_W32CE_SYSTEM
#undef strerror
#define strerror(a) ("[errno not available]")
#endif

/*
 * Yes, this is a very simple implementation. We should really
 * use a page aligned buffer and read complete pages.
 * To implement a simple trannsaction system, this is sufficient.
 */
typedef struct cache_ctrl_struct *CACHE_CTRL;
struct cache_ctrl_struct
{
  CACHE_CTRL next;
  struct {
    unsigned used:1;
    unsigned dirty:1;
  } flags;
  ulong recno;
  char data[TRUST_RECORD_LEN];
};

/* Size of the cache.  The SOFT value is the general one.  While in a
   transaction this may not be sufficient and thus we may increase it
   then up to the HARD limit.  */
#define MAX_CACHE_ENTRIES_SOFT	200
#define MAX_CACHE_ENTRIES_HARD	10000


/* The cache is controlled by these variables.  */
static CACHE_CTRL cache_list;
static int cache_entries;
static int cache_is_dirty;


/* An object to pass information to cmp_krec_fpr. */
struct cmp_krec_fpr_struct
{
  int pubkey_algo;
  const char *fpr;
  int fprlen;
};

/* An object used to pass information to cmp_[s]dir. */
struct cmp_xdir_struct
{
  int pubkey_algo;
  u32 keyid[2];
};


/* The name of the trustdb file.  */
static char *db_name;

/* The handle for locking the trustdb file and a counter to record how
 * often this lock has been taken.  That counter is required becuase
 * dotlock does not implemen recursive locks.  */
static dotlock_t lockhandle;
static unsigned int is_locked;

/* The file descriptor of the trustdb.  */
static int  db_fd = -1;

/* A flag indicating that a transaction is active.  */
/* static int in_transaction;   Not yet used. */



static void open_db (void);
static void create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type);



/*
 * Take a lock on the trustdb file name.  I a lock file can't be
 * created the function terminates the process.  Except for a
 * different return code the function does nothing if the lock has
 * already been taken.
 *
 * Returns: True if lock already exists, False if the lock has
 *          actually been taken.
 */
static int
take_write_lock (void)
{
  int rc;

  if (!lockhandle)
    lockhandle = dotlock_create (db_name, 0);
  if (!lockhandle)
    log_fatal ( _("can't create lock for '%s'\n"), db_name );

  if (!is_locked)
    {
      if (dotlock_take (lockhandle, -1) )
        log_fatal ( _("can't lock '%s'\n"), db_name );
      rc = 0;
    }
  else
    rc = 1;

  if (opt.lock_once)
    is_locked = 1;
  else
    is_locked++;
  return rc;
}


/*
 * Release a lock from the trustdb file unless the global option
 * --lock-once has been used.
 */
static void
release_write_lock (void)
{
  if (opt.lock_once)
    return;  /* Don't care; here IS_LOCKED is fixed to 1.  */

  if (!is_locked)
    {
      log_error ("Ooops, tdbio:release_write_lock with no lock held\n");
      return;
    }
  if (--is_locked)
    return;

  if (dotlock_release (lockhandle))
    log_error ("Oops, tdbio:release_write_locked failed\n");
}



/*************************************
 ************* record cache **********
 *************************************/

/*
 * Get the data from the record cache and return a pointer into that
 * cache.  Caller should copy the returned data.  NULL is returned on
 * a cache miss.
 */
static const char *
get_record_from_cache (ulong recno)
{
  CACHE_CTRL r;

  for (r = cache_list; r; r = r->next)
    {
      if (r->flags.used && r->recno == recno)
        return r->data;
    }
  return NULL;
}


/*
 * Write a cached item back to the trustdb file.
 *
 * Returns: 0 on success or an error code.
 */
static int
write_cache_item (CACHE_CTRL r)
{
  gpg_error_t err;
  int n;

  if (lseek (db_fd, r->recno * TRUST_RECORD_LEN, SEEK_SET) == -1)
    {
      err = gpg_error_from_syserror ();
      log_error (_("trustdb rec %lu: lseek failed: %s\n"),
                 r->recno, strerror (errno));
      return err;
    }
  n = write (db_fd, r->data, TRUST_RECORD_LEN);
  if (n != TRUST_RECORD_LEN)
    {
      err = gpg_error_from_syserror ();
      log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"),
                 r->recno, n, strerror (errno) );
      return err;
    }
  r->flags.dirty = 0;
  return 0;
}


/*
 * Put data into the cache.  This function may flush
 * some cache entries if the cache is filled up.
 *
 * Returns: 0 on success or an error code.
 */
static int
put_record_into_cache (ulong recno, const char *data)
{
  CACHE_CTRL r, unused;
  int dirty_count = 0;
  int clean_count = 0;

  /* See whether we already cached this one.  */
  for (unused = NULL, r = cache_list; r; r = r->next)
    {
      if (!r->flags.used)
        {
          if (!unused)
            unused = r;
	}
      else if (r->recno == recno)
        {
          if (!r->flags.dirty)
            {
              /* Hmmm: should we use a copy and compare? */
              if (memcmp (r->data, data, TRUST_RECORD_LEN))
                {
                  r->flags.dirty = 1;
                  cache_is_dirty = 1;
		}
	    }
          memcpy (r->data, data, TRUST_RECORD_LEN);
          return 0;
	}
      if (r->flags.used)
        {
          if (r->flags.dirty)
            dirty_count++;
          else
            clean_count++;
	}
    }

  /* Not in the cache: add a new entry. */
  if (unused)
    {
      /* Reuse this entry. */
      r = unused;
      r->flags.used = 1;
      r->recno = recno;
      memcpy (r->data, data, TRUST_RECORD_LEN);
      r->flags.dirty = 1;
      cache_is_dirty = 1;
      cache_entries++;
      return 0;
    }

  /* See whether we reached the limit. */
  if (cache_entries < MAX_CACHE_ENTRIES_SOFT)
    {
      /* No: Put into cache.  */
      r = xmalloc (sizeof *r);
      r->flags.used = 1;
      r->recno = recno;
      memcpy (r->data, data, TRUST_RECORD_LEN);
      r->flags.dirty = 1;
      r->next = cache_list;
      cache_list = r;
      cache_is_dirty = 1;
      cache_entries++;
      return 0;
    }

  /* Cache is full: discard some clean entries.  */
  if (clean_count)
    {
      int n;

      /* We discard a third of the clean entries.  */
      n = clean_count / 3;
      if (!n)
        n = 1;

      for (unused = NULL, r = cache_list; r; r = r->next)
        {
          if (r->flags.used && !r->flags.dirty)
            {
              if (!unused)
                unused = r;
              r->flags.used = 0;
              cache_entries--;
              if (!--n)
                break;
	    }
	}

      /* Now put into the cache.  */
      log_assert (unused);
      r = unused;
      r->flags.used = 1;
      r->recno = recno;
      memcpy (r->data, data, TRUST_RECORD_LEN);
      r->flags.dirty = 1;
      cache_is_dirty = 1;
      cache_entries++;
      return 0;
    }

  /* No clean entries: We have to flush some dirty entries.  */
#if 0 /* Transactions are not yet used.  */
  if (in_transaction)
    {
      /* But we can't do this while in a transaction.  Thus we
       * increase the cache size instead.  */
      if (cache_entries < MAX_CACHE_ENTRIES_HARD)
        {
          if (opt.debug && !(cache_entries % 100))
            log_debug ("increasing tdbio cache size\n");
          r = xmalloc (sizeof *r);
          r->flags.used = 1;
          r->recno = recno;
          memcpy (r->data, data, TRUST_RECORD_LEN);
          r->flags.dirty = 1;
          r->next = cache_list;
          cache_list = r;
          cache_is_dirty = 1;
          cache_entries++;
          return 0;
	}
      /* Hard limit for the cache size reached.  */
      log_info (_("trustdb transaction too large\n"));
      return GPG_ERR_RESOURCE_LIMIT;
    }
#endif

  if (dirty_count)
    {
      int n;

      /* Discard some dirty entries. */
      n = dirty_count / 5;
      if (!n)
        n = 1;

      take_write_lock ();
      for (unused = NULL, r = cache_list; r; r = r->next)
        {
          if (r->flags.used && r->flags.dirty)
            {
              int rc;

              rc = write_cache_item (r);
              if (rc)
                return rc;
              if (!unused)
                unused = r;
              r->flags.used = 0;
              cache_entries--;
              if (!--n)
                break;
	    }
	}
      release_write_lock ();

      /* Now put into the cache.  */
      log_assert (unused);
      r = unused;
      r->flags.used = 1;
      r->recno = recno;
      memcpy (r->data, data, TRUST_RECORD_LEN);
      r->flags.dirty = 1;
      cache_is_dirty = 1;
      cache_entries++;
      return 0;
    }

  /* We should never reach this.  */
  BUG();
}


/* Return true if the cache is dirty.  */
int
tdbio_is_dirty()
{
  return cache_is_dirty;
}


/*
 * Flush the cache.  This cannot be used while in a transaction.
 */
int
tdbio_sync()
{
    CACHE_CTRL r;
    int did_lock = 0;

    if( db_fd == -1 )
	open_db();
#if 0 /* Transactions are not yet used.  */
    if( in_transaction )
	log_bug("tdbio: syncing while in transaction\n");
#endif

    if( !cache_is_dirty )
	return 0;

    if (!take_write_lock ())
        did_lock = 1;

    for( r = cache_list; r; r = r->next ) {
	if( r->flags.used && r->flags.dirty ) {
	    int rc = write_cache_item( r );
	    if( rc )
		return rc;
	}
    }
    cache_is_dirty = 0;
    if (did_lock)
        release_write_lock ();

    return 0;
}


#if 0  /* Not yet used.  */
/*
 * Simple transactions system:
 * Everything between begin_transaction and end/cancel_transaction
 * is not immediately written but at the time of end_transaction.
 *
 * NOTE: The transaction code is disabled in the 1.2 branch, as it is
 * not yet used.
 */
int
tdbio_begin_transaction ()  /* Not yet used.  */
{
  int rc;

  if (in_transaction)
    log_bug ("tdbio: nested transactions\n");
  /* Flush everything out. */
  rc = tdbio_sync();
  if (rc)
    return rc;
  in_transaction = 1;
  return 0;
}

int
tdbio_end_transaction ()  /* Not yet used.  */
{
  int rc;

  if (!in_transaction)
    log_bug ("tdbio: no active transaction\n");
  take_write_lock ();
  gnupg_block_all_signals ();
  in_transaction = 0;
  rc = tdbio_sync();
  gnupg_unblock_all_signals();
  release_write_lock ();
  return rc;
}

int
tdbio_cancel_transaction () /* Not yet used.  */
{
  CACHE_CTRL r;

  if (!in_transaction)
    log_bug ("tdbio: no active transaction\n");

  /* Remove all dirty marked entries, so that the original ones are
   * read back the next time.  */
  if (cache_is_dirty)
    {
      for (r = cache_list; r; r = r->next)
        {
          if (r->flags.used && r->flags.dirty)
            {
              r->flags.used = 0;
              cache_entries--;
	    }
	}
      cache_is_dirty = 0;
    }

  in_transaction = 0;
  return 0;
}
#endif  /* Not yet used.  */



/********************************************************
 **************** cached I/O functions ******************
 ********************************************************/

/* The cleanup handler for this module.  */
static void
cleanup (void)
{
  if (is_locked)
    {
      if (!dotlock_release (lockhandle))
        is_locked = 0;
    }
}


/*
 * Update an existing trustdb record.  The caller must call
 * tdbio_sync.
 *
 * Returns: 0 on success or an error code.
 */
int
tdbio_update_version_record (ctrl_t ctrl)
{
  TRUSTREC rec;
  int rc;
  int opt_tm;

  /* Never store a TOFU trust model in the trustdb.  Use PGP instead.  */
  opt_tm = opt.trust_model;
  if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP)
    opt_tm = TM_PGP;

  memset (&rec, 0, sizeof rec);

  rc = tdbio_read_record (0, &rec, RECTYPE_VER);
  if (!rc)
    {
      rec.r.ver.created     = make_timestamp();
      rec.r.ver.marginals   = opt.marginals_needed;
      rec.r.ver.completes   = opt.completes_needed;
      rec.r.ver.cert_depth  = opt.max_cert_depth;
      rec.r.ver.trust_model = opt_tm;
      rec.r.ver.min_cert_level = opt.min_cert_level;
      rc = tdbio_write_record (ctrl, &rec);
    }

  return rc;
}


/*
 * Create and write the trustdb version record.
 * This is called with the writelock activ.
 * Returns: 0 on success or an error code.
 */
static int
create_version_record (ctrl_t ctrl)
{
  TRUSTREC rec;
  int rc;
  int opt_tm;

  /* Never store a TOFU trust model in the trustdb.  Use PGP instead.  */
  opt_tm = opt.trust_model;
  if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP)
    opt_tm = TM_PGP;

  memset (&rec, 0, sizeof rec);
  rec.r.ver.version     = 3;
  rec.r.ver.created     = make_timestamp ();
  rec.r.ver.marginals   = opt.marginals_needed;
  rec.r.ver.completes   = opt.completes_needed;
  rec.r.ver.cert_depth  = opt.max_cert_depth;
  if (opt_tm == TM_PGP || opt_tm == TM_CLASSIC)
    rec.r.ver.trust_model = opt_tm;
  else
    rec.r.ver.trust_model = TM_PGP;
  rec.r.ver.min_cert_level = opt.min_cert_level;
  rec.rectype = RECTYPE_VER;
  rec.recnum = 0;
  rc = tdbio_write_record (ctrl, &rec);

  if (!rc)
    tdbio_sync ();

  if (!rc)
    create_hashtable (ctrl, &rec, 0);

  return rc;
}


/*
 * Set the file name for the trustdb to NEW_DBNAME and if CREATE is
 * true create that file.  If NEW_DBNAME is NULL a default name is
 * used, if the it does not contain a path component separator ('/')
 * the global GnuPG home directory is used.
 *
 * Returns: 0 on success or an error code.
 *
 * On the first call this function registers an atexit handler.
 *
 */
int
tdbio_set_dbname (ctrl_t ctrl, const char *new_dbname,
                  int create, int *r_nofile)
{
  char *fname, *p;
  struct stat statbuf;
  static int initialized = 0;
  int save_slash;

  if (!initialized)
    {
      atexit (cleanup);
      initialized = 1;
    }

  *r_nofile = 0;

  if (!new_dbname)
    {
      fname = make_filename (gnupg_homedir (),
                             "trustdb" EXTSEP_S GPGEXT_GPG, NULL);
    }
  else if (*new_dbname != DIRSEP_C )
    {
      if (strchr (new_dbname, DIRSEP_C))
        fname = make_filename (new_dbname, NULL);
      else
        fname = make_filename (gnupg_homedir (), new_dbname, NULL);
    }
  else
    {
      fname = xstrdup (new_dbname);
    }

  xfree (db_name);
  db_name = fname;

  /* Quick check for (likely) case where there already is a
   * trustdb.gpg.  This check is not required in theory, but it helps
   * in practice avoiding costly operations of preparing and taking
   * the lock.  */
  if (!gnupg_stat (fname, &statbuf) && statbuf.st_size > 0)
    {
      /* OK, we have the valid trustdb.gpg already.  */
      return 0;
    }
  else if (!create)
    {
      *r_nofile = 1;
      return 0;
    }

  /* Here comes: No valid trustdb.gpg AND CREATE==1 */

  /*
   * Make sure the directory exists.  This should be done before
   * acquiring the lock, which assumes the existence of the directory.
   */
  p = strrchr (fname, DIRSEP_C);
#if HAVE_W32_SYSTEM
  {
    /* Windows may either have a slash or a backslash.  Take
       care of it.  */
    char *pp = strrchr (fname, '/');
    if (!p || pp > p)
      p = pp;
  }
#endif /*HAVE_W32_SYSTEM*/
  log_assert (p);
  save_slash = *p;
  *p = 0;
  if (gnupg_access (fname, F_OK))
    {
      try_make_homedir (fname);
      if (gnupg_access (fname, F_OK))
        log_fatal (_("%s: directory does not exist!\n"), fname);
    }
  *p = save_slash;

  take_write_lock ();

  if (gnupg_access (fname, R_OK)
      || gnupg_stat (fname, &statbuf)
      || statbuf.st_size == 0)
    {
      estream_t fp;
      TRUSTREC rec;
      int rc;
      mode_t oldmask;

#ifdef HAVE_W32CE_SYSTEM
      /* We know how the cegcc implementation of access works ;-). */
      if (GetLastError () == ERROR_FILE_NOT_FOUND)
        gpg_err_set_errno (ENOENT);
      else
        gpg_err_set_errno (EIO);
#endif /*HAVE_W32CE_SYSTEM*/
      if (errno && errno != ENOENT)
        log_fatal ( _("can't access '%s': %s\n"), fname, strerror (errno));

      oldmask = umask (077);
      if (is_secured_filename (fname))
        {
          fp = NULL;
          gpg_err_set_errno (EPERM);
        }
      else
        fp = es_fopen (fname, "wb");
      umask(oldmask);
      if (!fp)
        log_fatal (_("can't create '%s': %s\n"), fname, strerror (errno));
      es_fclose (fp);

      db_fd = gnupg_open (db_name, O_RDWR | MY_O_BINARY, 0);
      if (db_fd == -1)
        log_fatal (_("can't open '%s': %s\n"), db_name, strerror (errno));

      rc = create_version_record (ctrl);
      if (rc)
        log_fatal (_("%s: failed to create version record: %s"),
                   fname, gpg_strerror (rc));

      /* Read again to check that we are okay. */
      if (tdbio_read_record (0, &rec, RECTYPE_VER))
        log_fatal (_("%s: invalid trustdb created\n"), db_name);

      if (!opt.quiet)
        log_info (_("%s: trustdb created\n"), db_name);
    }

  release_write_lock ();
  return 0;
}


/*
 * Return the full name of the trustdb.
 */
const char *
tdbio_get_dbname ()
{
  return db_name;
}


/*
 * Open the trustdb.  This may only be called if it has not yet been
 * opened and after a successful call to tdbio_set_dbname.  On return
 * the trustdb handle (DB_FD) is guaranteed to be open.
 */
static void
open_db ()
{
  TRUSTREC rec;

  log_assert( db_fd == -1 );

#ifdef HAVE_W32CE_SYSTEM
  {
    DWORD prevrc = 0;
    wchar_t *wname = utf8_to_wchar (db_name);
    if (wname)
      {
        db_fd = (int)CreateFile (wname, GENERIC_READ|GENERIC_WRITE,
                                 FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,
                                 OPEN_EXISTING, 0, NULL);
        xfree (wname);
      }
    if (db_fd == -1)
      log_fatal ("can't open '%s': %d, %d\n", db_name,
                 (int)prevrc, (int)GetLastError ());
  }
#else /*!HAVE_W32CE_SYSTEM*/
  db_fd = gnupg_open (db_name, O_RDWR | MY_O_BINARY, 0);
  if (db_fd == -1 && (errno == EACCES
#ifdef EROFS
                      || errno == EROFS
#endif
                      )
      ) {
      /* Take care of read-only trustdbs.  */
      db_fd = gnupg_open (db_name, O_RDONLY | MY_O_BINARY, 0);
      if (db_fd != -1 && !opt.quiet)
          log_info (_("Note: trustdb not writable\n"));
  }
  if ( db_fd == -1 )
    log_fatal( _("can't open '%s': %s\n"), db_name, strerror(errno) );
#endif /*!HAVE_W32CE_SYSTEM*/
  register_secured_file (db_name);

  /* Read the version record. */
  if (tdbio_read_record (0, &rec, RECTYPE_VER ) )
    log_fatal( _("%s: invalid trustdb\n"), db_name );
}


/*
 * Append a new empty hashtable to the trustdb.  TYPE gives the type
 * of the hash table.  The only defined type is 0 for a trust hash.
 * On return the hashtable has been created, written, the version
 * record update, and the data flushed to the disk.  On a fatal error
 * the function terminates the process.
 */
static void
create_hashtable (ctrl_t ctrl, TRUSTREC *vr, int type)
{
  TRUSTREC rec;
  off_t offset;
  ulong recnum;
  int i, n, rc;

  offset = lseek (db_fd, 0, SEEK_END);
  if (offset == -1)
    log_fatal ("trustdb: lseek to end failed: %s\n", strerror(errno));
  recnum = offset / TRUST_RECORD_LEN;
  log_assert (recnum); /* This is will never be the first record. */

  if (!type)
    vr->r.ver.trusthashtbl = recnum;

  /* Now write the records making up the hash table. */
  n = (256+ITEMS_PER_HTBL_RECORD-1) / ITEMS_PER_HTBL_RECORD;
  for (i=0; i < n; i++, recnum++)
    {
      memset (&rec, 0, sizeof rec);
      rec.rectype = RECTYPE_HTBL;
      rec.recnum = recnum;
      rc = tdbio_write_record (ctrl, &rec);
      if (rc)
        log_fatal (_("%s: failed to create hashtable: %s\n"),
                   db_name, gpg_strerror (rc));
    }
  /* Update the version record and flush. */
  rc = tdbio_write_record (ctrl, vr);
  if (!rc)
    rc = tdbio_sync ();
  if (rc)
    log_fatal (_("%s: error updating version record: %s\n"),
               db_name, gpg_strerror (rc));
}


/*
 * Check whether open trustdb matches the global trust options given
 * for this process.  On a read problem the process is terminated.
 *
 * Return: 1 for yes, 0 for no.
 */
int
tdbio_db_matches_options()
{
  static int yes_no = -1;

  if (yes_no == -1)
    {
      TRUSTREC vr;
      int rc;
      int opt_tm, tm;

      rc = tdbio_read_record (0, &vr, RECTYPE_VER);
      if( rc )
	log_fatal( _("%s: error reading version record: %s\n"),
		   db_name, gpg_strerror (rc) );

      /* Consider tofu and pgp the same.  */
      tm = vr.r.ver.trust_model;
      if (tm == TM_TOFU || tm == TM_TOFU_PGP)
        tm = TM_PGP;
      opt_tm  = opt.trust_model;
      if (opt_tm == TM_TOFU || opt_tm == TM_TOFU_PGP)
        opt_tm = TM_PGP;

      yes_no = vr.r.ver.marginals == opt.marginals_needed
	&& vr.r.ver.completes == opt.completes_needed
	&& vr.r.ver.cert_depth == opt.max_cert_depth
	&& tm == opt_tm
	&& vr.r.ver.min_cert_level == opt.min_cert_level;
    }

  return yes_no;
}


/*
 * Read and return the trust model identifier from the trustdb.  On a
 * read problem the process is terminated.
 */
byte
tdbio_read_model (void)
{
  TRUSTREC vr;
  int rc;

  rc = tdbio_read_record (0, &vr, RECTYPE_VER );
  if (rc)
    log_fatal (_("%s: error reading version record: %s\n"),
	       db_name, gpg_strerror (rc) );
  return vr.r.ver.trust_model;
}


/*
 * Read and return the nextstamp value from the trustdb.  On a read
 * problem the process is terminated.
 */
ulong
tdbio_read_nextcheck ()
{
  TRUSTREC vr;
  int rc;

  rc = tdbio_read_record (0, &vr, RECTYPE_VER);
  if (rc)
    log_fatal (_("%s: error reading version record: %s\n"),
               db_name, gpg_strerror (rc));
  return vr.r.ver.nextcheck;
}


/*
 * Write the STAMP nextstamp timestamp to the trustdb.  On a read or
 * write problem the process is terminated.
 *
 * Return: True if the stamp actually changed.
 */
int
tdbio_write_nextcheck (ctrl_t ctrl, ulong stamp)
{
  TRUSTREC vr;
  int rc;

  rc = tdbio_read_record (0, &vr, RECTYPE_VER);
  if (rc)
    log_fatal (_("%s: error reading version record: %s\n"),
               db_name, gpg_strerror (rc));

  if (vr.r.ver.nextcheck == stamp)
    return 0;

  vr.r.ver.nextcheck = stamp;
  rc = tdbio_write_record (ctrl, &vr);
  if (rc)
    log_fatal (_("%s: error writing version record: %s\n"),
               db_name, gpg_strerror (rc));
  return 1;
}



/*
 * Return the record number of the trusthash table or create one if it
 * does not yet exist.  On a read or write problem the process is
 * terminated.
 *
 * Return: record number
 */
static ulong
get_trusthashrec (ctrl_t ctrl)
{
  static ulong trusthashtbl; /* Record number of the trust hashtable.  */

  (void)ctrl;

  if (!trusthashtbl)
    {
      TRUSTREC vr;
      int rc;

      rc = tdbio_read_record (0, &vr, RECTYPE_VER );
      if (rc)
        log_fatal (_("%s: error reading version record: %s\n"),
                   db_name, gpg_strerror (rc) );

      if (!vr.r.ver.trusthashtbl)
        {
          /* Oops: the trustdb is corrupt because the hashtable is
           * always created along with the version record.  However,
           * if something went initially wrong it may happen that
           * there is just the version record.  We try to fix it here.
           * If we can't do that we return 0 - this is the version
           * record and thus the actual read will detect the mismatch
           * and bail out.  Note that create_hashtable updates VR.  */
          take_write_lock ();
          if (lseek (db_fd, 0, SEEK_END) == TRUST_RECORD_LEN)
            create_hashtable (ctrl, &vr, 0);
          release_write_lock ();
        }
      trusthashtbl = vr.r.ver.trusthashtbl;
    }

  return trusthashtbl;
}



/*
 * Update a hashtable in the trustdb.  TABLE gives the start of the
 * table, KEY and KEYLEN are the key, NEWRECNUM is the record number
 * to insert into the table.
 *
 * Return: 0 on success or an error code.
 */
static int
upd_hashtable (ctrl_t ctrl, ulong table, byte *key, int keylen, ulong newrecnum)
{
  TRUSTREC lastrec, rec;
  ulong hashrec, item;
  int msb;
  int level = 0;
  int rc, i;

  hashrec = table;
 next_level:
  msb = key[level];
  hashrec += msb / ITEMS_PER_HTBL_RECORD;
  rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL);
  if (rc)
    {
      log_error ("upd_hashtable: read failed: %s\n", gpg_strerror (rc));
      return rc;
    }

  item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD];
  if (!item)  /* Insert a new item into the hash table.  */
    {
      rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = newrecnum;
      rc = tdbio_write_record (ctrl, &rec);
      if (rc)
        {
          log_error ("upd_hashtable: write htbl failed: %s\n",
                     gpg_strerror (rc));
          return rc;
	}
    }
  else if (item != newrecnum) /* Must do an update.  */
    {
      lastrec = rec;
      rc = tdbio_read_record (item, &rec, 0);
      if (rc)
        {
          log_error ("upd_hashtable: read item failed: %s\n",
                     gpg_strerror (rc));
          return rc;
	}

      if (rec.rectype == RECTYPE_HTBL)
        {
          hashrec = item;
          level++;
          if (level >= keylen)
            {
              log_error ("hashtable has invalid indirections.\n");
              return GPG_ERR_TRUSTDB;
	    }
          goto next_level;
	}
      else if (rec.rectype == RECTYPE_HLST) /* Extend the list.  */
        {
          /* Check whether the key is already in this list. */
          for (;;)
            {
              for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
                {
                  if (rec.r.hlst.rnum[i] == newrecnum)
                    {
                      return 0; /* Okay, already in the list.  */
		    }
		}
              if (rec.r.hlst.next)
                {
                  rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST);
                  if (rc)
                    {
                      log_error ("upd_hashtable: read hlst failed: %s\n",
                                 gpg_strerror (rc) );
                      return rc;
		    }
		}
              else
                break; /* key is not in the list */
	    }

          /* Find the next free entry and put it in.  */
          for (;;)
            {
              for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
                {
                  if (!rec.r.hlst.rnum[i])
                    {
                      /* Empty slot found.  */
                      rec.r.hlst.rnum[i] = newrecnum;
                      rc = tdbio_write_record (ctrl, &rec);
                      if (rc)
                        log_error ("upd_hashtable: write hlst failed: %s\n",
                                   gpg_strerror (rc));
                      return rc; /* Done.  */
		    }
		}

              if (rec.r.hlst.next)
                {
                  /* read the next reord of the list.  */
                  rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST);
                  if (rc)
                    {
                      log_error ("upd_hashtable: read hlst failed: %s\n",
                                 gpg_strerror (rc));
                      return rc;
		    }
		}
              else
                {
                  /* Append a new record to the list.  */
                  rec.r.hlst.next = item = tdbio_new_recnum (ctrl);
                  rc = tdbio_write_record (ctrl, &rec);
                  if (rc)
                    {
                      log_error ("upd_hashtable: write hlst failed: %s\n",
                                 gpg_strerror (rc));
                      return rc;
		    }
                  memset (&rec, 0, sizeof rec);
                  rec.rectype = RECTYPE_HLST;
                  rec.recnum = item;
                  rec.r.hlst.rnum[0] = newrecnum;
                  rc = tdbio_write_record (ctrl, &rec);
                  if (rc)
                    log_error ("upd_hashtable: write ext hlst failed: %s\n",
                               gpg_strerror (rc));
                  return rc; /* Done.  */
		}
	    } /* end loop over list slots */

	}
      else if (rec.rectype == RECTYPE_TRUST) /* Insert a list record.  */
        {
          if (rec.recnum == newrecnum)
            {
              return 0;
            }
          item = rec.recnum; /* Save number of key record.  */
          memset (&rec, 0, sizeof rec);
          rec.rectype = RECTYPE_HLST;
          rec.recnum = tdbio_new_recnum (ctrl);
          rec.r.hlst.rnum[0] = item;	    /* Old key record */
          rec.r.hlst.rnum[1] = newrecnum; /* and new key record */
          rc = tdbio_write_record (ctrl, &rec);
          if (rc)
            {
              log_error( "upd_hashtable: write new hlst failed: %s\n",
                           gpg_strerror (rc) );
              return rc;
            }
          /* Update the hashtable record.  */
          lastrec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = rec.recnum;
          rc = tdbio_write_record (ctrl, &lastrec);
          if (rc)
            log_error ("upd_hashtable: update htbl failed: %s\n",
                       gpg_strerror (rc));
          return rc; /* Ready.  */
        }
      else
        {
          log_error ("hashtbl %lu: %lu/%d points to an invalid record %lu\n",
                     table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item);
          if (opt.verbose > 1)
            list_trustdb (ctrl, es_stderr, NULL);
          return GPG_ERR_TRUSTDB;
	}
    }

  return 0;
}


/*
 * Drop an entry from a hashtable.  TABLE gives the start of the
 * table, KEY and KEYLEN are the key.
 *
 * Return: 0 on success or an error code.
 */
static int
drop_from_hashtable (ctrl_t ctrl, ulong table,
                     byte *key, int keylen, ulong recnum)
{
  TRUSTREC rec;
  ulong hashrec, item;
  int msb;
  int level = 0;
  int rc, i;

  hashrec = table;
 next_level:
  msb = key[level];
  hashrec += msb / ITEMS_PER_HTBL_RECORD;
  rc = tdbio_read_record (hashrec, &rec, RECTYPE_HTBL );
  if (rc)
    {
      log_error ("drop_from_hashtable: read failed: %s\n", gpg_strerror (rc));
      return rc;
    }

  item = rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD];
  if (!item)
    return 0;   /* Not found - forget about it.  */

  if (item == recnum) /* Table points direct to the record.  */
    {
      rec.r.htbl.item[msb % ITEMS_PER_HTBL_RECORD] = 0;
      rc = tdbio_write_record (ctrl, &rec);
      if (rc)
        log_error ("drop_from_hashtable: write htbl failed: %s\n",
                   gpg_strerror (rc));
      return rc;
    }

  rc = tdbio_read_record (item, &rec, 0);
  if (rc)
    {
      log_error ("drop_from_hashtable: read item failed: %s\n",
                 gpg_strerror (rc));
      return rc;
    }

  if (rec.rectype == RECTYPE_HTBL)
    {
      hashrec = item;
      level++;
      if (level >= keylen)
        {
          log_error ("hashtable has invalid indirections.\n");
          return GPG_ERR_TRUSTDB;
	}
      goto next_level;
    }

  if (rec.rectype == RECTYPE_HLST)
    {
      for (;;)
        {
          for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
            {
              if (rec.r.hlst.rnum[i] == recnum)
                {
                  rec.r.hlst.rnum[i] = 0; /* Mark as free.  */
                  rc = tdbio_write_record (ctrl, &rec);
                  if (rc)
                    log_error("drop_from_hashtable: write htbl failed: %s\n",
                              gpg_strerror (rc));
                  return rc;
		}
	    }
          if (rec.r.hlst.next)
            {
              rc = tdbio_read_record (rec.r.hlst.next, &rec, RECTYPE_HLST);
              if (rc)
                {
                  log_error ("drop_from_hashtable: read hlst failed: %s\n",
                             gpg_strerror (rc));
                  return rc;
		}
	    }
          else
            return 0; /* Key not in table.  */
	}
    }

  log_error ("hashtbl %lu: %lu/%d points to wrong record %lu\n",
             table, hashrec, (msb % ITEMS_PER_HTBL_RECORD), item);
  return GPG_ERR_TRUSTDB;
}



/*
 * Lookup a record via the hashtable TABLE by (KEY,KEYLEN) and return
 * the result in REC.  The return value of CMP() should be True if the
 * record is the desired one.
 *
 * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code.
 */
static gpg_error_t
lookup_hashtable (ulong table, const byte *key, size_t keylen,
		  int (*cmpfnc)(const void*, const TRUSTREC *),
                  const void *cmpdata, TRUSTREC *rec )
{
  int rc;
  ulong hashrec, item;
  int msb;
  int level = 0;

  if (!table)
    {
      rc = gpg_error (GPG_ERR_INV_RECORD);
      log_error("lookup_hashtable failed: %s\n", "request for record 0");
      return rc;
    }

  hashrec = table;
 next_level:
  msb = key[level];
  hashrec += msb / ITEMS_PER_HTBL_RECORD;
  rc = tdbio_read_record (hashrec, rec, RECTYPE_HTBL);
  if (rc)
    {
      log_error("lookup_hashtable failed: %s\n", gpg_strerror (rc) );
      return rc;
    }

  item = rec->r.htbl.item[msb % ITEMS_PER_HTBL_RECORD];
  if (!item)
    return gpg_error (GPG_ERR_NOT_FOUND);

  rc = tdbio_read_record (item, rec, 0);
  if (rc)
    {
      log_error( "hashtable read failed: %s\n", gpg_strerror (rc) );
      return rc;
    }
  if (rec->rectype == RECTYPE_HTBL)
    {
      hashrec = item;
      level++;
      if (level >= keylen)
        {
          log_error ("hashtable has invalid indirections\n");
          return GPG_ERR_TRUSTDB;
	}
      goto next_level;
    }
  else if (rec->rectype == RECTYPE_HLST)
    {
      for (;;)
        {
          int i;

          for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
            {
              if (rec->r.hlst.rnum[i])
                {
                  TRUSTREC tmp;

                  rc = tdbio_read_record (rec->r.hlst.rnum[i], &tmp, 0);
                  if (rc)
                    {
                      log_error ("lookup_hashtable: read item failed: %s\n",
                                 gpg_strerror (rc));
                      return rc;
		    }
                  if ((*cmpfnc)(cmpdata, &tmp))
                    {
                      *rec = tmp;
                      return 0;
		    }
		}
	    }
          if (rec->r.hlst.next)
            {
              rc = tdbio_read_record (rec->r.hlst.next, rec, RECTYPE_HLST);
              if (rc)
                {
                  log_error ("lookup_hashtable: read hlst failed: %s\n",
                             gpg_strerror (rc) );
                  return rc;
		}
	    }
          else
            return gpg_error (GPG_ERR_NOT_FOUND);
	}
    }

  if ((*cmpfnc)(cmpdata, rec))
    return 0; /* really found */

  return gpg_error (GPG_ERR_NOT_FOUND); /* no: not found */
}


/*
 * Update the trust hash table TR or create the table if it does not
 * exist.
 *
 * Return: 0 on success or an error code.
 */
static int
update_trusthashtbl (ctrl_t ctrl, TRUSTREC *tr)
{
  return upd_hashtable (ctrl, get_trusthashrec (ctrl),
                        tr->r.trust.fingerprint, 20, tr->recnum);
}


/*
 * Dump the trustdb record REC to stream FP.
 */
void
tdbio_dump_record (TRUSTREC *rec, estream_t fp)
{
  int i;
  ulong rnum = rec->recnum;

  es_fprintf (fp, "rec %5lu, ", rnum);

  switch (rec->rectype)
    {
    case 0:
      es_fprintf (fp, "blank\n");
      break;

    case RECTYPE_VER:
      es_fprintf (fp,
         "version, td=%lu, f=%lu, m/c/d=%d/%d/%d tm=%d mcl=%d nc=%lu (%s)\n",
                  rec->r.ver.trusthashtbl,
                  rec->r.ver.firstfree,
                  rec->r.ver.marginals,
                  rec->r.ver.completes,
                  rec->r.ver.cert_depth,
                  rec->r.ver.trust_model,
                  rec->r.ver.min_cert_level,
                  rec->r.ver.nextcheck,
                  strtimestamp(rec->r.ver.nextcheck)
                  );
      break;

    case RECTYPE_FREE:
      es_fprintf (fp, "free, next=%lu\n", rec->r.free.next);
      break;

    case RECTYPE_HTBL:
      es_fprintf (fp, "htbl,");
      for (i=0; i < ITEMS_PER_HTBL_RECORD; i++)
        es_fprintf (fp, " %lu", rec->r.htbl.item[i]);
      es_putc ('\n', fp);
      break;

    case RECTYPE_HLST:
      es_fprintf (fp, "hlst, next=%lu,", rec->r.hlst.next);
      for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
        es_fprintf (fp, " %lu", rec->r.hlst.rnum[i]);
      es_putc ('\n', fp);
      break;

    case RECTYPE_TRUST:
      es_fprintf (fp, "trust ");
      for (i=0; i < 20; i++)
        es_fprintf (fp, "%02X", rec->r.trust.fingerprint[i]);
      es_fprintf (fp, ", ot=%d, d=%d, vl=%lu, mo=%d, f=%02x\n",
                  rec->r.trust.ownertrust,
                  rec->r.trust.depth, rec->r.trust.validlist,
                  rec->r.trust.min_ownertrust, rec->r.trust.flags);
      break;

    case RECTYPE_VALID:
      es_fprintf (fp, "valid ");
      for (i=0; i < 20; i++)
        es_fprintf(fp, "%02X", rec->r.valid.namehash[i]);
      es_fprintf (fp, ", v=%d, next=%lu, f=%d, m=%d\n",
                  rec->r.valid.validity, rec->r.valid.next,
                  rec->r.valid.full_count, rec->r.valid.marginal_count);
      break;

    default:
      es_fprintf (fp, "unknown type %d\n", rec->rectype );
      break;
    }
}


/*
 * Read the record with number RECNUM into the structure REC.  If
 * EXPECTED is not 0 reading any other record type will return an
 * error.
 *
 * Return: 0 on success or an error code.
 */
int
tdbio_read_record (ulong recnum, TRUSTREC *rec, int expected)
{
  byte readbuf[TRUST_RECORD_LEN];
  const byte *buf, *p;
  gpg_error_t err = 0;
  int n, i;

  if (db_fd == -1)
    open_db ();

  buf = get_record_from_cache( recnum );
  if (!buf)
    {
      if (lseek (db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1)
        {
          err = gpg_error_from_syserror ();
          log_error (_("trustdb: lseek failed: %s\n"), strerror (errno));
          return err;
	}
      n = read (db_fd, readbuf, TRUST_RECORD_LEN);
      if (!n)
        {
          return gpg_error (GPG_ERR_EOF);
	}
      else if (n != TRUST_RECORD_LEN)
        {
          err = gpg_error_from_syserror ();
          log_error (_("trustdb: read failed (n=%d): %s\n"),
                     n, strerror(errno));
          return err;
	}
      buf = readbuf;
    }
  rec->recnum = recnum;
  rec->dirty = 0;
  p = buf;
  rec->rectype = *p++;
  if (expected && rec->rectype != expected)
    {
      log_error ("%lu: read expected rec type %d, got %d\n",
                 recnum, expected, rec->rectype);
      return gpg_error (GPG_ERR_TRUSTDB);
    }
  p++;    /* Skip reserved byte.  */
  switch (rec->rectype)
    {
    case 0:  /* unused (free) record */
      break;

    case RECTYPE_VER: /* version record */
      if (memcmp(buf+1, GPGEXT_GPG, 3))
        {
          log_error (_("%s: not a trustdb file\n"), db_name );
          err = gpg_error (GPG_ERR_TRUSTDB);
        }
      else
        {
          p += 2; /* skip "gpg" */
          rec->r.ver.version  = *p++;
          rec->r.ver.marginals = *p++;
          rec->r.ver.completes = *p++;
          rec->r.ver.cert_depth = *p++;
          rec->r.ver.trust_model = *p++;
          rec->r.ver.min_cert_level = *p++;
          p += 2;
          rec->r.ver.created  = buf32_to_ulong(p);
          p += 4;
          rec->r.ver.nextcheck = buf32_to_ulong(p);
          p += 4;
          p += 4;
          p += 4;
          rec->r.ver.firstfree = buf32_to_ulong(p);
          p += 4;
          p += 4;
          rec->r.ver.trusthashtbl = buf32_to_ulong(p);
          if (recnum)
            {
              log_error( _("%s: version record with recnum %lu\n"), db_name,
                         (ulong)recnum );
              err = gpg_error (GPG_ERR_TRUSTDB);
            }
          else if (rec->r.ver.version != 3)
            {
              log_error( _("%s: invalid file version %d\n"), db_name,
                         rec->r.ver.version );
              err = gpg_error (GPG_ERR_TRUSTDB);
            }
        }
      break;

    case RECTYPE_FREE:
      rec->r.free.next  = buf32_to_ulong(p);
      break;

    case RECTYPE_HTBL:
      for (i=0; i < ITEMS_PER_HTBL_RECORD; i++)
        {
          rec->r.htbl.item[i] = buf32_to_ulong(p);
          p += 4;
	}
      break;

    case RECTYPE_HLST:
      rec->r.hlst.next = buf32_to_ulong(p);
      p += 4;
      for (i=0; i < ITEMS_PER_HLST_RECORD; i++)
        {
          rec->r.hlst.rnum[i] = buf32_to_ulong(p);
          p += 4;
	}
      break;

    case RECTYPE_TRUST:
      memcpy (rec->r.trust.fingerprint, p, 20);
      p+=20;
      rec->r.trust.ownertrust = *p++;
      rec->r.trust.depth = *p++;
      rec->r.trust.min_ownertrust = *p++;
      rec->r.trust.flags = *p++;
      rec->r.trust.validlist = buf32_to_ulong(p);
      break;

    case RECTYPE_VALID:
      memcpy (rec->r.valid.namehash, p, 20);
      p+=20;
      rec->r.valid.validity = *p++;
      rec->r.valid.next = buf32_to_ulong(p);
      p += 4;
      rec->r.valid.full_count = *p++;
      rec->r.valid.marginal_count = *p++;
      break;

    default:
      log_error ("%s: invalid record type %d at recnum %lu\n",
                 db_name, rec->rectype, (ulong)recnum);
      err = gpg_error (GPG_ERR_TRUSTDB);
      break;
    }

  return err;
}


/*
 * Write the record from the struct REC.
 *
 * Return: 0 on success or an error code.
 */
int
tdbio_write_record (ctrl_t ctrl, TRUSTREC *rec)
{
  byte buf[TRUST_RECORD_LEN];
  byte *p;
  int rc = 0;
  int i;
  ulong recnum = rec->recnum;

  if (db_fd == -1)
    open_db ();

  memset (buf, 0, TRUST_RECORD_LEN);
  p = buf;
  *p++ = rec->rectype; p++;

  switch (rec->rectype)
    {
    case 0:  /* unused record */
      break;

    case RECTYPE_VER: /* version record */
      if (recnum)
        BUG ();
      memcpy(p-1, GPGEXT_GPG, 3 ); p += 2;
      *p++ = rec->r.ver.version;
      *p++ = rec->r.ver.marginals;
      *p++ = rec->r.ver.completes;
      *p++ = rec->r.ver.cert_depth;
      *p++ = rec->r.ver.trust_model;
      *p++ = rec->r.ver.min_cert_level;
      p += 2;
      ulongtobuf(p, rec->r.ver.created); p += 4;
      ulongtobuf(p, rec->r.ver.nextcheck); p += 4;
      p += 4;
      p += 4;
      ulongtobuf(p, rec->r.ver.firstfree ); p += 4;
      p += 4;
      ulongtobuf(p, rec->r.ver.trusthashtbl ); p += 4;
      break;

    case RECTYPE_FREE:
      ulongtobuf(p, rec->r.free.next); p += 4;
      break;

    case RECTYPE_HTBL:
      for (i=0; i < ITEMS_PER_HTBL_RECORD; i++)
        {
          ulongtobuf( p, rec->r.htbl.item[i]); p += 4;
        }
      break;

    case RECTYPE_HLST:
      ulongtobuf( p, rec->r.hlst.next); p += 4;
      for (i=0; i < ITEMS_PER_HLST_RECORD; i++ )
        {
          ulongtobuf( p, rec->r.hlst.rnum[i]); p += 4;
	}
      break;

    case RECTYPE_TRUST:
      memcpy (p, rec->r.trust.fingerprint, 20); p += 20;
      *p++ = rec->r.trust.ownertrust;
      *p++ = rec->r.trust.depth;
      *p++ = rec->r.trust.min_ownertrust;
      *p++ = rec->r.trust.flags;
      ulongtobuf( p, rec->r.trust.validlist); p += 4;
      break;

    case RECTYPE_VALID:
      memcpy (p, rec->r.valid.namehash, 20); p += 20;
      *p++ = rec->r.valid.validity;
      ulongtobuf( p, rec->r.valid.next); p += 4;
      *p++ = rec->r.valid.full_count;
      *p++ = rec->r.valid.marginal_count;
      break;

    default:
      BUG();
    }

  rc = put_record_into_cache (recnum, buf);
  if (rc)
    ;
  else if (rec->rectype == RECTYPE_TRUST)
    rc = update_trusthashtbl (ctrl, rec);

  return rc;
}


/*
 * Delete the record at record number RECNUm from the trustdb.
 *
 * Return: 0 on success or an error code.
 */
int
tdbio_delete_record (ctrl_t ctrl, ulong recnum)
{
  TRUSTREC vr, rec;
  int rc;

  /* Must read the record fist, so we can drop it from the hash tables */
  rc = tdbio_read_record (recnum, &rec, 0);
  if (rc)
    ;
  else if (rec.rectype == RECTYPE_TRUST)
    {
      rc = drop_from_hashtable (ctrl, get_trusthashrec (ctrl),
                                rec.r.trust.fingerprint, 20, rec.recnum);
    }

  if (rc)
    return rc;

  /* Now we can change it to a free record.  */
  rc = tdbio_read_record (0, &vr, RECTYPE_VER);
  if (rc)
    log_fatal (_("%s: error reading version record: %s\n"),
               db_name, gpg_strerror (rc));

  rec.recnum = recnum;
  rec.rectype = RECTYPE_FREE;
  rec.r.free.next = vr.r.ver.firstfree;
  vr.r.ver.firstfree = recnum;
  rc = tdbio_write_record (ctrl, &rec);
  if (!rc)
    rc = tdbio_write_record (ctrl, &vr);

  return rc;
}


/*
 * Create a new record and return its record number.
 */
ulong
tdbio_new_recnum (ctrl_t ctrl)
{
  off_t offset;
  ulong recnum;
  TRUSTREC vr, rec;
  int rc;

  /* Look for unused records.  */
  rc = tdbio_read_record (0, &vr, RECTYPE_VER);
  if (rc)
    log_fatal( _("%s: error reading version record: %s\n"),
               db_name, gpg_strerror (rc));
  if (vr.r.ver.firstfree)
    {
      recnum = vr.r.ver.firstfree;
      rc = tdbio_read_record (recnum, &rec, RECTYPE_FREE);
      if (rc)
        log_fatal (_("%s: error reading free record: %s\n"),
                   db_name, gpg_strerror (rc));
      /* Update dir record.  */
      vr.r.ver.firstfree = rec.r.free.next;
      rc = tdbio_write_record (ctrl, &vr);
      if (rc)
        log_fatal (_("%s: error writing dir record: %s\n"),
                   db_name, gpg_strerror (rc));
      /* Zero out the new record.  */
      memset (&rec, 0, sizeof rec);
      rec.rectype = 0; /* Mark as unused record (actually already done
                          my the memset).  */
      rec.recnum = recnum;
      rc = tdbio_write_record (ctrl, &rec);
      if (rc)
        log_fatal (_("%s: failed to zero a record: %s\n"),
                   db_name, gpg_strerror (rc));
    }
  else /* Not found - append a new record.  */
    {
      offset = lseek (db_fd, 0, SEEK_END);
      if (offset == (off_t)(-1))
        log_fatal ("trustdb: lseek to end failed: %s\n", strerror (errno));
      recnum = offset / TRUST_RECORD_LEN;
      log_assert (recnum); /* This will never be the first record */
      /* We must write a record, so that the next call to this
       * function returns another recnum.  */
      memset (&rec, 0, sizeof rec);
      rec.rectype = 0; /* unused record */
      rec.recnum = recnum;
      rc = 0;
      if (lseek( db_fd, recnum * TRUST_RECORD_LEN, SEEK_SET) == -1)
        {
          rc = gpg_error_from_syserror ();
          log_error (_("trustdb rec %lu: lseek failed: %s\n"),
                     recnum, strerror (errno));
	}
      else
        {
          int n;

          n = write (db_fd, &rec, TRUST_RECORD_LEN);
          if (n != TRUST_RECORD_LEN)
            {
              rc = gpg_error_from_syserror ();
              log_error (_("trustdb rec %lu: write failed (n=%d): %s\n"),
                         recnum, n, gpg_strerror (rc));
	    }
	}

      if (rc)
        log_fatal (_("%s: failed to append a record: %s\n"),
                   db_name, gpg_strerror (rc));
    }

  return recnum ;
}



/* Helper function for tdbio_search_trust_byfpr.  */
static int
cmp_trec_fpr ( const void *fpr, const TRUSTREC *rec )
{
  return (rec->rectype == RECTYPE_TRUST
          && !memcmp (rec->r.trust.fingerprint, fpr, 20));
}


/*
 * Given a 20 byte FINGERPRINT search its trust record and return
 * that at REC.
 *
 * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code.
 */
gpg_error_t
tdbio_search_trust_byfpr (ctrl_t ctrl, const byte *fingerprint, TRUSTREC *rec)
{
  int rc;

  /* Locate the trust record using the hash table */
  rc = lookup_hashtable (get_trusthashrec (ctrl), fingerprint, 20,
                         cmp_trec_fpr, fingerprint, rec );
  return rc;
}


/*
 * Given a primary public key object PK search its trust record and
 * return that at REC.
 *
 * Return: 0 if found, GPG_ERR_NOT_FOUND, or another error code.
 */
gpg_error_t
tdbio_search_trust_bypk (ctrl_t ctrl, PKT_public_key *pk, TRUSTREC *rec)
{
  byte fingerprint[MAX_FINGERPRINT_LEN];
  size_t fingerlen;

  fingerprint_from_pk( pk, fingerprint, &fingerlen );
  for (; fingerlen < 20; fingerlen++)
    fingerprint[fingerlen] = 0;
  return tdbio_search_trust_byfpr (ctrl, fingerprint, rec);
}


/*
 * Terminate the process with a message about a corrupted trustdb.
 */
void
tdbio_invalid (void)
{
  log_error (_("Error: The trustdb is corrupted.\n"));
  how_to_fix_the_trustdb ();
  g10_exit (2);
}
