/*
 * Copyright (c) 1995, 1996, 1997, 1998 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the Kungliga Tekniska
 *      Högskolan and its contributors.
 * 
 * 4. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* 	$Id: xfs_inodeops.c,v 1.10 1998/01/21 16:19:50 map Exp $	 */
/*      $Source: /usr/local/cvsroot/arla/xfs/linux/xfs_inodeops.c,v $      */

#ifndef lint
static char vcid[] = "$Id: xfs_inodeops.c,v 1.10 1998/01/21 16:19:50 map Exp $";
#endif /* lint */

#define __NO_VERSION__

#include "xfs_locl.h"
#include "xfs_cache.h"
#include "xfs/xfs_dirent.h"
#include <linux/binfmts.h>
#include <linux/file.h>

static int
xfs_lookup (struct inode * dir, const char * name, int len,
	    struct inode ** result);

static int
xfs_open(struct inode *i, struct file *f);

static int
xfs_read_file(struct inode *inode, struct file *file, char *buf, int count);

static int
xfs_readdir(struct inode * inode, struct file * file,
	    void * dirent, filldir_t filldir);

static int
xfs_readlink(struct inode *inode, char *buffer, int buflen);

static int
xfs_follow_link(struct inode * dir, struct inode * inode,
		int flag, int mode, struct inode ** res_inode);

static int
xfs_release_file (struct inode *inode, struct file *file);

static struct file_operations xfs_file_operations = {
        NULL,                   /* lseek */
        xfs_read_file,          /* read */
        NULL,                   /* write */
        NULL,                   /* readdir */
        NULL,                   /* select */
        NULL,                   /* ioctl */
        NULL,                   /* mmap */
        xfs_open,               /* open */
        xfs_release_file,       /* release */
        NULL,                   /* fsync */
	NULL,			/* fasync */
	NULL,			/* check_media_change */
	NULL			/* revalidate */
};

struct inode_operations xfs_file_inode_operations = {
        &xfs_file_operations,   /* default file operations */
        NULL,                   /* create */
        NULL,                   /* lookup */
        NULL,                   /* link */
        NULL,                   /* unlink */
        NULL,                   /* symlink */
        NULL,                   /* mkdir */
        NULL,                   /* rmdir */
        NULL,                   /* mknod */
        NULL,                   /* rename */
        NULL,                   /* readlink */
        NULL,                   /* follow_link */
        generic_readpage,       /* readpage */
        NULL,                   /* writepage */
        NULL,                   /* bmap */
        NULL,                   /* truncate */
        NULL,                   /* permission */
        NULL                    /* smap */
};

static struct file_operations xfs_dead_operations = {
        NULL,                   /* lseek */
        NULL,                   /* read */
        NULL,                   /* write */
        NULL,                   /* readdir */
        NULL,                   /* select */
        NULL,                   /* ioctl */
        NULL,                   /* mmap */
        NULL,                   /* open */
        NULL,                   /* release */
        NULL,                   /* fsync */
	NULL,			/* fasync */
	NULL,			/* check_media_change */
	NULL			/* revalidate */
};

struct inode_operations xfs_dead_inode_operations = {
        &xfs_dead_operations,   /* default file operations */
        NULL,                   /* create */
        NULL,                   /* lookup */
        NULL,                   /* link */
        NULL,                   /* unlink */
        NULL,                   /* symlink */
        NULL,                   /* mkdir */
        NULL,                   /* rmdir */
        NULL,                   /* mknod */
        NULL,                   /* rename */
        NULL,                   /* readlink */
        NULL,                   /* follow_link */
        NULL,                   /* readpage */
        NULL,                   /* writepage */
        NULL,                   /* bmap */
        NULL,                   /* truncate */
        NULL,                   /* permission */
        NULL                    /* smap */
};

static struct file_operations xfs_dir_operations = {
        NULL,                   /* lseek */
        NULL,                   /* read */
        NULL,                   /* write */
        xfs_readdir,            /* readdir */
        NULL,                   /* select */
        NULL,                   /* ioctl */
        NULL,                   /* mmap */
        NULL,                   /* open */
        NULL,                   /* release */
        NULL,                   /* fsync */
	NULL,			/* fasync */
	NULL,			/* check_media_change */
	NULL			/* revalidate */
};

struct inode_operations xfs_dir_inode_operations = {
        &xfs_dir_operations,   /* default file operations */
        NULL,                   /* create */
        xfs_lookup,             /* lookup */
        NULL,                   /* link */
        NULL,                   /* unlink */
        NULL,                   /* symlink */
        NULL,                   /* mkdir */
        NULL,                   /* rmdir */
        NULL,                   /* mknod */
        NULL,                   /* rename */
        NULL,                   /* readlink */
        NULL,                   /* follow_link */
        NULL,                   /* readpage */
        NULL,                   /* writepage */
        NULL,                   /* bmap */
        NULL,                   /* truncate */
        NULL,                   /* permission */
        NULL                    /* smap */
};

static struct file_operations xfs_link_operations = {
        NULL,                   /* lseek */
        NULL,                   /* read */
        NULL,                   /* write */
        NULL,                   /* readdir */
        NULL,                   /* select */
        NULL,                   /* ioctl */
        NULL,                   /* mmap */
        NULL,                   /* open */
        NULL,                   /* release */
        NULL,                   /* fsync */
	NULL,			/* fasync */
	NULL,			/* check_media_change */
	NULL			/* revalidate */
};

struct inode_operations xfs_link_inode_operations = {
        &xfs_link_operations,   /* default file operations */
        NULL,                   /* create */
        NULL,                   /* lookup */
        NULL,                   /* link */
        NULL,                   /* unlink */
        NULL,                   /* symlink */
        NULL,                   /* mkdir */
        NULL,                   /* rmdir */
        NULL,                   /* mknod */
        NULL,                   /* rename */
        xfs_readlink,           /* readlink */
        xfs_follow_link,        /* follow_link */
        NULL,                   /* readpage */
        NULL,                   /* writepage */
        NULL,                   /* bmap */
        NULL,                   /* truncate */
        NULL,                   /* permission */
        NULL                    /* smap */
};

int xfs_lookup (struct inode * dir, const char * name, int len,
		 struct inode ** result)
{
    struct xfs_message_getnode msg;
    struct xfs *xfsp;
    int errno = 0;
    
    struct xfs_node *d;
    struct xfs_node *x;
    
    XFSDEB(XDEBVNOPS, ("xfs_lookup\n"));
    XFSDEB(XDEBVNOPS, ("xfs_lookup: name: %s\n", name));
    XFSDEB(XDEBVNOPS, ("xfs_lookup: namelen: %d\n", len));
    
    if (dir == NULL)
	return -ENOENT;
    if (!S_ISDIR(dir->i_mode)) {
	iput(dir);
	return -ENOTDIR;
    }
    xfsp = XFS_FROM_VNODE(dir);
    d = VNODE_TO_XNODE(dir);
    if (d == NULL || xfsp == NULL) {
	iput(dir);
	return -ENODEV;
    }

    *result = NULL;
    do {
#ifdef notdef_but_correct_and_not_ported
	errno = xfs_access(dvp, VEXEC, cred);
	if (errno != 0)
	    goto done;
#endif

	if ((x = xfs_cache_lookup(VNODE_TO_XNODE(dir), name, len)) == NULL) {
	    msg.header.opcode = XFS_MSG_GETNODE;

	    msg.cred.uid = current->uid;
	    msg.cred.pag = xfs_get_pag();
	    msg.parent_handle = d->handle;
	    
	    memcpy(msg.name, name, len);
	    msg.name[len]='\0';
	    
	    errno = xfs_message_rpc(xfsp->fd, &msg.header, sizeof(msg));
	    
	    if (errno == 0)
		errno = ((struct xfs_message_wakeup *) &msg)->errno;
	    XFSDEB(XDEBVNOPS, ("xfs_lookup errno: %d\n", errno));
	} else {
	    *result=iget(dir->i_sb,(ino_t) x);
	    XFSDEB(XDEBVNOPS, ("xfs_lookup inode: %p xnode: %p\n", *result, VNODE_TO_XNODE(*result)));
	    XFSDEB(XDEBVNOPS, ("xfs_lookup mode: 0%o\n", (*result)->i_mode));
	    goto done;
	}
    } while (errno == 0);

done:
    XFSDEB(XDEBVNOPS, ("xfs_lookup() errno = %d\n", errno));
    iput(dir);
    return -errno;
}

static int
xfs_open_valid(struct inode *vp, u_int tok)
{
  struct xfs *xfsp = XFS_FROM_VNODE(vp);
  struct xfs_node *xn = VNODE_TO_XNODE(vp);
  int errno = 0;

  XFSDEB(XDEBVFOPS, ("xfs_open_valid\n"));
  
  do {
    if (!XFS_TOKEN_GOT(xn, tok))
      {
	struct xfs_message_open msg;
	msg.header.opcode = XFS_MSG_OPEN;
	msg.cred.uid = current->uid;
	msg.cred.pag = xfs_get_pag();
	msg.handle = xn->handle;
	msg.tokens = tok;
	errno = xfs_message_rpc(xfsp->fd, &msg.header, sizeof(msg));
	if (errno == 0)
	  errno = ((struct xfs_message_wakeup *) &msg)->errno;
      }
    else
      {
	goto done;
      }
  } while (errno == 0);

 done:
  return -errno;
}

static int
xfs_open(struct inode *i, struct file *f)
{
  XFSDEB(XDEBVNOPS, ("xfs_open f->f_mode: %d\n",f->f_mode));

  if (f->f_mode & FMODE_WRITE)
    return xfs_open_valid(i, XFS_OPEN_NW);
  else
    return xfs_open_valid(i, XFS_OPEN_NR);
}

static int
xfs_data_valid(struct inode *vp, u_int tok)
{
  struct xfs *xfsp = XFS_FROM_VNODE(vp);
  struct xfs_node *xn = VNODE_TO_XNODE(vp);
  int errno = 0;
  
  do {
    if (!XFS_TOKEN_GOT(xn, tok))
      {
	struct xfs_message_getdata msg;
	msg.header.opcode = XFS_MSG_GETDATA;
	msg.cred.uid = current->uid;
	msg.cred.pag = xfs_get_pag();
	msg.handle = xn->handle;
	msg.tokens = tok;
	errno = xfs_message_rpc(xfsp->fd, &msg.header, sizeof(msg));
	if (errno == 0)
	  errno = ((struct xfs_message_wakeup *) &msg)->errno;
      }
    else
      {
	goto done;
      }
  } while (errno == 0);

 done:
  return -errno;
}


static int xfs_read_file(struct inode *inode, struct file *file, char *buf, int count)
{
  int errno = 0;

  XFSDEB(XDEBVNOPS, ("xfs_read_file file->f_pos: %d count: %d\n",file->f_pos,count));

  errno = xfs_data_valid(inode, XFS_DATA_R);

  if (errno == 0) {
    struct inode *t = DATA_FROM_VNODE(inode);
    t->i_count++;
    if (t->i_op &&
	t->i_op->default_file_ops &&
	t->i_op->default_file_ops->read)
      errno = t->i_op->default_file_ops->read(t, file, buf, count);
    else {	
	printk("XFS Panic! read not defined in cache file system\n");
	errno = -EINVAL;
    }
    iput(t);
  }
  return errno;
}

static int xfs_readdir(struct inode * inode, struct file * file,
		       void * dirent, filldir_t filldir)
{
    int errno = 0;
    off_t offset, begin_offset;
    char *buf;

    XFSDEB(XDEBVNOPS, ("xfs_readdir\n"));
    
    errno = xfs_data_valid(inode, XFS_DATA_R);
    if (errno == 0) {
	struct inode *t = DATA_FROM_VNODE(inode);
	t->i_count++;
	
	while (file->f_pos < t->i_size && errno >= 0) {
	  XFSDEB(XDEBVNOPS, ("xfs_readdir file->f_pos: %d t->i_size: %d\n",file->f_pos,t->i_size));
	    if (t->i_op &&
		t->i_op->default_file_ops &&
		t->i_op->default_file_ops->read) {
		buf = xfs_alloc(XFS_DIRENT_BLOCKSIZE);
		begin_offset = file->f_pos &~ (XFS_DIRENT_BLOCKSIZE - 1);
		offset = file->f_pos & (XFS_DIRENT_BLOCKSIZE - 1);
		
		XFSDEB(XDEBVNOPS, ("xfs_readdir begin_offset: %d offset: %d\n",begin_offset,offset));
		errno = t->i_op->default_file_ops->read(t, file, buf, XFS_DIRENT_BLOCKSIZE - offset);
		if (errno == XFS_DIRENT_BLOCKSIZE - offset) {
		  XFSDEB(XDEBVNOPS, ("xfs_readdir errno: %d\n",errno));
		    while (offset < XFS_DIRENT_BLOCKSIZE) {
		      XFSDEB(XDEBVNOPS, ("xfs_readdir offset: %d\n",offset));
			if (filldir (dirent,
				     ((struct xfs_dirent *) (buf + offset))->d_name,
				     ((struct xfs_dirent *) (buf + offset))->d_namlen,
				     offset+begin_offset,
				     ((struct xfs_dirent *) (buf + offset))->d_fileno) < 0) {
			    iput(t);
			    return 0;
			}
			offset += ((struct xfs_dirent *) (buf + offset))->d_reclen;
		    }
		} else if (errno > 0) {
		    printk("XFS Panic! expected %d bytes, got %d in xfs_readdir\n", XFS_DIRENT_BLOCKSIZE, errno);
		    errno = -EINVAL;
		}
	    } else {
		errno = -EINVAL;
		printk("XFS Panic! read not defined in cache file system\n");
	    }
	}
	iput(t);
    }
    
    return errno;
}

static int
xfs_readlink(struct inode *inode, char *buffer, int buflen)
{
  int errno = 0;
  int fd;
  struct file *file;

  XFSDEB(XDEBVNOPS, ("xfs_readlink\n"));

  errno = xfs_data_valid(inode, XFS_DATA_R);
  if (errno == 0)
  {
      off_t size;

      struct inode *t = DATA_FROM_VNODE(inode);
      if (t == NULL)
	  return -EINVAL;
      fd = open_inode (t, 0);
      if (fd < 0) {
	  printk("XFS Panic! cannot open file\n");
	  return fd;
      }
      file = current->files->fd[fd];
      if (!file) {
	  printk("XFS Panic! cannot fget open file\n");
	  iput(t);
	  return -EINVAL;
      }
      size = (buflen > inode->i_size) ?
	(inode->i_size) :
	(buflen);
      XFSDEB(XDEBVNOPS, ("xfs_readlink buflen: %d inode->i_size: %d size: %d\n",buflen, inode->i_size, size));
      if (t->i_op &&
	  t->i_op->default_file_ops &&
	  t->i_op->default_file_ops->read)
	  errno = t->i_op->default_file_ops->read(t, file, buffer, size);
      else {	
	  printk("XFS Panic! read not defined in cache file system\n");
	  errno = -EINVAL;
      }
      sys_close(fd);
  }

  return errno;
}

static int xfs_follow_link(struct inode * dir, struct inode * inode,
			   int flag, int mode, struct inode ** res_inode)
{
    int error;
    char *buffer;

    *res_inode = NULL;
    if (dir == NULL) {
	dir = current->fs->root;
	dir->i_count++;
    }
    if (!inode) {
	iput(dir);
	return -ENOENT;
    }
    if (!S_ISLNK(inode->i_mode)) {
	iput(dir);
	*res_inode = inode;
	return 0;
    }
    if (current->link_count > 5) {
	iput(dir);
	iput(inode);
	return -ELOOP;
    }
    buffer = xfs_alloc(MAXPATHLEN + 1);
    if (buffer == NULL) {
	iput(dir);
	iput(inode);
	return -ENOMEM;
    }
    error = xfs_readlink(inode, buffer, MAXPATHLEN);
    if (error < 0) {
	iput(dir);
	iput(inode);
	xfs_free(buffer);
	return error;
    }
    if (error <= MAXPATHLEN)
	buffer[error]='\0';
    current->link_count++;
    error = open_namei (buffer, 0, 0, res_inode, dir);
    current->link_count--;
    iput(inode);
    return error;
}

static int
xfs_release_file (struct inode *inode, struct file *file)
{
    struct xfs *xfsp = XFS_FROM_VNODE(inode);
    struct xfs_node *xn = VNODE_TO_XNODE(inode);
    int errno = 0;
    u_int flag = 0 ;
    struct xfs_message_putdata msg;
    
    XFSDEB(XDEBVNOPS, ("xfs_close\n"));
    
    if (file->f_mode & FMODE_WRITE)
	flag |= XFS_WRITE;
    if (file->f_mode & FMODE_READ)
	flag |= XFS_READ;
    
    msg.header.opcode = XFS_MSG_PUTDATA;
    msg.cred.uid = current->uid;
    msg.cred.pag = xfs_get_pag();
    msg.handle = xn->handle;
    msg.flag = flag ;
    errno = xfs_message_rpc(xfsp->fd, &msg.header, sizeof(msg));
    if (errno == 0)
        errno = ((struct xfs_message_wakeup *) &msg)->errno;
    
    return errno;

}
