/*
 *  linux/ibcs/xstat.c
 *
 *  Copyright (C) 1991, 1992  Linus Torvalds
 *
 *  Hacked by Eric Youngdale for iBCS (1993, 1994).
 *  Added to by Drew Sullivan, modified by EY for xstat (used by SVr4).
 *
 * $Id: xstat.c,v 1.3 1994/05/05 10:45:42 mike Exp $
 * $Source: /var/CVS/ibcs/xstat.c,v $
 */

/*
 * The xstat interface is used by SVr4, and is effectively an extension
 * to stat.  The general idea is that stat has some limitations (chiefly
 * 16 bit inode numbers), and the solution in SVr4 was to add an entirely
 * new syscall.  The /usr/include/sys/stat.h header file defines stat as xstat
 * so that the new interface is used.  The one advantage of xstat is that
 * we pass a version number so that it is possible to tell exactly what
 * the application is expecting, and it is easy to do the right thing.
 * There is usually an inline wrapper function in /usr/include/sys/stat.h
 * to perform this conversion.
 */

#define R3_MKNOD_VERSION 1     /* SVr3 */
#define R4_MKNOD_VERSION 2     /* SVr4 */
#define R3_STAT_VERSION 1      /* SVr3 */
#define R4_STAT_VERSION 2      /* SVr4 */

/* Various functions to provide compatibility between the linux
   syscalls and the ABI ABI compliant calls */

#include <linux/kernel.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <asm/segment.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/sys.h>
#include <ibcs/ibcs.h>
#include <ibcs/abi4.h>

/* Convert a linux dev number into the SVr4 equivalent. */
#define R4_DEV(DEV) ((DEV & 0xff) | ((DEV & 0xff00) << 10))

static void cp_xstat(struct inode * inode, struct xstat * statbuf)
{
	struct xstat tmp = {0, };
	unsigned int blocks, indirect;

	tmp.st_dev = R4_DEV(inode->i_dev);
	tmp.st_ino = inode->i_ino;
	tmp.st_mode = inode->i_mode;
	tmp.st_nlink = inode->i_nlink;
	tmp.st_uid = inode->i_uid;
	tmp.st_gid = inode->i_gid;
	tmp.st_rdev = R4_DEV(inode->i_rdev);
	tmp.st_size = inode->i_size;
	tmp.st_atim.tv_sec = inode->i_atime;
	tmp.st_mtim.tv_sec = inode->i_mtime;
	tmp.st_ctim.tv_sec = inode->i_ctime;

/*
 * st_blocks and st_blksize are approximated with a simple algorithm if
 * they aren't supported directly by the filesystem. The minix and msdos
 * filesystems don't keep track of blocks, so they would either have to
 * be counted explicitly (by delving into the file itself), or by using
 * this simple algorithm to get a reasonable (although not 100% accurate)
 * value.
 */

/*
 * Use minix fs values for the number of direct and indirect blocks.  The
 * count is now exact for the minix fs except that it counts zero blocks.
 * Everything is in BLOCK_SIZE'd units until the assignment to
 * tmp.st_blksize.
 */
#define D_B   7
#define I_B   (BLOCK_SIZE / sizeof(unsigned short))

	if (!inode->i_blksize) {
		blocks = (tmp.st_size + BLOCK_SIZE - 1) / BLOCK_SIZE;
		if (blocks > D_B) {
			indirect = (blocks - D_B + I_B - 1) / I_B;
			blocks += indirect;
			if (indirect > 1) {
				indirect = (indirect - 1 + I_B - 1) / I_B;
				blocks += indirect;
				if (indirect > 1)
					blocks++;
			}
		}
		tmp.st_blocks = (BLOCK_SIZE / 512) * blocks;
		tmp.st_blksize = BLOCK_SIZE;
	} else {
		tmp.st_blocks = inode->i_blocks;
		tmp.st_blksize = inode->i_blksize;
	}
	memcpy_tofs(statbuf,&tmp,sizeof(tmp));
}

int ibcs_xstat(int vers, char * path, struct xstat * stat4){
  int status;

  switch(vers){
  case R3_STAT_VERSION:
    return ibcs_stat(path, (struct ibcs_stat *) &stat4);
    break;
  case R4_STAT_VERSION:
    {
      int error;
      struct inode * inode;

      error = verify_area(VERIFY_WRITE, stat4, sizeof (*stat4));
      if (error)
	return error;

	error = namei(path, &inode);
	if (error)
		return error;
	cp_xstat(inode,stat4);
	iput(inode);
	return 0;
    };
    break;
  default:
    return -EINVAL;
  };
  return status;
}

int ibcs_lxstat(int vers, char * path, struct xstat * stat4){
  int status;

  switch(vers){
  case R3_STAT_VERSION:
    return ibcs_lstat(path, (struct ibcs_stat *) stat4);
    break;
  case R4_STAT_VERSION:
    {
      int error;
      struct inode * inode;

      error = verify_area(VERIFY_WRITE, stat4, sizeof (*stat4));
      if (error)
	return error;

      error = lnamei(path, &inode);
      if (error)
	return error;
      cp_xstat(inode,stat4);
      iput(inode);
      return 0;
    };
    break;
  default:
    return -EINVAL;
  };
  return status;
}

int ibcs_fxstat(int vers, int fd, struct xstat * stat4){
  int status;

  switch(vers){
  case R3_STAT_VERSION:
    return ibcs_fstat(fd, (struct ibcs_stat *) &stat4);
    break;
  case R4_STAT_VERSION:
    {
      int error;
      struct file * f;
      struct inode * inode;

      error = verify_area(VERIFY_WRITE, stat4, sizeof (*stat4));
      if (error)
	return error;

      if (fd >= NR_OPEN || !(f=current->FD[fd]) || !(inode=f->f_inode))
	return -EBADF;

      cp_xstat(inode,stat4);
      return 0;
    };
    break;
  default:
    return -EINVAL;
  };
  return status;
}

int ibcs_xmknod(int vers, const char * path, mode_t mode, dev_t dev){
  unsigned int major, minor;

  switch(vers){
  case R3_MKNOD_VERSION:  /* More or less compatible with current linux */
    return SYS(mknod)(path, mode, dev);
  case R4_MKNOD_VERSION:
    minor = dev & 0x3ffff;
    major = dev >> 18;
    if(minor > 0xff || major > 0xff) return -EINVAL;
    return SYS(mknod)(path, mode, ((major << 8) | minor));
  default:
    return -EINVAL;
  };
}
