#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include "basic.h"

#define ALLOC My_Malloc
#define FREE(x) free(x)

static void *
My_Malloc(size_t size);
#include <stdlib.h>

static void *
My_Malloc(size_t size)
{
  return malloc(size+1);
}
  

# include	"fslib.h"

# include	<linux/userfs_fs.h>
# include	<linux/userfs_types.h>
# include	<linux/userfs_types.c>

static void	(*errorlog) (const char *str);

static void 
ERROR(const char *str)
{
  if (errorlog != NULL)
    (*errorlog)(str);
}

/*****************************************************************************/
/*                                                                           */
/*  namalloc                                                                 */
/*                                                                           */
/* From ALLOC we got 1 byte more than needed!                                */
/*                                                                           */
/*****************************************************************************/
static char *
namalloc(up_name *nam)
{
  char *result = nam->elems;
  result[nam->nelem] = '\0';
  return result;
}

static int
do_kernel_read (register int fd, register char *ptr, register int len)
{
  register int	n;
  register int	got;

  got = 0;
  while (len > 0)
    if ((n = read (fd, ptr, len)) > 0) {
      got += n;
      len -= n;
      ptr += n;
    } else if (n == 0)
      break;
    else if (errno != EINTR) {
      ERROR ("do_kernel_read: Got unknown error");
      got = -1;
      break;
    }
  return (got);
}

static int
do_kernel_write (register int fd, register char *ptr, register int len)
{
  register int	n;
  register int	wrote;

  wrote = 0;
  while (len > 0)
    if ((n = write (fd, ptr, len)) > 0) {
      wrote += n;
      len -= n;
      ptr += n;
    } else if (n == 0) {
      ERROR ("do_kernel_write: Write returns 0?");
      break;
    } else if (errno != EINTR) {
      ERROR ("do_kernel_write: Got unknown error");
      wrote = -1;
      break;
    }
  return (wrote);
}

static void
inode2fsino (Inode *src, up_inode *dst)
{
  dst -> mode = src -> mode;
  dst -> nlink = src -> nlink;
  dst -> uid = src -> uid;
  dst -> gid = src -> gid;
  dst -> size = src -> size;
  dst -> atime = src -> atime;
  dst -> mtime = src -> mtime;
  dst -> ctime = src -> ctime;
  dst -> rdev = src -> rdev;
  dst -> blksize = (Ulong) src -> blksize;
  dst -> blocks = (Ulong) src -> blocks;
}

static void
fsino2inode (up_inode *src, Inode *dst)
{
  dst -> mode = src -> mode;
  dst -> nlink = src -> nlink;
  dst -> uid = src -> uid;
  dst -> gid = src -> gid;
  dst -> size = src -> size;
  dst -> atime = src -> atime;
  dst -> mtime = src -> mtime;
  dst -> ctime = src -> ctime;
  dst -> rdev = src -> rdev;
  dst -> blksize = (ulong) src -> blksize;
  dst -> blocks = (ulong) src -> blocks;
}

static void
cred2perm (up_cred *cred, Permission *perm)
{
  perm -> uid = cred -> uid;
  perm -> euid = cred -> euid;
  perm -> suid = cred -> suid;
  perm -> gid = cred -> gid;
  perm -> egid = cred -> egid;
  perm -> sgid = cred -> sgid;
  perm -> umask = cred -> umask;
  perm -> gcnt = cred -> groups.nelem;
  perm -> groups = cred->groups.elems;
  cred->groups.elems = NULL;
}

static void 
filesystem (int infd, int outfd, char *mpoint, fscalls *fc)
{
  up_preamble	pre;
  upp_repl	reply;
  upp_create_s	create_s;
  upp_create_r	create_r;
  upp_lookup_s	lookup_s;
  upp_lookup_r	lookup_r;
  upp_close_s	close_s;
  upp_read_s	read_s;
  upp_read_r	read_r;
  upp_write_s	write_s;
  upp_write_r	write_r;
  upp_truncate_s	truncate_s;
  upp_readdir_s	readdir_s;
  upp_readdir_r	readdir_r;
  upp_link_s	link_s;
  upp_unlink_s	unlink_s;
  upp_symlink_s	symlink_s;
  upp_readlink_s	readlink_s;
  upp_readlink_r	readlink_r;
  upp_followlink_s
    followlink_s;
  upp_followlink_r
    followlink_r;
  upp_mount_r	mount_r;
  upp_iread_s	iread_s;
  upp_iread_r	iread_r;
  upp_iwrite_s	iwrite_s;
  upp_statfs_r	statfs_r;
  upp_iput_s	iput_s;
  upp_open_s	open_s;
  upp_open_r	open_r;
  upp_permission_s
    permission_s;
  upp_rename_s	rename_s;
  upp_multireaddir_s
    multireaddir_s;
  upp_multireaddir_r
    multireaddir_r;
  upp_notify_change_s
    notify_change_s;
  TBuffer		*tbuf;
  TBuffer		tbuff;
  Directory	*dir;
  Inode		*inode;
  Inode		inobuf;
  Permission	perm;
  FS_Status	*fsstat;
  int		psiz = sizeof_up_preamble (& pre);
  int		rplsiz = sizeof_upp_repl (& reply);
  unsigned char	pbuf[psiz + 4],
  rbuf[rplsiz + 4];
  unsigned char	buf[4096];
  char		*tmp, *tmp2;
  int		n;
  ulong		count, t;

  errorlog = fc -> errorlog;
  for (;;) {
    n = do_kernel_read (infd, (char *) pbuf, psiz);
    if (n <= 0)
      break;
    else if (n < psiz) {
      ERROR ("Header read couldn't be satisfied");
      continue;
    }
    decode_up_preamble (& pre, pbuf);
    if (pre.version != UP_VERSION) {
      ERROR ("Version mismatch");
      break;
    }
    if (pre.size != 0)
      if ((n = do_kernel_read (infd, (char *) buf, pre.size)) != pre.size) {
        ERROR ("Body read couldn't be satisfied");
        continue;
      }
    if ((pre.isreq != UP_ENQ) && (pre.isreq != UP_REQ)) {
      ERROR ("Unknown request type received");
      continue;
    }
    reply.version = UP_VERSION;
    reply.seq = pre.seq;
    reply.op = pre.op;
    reply.isreq = UP_REPL;
    reply.size = 0;
    reply.errno = ENOSYS;
    switch (pre.op) {
    case up_create:
      if (fc -> ufs_create) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_create_s (& create_s, buf);
          if ((tmp = namalloc (& create_s.name))) {
            cred2perm (& create_s.cred, & perm);
            create_r.file.handle =
              (*fc -> ufs_create) (create_s.dir.handle,
                                   create_s.mode,
                                   create_s.rdev,
                                   & perm,
                                   tmp);
            free (tmp);
            free (perm.groups);
            if (create_r.file.handle == 0)
              reply.errno = errno;
            else
              reply.size = encode_upp_create_r (& create_r, buf) - buf;
          } else
            reply.errno = ENOMEM;
        }
      }
      break;
    case up_lookup:
      if (fc -> ufs_lookup) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_lookup_s (& lookup_s, buf);
          if ((tmp = namalloc (& lookup_s.name))) {
            lookup_r.handle.handle =
              (*fc -> ufs_lookup) (lookup_s.dir.handle,
                                   tmp);
            free (tmp);
            if (lookup_r.handle.handle == 0)
              reply.errno = errno;
            else
              reply.size = encode_upp_lookup_r (& lookup_r, buf) - buf;
          } else
            reply.errno = ENOMEM;
        }
      }
      break;
    case up_close:
      if (fc -> ufs_close) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_close_s (& close_s, buf);
          if ((*fc -> ufs_close) (close_s.file.handle,
                                  (ulong) close_s.ctok) == Fail)
            reply.errno = errno;
        }
      }
      break;
    case up_read:
      if (fc -> ufs_read) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_read_s (& read_s, buf);
          if ((tbuf = (*fc -> ufs_read) (read_s.file.handle,
                                         read_s.off,
                                         read_s.size,
                                         (ulong) read_s.ctok))) {
            read_r.data.nelem = tbuf -> size;
            read_r.data.elems = tbuf -> ptr;
            reply.size = encode_upp_read_r (& read_r, buf) - buf;
          } else
            reply.errno = errno;
        }
      }
      break;
    case up_write:
      if (fc -> ufs_write) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_write_s (& write_s, buf);
          tbuff.size = write_s.data.nelem;
          tbuff.ptr = write_s.data.elems;
          write_r.wrote = (*fc -> ufs_write) (write_s.file.handle,
                                              write_s.off,
                                              & tbuff,
                                              (ulong) write_s.ctok);
          FREE (write_s.data.elems);
          if (write_r.wrote != 0)
            reply.size = encode_upp_write_r (& write_r, buf) - buf;
          else
            reply.errno = errno;
        }
      }
      break;
    case up_truncate:
      if (fc -> ufs_truncate) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_truncate_s (& truncate_s, buf);
          if ((*fc -> ufs_truncate) (truncate_s.file.handle,
                                     truncate_s.size) == Fail)
            reply.errno = errno;
        }
      }
      break;
    case up_fsync:
      if (fc -> ufs_fsync) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          if ((*fc -> ufs_fsync) () == Fail)
            reply.errno = errno;
        }
      }
      break;
    case up_readdir:
      if (fc -> ufs_readdir) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_readdir_s (& readdir_s, buf);
          if ((dir = (fc -> ufs_readdir) (readdir_s.dir.handle,
                                          readdir_s.off,
                                          (ulong) readdir_s.ctok))) {
            readdir_r.file.handle = dir -> handle;
            readdir_r.off = dir -> off;
            readdir_r.name.nelem = (Ulong) strlen (dir -> fname);
            readdir_r.name.elems = (char *)(dir -> fname);
            reply.size = encode_upp_readdir_r (& readdir_r, buf) - buf;
          } else
            reply.errno = errno;
        }
      }
      break;
    case up_link:
      if (fc -> ufs_link) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_link_s (& link_s, buf);
          if ((tmp = namalloc (& link_s.name))) {
            if ((*fc -> ufs_link) (link_s.ofile.handle,
                                   link_s.dir.handle,
                                   tmp) == Fail)
              reply.errno = errno;
            free (tmp);
          } else
            reply.errno = ENOMEM;
        }
      }
      break;
    case up_unlink:
      if (fc -> ufs_unlink) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_unlink_s (& unlink_s, buf);
          if ((tmp = namalloc (& unlink_s.name))) {
            if ((*fc -> ufs_unlink) (unlink_s.dir.handle,
                                     tmp) == Fail)
              reply.errno = errno;
            free (tmp);
          }
        }
      }
      break;
    case up_symlink:
      if (fc -> ufs_symlink) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_symlink_s (& symlink_s, buf);
          tmp = namalloc (& symlink_s.name);
          tmp2 = namalloc (& symlink_s.symname);
          if (tmp && tmp2) {
            cred2perm (& symlink_s.cred, & perm);
            if ((*fc -> ufs_symlink) (symlink_s.dir.handle,
                                      tmp,
                                      tmp2,
                                      & perm) == Fail)
              reply.errno = errno;
            free (tmp);
            free (tmp2);
            free (perm.groups);
          } else
            reply.errno = ENOMEM;
        }
      }
      break;
    case up_readlink:
      if (fc -> ufs_readlink) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_readlink_s (& readlink_s, buf);
          if ((tmp = (*fc -> ufs_readlink) (readlink_s.link.handle))) {
            readlink_r.name.nelem = strlen (tmp);
            readlink_r.name.elems = tmp;
            reply.size = encode_upp_readlink_r (& readlink_r, buf) - buf;
          } else
            reply.errno = errno;
        }
      }
      break;
    case up_followlink:
      if (fc -> ufs_followlink) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_followlink_s (& followlink_s, buf);
          if ((tmp = (*fc -> ufs_followlink) (followlink_s.dir.handle,
                                              followlink_s.link.handle,
                                              followlink_s.flag,
                                              followlink_s.mode))) {
            followlink_r.path.nelem = strlen (tmp);
            followlink_r.path.elems = tmp;
            reply.size = encode_upp_followlink_r (& followlink_r, buf) - buf;
          } else
            reply.errno = errno;
        }
      }
      break;
    case up_mount:
      if (fc -> ufs_mount) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          mount_r.root.handle = (*fc -> ufs_mount) ();
          if (mount_r.root.handle != 0)
            reply.size = encode_upp_mount_r (& mount_r, buf) - buf;
          else
            reply.errno = errno;
        }
      }
      break;
    case up_umount:
      if (fc -> ufs_umount) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          if ((*fc -> ufs_umount) () == Fail)
            reply.errno = errno;
        }
      }
      break;
    case up_iread:
      if (fc -> ufs_iread) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_iread_s (& iread_s, buf);
          if ((inode = (*fc -> ufs_iread) (iread_s.handle.handle))) {
            iread_r.handle = iread_s.handle;
            inode2fsino (inode, & iread_r.ino);
            reply.size = encode_upp_iread_r (& iread_r, buf) - buf;
          } else
            reply.errno = errno;
        }
      }
      break;
    case up_iwrite:
      if (fc -> ufs_iwrite) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_iwrite_s (& iwrite_s, buf);
          fsino2inode (& iwrite_s.ino, & inobuf);
          if ((*fc -> ufs_iwrite) (iwrite_s.handle.handle, & inobuf) == Fail)
            reply.errno = errno;
        }
      }
      break;
    case up_statfs:
      if (fc -> ufs_statfs) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ)
          if ((fsstat = (*fc -> ufs_statfs) ())) {
            statfs_r.bsize = fsstat -> bsize;
            statfs_r.blocks = fsstat -> blocks;
            statfs_r.bfree = fsstat -> bfree;
            statfs_r.bavail = fsstat -> bavail;
            statfs_r.files = fsstat -> files;
            statfs_r.ffree = fsstat -> ffree;
            statfs_r.fsid = fsstat -> fsid;
            statfs_r.namelen = fsstat -> namelen;
            reply.size = encode_upp_statfs_r (& statfs_r, buf) - buf;
          } else
            reply.errno = errno;
      }
      break;
    case up_iput:
      if (fc -> ufs_iput) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_iput_s (& iput_s, buf);
          if ((*fc -> ufs_iput) (iput_s.handle.handle) == Fail)
            reply.errno = errno;
        }
      }
      break;
    case up_open:
      if (fc -> ufs_open) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_open_s (& open_s, buf);
          cred2perm (& open_s.cred, & perm);
          open_r.ctok = (ulong) (*fc -> ufs_open) (open_s.file.handle, & perm);
          free (perm.groups);
          if (open_r.ctok != 0)
            reply.size = encode_upp_open_r (& open_r, buf) - buf;
          else
            reply.errno = errno;
        }
      }
      break;
    case up_permission:
      if (fc -> ufs_permission) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_permission_s (& permission_s, buf);
          cred2perm (& permission_s.cred, & perm);
          if ((*fc -> ufs_permission) (permission_s.file.handle,
                                       permission_s.mask,
                                       & perm) == Fail)
            reply.errno = errno;
        }
      }
      break;
    case up_rename:
      if (fc -> ufs_rename) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_rename_s (& rename_s, buf);
          tmp = namalloc (& rename_s.oname);
          tmp2 = namalloc (& rename_s.nname);
          if (tmp && tmp2) {
            if ((*fc -> ufs_rename) (rename_s.odir.handle,
                                     tmp,
                                     rename_s.ndir.handle,
                                     tmp2) == Fail)
              reply.errno = errno;
            free (tmp);
            free (tmp2);
          } else
            reply.errno = ENOMEM;
        }
      }
      break;
    case up_multireaddir:
      if (fc -> ufs_multireaddir) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_multireaddir_s (& multireaddir_s, buf);
          if ((dir = (*fc -> ufs_multireaddir) (multireaddir_s.dir.handle,
                                                multireaddir_s.off,
                                                (ulong) multireaddir_s.ctok,
                                                & count))) {
            if ((multireaddir_r.elems =
                 (upp_readdir_r *) cmalloc ((count + 1) *
                                            sizeof (upp_readdir_r)))) {
              for (t = 0; t < count; ++t) {
                multireaddir_r.elems[t].file.handle = dir[t].handle;
                multireaddir_r.elems[t].off = dir[t].off;
                multireaddir_r.elems[t].name.elems = (char *)dir[t].fname;
                multireaddir_r.elems[t].name.nelem = strlen (dir[t].fname);
              }
              multireaddir_r.nelem = count;
              reply.size =
                encode_upp_multireaddir_r (& multireaddir_r, buf) - buf;
              free (multireaddir_r.elems);
            } else
              reply.errno = ENOMEM;
          } else if (! errno) {
            multireaddir_r.nelem = 0;
            multireaddir_r.elems = NULL;
            reply.size =
              encode_upp_multireaddir_r (& multireaddir_r, buf) - buf;
          } else
            reply.errno = errno;
        }
      }
      break;
    case up_notify_change:
      if (fc -> ufs_notify_change) {
        reply.errno = 0;
        if (pre.isreq == UP_REQ) {
          decode_upp_notify_change_s (& notify_change_s, buf);
          if ((*fc -> ufs_notify_change)(notify_change_s.handle.handle,
                                         &(notify_change_s.iattr)) == Fail)
            reply.errno = errno;
        }
      }
      break;
    }
    encode_upp_repl (& reply, rbuf);
    if ((n = do_kernel_write (outfd, (char *) rbuf, rplsiz)) != rplsiz) {
      ERROR ("Header write request couldn't be satisfied");
      continue;
    }
    if ((reply.size != 0) && (! reply.errno))
      if ((n = do_kernel_write (outfd,
                                (char *) buf,
                                reply.size)) != reply.size) {
        ERROR ("Body write request couldn't be satisfied");
        continue;
      }
  }
}

int
userfs (int argc, char **argv, fscalls *fc)
{
  int	infd, outfd, found;
  char	*mpoint;

  infd = -1;
  outfd = -1;
  mpoint = NULL;

  argv += 1;                    /* Progname not used */
  argc -= 1;

  while (argc >= 2) {
    found = 0;
    if (   (strcmp(argv[0], "-i") == 0)
        && (infd == -1)) {
      infd = atoi(argv[1]);
      found = 1;
    }
    if (   (strcmp(argv[0], "-o") == 0)
        && (outfd == -1)) {
      outfd = atoi(argv[1]);
      found = 1;
    }
    if (   (strcmp(argv[0], "-m") == 0)
        && (mpoint == NULL)) {
      mpoint = argv[1];
      found = 1;
    }
    if (found) {
      argv += 2;
      argc -= 2;
    } else {
      break;
    }
  }

  if (infd < 0) {
    ERROR ("No read descriptor given");
    return (-1);
  }
  if (outfd < 0) {
    ERROR ("No write descriptor given");
    return (-1);
  }
  if (mpoint == NULL) {
    ERROR ("No mount point given");
    return (-1);
  }
  if (fc -> init)
    if ((*fc -> init) (argc, argv) == Fail) {
      ERROR ("init failed");
      return (-1);
    }
  filesystem (infd, outfd, mpoint, fc);
  if (fc -> deinit)
    if ((*fc -> deinit) () == Fail) {
      ERROR ("deinit failed");
      return (-1);
    }
  {
    /*
     *	These lines are to SHUT UP gcc!
     */
    char	*p = 0;
    char	x[32];
    int	a;
    float	f = 0.0;
    double	d = 0.0;

    (void) encode_void (p, x);
    (void) decode_void (p, x);
    a = sizeof_void (p);
    (void) encode_float (& f, x);
    (void) decode_float (& f, x);
    a = sizeof_float (& f);
    (void) encode_double (& d, x);
    (void) decode_double (& d, x);
    a = sizeof_double (& d);
  }
  return (0);
}
