/*
 *  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.
 *
 *  __fstat.c,v 1.1.1.1 1994/04/04 04:30:08 amiga Exp
 *
 *  __fstat.c,v
 * Revision 1.1.1.1  1994/04/04  04:30:08  amiga
 * Initial CVS check in.
 *
 *  Revision 1.2  1993/11/05  21:49:59  mw
 *  "grp/oth-perms,
 *
 *  Revision 1.1  1992/05/14  19:55:40  mwild
 *  Initial revision
 *
 */

#define KERNEL
#include "ixemul.h"
#include "kprintf.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 */
#define ST_PIPEFILE	-5	/* for pipes that support ExamineFH */
#endif

extern void *alloca ();

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

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

  omask = syscall (SYS_sigsetmask, ~0);
  __get_file (f);

  /* take special care of NIL:, /dev/null and friends ;-) */
  if (HANDLER_NIL(f))
    {
      f->f_stb.st_mode = S_IFCHR | 0777;
      f->f_stb.st_nlink = 1;
      f->f_stb.st_blksize = ix.ix_fs_buf_factor * 512;
      f->f_stb.st_blocks = 0;
      goto end;
    }

  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;
      if (!(fib->fib_Protection & FIBF_WRITE))
        mode |= S_IWUSR;
      if (!(fib->fib_Protection & FIBF_READ))
        mode |= S_IRUSR;
#ifdef FIBF_GRP_EXECUTE
      /* FIBF_GRP_EXECUTE requires at least OS3 headers */
      if (!(fib->fib_Protection & FIBF_GRP_EXECUTE))
        mode |= S_IXGRP;
      if (!(fib->fib_Protection & FIBF_GRP_WRITE))
        mode |= S_IWGRP;
      if (!(fib->fib_Protection & FIBF_GRP_READ))
        mode |= S_IRGRP;
      if (!(fib->fib_Protection & FIBF_OTR_EXECUTE))
        mode |= S_IXOTH;
      if (!(fib->fib_Protection & FIBF_OTR_WRITE))
        mode |= S_IWOTH;
      if (!(fib->fib_Protection & FIBF_OTR_READ))
        mode |= S_IROTH;
#endif

      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_PIPEFILE:
	  /* don't use S_IFIFO, we don't have a mkfifo() call ! */
	  mode |= S_IFCHR;
	  break;

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

      /* ARGLLLLL !!!
         Some (newer, hi Bill Hawes ;-)) handlers support EXAMINE_FH, but
         don't know yet about ST_PIPEFILE. So console windows claim they're
         plain files... Until this problem is fixed in a majority of
         handlers, do an explicit SEEK here to find those fakers.. */
      LastError(f) = 0;
      SendPacket3(f,__rwport,ACTION_SEEK,f->f_fh->fh_Arg1,0,OFFSET_CURRENT);
      __wait_packet (&f->f_sp);
      if (LastError (f))
        mode = (mode & ~S_IFREG) | S_IFCHR;

      f->f_stb.st_mode = mode;

      /* support for annotated attributes (cool name ;-)) */
      if (! strncmp (fib->fib_Comment, "!SP!", 4))
        {
          int sp_mode, sp_addr;

          if (sscanf (fib->fib_Comment + 4, "%x!%x", &sp_mode, &sp_addr) == 2)
            f->f_stb.st_mode = sp_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 always valid */
      f->f_stb.st_dev = (dev_t)(long)(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)) && S_ISREG(f->f_stb.st_mode))
	f->f_stb.st_blksize *= ix.ix_fs_buf_factor;
      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;
    }

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

  __release_file (f);
  syscall (SYS_sigsetmask, omask);
  return 0;
}
