/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define KERNEL
#include "ixemul.h"

#if __GNUC__ != 2
#define alloca __builtin_alloca
#endif

#ifndef ACTION_EXAMINE_FH
#define ACTION_EXAMINE_FH 1034
#endif

#ifndef ST_LINKDIR
#define ST_ROOT		1
#define ST_USERDIR	2
#define ST_SOFTLINK	3	/* looks like dir, but may point to a file! */
#define ST_LINKDIR	4	/* hard link to dir */
#define ST_FILE		-3	/* must be negative for FIB! */
#define ST_LINKFILE	-4	/* hard link to file */
#endif

extern void *alloca ();

/************************************************************************/
/*                                                                      */
/*    fstat() function.                                                 */
/*                                                                      */
/************************************************************************/

int
__fstat(struct file *f)
{
  long len, pos;
  struct FileInfoBlock *fib;
  struct InfoData *info;
  int omask;

  if (HANDLER_NIL(f)) return -1;

  omask = syscall (SYS_sigsetmask, ~0);

  info = alloca (sizeof (*info) + 2);
  info = LONG_ALIGN (info);
  fib  = alloca (sizeof (*fib) + 2);
  fib  = LONG_ALIGN (fib);
	
  __wait_packet (&f->f_sp);
  /* reset the error field */
  LastError (f) = 0;

  /* we now have two possibilities.. either the filesystem understands the
   * new ACTION_EXAMINE_FH packet, or we have to do some guesses at fields.. */
/*  SendPacket2 (f, __rwport, ACTION_EXAMINE_FH, CTOBPTR(f->f_fh), CTOBPTR(fib));*/
  SendPacket2 (f, __rwport, ACTION_EXAMINE_FH, f->f_fh->fh_Arg1, CTOBPTR(fib));
  __wait_packet (&f->f_sp);
  if (LastResult(f))
    {
      int mode;

      f->f_stb.st_nlink = 1; /* always one link entry per file, unless... */
      f->f_stb.st_amode = fib->fib_Protection;

      mode = 0;
      if (!(fib->fib_Protection & FIBF_EXECUTE))
        mode |= S_IXUSR | S_IXGRP | S_IXOTH;
      if (!(fib->fib_Protection & FIBF_WRITE))
        mode |= S_IWUSR | S_IWGRP | S_IWOTH;
      if (fib->fib_Protection & FIBF_DELETE)
        mode |= S_ISVTX;
      if (!(fib->fib_Protection & FIBF_READ))
        mode |= S_IRUSR | S_IRGRP | S_IROTH;

      switch (fib->fib_DirEntryType)
        {
        case ST_LINKDIR:
          f->f_stb.st_nlink ++;  /* we never get more than a link count of two.. */
        case ST_ROOT:
        case ST_USERDIR:
          mode |= S_IFDIR;
          break;

        /* at the moment, we NEVER get this entry, since we can't get a lock
         * on a symlink */
        case ST_SOFTLINK:
          mode |= S_IFLNK;
          break;

        case ST_LINKFILE:
          f->f_stb.st_nlink ++;
        case ST_FILE:
        default:
          mode |= S_IFREG;
        }

      f->f_stb.st_mode = mode;
      /* some kind of a default-size for directories... */
      f->f_stb.st_size = fib->fib_DirEntryType<0 ? fib->fib_Size : 1024; 
      f->f_stb.st_handler = (long)f->f_fh->fh_Type;
      f->f_stb.st_dev = (dev_t)f->f_stb.st_handler; /* trunc to 16 bit */
      f->f_stb.st_ino = fib->fib_DiskKey; 
      f->f_stb.st_atime =
      f->f_stb.st_ctime =
      f->f_stb.st_mtime = (8*365+2)*24*3600 + /* offset to unix-timesystem */
				  fib->fib_Date.ds_Days * 24 * 60 * 60 +
				  fib->fib_Date.ds_Minute * 60 +
				  fib->fib_Date.ds_Tick/TICKS_PER_SECOND;
      /* in a try to count the blocks used for filemanagement, we add one for
       * the fileheader. Note, that this is wrong for large files, where there
       * are some extension-blocks as well */
      f->f_stb.st_blocks = fib->fib_NumBlocks + 1;
    }
  else
    {
      /* ATTENTION: see lseek.c for Bugs in Seek() and ACTION_SEEK ! */
      /* seek to EOF */
      SendPacket3 (f, __rwport, ACTION_SEEK, f->f_fh->fh_Arg1, 0, OFFSET_END);
      __wait_packet (&f->f_sp);
      pos = LastResult (f);
      if (pos >= 0 && LastError (f) != ERROR_ACTION_NOT_KNOWN)
        {
          SendPacket3 (f, __rwport,ACTION_SEEK, f->f_fh->fh_Arg1, 
				   pos, OFFSET_BEGINNING);
          __wait_packet (&f->f_sp);
          len = LastError (f) ? -1 : LastResult (f);
        }
      else
        len = 0;

      bzero (&f->f_stb, sizeof(struct stat));

      f->f_stb.st_mode = ((len >= 0 && 
				     /* !IsInteractive(CTOBPTR(f->f_fh)) */! f->f_fh->fh_Port)
				     ? S_IFREG : S_IFCHR) | 0777;
      f->f_stb.st_handler = (long)f->f_fh->fh_Type;
      /* the following is a limited try to support programs, that assume that
       * st_dev is allways valid */
      f->f_stb.st_dev = (dev_t)f->f_fh->fh_Type; /* truncate to 16 bit */
      /* yet another kludge.. if we call this with different descriptors, we
       * should get different inode numbers.. grmpf.. */
      f->f_stb.st_ino = (ino_t)~(long)(f->f_fh);
      f->f_stb.st_nlink = 1; /* for now no problem.. 2.0... */
      /* len could be -1, if the device doesn't allow seeking.. */
      f->f_stb.st_size = len >= 0 ? len : 0;
      f->f_stb.st_atime =  
      f->f_stb.st_mtime =
      f->f_stb.st_ctime = syscall (SYS_time, 0);
    }

  /* try to find out block size of device, hey postman, it's packet-time
   * again:-)) */
  /* make sure, the packet is not in use anymore */
  __wait_packet(&f->f_sp);

  /* clear the info-structure. Since this packet is used by the console
   * handler to transmit the window pointer, it actually answers the
   * request, but doesn't set the not used fields to 0.. this gives HUGE
   * block lengths :-)) */
  bzero (info, sizeof(*info));
  SendPacket1(f,__rwport,ACTION_DISK_INFO,CTOBPTR(info));
  __wait_packet(&f->f_sp);

  f->f_stb.st_blksize = 0;
  if (LastResult (f) && info->id_BytesPerBlock)
    {
      f->f_stb.st_blksize = info->id_BytesPerBlock;
      if (!IsInteractive(CTOBPTR(f->f_fh))) 
	f->f_stb.st_blksize <<= 2; /* cut down on memory use... */
      if (! f->f_stb.st_blocks) 
	f->f_stb.st_blocks = ((f->f_stb.st_size + info->id_BytesPerBlock - 1) 
	 		       / info->id_BytesPerBlock);
    }

  if (! f->f_stb.st_blksize) 
    {
      f->f_stb.st_blksize = 512;
      f->f_stb.st_blocks = (f->f_stb.st_size + 511) >> 9;
    }

  /* reset error of packet. write() would fail if not */
  LastResult(f) = 0;

  syscall (SYS_sigsetmask, omask);
  return 0;
}
