#include "fs.h"
#include "fsrep.h"

fs::fs(blockdbc *xdb)
{
  db = xdb;

  
  // try to get some random bits so we can create
  // unique file handles.
  struct timeval tv;
  gettimeofday(&tv, 0);
  seq = ((long long) tv.tv_usec << 30) ^ tv.tv_sec;
  int fd = open("/dev/urandom", 0);
  if(fd != -1){
    read(fd, &seq, sizeof(seq));
    close(fd);
  }
}


// utility to turn a file handle into a str with the same bits.
str
fs::fh2s(nfs_fh3 fh)
{
  return str(fh.data.base(), fh.data.size());
}

// utility to turn a str into a file handle with the same bits.
nfs_fh3
fs::s2fh(str s)
{
  nfs_fh3 fh;
  fh.data.setsize(s.len());
  bcopy(s.cstr(), fh.data.base(), s.len());
  return fh;
}

void
fs::dispatch(nfscall *nc)
{
  switch(nc->proc()){
  case NFSPROC3_NULL:
    nc->reply(nc->getvoidres());
    break;
  case NFSPROC3_GETATTR:
    nfs3_getattr(nc);
    break;
  case NFSPROC3_SETATTR:
    nfs3_setattr(nc);
    break;
  case NFSPROC3_LOOKUP:
    nfs3_lookup(nc);
    break;
  case NFSPROC3_ACCESS:
    nfs3_access(nc);
    break;
  case NFSPROC3_READ:
    nfs3_read(nc);
    break;
  case NFSPROC3_WRITE:
    nfs3_write(nc);
    break;
  case NFSPROC3_CREATE:
    nfs3_create(nc);
    break;
  case NFSPROC3_MKDIR:
    nfs3_mkdir(nc);
    break;
  case NFSPROC3_REMOVE:
    nfs3_remove(nc);
    break;
  case NFSPROC3_READDIR:
    nfs3_readdir(nc);
    break;
  case NFSPROC3_FSSTAT:
    nfs3_fsstat(nc);
    break;
  case NFSPROC3_FSINFO:
    nfs3_fsinfo(nc);
    break;
  default:
    fprintf(stderr, "fs:dispatch unknown proc %d\n", nc->proc());
    nc->reject(SYSTEM_ERR);
  }
}

str
fs::fh2hex(nfs_fh3 fh)
{
  char buf[64];
  unsigned int len = fh.data.size();
  char *p = fh.data.base();
  unsigned int i;

  buf[0] = '\0';
  for(i = 0; i < len && i*2+1 < sizeof(buf); i++){
    sprintf(buf + (i * 2), "%02x", p[i] & 0xff);
  }
  
  return str(buf);
}

nfs_fh3
fs::hex2fh(str s)
{
  nfs_fh3 fh;
  fh.data.setsize(s.len() / 2);
  char *p = fh.data.base();
  unsigned int i;

  for(i = 0; i*2 < s.len(); i++){
    int x;
    sscanf(s.cstr() + (i*2), "%2x", &x);
    p[i] = x;
  }
  
  return fh;
}

void
fs::get_fh_cb(nfs_fh3 fh, callback<void, bool, fattr3>::ref cb,
              bool ok, str value)
{
  if(ok && value.len() == sizeof(fattr3)){
    fattr3 *fap = (fattr3*) value.cstr();
    cb(true, *fap);
  } else {
    fattr3 dummy;
    cb(false, dummy);
  }
}

// given a file handle, fetch the corresponding
// inode (really an fattr3) out of the DB.
void
fs::get_fh(nfs_fh3 fh, callback<void, bool, fattr3>::ref cb)
{
  db->get(str(fh.data.base(), fh.data.size()),
          wrap(this, &fs::get_fh_cb, fh, cb));
}



// write a brand-new fh to the db.
void
fs::put_new_fh(nfs_fh3 fh, fattr3 fa, callback<void, bool, fattr3>::ref cb)
{
  put_fh(fh, fa, cb);
}

void
fs::put_fh_cb(callback<void, bool, fattr3>::ref cb, fattr3 fa, bool ok)
{
  cb(ok, fa);
}

// write an "inode", which is just an fattr3.
// give the updated attributes to callback since we changed
// the ctime, which is needed for wcc &c.
void
fs::put_fh(nfs_fh3 fh, fattr3 fa, callback<void, bool, fattr3>::ref cb)
{
  fa.ctime = nfstime();
  db->put(fh2s(fh),
          str((char *) &fa, sizeof(fa)),
          wrap(this, &fs::put_fh_cb, cb, fa));
}


// ask the block DB to delete an i-node.
void
fs::remove_fh(nfs_fh3 fh, callback<void, bool>::ref cb)
{
  db->remove(str(fh.data.base(), fh.data.size()), cb);
}


// get_fh() has finished fetching a file's attributes
// from the block server.
void
fs::nfs3_getattr_cb(nfscall *nc, bool ok, fattr3 fa)
{
  nfs_fh3 *fh = nc->Xtmpl getarg<nfs_fh3> ();

  if(ok){
    // ask the RPC system for a place to put the RPC reply
    getattr3res *res = nc->Xtmpl getres<getattr3res> ();

    res->set_status(NFS3_OK);
    *(res->attributes) = fa;

    // send the RPC reply
    nc->reply(nc->getvoidres());
  } else {
    fprintf(stderr, "getattr: stale file handle\n");

    // send an RPC error reply
    nc->error(NFS3ERR_STALE);
  }
}

// implementation of the NFS GETATTR RPC
// called by fs::dispatch()
// fetch the attributes from the block server using the
// file handle as key
void
fs::nfs3_getattr(nfscall *nc)
{
  // ask the RPC system to parse the RPC arguments (a file handle)
  nfs_fh3 *fh = nc->Xtmpl getarg<nfs_fh3> ();

  // fetch the attributes
  get_fh(*fh, wrap(this, &fs::nfs3_getattr_cb, nc));
}


void
fs::nfs3_setattr(nfscall *nc)
{
  setattr3args *a = nc->Xtmpl getarg<setattr3args> ();

  // you'll want to get rid of this error reply, it's
  // only here to prevent clients hanging when they
  // talk to the skeleton server.
  nc->error(NFS3ERR_IO);
}

void
fs::nfs3_access_cb(nfscall *nc, bool ok, fattr3 fa)
{
  access3args *aa = nc->Xtmpl getarg<access3args> ();
  if(ok){
    access3res *res = nc->Xtmpl getres<access3res> ();
    res->set_status(NFS3_OK);
    res->resok->access = aa->access; // XXX
    res->resok->obj_attributes.set_present(true);
    *res->resok->obj_attributes.attributes = fa;
    nc->reply(nc->getvoidres());
  } else {
    fprintf(stderr, "access: stale file handle\n");
    nc->error(NFS3ERR_STALE);
  }
}

void
fs::nfs3_access(nfscall *nc)
{
  access3args *aa = nc->Xtmpl getarg<access3args> ();
  get_fh(aa->object, wrap(this, &fs::nfs3_access_cb, nc));
}


// handle a LOOKUP RPC.
void
fs::nfs3_lookup(nfscall *nc)
{
  // Get a pointer to the un-marshalled arguments.
  // The diropargs3 structure is defined in nfs3_prot.x
  // nfs_fh3 a->dir : directory file handle
  // str a->name : the file name the client wants to find
  diropargs3 *a = nc->Xtmpl getarg<diropargs3> ();

  // once you've figured out the right file handle fh to
  // put in the reply, here's how to format and send
  // the reply. you'll probably have to put this code
  // in a different function.
  //
  // lookup3res *res = nc->Xtmpl getres<lookup3res> ();
  // res->set_status(NFS3_OK);
  // res->resok->object = fh;
  // res->resok->obj_attributes.set_present(false);
  // res->resok->dir_attributes.set_present(false);
  // nc->reply(nc->getvoidres());

  // you'll want to get rid of this error reply, it's
  // only here to prevent clients hanging when they
  // talk to the skeleton server.
  nc->error(NFS3ERR_IO);
}


// use the first 32 bits of the file handle as the file ID.
// it's not clear what the client really depends on about
// the file ID, I think it's just for use in stat().
// but file IDs should not be zero, they should be unique,
// and they should stay the same for the same file.
uint64
fs::fh2fileid(nfs_fh3 fh)
{
  uint64 id;
  bcopy(fh.data.base(), &id, sizeof(id));
  return id;
}


wcc_data
fs::make_wcc(fattr3 before, fattr3 after)
{
  wcc_data d;

#if 0
  // because there's no locking, we cannot guarantee that
  // there were no changes between the before and after
  // attributes. so don't send any. XXX
  d.before.set_present(false);
  d.after.set_present(false);
#else
  d.before.set_present(true);
  d.before.attributes->size = before.size;
  d.before.attributes->mtime = before.mtime;
  d.before.attributes->ctime = before.ctime;
  d.after.set_present(true);
  (*d.after.attributes) = after;
#endif

  return d;
}

// return the current time in NFS format
nfstime3
fs::nfstime()
{
  struct timeval tv;
  gettimeofday(&tv, (struct timezone *) 0);
  nfstime3 t;
  t.seconds = tv.tv_sec;
  t.nseconds = tv.tv_usec * 1000;
  return t;
}


void
fs::nfs3_write(nfscall *nc)
{
  write3args *a = nc->Xtmpl getarg<write3args> ();

  // you'll want to get rid of this error reply, it's
  // only here to prevent clients hanging when they
  // talk to the skeleton server.
  nc->error(NFS3ERR_IO);
}


void
fs::nfs3_read(nfscall *nc)
{
  read3args *a = nc->Xtmpl getarg<read3args> ();

  // you'll want to get rid of this error reply, it's
  // only here to prevent clients hanging when they
  // talk to the skeleton server.
  nc->error(NFS3ERR_IO);
}


void
fs::nfs3_create(nfscall *nc)
{
  create3args *a = nc->Xtmpl getarg<create3args> ();

  // create3args has these fields:
  // nfs_fh3 a->where.dir
  // str a->where.name
  // don't worry about a->how
  
  // don't try to implement EXCLUSIVE, which is supposed to
  // be atomic w.r.t. server crashes.
  if(a->how.mode == EXCLUSIVE){
    nc->error(NFS3ERR_NOTSUPP);
    return;
  }

  // once you've created the new file (or discovered that the
  // file already exists), and you have put the file's file
  // handle in fh, here's how to format and send the reply.
  // you'll probably have to move this code to a new function.
  //
  // diropres3 *res = nc->Xtmpl getres<diropres3> ();
  // res->set_status(NFS3_OK);
  // res->resok->obj.set_present(true);
  // *res->resok->obj.handle = fh;
  // res->resok->obj_attributes.set_present(false);
  // res->resok->dir_wcc = make_wcc(dfa_before, dfa_after);
  // nc->reply(nc->getvoidres());
  //
  // dfa_before and dfa_after are fattr3 structures describing
  // the directory's attributes before and after the create.
  // this is part of the NFS v3 cache consistency machinery.
  
  // you'll want to get rid of this error reply, it's
  // only here to prevent clients hanging when they
  // talk to the skeleton server.
  nc->error(NFS3ERR_IO);
}


void
fs::nfs3_mkdir(nfscall *nc)
{
  mkdir3args *a = nc->Xtmpl getarg<mkdir3args> ();

  // you'll want to get rid of this error reply, it's
  // only here to prevent clients hanging when they
  // talk to the skeleton server.
  nc->error(NFS3ERR_IO);
}


void
fs::nfs3_remove(nfscall *nc)
{
  diropargs3 *a = nc->Xtmpl getarg<diropargs3> ();

  // you'll want to get rid of this error reply, it's
  // only here to prevent clients hanging when they
  // talk to the skeleton server.
  nc->error(NFS3ERR_IO);
}


void
fs::nfs3_readdir(nfscall *nc)
{
  readdir3args *a = nc->Xtmpl getarg<readdir3args> ();
  
  // arguments:
  // nfs_fh3 a->dir
  // int a->cookie
  // the cookie indicates where the previous READDIR left off,
  // and where this READDIR should start. that is, it tells
  // you how many directory entries to skip.
  // the cookie is just a copy of whatever you put in the reply.

  // once you've read the directory entry you want to return
  // from the block server, and have the information in
  // name and ffh (the file's file handle), here's how to
  // format and send the RPC reply. this code sends back
  // on directory entry at a time.
  //
  // readdir3res *res = nc->Xtmpl getres<readdir3res> ();
  // res->set_status(NFS3_OK);
  // res->resok->reply.eof = false;
  // res->resok->dir_attributes.set_present(false);
  // rpc_ptr<entry3> *e = &res->resok->reply.entries;
  // (*e).alloc();
  // (*e)->name = name;
  // (*e)->fileid = fh2fileid(ffh);
  // (*e)->cookie = index + 1;
  // nc->reply(nc->getvoidres());

  // if there are no more entries to return:
  // readdir3res *res = nc->Xtmpl getres<readdir3res> ();
  // res->set_status(NFS3_OK);
  // res->resok->dir_attributes.set_present(false);
  // res->resok->reply.eof = true;
  // nc->reply(nc->getvoidres());

  // you'll want to get rid of this error reply, it's
  // only here to prevent clients hanging when they
  // talk to the skeleton server.
  nc->error(NFS3ERR_IO);
}

void
fs::nfs3_fsstat(nfscall *nc)
{
  fsstat3res *res = nc->Xtmpl getres<fsstat3res> ();
  res->set_status(NFS3_OK);
  res->resok->tbytes = 0;
  res->resok->fbytes = 0;
  res->resok->abytes = 0;
  res->resok->tfiles = 0;
  res->resok->ffiles = 0;
  res->resok->afiles = 0;
  res->resok->invarsec = 0;
  nc->reply(nc->getvoidres());
}

void
fs::nfs3_fsinfo(nfscall *nc)
{
  //  nfs_fh3 *fh = nc->Xtmpl getarg<nfs_fh3> ();
  fsinfo3res *res = nc->Xtmpl getres<fsinfo3res> ();
  res->set_status(NFS3_OK);
  res->resok->obj_attributes.set_present(false);
  res->resok->rtmax = 8192;  // max read size
  res->resok->rtpref = 8192; // preferred read size
  res->resok->rtmult = 512;  // reads should be multiple of this (?)
  res->resok->wtmax = 8192;
  res->resok->wtpref = 8192;
  res->resok->wtmult = 8192;
  res->resok->dtpref = 512;  // preferred readdir size
  res->resok->maxfilesize = 0x7fffffff;
  res->resok->time_delta.seconds = 0;
  res->resok->time_delta.nseconds = 1;
  res->resok->properties = FSF3_HOMOGENEOUS;
  // res->resok->properties = (FSF3_LINK | FSF3_SYMLINK | FSF3_HOMOGENEOUS);
  nc->reply(nc->getvoidres());
}

// allocate a new file handle with the next available number
void
fs::new_fh(nfs_fh3 *fh)
{
  fh->data.setsize(sizeof(seq));
  bcopy(&seq, fh->data.base(), sizeof(seq));
  seq++;
}



void
fs::new_root_cb(nfs_fh3 fh, callback<void, bool, nfs_fh3>::ref cb, bool ok,
                fattr3 fa)
{
  if(ok){
    cb(true, fh);
  } else {
    fprintf(stderr, "fs::new_root failed\n");
    cb(false, fh);
  }
}

// If root file handle (fhname) specified, just use it,
// since we've been started to serve an existing file system.
// Otherwise create a new root i-node for a new file system,
// insert it into the db, and call cb w/ root file handle.
void
fs::new_root(char *fhname, callback<void, bool, nfs_fh3>::ref cb)
{
  if(fhname){
    nfs_fh3 fh = hex2fh(fhname);
    cb(true, fh);
  } else {
    nfs_fh3 fh;
    new_fh(&fh);

    // an i-node just contains the NFS attributes
    fattr3 fa;
    bzero(&fa, sizeof(fa));
    fa.type = NF3DIR;
    fa.mode = 0777; // rwxrwxrwx
    fa.nlink = 2;   // why 2? maybe . and .. both point here?
    fa.fileid = fh2fileid(fh);
    fa.atime = fa.mtime = nfstime();

    // write the i-node to the block server.
    put_new_fh(fh, fa, wrap(this, &fs::new_root_cb, fh, cb));
  }
}
