/* $Id: sfsrodb.C,v 1.29 2002/01/02 18:55:08 fubob Exp $ */

/*
 * Copyright (C) 1999 Kevin Fu (fubob@mit.edu)
 * Copyright (C) 1999 Frans Kaashoek (kaashoek@mit.edu)
 *
 * 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.,4 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

/*
 * This program will generate the integrity database for the SFS read-only
 * file system.  Run this program after every change to exported files 
 */


/* The hash tree works very similar to that of the indirect data pointers
   in an inode. */

#include "sysconf.h"

#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include "sfsrodb.h"
#include "parseopt.h"

dbfe *sfsrodb;
dbfe *sfsrofhdb;

bool incremental_mode;
bool verbose_mode;
bool error_check;
u_int32_t blocksize;
extern int errno;
char IV[SFSRO_IVSIZE];
int relpathlen;
extern ptr < rabin_priv > sfsrokey;
str hostname;

/* Statistics */
u_int32_t reginode_cnt = 0;
u_int32_t lnkinode_cnt = 0;
u_int32_t filedatablk_cnt = 0;
u_int32_t indir_cnt = 0;
u_int32_t directory_cnt = 0;
u_int32_t fhdb_cnt = 0;
u_int32_t fh_cnt = 0;

u_int32_t identical_block = 0;
u_int32_t identical_indir = 0;
u_int32_t identical_dir = 0;
u_int32_t identical_inode = 0;
u_int32_t identical_sym = 0;
u_int32_t identical_fhdb = 0;
u_int32_t identical_fh = 0;

time_t sfsro_duration = 31536000; /* default to 365 days */


/* True if only can LOOKUP, not READDIR 
   Really should make more fine grained.
   Allow specification of which directories to 
   make opaque.
*/
bool opaque_directory = false; 


/* Given: A filled stat structure and allocated inode
   Return: A SFSRO inode which reflects all of the stat values.
   The data pointers are initialized with .setsize(0).
   The array of direct pointers is initialize with no members.
   However, the caller will eventually have to set the
   following values: 

   .size, .used, and direct/indirect data pointers.   
 */
void
sfsrodb_setinode (const struct stat *st, sfsro_inode *inode)
{

  /*
     SFSRO has implied read-access by all.  We only care
     whether the file is a non-directory executable.  Everything
     else is synthesised by sfsrocd.

   */

  ftypero t;
  if (S_ISREG (st->st_mode)) {
    t = ((st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) ?
		 SFSROREG_EXEC : SFSROREG);
  }
  else if (S_ISDIR (st->st_mode))
    t = opaque_directory ? SFSRODIR_OPAQ : SFSRODIR;
  else if (S_ISLNK (st->st_mode))
    t = SFSROLNK;
  else {
    warn << "Non-supported file type " << st->st_mode << "\n";
    exit (1);
  }

  inode->set_type (t);

  if (inode->type == SFSROLNK) {
    rpc_clear (*inode->lnk);

    inode->lnk->nlink = st->st_nlink; // XXX bogus! cannot rely on this number

#ifdef SFS_HAVE_STAT_ST_ATIMESPEC
    inode->lnk->mtime.seconds = st->st_mtimespec.tv_sec;
    inode->lnk->mtime.nseconds = st->st_mtimespec.tv_nsec;
    inode->lnk->ctime.seconds = st->st_ctimespec.tv_sec;
    inode->lnk->ctime.nseconds = st->st_ctimespec.tv_nsec;
#else
    inode->lnk->mtime.seconds = st->st_mtime;
    inode->lnk->mtime.nseconds = 0;
    inode->lnk->ctime.seconds = st->st_ctime;
    inode->lnk->ctime.nseconds = 0;
#endif /* SFS_HAVE_ST_ATIMESPEC */

  } else {
    rpc_clear (*inode->reg);

    inode->reg->nlink = st->st_nlink;  // XXX bogus! cannot rely on this number
    inode->reg->size = 0;
    inode->reg->used = 0;
    
#ifdef SFS_HAVE_STAT_ST_ATIMESPEC
    inode->reg->mtime.seconds = st->st_mtimespec.tv_sec;
    inode->reg->mtime.nseconds = st->st_mtimespec.tv_nsec;
    inode->reg->ctime.seconds = st->st_ctimespec.tv_sec;
    inode->reg->ctime.nseconds = st->st_ctimespec.tv_nsec;
#else
    inode->reg->mtime.seconds = st->st_mtime;
    inode->reg->mtime.nseconds = 0;
    inode->reg->ctime.seconds = st->st_ctime;
    inode->reg->ctime.nseconds = 0;
#endif /* SFS_HAVE_ST_ATIMESPEC */

    inode->reg->direct.setsize (SFSRO_NDIR);

  }

  // strbuf sb;
  // rpc_print (sb, inode, 5, NULL, NULL);
  // warn << "setinode " << sb << "\n";

}


/* Given: A fully specified inode
   Effects: Stores the inode in the database with the fh as the key
   Return: A file handle in fh.  fh must already be allocated.
   This function will set the IV appropriately with prandom bits.
 */
void
store_inode (sfsro_inode *inode, sfs_hash *fh)
{
  sfsro_data dat (SFSRO_INODE);
  size_t calllen = 0;
  char *callbuf = NULL;
  xdrsuio x (XDR_ENCODE);

  *dat.inode = *inode;
  if (xdr_sfsro_data (x.xdrp (), &dat)) {
    calllen = x.uio ()->resid ();
    callbuf = suio_flatten (x.uio ());
  }

  create_sfsrofh (IV, SFSRO_IVSIZE, fh, callbuf, calllen);

  // Store the inode of this path in the database
  if (!sfsrodb_put (sfsrodb, fh->base (), fh->size (), callbuf, calllen)) {
    //    warn << "Found identical inode, compressing.\n";
    identical_inode++;
    identical_fh++;
  } else {
    fh_cnt++;
    if (inode->type == SFSROLNK)
      lnkinode_cnt++;
    else
      reginode_cnt++;
  }

  xfree (callbuf);
}


bool
store_file_block (sfs_hash *fh, const char *block, size_t size)
{
  sfsro_data res (SFSRO_FILEBLK);
  res.data->setsize (size);
  memcpy (res.data->base (), block, size);

  size_t calllen = 0;
  char *callbuf = NULL;
  xdrsuio x (XDR_ENCODE);

  if (xdr_sfsro_data (x.xdrp (), &res)) {
    calllen = x.uio ()->resid ();
    callbuf = suio_flatten (x.uio ());
  }

  create_sfsrofh (IV, SFSRO_IVSIZE, fh, callbuf, calllen);

  if (!sfsrodb_put (sfsrodb, fh->base (), fh->size (), callbuf, calllen)) {
    //    warnx << "Found identical block, compressing\n"; 
    identical_block++;
    identical_fh++;

    xfree (callbuf);
    return false;
  }

  filedatablk_cnt++;
  fh_cnt++;
      
  xfree (callbuf);
  return true;
}

inline bool
process_sindirect (int &fd, bool &wrote_stuff, sfsro_inode *inode,
		   char *block, sfs_hash &block_fh, sfs_hash *fh) {

  size_t size = 0;

  //  warnx << "Adding sindirect pointers\n"; 
  uint32 blocknum = 0;
  sfsro_data sindir (SFSRO_INDIR);
  sindir.indir->handles.setsize (SFSRO_NFH);
  
  while (blocknum < SFSRO_NFH) {
    
    size = read (fd, block, SFSRO_BLKSIZE);

    if (size <= 0)
      break;

    inode->reg->size += size;

    /* Check for identical blocks */
    if (store_file_block (&block_fh, block, size)) {
      inode->reg->used += size;
      
      //	warnx << "Added direct, size " << size << ", blocknum " 
      //    << blocknum << "\n";
    }
      
    sindir.indir->handles[blocknum] = block_fh;
    blocknum++;      
  }
    
  if (size < 0) {
    warnx << "store_file: Read failed in sindirect pointers\n";
    exit (1);
  }

  if (blocknum != 0) {
    size_t calllen = 0;
    char *callbuf = NULL;
    xdrsuio x (XDR_ENCODE);
    if (xdr_sfsro_data (x.xdrp(), &sindir)) {
      calllen = x.uio ()->resid ();
      callbuf = suio_flatten (x.uio ());
    }
    
    create_sfsrofh (IV, SFSRO_IVSIZE, fh, 
		    callbuf, calllen);

    if (!sfsrodb_put (sfsrodb, fh->base(), fh->size(),
		      callbuf, calllen)) {
      // warn << "Found identical sindirect, compressing.\n";
      identical_indir++;
      identical_fh++;
    } else {
      indir_cnt++;
      fh_cnt++;
    }
    xfree (callbuf);
    wrote_stuff = true;
  } else {
    wrote_stuff = false;
  }
    
  return (size == 0);
}


inline bool
process_dindirect (int &fd, bool &wrote_stuff, sfsro_inode *inode, 
		   char *block, sfs_hash &block_fh, sfs_hash *fh) {
  
  bool done = false;

  //  warnx << "Adding dindirect pointers\n"; 
  uint32 blocknum = 0;
  sfsro_data dindir (SFSRO_INDIR);
  dindir.indir->handles.setsize (SFSRO_NFH);
  // XXX we could be smarter here by only allocating
  // the number of file handles we actually need in the dindirect.

  while (!done && blocknum < SFSRO_NFH) {

    done = process_sindirect (fd, wrote_stuff, inode, block, block_fh, 
			      &dindir.indir->handles[blocknum]);

    if (!wrote_stuff)
      break;
	
    //    warnx << "Added ddirect, blocknum " 
    //  << blocknum << "\n";
    
    blocknum++;      
  }
  
  if (blocknum != 0) {
    size_t calllen = 0;
    char *callbuf = NULL;
    xdrsuio x (XDR_ENCODE);
    if (xdr_sfsro_data (x.xdrp(), &dindir)) {
      calllen = x.uio ()->resid ();
      callbuf = suio_flatten (x.uio ());
    }
    
    create_sfsrofh (IV, SFSRO_IVSIZE, fh, 
		    callbuf, calllen);

    if (!sfsrodb_put (sfsrodb, fh->base(), fh->size(),
		      callbuf, calllen)) {
      // warn << "Found identical dindirect, compressing.\n";
      identical_indir++;
      identical_fh++;
    } else {
      indir_cnt++;
      fh_cnt++;
    }
    
    xfree (callbuf);
    wrote_stuff = true;
  } else {
    wrote_stuff = false;
  }
  
  return done;
}

inline bool
process_tindirect (int &fd, bool &wrote_stuff, sfsro_inode *inode, 
		   char *block, sfs_hash &block_fh, sfs_hash *fh) {
  
  bool done = false;

  //  warnx << "Adding tindirect pointers\n"; 
  uint32 blocknum = 0;
  sfsro_data tindir (SFSRO_INDIR);
  tindir.indir->handles.setsize (SFSRO_NFH);
  // XXX we could be smarter here by only allocating
  // the number of file handles we actually need in the tindirect.
  
  while (!done && blocknum < SFSRO_NFH) {
    
    done = process_dindirect (fd, wrote_stuff, inode, block, block_fh,
			      &tindir.indir->handles[blocknum]);

    if (!wrote_stuff)
      break;
    
    //    warnx << "Added tdirect, blocknum " 
    //	  << blocknum << "\n";
      
    blocknum++;      
  }

  if (blocknum != 0) {
    size_t calllen = 0;
    char *callbuf = NULL;
    xdrsuio x (XDR_ENCODE);
    if (xdr_sfsro_data (x.xdrp(), &tindir)) {
      calllen = x.uio ()->resid ();
      callbuf = suio_flatten (x.uio ());
    }
    
    create_sfsrofh (IV, SFSRO_IVSIZE, fh, 
		    callbuf, calllen);

    if (!sfsrodb_put (sfsrodb, fh->base(),
		      fh->size(),
		      callbuf, calllen)) {
      // warn << "Found identical tindirect, compressing.\n";
      identical_indir++;
      identical_fh++;
    } else {
      indir_cnt++;
      fh_cnt++;
    }
    
    xfree (callbuf);
    wrote_stuff = true;
  } else {
    wrote_stuff = false;
  }

  return done;
}

/* Given: A fully specified inode for a file and pointer to its data
   (but not file sizes or data pointers)
   Effects: Store the file data, fully specify the inode
   
   Return: A file handle in fh.  fh must already be allocated.
   This function will set the IV appropriately with prandom bits.
 */
void
store_file (sfsro_inode *inode, str path)
{
  char block[SFSRO_BLKSIZE];
  sfs_hash block_fh;
  int fd;
  size_t size = 0;
  bool done = false;

  if ((fd = open (path, O_RDONLY)) < 0) {
    warn << "store_file: open failed" << fd << "\n";
    exit (1);
  }


  // Deal with direct pointers
  uint32 blocknum = 0;
  bzero (inode->reg->direct.base (), SFSRO_NDIR * sizeof (sfs_hash));
  
  while (blocknum < SFSRO_NDIR) {      
    
    size = read (fd, block, SFSRO_BLKSIZE);

    if (size <= 0)
      break;

    inode->reg->size += size;

    /* Check for identical blocks */
    if (store_file_block (&block_fh, block, size)) {
      inode->reg->used += size;
      //      warnx << "Added direct, size " << size << ", blocknum " 
      //    << blocknum << "\n";
    }

    inode->reg->direct[blocknum] = block_fh;
    blocknum++;
  }

  if (size < 0) {
    warnx << "store_file: Read failed in direct pointers\n";
    exit (1);
  } else if (size != 0) {
    
    bool wrote_stuff = false;
    
    // Deal with sindirect pointers
    done = process_sindirect (fd, wrote_stuff, inode, 
			      block, block_fh,
			      &inode->reg->indirect);
    
    // Deal with dindirect pointers
    if (!done) {
      done = process_dindirect (fd, wrote_stuff, inode, 
				block, block_fh,
				&inode->reg->double_indirect);
      
      // Deal with tindirect pointers
      if (!done)
	done = process_tindirect (fd, wrote_stuff, inode, 
				  block, block_fh,
				  &inode->reg->triple_indirect);
    }
  }
  
  if (close (fd) < 0) {
    warn << "store_file: close failed\n";
    exit (1);
  }
  
  
}

void
vec2indir (sfs_hash *fh, vec<sfs_hash> &ib_vec)
{
  sfsro_data data (SFSRO_INDIR);
  data.indir->handles.setsize (SFSRO_NFH);
  
  int blocknum = 0;

  while (!ib_vec.empty ()) {
    data.indir->handles[blocknum] = ib_vec.pop_front ();
    blocknum++;
  }
    
  if (blocknum != 0) {
    size_t calllen = 0;
    char *callbuf = NULL;
    xdrsuio x (XDR_ENCODE);
    if (xdr_sfsro_data (x.xdrp(), &data)) {
      calllen = x.uio ()->resid ();
      callbuf = suio_flatten (x.uio ());
    }
    
    create_sfsrofh (IV, SFSRO_IVSIZE, fh, 
		    callbuf, calllen);

    if (!sfsrodb_put (sfsrodb, fh->base(), fh->size(),
		      callbuf, calllen)) {
      // warn << "Found identical indirect, compressing.\n";
      identical_indir++;
      identical_fh++;
    } else {
      indir_cnt++;
      fh_cnt++;
    }
    xfree (callbuf);
  }
    
}

/* Given: a fully specified directory, inode filled in by setinode,
   allocated fh

   Return: file handle, store directory contents, final inode values

   Effects: After filling in a directory structure, fill in an inode
   structure, store the directory in the database, and compute file
   handle.  
 */
bool
store_directory_block (sfsro_inode *inode, xdrsuio &x,
		       vec<sfs_hash> &sib_vec, // single
		       vec<sfs_hash> &dib_vec, // double
		       vec<sfs_hash> &tib_vec, // triple
		       bool eof, uint32 b)
{
  // assert?  xdrsuio x (XDR_ENCODE);

  size_t calllen = 0;
  char *callbuf = NULL;
  sfs_hash fh;

  calllen = x.uio ()->resid ();
  callbuf = suio_flatten (x.uio ());

  create_sfsrofh (IV, SFSRO_IVSIZE, &fh, callbuf, calllen);

  if (!sfsrodb_put (sfsrodb, fh.base (), fh.size (), callbuf, calllen)) {
    //warn << "Found identical directory, compressing.\n";
    identical_dir++;
    identical_fh++;
  } else {
    directory_cnt++;
    fh_cnt++;
  }


  xfree (callbuf);

  // This is bogus, we're just using one data pointer regardless
  // of the size of the directory.  In a correct implementation,
  // we should only store 8KB per data block
  inode->reg->size += calllen;
  inode->reg->used += calllen;

  if (b < SFSRO_NDIR)
    inode->reg->direct[b] = fh;
  else {
    sib_vec.push_back (fh);
  
    size_t i = (b - SFSRO_NDIR);
    
    if (i < SFSRO_NFH) {
      if (eof || sib_vec.size () % SFSRO_NFH == 0)
	vec2indir (&inode->reg->indirect, sib_vec);
    } else {
      i -= SFSRO_NFH;
      
      if (i < SFSRO_NFH * SFSRO_NFH) {

	if (eof || sib_vec.size () % SFSRO_NFH == 0) {
	  vec2indir (&fh, sib_vec);
	  dib_vec.push_back (fh);
	}

	if (eof || dib_vec.size () % SFSRO_NFH == 0) {
	  vec2indir (&inode->reg->double_indirect, dib_vec);
	}

      } else { 
	i -= SFSRO_NFH * SFSRO_NFH;
	
	if (i < SFSRO_NFH * SFSRO_NFH * SFSRO_NFH) {

	  if (eof || sib_vec.size () % SFSRO_NFH == 0) {
	    vec2indir (&fh, sib_vec);
	    dib_vec.push_back (fh);
	  }

	  if (eof || dib_vec.size () % SFSRO_NFH == 0) {
	    vec2indir (&fh, dib_vec);
	    tib_vec.push_back (fh);
	  }

	  if (eof || tib_vec.size () % SFSRO_NFH == 0) {
	    vec2indir (&inode->reg->triple_indirect, tib_vec);
	  }

	} else {
	  assert(0);  // too big
	  // XX should fail gracefully?  hang?
	}
      }
    } 
  }
  
  
  //  strbuf sb;
  //rpc_print (sb, *inode, 5, NULL, " ");
  //warn << "storedirectory " << sb << "\n";
  
  return true;
}


// Note, this ordering is different from that of
// the default "ls" sorting routine, but it is
// internally consistent within SFSRO
static int
compare_name (const void *file1, const void *file2)
{
  return (strcmp (*(static_cast < char *const *>(file1)),
		  *(static_cast < char *const *>(file2))));
}


void
sort_dir (const str path, vec<str> &file_list)
{
  DIR *dirp;
  struct dirent *de = NULL;

  if ((dirp = opendir (path)) == 0) {
    warn << path << " is not a directory\n";
    return;
  }

  while ((de = readdir (dirp))) {
    str filename (de->d_name);

    /* The client manages . and .. */
    if ((filename.cmp (".") == 0) 
	|| (filename.cmp ("..") == 0)) 
      continue;
    
    file_list.push_back (filename);
  }

  /*
  warnx << "Before:\n";

  for (unsigned int i = 0; i < file_list.size (); i++) {
       warnx << file_list[i] << "\n";
  }
  */

  qsort (file_list.base (), file_list.size (),
	 sizeof (str), compare_name);

  /*
  warnx << "After:\n";

  for (unsigned int i = 0; i < file_list.size (); i++) {
    warnx << file_list[i] << "\n";
  }
  */
}


static bool
xdr_putsfsro_dirent (XDR *x, sfs_hash &fh, str filename)
{

  rpc_str<RPC_INFINITY> name = filename;

  return
    // sfsro_dirent * (non-null):
    xdr_putint (x, 1)
    // sfs_hash fh
    && rpc_traverse (x, fh)
    // string name<>
    && rpc_traverse (x, name);
}

/*
   Given: Path to file (any kind), an allocated fh, the inode
   number of the path's parent

   Return: The hash and IV in the fh.

   Effects: Recursively hash everything beneath a directory
   It computes the cryptographic file handle for the
   given path, inserts the mapping from the fh to its data

 */
int
recurse_path (const str path, sfs_hash * fh)
{
  struct stat st;

  if (verbose_mode) {
    warn << "recurse_path (" << path << ", " << "fh)\n";
  }

  if (lstat (path, &st) < 0)
    fatal << path << ": " << strerror (errno) << "\n";
  
  sfsro_inode inode;

  if (S_ISLNK (st.st_mode)) {
    
    char *buf = New char[st.st_size + 1];
    int nchars = readlink (path, buf, st.st_size);

    if (nchars > MAXPATHLEN) {
      warn << "symlink target too large "
	<< path << " " << nchars << "\n";
      return -1;
    }

    //    buf[nchars] = 0;

    //    warn << "recurse_path: Link\n";

    sfsrodb_setinode (&st, &inode);
    inode.lnk->dest = nfspath3 (buf, nchars);

    delete[] buf;


  } else if (S_ISREG (st.st_mode)) {
    //    warn << "recurse_path: Regular file\n";

    sfsrodb_setinode (&st, &inode);

    store_file (&inode, path);

    /*    strbuf sb;
       rpc_print (sb, inode, 5, NULL, " ");
       warn << "inode " << sb << "\n";
     */
  }
  else if (S_ISDIR (st.st_mode)) {
    //    warn << "recurse_path: Directory\n";

    sfsrodb_setinode (&st, &inode);
    vec<sfs_hash> sib_vec;
    vec<sfs_hash> dib_vec;
    vec<sfs_hash> tib_vec;

    uint32 blkcnt = 0;

    /*    strbuf sb;
       rpc_print (sb, inode, 5, NULL, " ");
       warn << "inode " << sb << "\n";
     */

    vec<str> file_list;
    sort_dir (path, file_list);

    
    if (verbose_mode) {
      warnx << "file_list.size=" << file_list.size () << "\n";
      
      for (unsigned int i = 0; i < file_list.size (); i++) 
	warnx << file_list[i] << "\n";
    }

    bool errors = false;

    while (file_list.size () != 0) {

      xdrsuio x (XDR_ENCODE, true);
      
      if (!xdr_putint (&x, SFSRO_DIRBLK))
	errors = true;

      /*      for (str filename = file_list.front ();
	   (file_list.size () != 0) &&
	     (XDR_GETPOS (&x) + 24 + ((filename.len () + 3) & ~3)) 
	     <= SFSRO_BLKSIZE;
	   filename = file_list.front ()) {
      */
      
      while (file_list.size () > 0) {
	
	str filename = file_list.front ();
	
	if (XDR_GETPOS (&x) + 24 + ((filename.len () + 3) & ~3) 
	    > SFSRO_BLKSIZE) { 
	  break;
	}

	file_list.pop_front ();
	sfs_hash fh;

	recurse_path (path << "/" << filename, &fh);

	if (!xdr_putsfsro_dirent (&x, fh, filename))
	  errors=true;
      }
      

      if (!xdr_putint (&x, 0) 	// NULL entry *
	  || !xdr_putint (&x, !file_list.size())) // bool eof
	errors = true;
     
      bool eof = false;
      if (file_list.size() == 0)
	eof = true;

      store_directory_block (&inode, x, sib_vec, dib_vec, tib_vec, 
			     eof, blkcnt);
      blkcnt++;
	
    }
  } else {
    warn << "Not symlink, reg file, or directory " << path << "\n";
    return -1;
  } 
  
  // XXX do something with errors?

  store_inode (&inode, fh);
  
  return 0;
}    





int
sfsrodb_main (const str root, const str keyfile, const char *dbfile)
{
  ref<dbImplInfo> info = dbGetImplInfo();

  for (unsigned int i=0; i < info->supportedOptions.size(); i++) 
    warn << info->supportedOptions[i] << "\n";

  //create the generic object
  sfsrodb = new dbfe();

  //set up the options we want
  dbOptions opts;
  opts.addOption("opt_async", 0);
  opts.addOption("opt_cachesize", 80000);
  opts.addOption("opt_nodesize", 4096);
  opts.addOption("opt_create", 1);

  if (int err = sfsrodb->createdb(const_cast < char *>(dbfile), opts)) {
    warn << "createdb failed\n" << strerror(err) << "\n";
  }
  if (int err = sfsrodb->opendb(const_cast < char *>(dbfile), opts)) {
    warn << "open returned: " << strerror(err) << err << "\n";
    exit (-1);
  }

  /* Set the sfs_connectres structure (with pub key) to db */
  sfs_connectres cres (SFS_OK);
  cres.reply->servinfo.release = SFS_RELEASE;
  cres.reply->servinfo.host.type = SFS_HOSTINFO;
  cres.reply->servinfo.host.hostname = hostname;

  ptr < rabin_priv > sk;

  if (!keyfile) {
    warn << "cannot locate default file sfs_host_key\n";
    fatal ("errors!\n");
  }
  else {
    str key = file2wstr (keyfile);
    if (!key) {
      warn << keyfile << ": " << strerror (errno) << "\n";
      fatal ("errors!\n");
    }
    else if (!(sk = import_rabin_priv (key, NULL))) {
      warn << "could not decode " << keyfile << "\n";
      warn << key << "\n";
      fatal ("errors!\n");
    }
  }

  cres.reply->servinfo.host.pubkey = sk->n;
  cres.reply->servinfo.prog = SFSRO_PROGRAM;
  cres.reply->servinfo.vers = SFSRO_VERSION;
  bzero (&cres.reply->charge, sizeof (sfs_hashcharge));

  // Set IV
  sfs_hash id;
  sfs_mkhostid (&id, cres.reply->servinfo.host);
  memcpy (&IV[0], id.base(), SFSRO_IVSIZE);

  // store file system in db
  sfs_hash root_fh;
  relpathlen = root.len ();
  recurse_path (root, &root_fh);

  sfs_fsinfo res (SFSRO_PROGRAM);
  res.sfsro->set_vers (SFSRO_VERSION);

  memcpy (res.sfsro->v1->info.iv.base (), &IV[0], SFSRO_IVSIZE);
  res.sfsro->v1->info.rootfh = root_fh;

#if 0
  // fhdb is not necessary in this shape; XXX FIX
  create_fhdb (&res.sfsro->v1->info.fhdb, dbfile, IV);
#endif

  time_t start, end;

  res.sfsro->v1->info.type = SFS_ROFSINFO;
  res.sfsro->v1->info.start = start = time (NULL);
  res.sfsro->v1->info.duration = sfsro_duration;

  end = start + sfsro_duration;

  // XX Should make sure timezone is correct
  str stime (ctime (&start));
  str etime (ctime (&end));
  warn << "Database good from: \n " << stime
       << "until:\n " << etime;

  create_sfsrosig (&(res.sfsro->v1->sig), &(res.sfsro->v1->info),
		   keyfile);


  xdrsuio x (XDR_ENCODE);
  if (xdr_sfs_fsinfo (x.xdrp (), &res)) {
    void *v = suio_flatten (x.uio ());
    int l =  x.uio ()->resid ();
    if (!sfsrodb_put (sfsrodb, "fsinfo", 6, v, l)) {
      warn << "Found identical fsinfo.  You found a collision!\n";
      exit (-1);
    }
    warn << "Added fsinfo\n";
  }

  xdrsuio x2 (XDR_ENCODE);
  if (xdr_sfs_connectres (x2.xdrp (), &cres)) {
    int l = x2.uio ()->resid ();
    void *v = suio_flatten (x2.uio ());
    warn << "put conres in db\n";
    if (!sfsrodb_put (sfsrodb, "conres", 6, v, l)) {
      warn << "Found identical conres. You found a collision!\n";
      exit (-1);
    }
  }

  if (verbose_mode) {
    warn << "identical blocks:   " << identical_block << "\n";
    warn << "identical indirs:   " << identical_indir << "\n";
    warn << "identical dirs:     " << identical_dir << "\n";
    warn << "identical inodes:   " << identical_inode << "\n";
    warn << "identical symlinks: " << identical_sym << "\n";
    warn << "identical fhdb:     " << identical_fhdb << "\n\n\n";

    warn << "Database contents:\n";
    warn << "Regular inodes:      " << reginode_cnt << "\n";
    warn << "Symlink inodes:      " << lnkinode_cnt << "\n";
    warn << "Directory blocks     " << directory_cnt << "\n";
    warn << "File data blocks:    " << filedatablk_cnt << "\n";
    warn << "Indir blocks:        " << indir_cnt << "\n";
    warn << "Fhdb blocks:         " << fhdb_cnt << "\n\n\n";

    warn << "identical fh's overall : " << identical_fh << "\n";
    warn << "unique fh's overall    : " << fh_cnt << "\n\n\n";
  }

  warn << "close db\n";

  sfsrodb->closedb ();

  delete sfsrodb;

  // XXX here we should verify for debugging that the number of entries
  // in fhdb is the same as the number of database entries (minus
  // the conres and root)

  return 0;
}


static void
usage ()
{
  warnx << "usage: " << progname
	<< " -d <export directory> -s <SK keyfile> -o <dbfile>\n";
  warnx << "              [-i] [-h <hostname for db>] [-v] [-b <blocksize>]\n";
  warnx << "-d <export directory> : The directory hierarchy to export\n";
  warnx << "-s <SK keyfile>       : Path to the secret key file\n";
  warnx << "-o <dbfile>           : Filename to output database\n";
  warnx << "Optional directives:\n";
  warnx << "-i                    : Incremental mode, update a database\n";
  warnx << "-h <hostname for db>  : Hostname of replication, if not this machine\n";
  warnx << "-p                    : Make all directories opaque\n";
  warnx << "-t <expiration>       : Seconds until signature expires\n";
  warnx << "-v                    : Verbose debugging output\n";
  warnx << "-b <blocksize>        : Page size of underlying database\n";


  //  warnx << "usage: " << progname << " [command] [options]\n\n";
  //warnx << "\tinit directory [-d sdb file] [-followsymlinks] [-maxdepth max] [-key keyfile]\n";
  //  warnx << "\tupdate directory \n";
  exit (1);
}


int
main (int argc, char **argv)
{
  setprogname (argv[0]);

  hostname = myname ();

  char *exp_dir = NULL;
  char *sk_file = NULL;
  char *output_file = NULL;

  incremental_mode = false;
  verbose_mode = false;
  error_check = true;

  int ch;
  while ((ch = getopt (argc, argv, "b:d:s:o:h:t:vip")) != -1)
    switch (ch) {
    case 'b':
      if (!convertint (optarg, &blocksize)
	  || blocksize < 512 || blocksize > 0x10000)
	usage ();
      break;
    case 'd':
      exp_dir = optarg;
      break;
    case 'e':
      error_check = false;
      break;
    case 'h':
      hostname = optarg;
      break;
    case 'i':
      incremental_mode = true;
      break;
    case 'o':
      output_file = optarg;
      break;
    case 'p':
      opaque_directory = true;
      break;
    case 's':
      sk_file = optarg;
      break;
    case 't':
      if (!convertint (optarg, &sfsro_duration))
	usage ();
      break;
    case 'v':
      verbose_mode = true;
      break;
    case '?':
    default:
      usage ();
    }
  argc -= optind;
  argv += optind;

  if ( (argc > 0) || !exp_dir || !sk_file || !output_file )
    usage ();

  const char *p = hostname;
  if (error_check && !sfsgethost (p)) {
    warnx << "The hostname " << hostname << " does not properly resolve.\n"
	  << "The SFS server requires hostnames to resolve via DNS,\n"
	  << "not /etc/hosts.  If you wish to continue, rerun SFSRODB\n"
	  << "with the -e flag.\n";
    usage ();
  }


  if (verbose_mode) {
    warnx << "export directory : " << exp_dir << "\n";
    warnx << "SK keyfile       : " << sk_file << "\n";
    warnx << "dbfile           : " << output_file << "\n";
    warnx << "Incremental mode : ";
    if (incremental_mode) 
      warnx << "On\n";
    else
      warnx << "Off\n";

    warnx << "hostname for db  : " << hostname << "\n";
  }

  return (sfsrodb_main (exp_dir, sk_file, output_file));
}
