#include "nfsprovider.h"

static const bool DEBUG_LOOKUP = false;
static const bool DEBUG_READDIR = false;

// XXX Fix access always returning full access rights

#define WRAP(args...) wrap(this, &nfsProvider::args)

#define NFSECB WRAP(nfsError, nc)

#define LOAD(fh, flags, cb) \
 fsfile::load(fs, fhToTS(fh), fhToNum(fh), flags, cb, NFSECB)

void
nfsProvider::create(ref<FS> fs, callback<void, ref<nfsProvider> >::ref cb,
                    cbe error)
{
  cb(New refcounted<nfsProvider>(fs));
}

nfsProvider::nfsProvider(ref<FS> fs)
  : fs(fs), rootCurrentName("now"), rootInfoName(".persifs")
{
  bzero(&rootAttr, sizeof(rootAttr));
  rootAttr.type = NF3DIR;
  rootAttr.mode = 0555;
  rootAttr.nlink = 2;
  rootAttr.atime = rootAttr.mtime = rootAttr.ctime = nfstime();

  rootFH = TSNumToFh(0, 0, true);

  bzero(&infoAttr, sizeof(infoAttr));
  infoAttr.type = NF3REG;
  infoAttr.mode = 0444;
  infoAttr.nlink = 1;
  infoAttr.atime = infoAttr.mtime = infoAttr.ctime = nfstime();

  infoFH = TSNumToFh(1, 0, true);
}

void
nfsProvider::dispatch(nfscall *nc)
{
  switch(nc->proc()){
  case NFSPROC3_NULL:
    nc->reply(nc->getvoidres());
    break;
  case NFSPROC3_GETATTR:
    getattr(nc);
    break;
  case NFSPROC3_SETATTR:
    setattr(nc);
    break;
  case NFSPROC3_LOOKUP:
    lookup(nc);
    break;
  case NFSPROC3_ACCESS:
    access(nc);
    break;
  case NFSPROC3_READ:
    read(nc);
    break;
  case NFSPROC3_WRITE:
    write(nc);
    break;
  case NFSPROC3_CREATE:
    create(nc);
    break;
  case NFSPROC3_MKDIR:
    mkdir(nc);
    break;
  case NFSPROC3_REMOVE:
    remove(nc);
    break;
  case NFSPROC3_READDIR:
    readdir(nc);
    break;
  case NFSPROC3_FSSTAT:
    fsstat(nc);
    break;
  case NFSPROC3_FSINFO:
    fsinfo(nc);
    break;
  case NFSPROC3_SYMLINK:
  case NFSPROC3_READLINK:
    nc->error(NFS3ERR_NOTSUPP);
    break;
  default:
    fatal << "Unknown NFS proc " << nc->proc() << "\n";
    nc->reject(SYSTEM_ERR);
  }
}

nfs_fh3
nfsProvider::getRootFH()
{
  return rootFH;
}

void
nfsProvider::nfsError(nfscall *nc, pStat err)
{
  struct 
  {
    pStat err;
    nfsstat3 nfs;
  } errorMap[] =
    {
      {PERR_IO, NFS3ERR_IO},
      {PERR_INVAL, NFS3ERR_INVAL},
      {PERR_NOENT, NFS3ERR_NOENT},
      {PERR_EXISTS, NFS3ERR_EXIST},
      {PERR_INTERNAL, NFS3ERR_SERVERFAULT},
      {POK, NFS3_OK}
    };

  for (int i = 0; errorMap[i].err != POK; ++i) {
    if (errorMap[i].err == err) {
      nc->error(errorMap[i].nfs);
      return;
    }
  }

  fatal << "Unknown error " << err << ", unable to translate into NFS error\n";
}

bool
nfsProvider::ensureNotRoot(nfscall *nc, nfs_fh3 fh)
{
  if (isRootFH(fh)) {
    nc->error(NFS3ERR_PERM);
    return false;
  } else {
    return true;
  }
}

bool
nfsProvider::ensureCurrent(nfscall *nc, nfs_fh3 fh)
{
  if (isMagicFH(fh) || fhToTS(fh) != TIMESTAMP_LATEST) {
    nc->error(NFS3ERR_PERM);
    return false;
  } else {
    return true;
  }
}

bool
nfsProvider::isRootFH(nfs_fh3 fh)
{
  return (fh.data == rootFH.data);
}

bool
nfsProvider::isInfoFH(nfs_fh3 fh)
{
  return (fh.data == infoFH.data);
}

bool
nfsProvider::isMagicFH(nfs_fh3 fh)
{
  return isRootFH(fh) || isInfoFH(fh);
}

timestamp
nfsProvider::fhToTS(nfs_fh3 fh)
{
  timestamp ts;

  bcopy(fh.data.base() + sizeof(inumber), &ts, sizeof(timestamp));

  return ts;
}

inumber
nfsProvider::fhToNum(nfs_fh3 fh)
{
  inumber num;

  bcopy(fh.data.base(), &num, sizeof(inumber));
  assert(num > 0);

  return num;
}

nfs_fh3
nfsProvider::TSNumToFh(timestamp ts, inumber num, bool allowZeroNum)
{
  nfs_fh3 fh;

  fh.data.setsize(FHSIZE);
  if (!allowZeroNum && num == 0)
    fatal << "Attempting to convert <" << ts << ", " << num << "> to an FH\n";
  bcopy(&num, fh.data.base(), sizeof(inumber));
  bcopy(&ts, fh.data.base() + sizeof(inumber), sizeof(timestamp));

  return fh;
}

uint64
nfsProvider::TSNumToFileid(timestamp ts, inumber num)
{
  // fileid's are stupid
  return (((uint64)ts<<32)^num)+1;
}

fattr3
nfsProvider::applySetAttrs(fattr3 base, sattr3 setAttrs)
{
  if (setAttrs.mode.set)
    base.mode = *(setAttrs.mode.val);
  if (setAttrs.uid.set)
    base.uid = *(setAttrs.uid.val);
  if (setAttrs.gid.set)
    base.gid = *(setAttrs.gid.val);

  if (setAttrs.mtime.set == SET_TO_CLIENT_TIME)
    base.mtime = *(setAttrs.mtime.time);
  else if (setAttrs.mtime.set == SET_TO_SERVER_TIME)
    base.mtime = nfstime();

  if (setAttrs.atime.set == SET_TO_CLIENT_TIME)
    base.atime = *(setAttrs.atime.time);
  else if (setAttrs.atime.set == SET_TO_SERVER_TIME)
    base.atime = nfstime();

  if (setAttrs.size.set)
    base.size = *(setAttrs.size.val);

  return base;
}

wcc_data
nfsProvider::makeWcc(fattr3 before, fattr3 after)
{
  wcc_data d;

  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;

  return d;
}

fattr3
nfsProvider::getInfoAttr()
{
  infoAttr.size = getInfoContents().len();
  infoAttr.mtime = infoAttr.ctime = nfstime();
  return infoAttr;
}

str
nfsProvider::getInfoContents()
{
  strbuf buf;
  
  buf << "New superblob entries: " << superblob::blobEntryCount << "\n";
  buf << "Superblob size: " << superblob::blobSize << " bytes\n";
  buf << "Chunk fusion hits: " << superblob::chunkFusionHits << "\n";
  buf << "New inode log entries: " << inodeLog::entryCount << "\n";
  buf << "Inode log size: " << inodeLog::logSize << " bytes\n";

  return str(buf);
}

void
nfsProvider::getattr(nfscall *nc)
{
  nfs_fh3 *fh = nc->template getarg<nfs_fh3> ();

  if (isRootFH(*fh)) {
    getattr_done(nc, rootAttr);
  } else if (isInfoFH(*fh)) {
    getattr_done(nc, getInfoAttr());
  } else {
    LOAD(*fh, fsfile::L_READ, WRAP(getattr_after_load, nc));
  }
}

void
nfsProvider::getattr_after_load(nfscall *nc, ref<fsfile> file)
{
  file->getAttr(WRAP(getattr_after_get, nc, file), NFSECB);
}

void
nfsProvider::getattr_after_get(nfscall *nc, ref<fsfile> file, fattr3 attr)
{
  // Now file can be GC'd
  getattr_done(nc, attr);
}

void
nfsProvider::getattr_done(nfscall *nc, fattr3 attr)
{
  getattr3res *res = nc->template getres<getattr3res> ();

  res->set_status(NFS3_OK);
  *(res->attributes) = attr;
  nc->reply(nc->getvoidres());
}

void
nfsProvider::setattr(nfscall *nc)
{
  setattr3args *a = nc->template getarg<setattr3args> ();

  if (ensureCurrent(nc, a->object)) {
    LOAD(a->object, fsfile::L_WRITE,
         WRAP(setattr_after_load, nc, a->new_attributes));
  }
}

void
nfsProvider::setattr_after_load(nfscall *nc, sattr3 newAttrs,
                                ref<fsfile> file)
{
  file->getAttr(WRAP(setattr_after_get, nc, newAttrs, file), NFSECB);
}

void
nfsProvider::setattr_after_get(nfscall *nc, sattr3 newAttrs, ref<fsfile> file,
                               fattr3 attrs)
{
  attrs = applySetAttrs(attrs, newAttrs);

  file->setAttr(attrs, WRAP(setattr_after_set, nc, file), NFSECB);
}

void
nfsProvider::setattr_after_set(nfscall *nc, ref<fsfile> file,
                               fattr3 oldAttrs, fattr3 newAttrs)
{
  wccstat3 *res = nc->template getres<wccstat3> ();

  res->set_status(NFS3_OK);
  *res->wcc = makeWcc(oldAttrs, newAttrs);
  nc->reply(nc->getvoidres());
}

void
nfsProvider::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->template getarg<diropargs3> ();

  if (DEBUG_LOOKUP)
    warn << "lookup " << a->name << " in " << debug_fh2hex(a->dir) << "\n";
  if (isRootFH(a->dir)) {
    lookup_root(nc, a->name);
  } else if (isMagicFH(a->dir)) {
    nfsError(nc, PERR_NOTDIR);
  } else {
    loadDir(a->dir, fsfile::L_READ,
            WRAP(lookup_after_loadDir, nc, a->name, fhToTS(a->dir)), NFSECB);
  }
}

void
nfsProvider::lookup_after_loadDir(nfscall *nc, str name, timestamp ts,
                                  ref<fsdir> dir)
{
  dir->lookup(name, WRAP(lookup_after_lookup, nc, ts, dir), NFSECB);
}

void
nfsProvider::lookup_after_lookup(nfscall *nc, timestamp ts, ref<fsdir> dir,
                                 ref<fsdirEntry> ent)
{
  if (ent->getInumber())
    lookup_done(nc, TSNumToFh(ts, ent->getInumber()));
  else
    // This the top level of a snapshot directory.  Return the magic root
    lookup_done(nc, getRootFH());
}

void
nfsProvider::lookup_root(nfscall *nc, str name)
{
  if (DEBUG_LOOKUP)
    warn << "lookup_root for " << name << "\n";

  if (name == str(".") ||
      name == str("..")) {
    // Requesting this directory
    lookup_done(nc, getRootFH());
  } else if (name == rootInfoName) {
    lookup_done(nc, infoFH);
  } else {
    timestamp ts;
    if (name == rootCurrentName)
      // Requesting current tree
      ts = TIMESTAMP_LATEST;
    else {
      // Decode time stamp
      tm t;
      if (strptime(name.cstr(), "%Y-%m-%d-%H-%M-%S", &t) == NULL) {
        // Requested directory is not a valid time stamp
        nfsError(nc, PERR_NOENT);
        return;
      }
      ts = mktime(&t);
      if (ts > inodeLog::now()) {
        // Requested directory is in the future
        nfsError(nc, PERR_NOENT);
        return;
      }
    }
    lookup_done(nc, TSNumToFh(ts, fs->getRootInumber()));
  }
}

void
nfsProvider::lookup_done(nfscall *nc, nfs_fh3 fh)
{
  if (DEBUG_LOOKUP)
    warn << "lookup_done returning " << debug_fh2hex(fh) << "\n";

  lookup3res *res = nc->template 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());
}

void
nfsProvider::access(nfscall *nc)
{
  access3args *a = nc->template getarg<access3args> ();

  if (isRootFH(a->object)) {
    access_done(nc, a->access, ACCESS3_READ | ACCESS3_LOOKUP, rootAttr);
  } else if (isInfoFH(a->object)) {
    access_done(nc, a->access, ACCESS3_READ, getInfoAttr());
  } else {
    LOAD(a->object, fsfile::L_READ, WRAP(access_after_load, nc, a->access));
  }
}

void
nfsProvider::access_after_load(nfscall *nc, uint32 access,
                               ref<fsfile> file)
{
  file->getAttr(WRAP(access_after_get, nc, access, file), NFSECB);
}

void
nfsProvider::access_after_get(nfscall *nc, uint32 access, ref<fsfile> file,
                              fattr3 attrs)
{
  // Translate attrs.mode into access mask
  uint32 a = 0;
  // XXX Why doesn't this work without the true's?
  if (true || attrs.mode & 0400)
    a |= ACCESS3_READ;
  if (true || attrs.mode & 0200)
    a |= ACCESS3_MODIFY | ACCESS3_EXTEND;
  if (true || attrs.mode & 0100)
    a |= ACCESS3_EXECUTE;
  // LOOKUP and DELETE have to do with the parent directory.  XXX
  // Assume they're both true
  a |= ACCESS3_LOOKUP | ACCESS3_DELETE;

  access_done(nc, access, a, attrs);
}

void
nfsProvider::access_done(nfscall *nc, uint32 access, uint32 realAccess,
                         fattr3 attrs)
{
  access3res *res = nc->template getres<access3res> ();

  res->set_status(NFS3_OK);
  res->resok->access = access & realAccess;
  res->resok->obj_attributes.set_present(true);
  *res->resok->obj_attributes.attributes = attrs;
  nc->reply(nc->getvoidres());
}

void
nfsProvider::read(nfscall *nc)
{
  read3args *a = nc->template getarg<read3args> ();

  if (isRootFH(a->file)) {
    nfsError(nc, PERR_INVAL);
  } else if (isInfoFH(a->file)) {
    str info = getInfoContents();
    read_done(nc, substr(info, a->offset, a->count),
              info.len() <= (a->offset+a->count));
  } else {
    LOAD(a->file, fsfile::L_READ,
         WRAP(read_after_load, nc, a->offset, a->count));
  }
}

void
nfsProvider::read_after_load(nfscall *nc, uint64 offset, uint32 count,
                             ref<fsfile> file)
{
  file->read(offset, count, WRAP(read_after_read, nc, offset+count, file),
             NFSECB);
}

void
nfsProvider::read_after_read(nfscall *nc, uint64 end, ref<fsfile> file,
                             str data)
{
  // Get attributes to check type and figure out if I've hit the EOF
  // (by NFS's definition)
  file->getAttr(WRAP(read_after_getAttr, nc, end, file, data), NFSECB);
}

void
nfsProvider::read_after_getAttr(nfscall *nc, uint64 end, ref<fsfile> file,
                                str data,
                                fattr3 attr)
{
  if (attr.type != NF3REG) {
    nc->error(NFS3ERR_INVAL);
  } else {
    read_done(nc, data, (attr.size <= end));
  }
}

void
nfsProvider::read_done(nfscall *nc, str data, bool eof)
{
  read3res *res = nc->template getres<read3res> ();

  res->set_status(NFS3_OK);
  res->resok->eof = eof;
  res->resok->file_attributes.set_present(false);
  res->resok->count = data.len();
  res->resok->data.set((char*)data.cstr(), data.len());
  nc->reply(nc->getvoidres());
}

void
nfsProvider::write(nfscall *nc)
{
  write3args *a = nc->template getarg<write3args> ();
  str data(a->data.base(), a->count);

  if (ensureCurrent(nc, a->file)) {
    LOAD(a->file, fsfile::L_WRITE,
         WRAP(write_after_load, nc, a->offset, data));
  }
}

void
nfsProvider::write_after_load(nfscall *nc, uint64 offset, str data,
                              ref<fsfile> file)
{
  file->getAttr(WRAP(write_after_getOldAttr, nc, offset, data, file), NFSECB);
}

void
nfsProvider::write_after_getOldAttr(nfscall *nc, uint64 offset, str data,
                                    ref<fsfile> file,
                                    fattr3 oldAttr)
{
  if (oldAttr.type != NF3REG) {
    nfsError(nc, PERR_INVAL);
  } else {
    file->write(offset, data,
                WRAP(write_after_write, nc, data.len(), file, oldAttr),
                NFSECB);
  }
}

void
nfsProvider::write_after_write(nfscall *nc, uint32 count, ref<fsfile> file,
                               fattr3 oldAttr)
{
  file->getAttr(WRAP(write_after_getNewAttr, nc, count, file, oldAttr),
                NFSECB);
}

void
nfsProvider::write_after_getNewAttr(nfscall *nc, uint32 count,
                                    ref<fsfile> file, fattr3 oldAttr,
                                    fattr3 newAttr)
{
  write3res *res = nc->template getres<write3res> ();

  res->set_status(NFS3_OK);
  res->resok->file_wcc = makeWcc(oldAttr, newAttr);
  res->resok->count = count;
  res->resok->committed = FILE_SYNC;
  nc->reply(nc->getvoidres());
}

void
nfsProvider::create(nfscall *nc)
{
  create3args *a = nc->template getarg<create3args> ();

  if (a->how.mode == EXCLUSIVE) {
    nc->error(NFS3ERR_NOTSUPP);
    return;
  }

  if (!ensureCurrent(nc, a->where.dir)) {
    return;
  }

  fattr3 fa;
  bzero(&fa, sizeof(fa));
  fa.type = NF3REG;
  fa.mode = 0777; // rwxrwxrwx
  fa.nlink = 0;   // will be incremented when added to dir
  fa.atime = fa.mtime = nfstime();
  fa = applySetAttrs(fa, *(a->how.obj_attributes));

  generalCreate(a->where.dir, a->where.name, fa,
                WRAP(createOrMkdir_after_done, nc), NFSECB);
}

void
nfsProvider::mkdir(nfscall *nc)
{
  mkdir3args *a = nc->template getarg<mkdir3args> ();

  if (!ensureCurrent(nc, a->where.dir)) {
    return;
  }

  fattr3 fa;
  bzero(&fa, sizeof(fa));
  fa.type = NF3DIR;
  fa.mode = 0777; // rwxrwxrwx
  fa.atime = fa.mtime = nfstime();
  fa = applySetAttrs(fa, a->attributes);

  generalCreate(a->where.dir, a->where.name, fa,
                WRAP(createOrMkdir_after_done, nc), NFSECB);
}

void
nfsProvider::createOrMkdir_after_done(nfscall *nc,
                                      ref<fsdir> parent, nfs_fh3 fileFH,
                                      wcc_data wcc)
{
  diropres3 *res = nc->template getres<diropres3> ();

  res->set_status(NFS3_OK);
  res->resok->obj.set_present(true);
  *res->resok->obj.handle = fileFH;
  res->resok->obj_attributes.set_present(false);
  res->resok->dir_wcc = wcc;
  nc->reply(nc->getvoidres());
}

void
nfsProvider::remove(nfscall *nc)
{
  diropargs3 *a = nc->template getarg<diropargs3> ();

  if (ensureCurrent(nc, a->dir)) {
    // Load parent directory
    loadDir(a->dir, fsfile::L_WRITE,
            WRAP(remove_after_load, nc, a->name), NFSECB);
  }
}

void
nfsProvider::remove_after_load(nfscall *nc, str name,
                               ref<fsdir> dir)
{
  dir->getFile()->getAttr(WRAP(remove_after_getOldAttr, nc, name, dir),
                          NFSECB);
}

void
nfsProvider::remove_after_getOldAttr(nfscall *nc, str name, ref<fsdir> dir,
                                     fattr3 oldAttr)
{
  dir->remove(name, WRAP(remove_after_remove, nc, dir, oldAttr), NFSECB);
}

void
nfsProvider::remove_after_remove(nfscall *nc, ref<fsdir> dir, fattr3 oldAttr)
{
  dir->getFile()->getAttr(WRAP(remove_after_getNewAttr, nc, dir, oldAttr),
                          NFSECB);
}

void
nfsProvider::remove_after_getNewAttr(nfscall *nc, ref<fsdir> dir,
                                     fattr3 oldAttr,
                                     fattr3 newAttr)
{
  wccstat3 *res = nc->template getres<wccstat3> ();

  res->set_status(NFS3_OK);
  *res->wcc = makeWcc(oldAttr, newAttr);
  nc->reply(nc->getvoidres());
}

void
nfsProvider::readdir(nfscall *nc)
{
  readdir3args *a = nc->template getarg<readdir3args> ();

  if (DEBUG_READDIR)
    warn << "readdir in " << debug_fh2hex(a->dir) << "\n";

  if (isRootFH(a->dir)) {
    readdir_root(nc, a->cookie);
  } else {
    loadDir(a->dir, fsfile::L_READ,
            WRAP(readdir_after_loadDir, nc, a->cookie, fhToTS(a->dir)),
            NFSECB);
  }
}

void
nfsProvider::readdir_after_loadDir(nfscall *nc, int cookie, timestamp ts,
                                   ref<fsdir> dir)
{
  ref<readdirEntryList> el = New refcounted<readdirEntryList>;
  int count = 0;

  dir->traverse(cookie, WRAP(readdir_on_entry, el, &count),
                WRAP(readdir_after_traverse, nc, dir, ts, el), NFSECB);
}

bool
nfsProvider::readdir_on_entry(ref<readdirEntryList> el, int *count,
                              int cookie, ref<fsdirEntry> entry)
{
  readdirEntry *re = New readdirEntry;
  re->name = entry->getName();
  re->num = entry->getInumber();
  re->cookie = cookie;
  el->insert_tail(re);
  ++(*count);

  if (*count > 20)
    return false;
  else
    return true;
}

void
nfsProvider::readdir_after_traverse(nfscall *nc, ref<fsdir> dir, timestamp ts,
                                    ref<readdirEntryList> el,
                                    bool eof)
{
  readdir3res *res = nc->template getres<readdir3res> ();

  res->set_status(NFS3_OK);
  res->resok->reply.eof = eof;
  res->resok->dir_attributes.set_present(false);
  rpc_ptr<entry3> *e;
  
  e = &res->resok->reply.entries;
  while (el->first) {
    readdirEntry *re = el->remove(el->first);
    (*e).alloc();
    (*e)->name = re->name;
    (*e)->fileid = TSNumToFileid(ts, re->num);
    (*e)->cookie = re->cookie;
    if (DEBUG_READDIR)
      warn << "readdir returning: " << re->name << " FH: "
           << (*e)->fileid << " cookie: " << re->cookie << "\n";
    e = &(*e)->nextentry;
    delete re;
  }

  nc->reply(nc->getvoidres());
}

void
nfsProvider::readdir_root(nfscall *nc, int cookie)
{
  readdir3res *res = nc->template getres<readdir3res> ();

  if (DEBUG_READDIR)
    warn << "readdir_root\n";

  res->set_status(NFS3_OK);
  res->resok->reply.eof = true;
  res->resok->dir_attributes.set_present(false);
  rpc_ptr<entry3> *e = &res->resok->reply.entries;;
  
  // Create entries for ., .., current, and info
  if (cookie == 0) {
    (*e).alloc();
    (*e)->name = str(".");
    (*e)->fileid = 1;
    (*e)->cookie = 0;
    e = &(*e)->nextentry;
  }
  if (cookie <= 1) {
    (*e).alloc();
    (*e)->name = str("..");
    (*e)->fileid = 1;
    (*e)->cookie = 1;
    e = &(*e)->nextentry;
  }
  if (cookie <= 2) {
    (*e).alloc();
    (*e)->name = rootCurrentName;
    (*e)->fileid = 2;
    (*e)->cookie = 2;
    e = &(*e)->nextentry;
  }
  if (cookie <= 3) {
    (*e).alloc();
    (*e)->name = rootInfoName;
    (*e)->fileid = 3;
    (*e)->cookie = 3;
    e = &(*e)->nextentry;
  }

  nc->reply(nc->getvoidres());
}

void
nfsProvider::fsstat(nfscall *nc)
{
  fsstat3res *res = nc->template 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
nfsProvider::fsinfo(nfscall *nc)
{
    //  nfs_fh3 *fh = nc->template getarg<nfs_fh3> ();
  fsinfo3res *res = nc->template 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());
}

void
nfsProvider::generalCreate(nfs_fh3 parent, str name, fattr3 attrs,
                           mcDoneCB doneCB, cbe error)
{
  // Look up parent directory
  timestamp ts = fhToTS(parent);

  ref<createState> s =
    New refcounted<createState>(name, attrs, doneCB, error);
  loadDir(parent, fsfile::L_WRITE, WRAP(generalCreate_after_loadDir, s),
          error);
}

void
nfsProvider::generalCreate_after_loadDir(ref<createState> s,
                                         ref<fsdir> parent)
{
  s->parent = parent;

  // Get the directory's current attributes
  parent->getFile()->getAttr(WRAP(generalCreate_after_getOldAttr, s),
                             s->error);
}

void
nfsProvider::generalCreate_after_getOldAttr(ref<createState> s,
                                            fattr3 oldAttr)
{
  s->oldAttr = oldAttr;

  // Does a file by this name already exist?
  s->parent->lookup(s->name, WRAP(generalCreate_after_lookup, s),
                    WRAP(generalCreate_after_lookupFail, s));
}

void
nfsProvider::generalCreate_after_lookup(ref<createState> s,
                                        ref<fsdirEntry> ent)
{
  // File already exists.  If this is creating a directory, this is
  // an error.
  if (s->attrs.type == NF3DIR) {
    s->error(PERR_EXISTS);
  } else {
    // If this is a regular file, return its fh
    // XXX Update its mtime?
    nfs_fh3 fh = TSNumToFh(TIMESTAMP_LATEST, ent->getInumber());
    s->doneCB(s->parent, fh, makeWcc(s->oldAttr, s->oldAttr));
  }
}

void
nfsProvider::generalCreate_after_lookupFail(ref<createState> s,
                                            pStat err)
{
  if (err != PERR_NOENT) {
    // Unexpected error
    s->error(err);
  } else {
    // File doesn't exist.  Create it

    if (s->attrs.type == NF3DIR) {
      fsdir::create(s->parent, s->attrs,
                    WRAP(generalCreate_after_createDir, s), s->error);
    } else {
      fsfile::create(fs, s->attrs, WRAP(generalCreate_after_createFile, s),
                     s->error);
    }
  }
}

void
nfsProvider::generalCreate_after_createDir(ref<createState> s,
                                           ref<fsdir> dir)
{
  generalCreate_after_createFile(s, dir->getFile());
}

void
nfsProvider::generalCreate_after_createFile(ref<createState> s,
                                            ref<fsfile> file)
{
  s->file = file;

  // Add file to the parent directory
  s->parent->add(s->name, file, WRAP(generalCreate_after_add, s), s->error);
}

void
nfsProvider::generalCreate_after_add(ref<createState> s,
                                     ref<fsdirEntry> ent)
{
  s->fileFH = TSNumToFh(TIMESTAMP_LATEST, ent->getInumber());

  // Get parent's new attributes
  s->parent->getFile()->getAttr(WRAP(generalCreate_after_getNewAttr, s),
                                s->error);
}

void
nfsProvider::generalCreate_after_getNewAttr(ref<createState> s,
                                            fattr3 newAttr)
{
  s->doneCB(s->parent, s->fileFH, makeWcc(s->oldAttr, newAttr));
}

void
nfsProvider::loadDir(nfs_fh3 dir, int flags,
                     callback<void, ref<fsdir> >::ref cb, cbe error)
{
  // Load the file associated with this directory
  fsfile::load(fs, fhToTS(dir), fhToNum(dir), flags,
               WRAP(loadDir_after_load, cb, error), error);
}

void
nfsProvider::loadDir_after_load(callback<void, ref<fsdir> >::ref cb, cbe error,
                                ref<fsfile> file)
{
  // Wrap ths directory around its file
  fsdir::load(file, cb, error);
}
