#include <stdio.h>
#include <ctype.h>
#include "nfs_prot.h"
#include "mp.h"

static struct device *devices;

/* FIXME: Send create attributes */
struct diropres *
create_it(ca, isdir)
  createargs *ca;
  int isdir;
{
  static struct diropres res;
  char *name = get_num(fh2inode(ca->where.dir.data))->name;
  fattr *fp;
  p_inode *inode;

  if(debug)
    printf("\tcreate: in %s %s (%#o, %d)\n",
	    name, ca->where.name, ca->attributes.mode, isdir);

  name = build_path(name, ca->where.name);

  if(sendop(isdir ? PFS_OP_MKDIR : PFS_OP_CREATE, name, (char *)0, 0))
    {
      res.status = psion_alive ? NFSERR_NAMETOOLONG : NFSERR_STALE;
      return &res;
    }

  inode = get_nam(name);
  inode2fh(inode->inode, res.diropres_u.diropres.file.data);

  fp = &res.diropres_u.diropres.attributes;
  bzero(fp, sizeof(fp));
  if(isdir)
    {
      fp->type = NFDIR;
      fp->mode = NFSMODE_DIR | 0700;
      fp->nlink = 2;
    }
  else
    {
      fp->type = NFREG;
      fp->mode = NFSMODE_REG | 0600;
      fp->nlink = 1;
    }

  fp->uid = root_fattr.uid;
  fp->gid = root_fattr.gid;
  fp->blocksize = BLOCKSIZE;
  fp->fileid = inode->inode;
  fp->atime.seconds = fp->mtime.seconds = fp->ctime.seconds = time(0);
  
  res.status = NFS_OK;
  return &res;
}

struct diropres *
nfsproc_create_2(ca)
  createargs *ca;
{
  return create_it(ca, 0);
}

struct diropres *
nfsproc_mkdir_2(ca)
  createargs *ca;
{
  return create_it(ca, 1);
}

void
pattr2attr(op, fp, fh)
  unsigned char *op, *fh;
  fattr *fp;
{
  bzero(fp, sizeof(*fp));

  if(op[2] & (1<<4))
    {
      fp->type   = NFDIR;
      fp->mode   = NFSMODE_DIR | 0700;
/* Damned filesystem.
   We have to count the number of subdirectories on the psion. */
      fp->nlink  = (op[0] | (op[1] << 8)) + 2;
      fp->size   = BLOCKSIZE;
      fp->blocks = 1;
    }
  else
    {
      fp->type   = NFREG;
      fp->mode   = NFSMODE_REG;
      fp->nlink  = 1;
      fp->size   = pstr2long(op+4);
      fp->blocks = (fp->size+BLOCKSIZE-1)/BLOCKSIZE;

/* Following flags have to be set in order to let backups work properly */
      if(op[3] & (1<<0))    fp->mode |= 0400; /* File readable (?) */
      if(op[2] & (1<<0))    fp->mode |= 0200; /* File writeable  */
      if(op[3] & (1<<1))    fp->mode |= 0100; /* File executable */
      if(!(op[2] & (1<<1))) fp->mode |= 0004; /* Not Hidden  <-> world read */
      if(op[2] & (1<<2))    fp->mode |= 0002; /* System      <-> world write */
      if(op[2] & (1<<3))    fp->mode |= 0001; /* Volume      <-> world exec */
      if(op[2] & (1<<5))    fp->mode |= 0020; /* Modified    <-> group write */
      if(op[3] & (1<<2))    fp->mode |= 0040; /* Byte        <-> group read */
      if(op[3] & (1<<3))    fp->mode |= 0010; /* Text        <-> group exec */
    }

  fp->uid = root_fattr.uid; fp->gid = root_fattr.gid;
  fp->blocksize = BLOCKSIZE;
  fp->fileid = fh2inode((char *)fh);
  fp->rdev = fp->fsid = FID;
  fp->atime.seconds = pstr2long(op+8) - gmtoffset;
  fp->mtime.seconds = fp->ctime.seconds = fp->atime.seconds;
}

int
query_devices()
{
  struct device *dp, *np, **pp;
  unsigned char *p, namebuf[256];
  int link_count = 2; /* set the root link count */

  for(dp = devices; dp; dp = np)
    {
      np = dp->next;
      free(dp->name); free(dp);
    }
  devices = 0; *namebuf = 0;
  if(sendop(PFS_OP_GETDEVS, namebuf, 0, 0))
    return 1;
  for(pp = &devices; ; pp = &(*pp)->next)
    {
      if(getstr(namebuf))
        return 1;
      if(!*namebuf)
        break;
      link_count++;
      *pp = (struct device *)malloc(sizeof(struct device));
      for(p = namebuf; *p; p++)
	*p = tolower(*p);
      (*pp)->name = strdup(namebuf);

      if(getcount((*pp)->attrib, 14))
        return 1;
      (*pp)->total = pstr2long((*pp)->attrib+6);
      (*pp)->free  = pstr2long((*pp)->attrib+10);
      (*pp)->next = 0;
    }
  root_fattr.nlink = link_count;
  return 0;
}

struct attrstat *
nfsproc_getattr_2(fh)
  struct nfs_fh *fh;
{
  static struct attrstat res;
  p_inode *inode = get_num(fh2inode(fh->data));
  fattr *fp = &res.attrstat_u.attributes;
  unsigned char op[12];
  struct cache *cp;
  int l;

  if(debug) printf("\tgetattr:'%s',%d\n", inode->name, inode->inode);
  res.status = NFS_OK;

  if ((cp = search_cache(attrcache, inode->inode, 0, 0)))
    {
      *fp = cp->attr;
      return &res;
    }

/* It's the root inode */
  if(inode->inode == root_fattr.fileid)
    {
      if(debug) printf("\t\tgetattr:root inode (%#o)\n", root_fattr.mode);
  
      if(fd_is_still_alive(psionfd,1) && query_devices())
        res.status = NFSERR_STALE; /* ??? */
      else
	*fp = root_fattr;
      add_cache(&attrcache, inode->inode, 0, 0, 0, fp);
      return &res;
    }
  l = strlen(inode->name);


/* It's a device */
  if(l > 4 && !strncmp(inode->name+3, "::", 2) && 
     (l == 5 || (l == 7 && *(inode->name+6) == ':')))
    {
      if(debug) printf("\tgetattr:device\n");
      res.status = NFSERR_STALE;
      if(!query_devices())
        {
	  struct device *dp;

	  for(dp = devices; dp; dp = dp->next)
	    if(!strcmp(dp->name, inode->name))
	      break;
	  if(dp)
	    {
	      res.status = NFS_OK;
	      *fp = root_fattr;
	      /* If it's writeable... */
	      if(!(dp->attrib[2] == 5 || dp->attrib[2] == 6))
	        fp->mode |= 0200; 
	      fp->fileid = inode->inode;
	      /* FIXME */
	      if(!strcmp(inode->name, "rom::"))
		fp->nlink = 2;
	      else
	        {
		  if(sendop(PFS_OP_STATDEV, inode->name, 0, 0) ||
		     getcount(op, 12))
		    {
		      res.status = psion_alive ? NFSERR_NOENT : NFSERR_STALE;
		      return &res;
		    }
		  fp->nlink  = (op[0] | (op[1] << 8)) + 2;	/* RUDI ???? */
		}
	    }
	    
	}
      add_cache(&attrcache, inode->inode, 0, 0, 0, fp);
      return &res;
    }

  if(sendop(PFS_OP_GETATTR, inode->name, 0, 0) || getcount(op, 12))
    {
      res.status = psion_alive ? NFSERR_NOENT : NFSERR_STALE;
      return &res;
    }
  pattr2attr(op, fp, fh->data);
  if(fp->type == NFDIR) /* Cache directory attributes */
    add_cache(&attrcache, inode->inode, 0, 0, 0, fp);

  return &res;
}

struct diropres *
nfsproc_lookup_2(da)
  diropargs *da;
{
  static struct diropres res;
  struct attrstat *gres;
  p_inode *inode = get_num(fh2inode(da->dir.data));
  char *fp = res.diropres_u.diropres.file.data;

  if(!inode)
    {
      if(debug) printf("lookup: stale fh\n");
      res.status = NFSERR_STALE;
      return &res;
    }

  if(debug)
    printf("\tlookup: in '%s'(%d) searching '%s'\n",
           inode->name, inode->inode, da->name);

  if(inode->inode == root_fattr.fileid && !strcmp(da->name, "exit"))
    {
      exiting = 5; /* Lets try it 5 times (10 sec) */
      res.status = NFSERR_EXIST;
      return &res;
    }
  if(inode->inode == root_fattr.fileid && !strcmp(da->name, "debug"))
    {
      debug = (debug+1) & 3;
      res.status = NFSERR_EXIST;
      return &res;
    }

  if(!strcmp(da->name, "."))
    inode2fh(fh2inode(da->dir.data), fp);
  else if(!strcmp(da->name, ".."))
    inode2fh(getpinode(inode), fp);
  else
    inode2fh(get_nam(build_path(inode->name, da->name))->inode, fp);

  gres = nfsproc_getattr_2((struct nfs_fh *)fp);

  res.status = gres->status;
  res.diropres_u.diropres.attributes = gres->attrstat_u.attributes;
  return &res;
}

static void
addentry(ra, where, searchinode, inode, name)
  readdirargs *ra;
  entry ***where;
  int inode, *searchinode;
  char *name;
{
  int l;

  if(*searchinode && *searchinode != inode)
    return;
  if(*searchinode)
    {
      *searchinode = 0;
      return;
    }

  l = strlen(name) + 1 + sizeof(entry);
  if(l > ra->count)
    {
      ra->count = -1; return;
    }
  ra->count -= l;

  **where = (entry *)malloc(sizeof(entry));
  (**where)->fileid = inode;
  (**where)->name = strdup(name);
  *(int *)(**where)->cookie = inode;
  (**where)->nextentry = 0;
  *where = &(**where)->nextentry;
}

struct readdirres *
nfsproc_readdir_2(ra)
  readdirargs *ra;
{
  static readdirres res;
  p_inode *inode = get_num(fh2inode(ra->dir.data));
  entry *centry, *fentry, **cp;
  unsigned char *p, *bp, buf[256];
  int searchinode;

  if(!inode)
    {
      if(debug) printf("readdir: stale fh\n");
      res.status = NFSERR_STALE;
      return &res;
    }
  cp = &res.readdirres_u.reply.entries;
  for(fentry = *cp; fentry; fentry = centry)
    {
      centry = fentry->nextentry;
      free(fentry->name);
      free(fentry);
    }
  *cp = 0; searchinode = *(int *)ra->cookie;

  if(debug)
    printf("\treaddir: %s, cookie:%x, count:%d\n",
	   inode->name, searchinode, ra->count);

/* . & .. */
  addentry(ra, &cp, &searchinode, inode->inode, ".");
  addentry(ra, &cp, &searchinode, getpinode(inode), "..");

  if(inode->inode == root_fattr.fileid) /* Root directory */
    {
      struct device *dp;

      if(query_devices())
	{
	  res.status = NFSERR_STALE;
	  return &res;
	}
      for(dp = devices; dp; dp = dp->next)
	addentry(ra, &cp, &searchinode, get_nam(dp->name)->inode, dp->name);
    }
  else
    {
      if(sendop(PFS_OP_READDIR, dirname(inode->name), 0, 0))
	{
	  res.status = psion_alive ? NFSERR_NOENT : NFSERR_STALE;
	  return &res;
	}
      for(;;)
	{
	  if(getstr(buf))
	    {
	      res.status = NFSERR_STALE;
	      return &res;
	    }
	  if(!*buf) break;
	  for(p = buf; *p; p++)
	    *p = tolower(*p);
	  bp = (unsigned char *)filname(buf);
	  addentry(ra, &cp, &searchinode,
		   get_nam(build_path(inode->name, bp))->inode, bp);
	}
    }

  res.readdirres_u.reply.eof = ra->count == -1 ? 0 : 1;
  res.status = NFS_OK;
  return &res;
}

/* FIXME */
struct attrstat *
nfsproc_setattr_2(sa)
  sattrargs *sa;
{
  static struct attrstat res;
  p_inode *inode = get_num(fh2inode(sa->file.data));
#if 0
  struct sattr *fp = &sa->attributes;
  int op = 0;
  char str[2]
#endif

  if(!inode)
    {
      if(debug) printf("setattr: stale fh\n");
      res.status = NFSERR_STALE;
      return &res;
    }
#if 0
  /* Set only the mode bits */
  if(fp->mode & 0400) op |= (1<<8);
  if(fp->mode & 0200) op |= (1<<0);
  if(fp->mode & 0100) op |= (1<<9);
  if(!(fp->mode & 0004)) op |= (1<<1);
  if(fp->mode & 0002) op |= (1<<2);
  if(fp->mode & 0001) op |= (1<<3);
  if(fp->mode & 0020) op |= (1<<5);
  if(fp->mode & 0040) op |= (1<<10);
  if(fp->mode & 0010) op |= (1<<11);
  short2pstr(op, str);
  if(sendop(PFS_OP_SETATTR, inode->name, str, 2))
    {
      res.status = psion_alive ? NFSERR_ACCES : NFSERR_STALE;
      return &res;
    }
#endif

  return nfsproc_getattr_2(&sa->file);
}

/* We need a '.' in each file handled by rm or rename */
char *
check_ext(s)
  char *s;
{
  static char namebuf[300];
  int l, i;

  for(i = 4, l = strlen(s)-1; i && l; i--, l--)
    if(s[l] == '.' || s[l] ==  '\\')
      break;
  if(s[l] == '.')
    return s;
  sprintf(namebuf,  "%s.", s);
  return namebuf;
}

nfsstat *
remove_it(da, isdir)
  diropargs *da;
  int isdir;
{
  static nfsstat res;
  p_inode *inode = get_num(fh2inode(da->dir.data));
  char *name;

  if(!inode)
    {
      if(debug) printf("setattr: stale fh\n");
      res = NFSERR_STALE;
      return &res;
    }
  name = check_ext(da->name);
  if(debug) printf("\tremove_it: in %s: %s (%d)\n", inode->name, name, isdir);

  if(sendop(isdir ? PFS_OP_RMDIR : PFS_OP_REMOVE,
            build_path(inode->name, name), 0, 0))
    {
      res = psion_alive ? NFSERR_ACCES : NFSERR_STALE;
      return &res;
    }
  res = NFS_OK;
  return &res;
}

nfsstat *
nfsproc_remove_2(da)
  diropargs *da;
{
  return remove_it(da, 0);
}

nfsstat *
nfsproc_rmdir_2(da)
  diropargs *da;
{
  return remove_it(da, 1);
}

nfsstat *
nfsproc_rename_2(ra)
  renameargs *ra;
{
  static nfsstat res;
  p_inode *from = get_num(fh2inode(ra->from.dir.data)),
          *to   = get_num(fh2inode(ra->to.dir.data));
  unsigned char ldata[300], *old, c;

  if(!from || !to)
    {
      if(debug) printf("rename: stale fh\n");
      res = NFSERR_STALE;
      return &res;
    }
  strcpy(ldata+1, build_path(to->name, check_ext(ra->to.name)));
  *ldata = strlen(ldata+1);
  c = *ldata+1;
  old = (unsigned char *)build_path(from->name, check_ext(ra->from.name));

  if(debug) printf("\tRename: %s -> %s\n", old, ldata+1);
  if(sendcmd(PFS_OP_RENAME, old, &c, 1) || senddata(ldata, c))
    {
      res = NFSERR_STALE;
      return &res;
    }
  res = getbyte() ?  NFSERR_ACCES : NFS_OK;
  if(res == NFS_OK)
    {
      /* Preserve inode */
      strcpy(ldata, build_path(to->name, ra->to.name));
      (void)re_nam(build_path(from->name, ra->from.name), ldata);
    }

  return &res;
}

/* ARGSUSED */
struct statfsres *
nfsproc_statfs_2(fh)
  struct nfs_fh *fh;
{
  static statfsres res;
  static int first_time = 1;
  statfsokres *rp;
  struct device *dp;

  if(debug) printf("\tstatfs..\n");

  rp = &res.statfsres_u.reply;
  rp->tsize = PBUFSIZE;
  rp->bsize = BLOCKSIZE;
  rp->blocks = rp->bfree = 0;
  res.status = NFS_OK;

  if(query_devices())
    {
      /* Allow to mount it whithout the psion attached */
      if(first_time)
        first_time = 0;
      else
	res.status = NFSERR_STALE;
      return &res;
    }
  for(dp = devices; dp; dp = dp->next)
    {
      rp->blocks += (dp->total + BLOCKSIZE -1)/BLOCKSIZE;
      rp->bfree += (dp->free + BLOCKSIZE -1)/BLOCKSIZE;
    }
  rp->bavail = rp->bfree;

  return &res;
}

/*
 * Problem:
 * Since we are slow (Max 2Kbyte/s) and the biod's are very impatient,
 * we receice each request (number of biod's + 1 for the kernel itself)
 * times, this number can be lower for the last block. :-(
 * Solution: (not the best, probably)
 * Cache the read data. This cache will be invalidated, if there are
 * no more requests in the queue for at least XXX seconds.
 * See also write :-(
 */
struct readres *
nfsproc_read_2(ra)
  struct readargs *ra;
{
  static struct readres res;
  static unsigned char rop[NFS_MAXDATA];
  p_inode *inode = get_num(fh2inode(ra->file.data));
  fattr *fp;
  struct cache *cp;
  int len;

  if(!inode)
    {
      if(debug) printf("read: stale fh\n");
      res.status = NFSERR_STALE;
      return &res;
    }

  if(debug)
    printf("\tread: %s off:%d count:%d\n", inode->name, ra->offset, ra->count);

  if ((cp = search_cache(readcache, inode->inode, ra->offset, ra->count)))
    {
      if(debug) printf("\tread: cache hit\n");

      res.readres_u.reply.attributes = cp->attr;
      bcopy(cp->data, res.readres_u.reply.data.data_val, ra->count);
      res.readres_u.reply.data.data_len = ra->count;

      res.status = NFS_OK;
      return &res;
    }

  long2pstr(ra->offset, rop);
  short2pstr(ra->count, rop+4);
  if(sendop(PFS_OP_READ, inode->name, rop, 6) ||
     getcount(rop, 12))
    {
      res.status = psion_alive ? NFSERR_NOENT : NFSERR_STALE;
      return &res;
    }

  fp = &res.readres_u.reply.attributes;
  pattr2attr(rop, fp, ra->file.data);

  len = fp->size - ra->offset;
  if(len > ra->count) len = ra->count;
  if(fp->size < ra->offset) len = 0;
  if (debug > 1)
     printf("Read: filesize %d read %d @ %d\n", fp->size, len,ra->offset);
  res.readres_u.reply.data.data_len = len;
  res.readres_u.reply.data.data_val = (char *)rop;

  if(getcount(rop, len))
    {
      res.status = NFSERR_STALE;
      return &res;
    }
  if (len) add_cache(&readcache, inode->inode, ra->offset, ra->count, rop, fp);

  res.status = NFS_OK;
  return &res;
}

/*
 * The same problem here as by read: we receive numerous requests,
 * not even in a specified order. The only positive is that each request
 * up to the last is exactly the same size.
 * A new problem:
 * Since I dunno how to seek to a point beyond the end of the file,
 * I can't sopport the exact semantic of write. Such files will never be 
 * written. :-(
 */
struct attrstat *
nfsproc_write_2(wa)
  writeargs *wa;
{
  static struct attrstat res;
  p_inode *inode = get_num(fh2inode(wa->file.data));
  struct cache *cp;
  fattr *fp;
  unsigned char wop[12];
  struct attrstat *gres;
  int c,len;

  if(!inode)
    {
      if(debug) printf("write: stale fh\n");
      res.status = NFSERR_STALE;
      return &res;
    }
  if(debug)
    printf("\twrite:%s off:%d l:%d\n",inode->name,wa->offset,wa->data.data_len);

  if ((cp = search_cache(writecache,inode->inode,wa->offset,wa->data.data_len)))
    {
      if(debug) printf("\twrite: cache hit\n");

      res.attrstat_u.attributes = cp->attr;
      res.status = NFS_OK;
      return &res;
    }

/* We have to get an attrib field */
  for(cp = writecache; cp; cp = cp->next)
    if(cp->inode == inode->inode)
      break;
  if(cp)
    {
      fp = &cp->attr;
      len = cp->actual_size;
    }
  else
    {
      gres = nfsproc_getattr_2((struct nfs_fh *)wa->file.data);
      if(gres->status != NFS_OK)
        {
	  res.status = gres->status;
	  return &res;
	}
      fp = &gres->attrstat_u.attributes;
      len = fp->size;
    }

/* modify this field for the wa */
  if(fp->size < wa->offset + wa->data.data_len)
    fp->size = wa->offset + wa->data.data_len;
  fp->blocks = (fp->size + (BLOCKSIZE - 1))/ BLOCKSIZE;

  fp->atime.seconds = fp->mtime.seconds = fp->ctime.seconds = time(0);
  res.attrstat_u.attributes = *fp;

  cp = add_cache(&writecache,
            inode->inode, wa->offset, wa->data.data_len, wa->data.data_val, fp);
  cp->actual_size = len;

/* Write out all possible blocks from the cache */
  for(;;)
    {
      for(cp = writecache; cp; cp = cp->next)
        {
	  if(debug > 2)
	    printf("\t\tCheck: %d=%d,%d,%d>=%d\n",
		inode->inode,cp->inode,cp->written,cp->actual_size,cp->offset);
	  if(cp->inode == inode->inode && !cp->written &&
	     cp->actual_size >= cp->offset)
	    break;
	}
      if(!cp) /* Can't write any blocks */
        break;
  
      if(debug)
        printf("\twriting off: %d, len: %d, act: %d\n",
					  cp->offset, cp->len, cp->actual_size);
      long2pstr(cp->offset, wop);
      short2pstr(cp->len, wop+4);
      if(sendcmd(PFS_OP_WRITE, inode->name, wop, 6) || 
	 senddata(cp->data, cp->len))
	{
	  if(debug) printf("write: dump failed\n");
	  res.status = psion_alive ? NFSERR_NOSPC : NFSERR_STALE;
	  return &res;
	}
      c = getbyte();
      
      cp->written = 1;
      len = cp->offset + cp->len;
      if(len > cp->actual_size)
        {
	  for(cp = writecache; cp; cp = cp->next)
	    if(cp->inode == inode->inode)
	       cp->actual_size = len;
	}
      if(debug)
        printf("\twritten: new length: %d\n", writecache->actual_size);
    }
    
  if(debug) 
    printf("\twrite -> ISOK (%d, %d %d)\n",
      res.attrstat_u.attributes.size,
      res.attrstat_u.attributes.fileid,
      res.attrstat_u.attributes.fsid);
  res.status = NFS_OK;
  return &res;
}

/* Dummy routines */
void *
nfsproc_writecache_2()
{
  static char res;
  if(debug) printf("writecache???\n");
  res = NFSERR_FBIG;
  return &res;
}

void *
nfsproc_null_2()
{
  static char res;
  if(debug) printf("null.\n");
  res = NFSERR_FBIG;
  return &res;
}

void *
nfsproc_root_2()
{
  static char res;
  if(debug) printf("root????\n");
  res = NFSERR_FBIG;
  return &res;
}

/* not possible operations */
nfsstat *
nfsproc_link_2(la)
  linkargs *la;
{
  static nfsstat res;

  if(debug) printf("link..\n");
  res = NFSERR_ACCES;
  return &res;
}


struct readlinkres *
nfsproc_readlink_2(fh)
  struct nfs_fh *fh;
{
  static readlinkres res;

  if(debug) printf("readlink...\n");
  res.status = NFSERR_ACCES;
  return &res;
}

nfsstat *
nfsproc_symlink_2(sa)
  symlinkargs *sa;
{
  static nfsstat res;

  if(debug) printf("symlink..\n");
  res = NFSERR_ACCES;
  return &res;
}
