/* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
/*
 * This is file XSTAT.C
 *
 * Internal assist functions which are common to stat(), fstat() and lstat().
 *
 *
 * Copyright (c) 1994-96 Eli Zaretskii <eliz@is.elta.co.il>
 *
 * This software may be used freely as long as the above copyright
 * notice is left intact.  There is no warranty on this software.
 *
 */

#include <libc/stubs.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <limits.h>
#include <time.h>
#include <errno.h>
#include <sys/vfs.h>
#include <dos.h>
#include <dpmi.h>
#include <libc/farptrgs.h>
#include <libc/dosio.h>
#include <libc/bss.h>

#include "xstat.h"

static int xstat_count = -1;

/* Some fields of struct stat are expensive to compute under DOS,
   because they require multiple disk accesses.  Fortunately, many
   DOS programs don't care about these.  To leave both pedants (like
   me) and performance-oriented guys happy, a variable is provided
   which controls which expensive fields should be computed.  To get
   the fastest stat() for your program, clear the bits for only those
   features you need and set the others.

   This improvement was suggested by Charles Sandmann
   <sandmann@clio.rice.edu> and DJ Delorie <dj@delorie.com>.  */

/* Please see the header file sys/stat.h for the definitions of _STAT_*. */

/* Should we bother about executables at all? */
#define _STAT_EXECBIT       (_STAT_EXEC_EXT | _STAT_EXEC_MAGIC)

/* By default, all the bits are reset (including as yet unused ones), so
   people who don't care will transparently have the full version.  */
unsigned short _djstat_flags;

/* As we depend on undocumented DOS features, we could fail in some
   incompatible environment or future DOS versions.  If we do, the
   following variable will have some of its bits set.  Each bit
   describes a single feature which we tried to use and failed.
   The function _djstat_describe_lossage() may be called to print a
   human-readable description of the bits which were set by the last
   call to f?stat().  This should make debugging f?stat() failures
   in an unanticipated environment a lot easier.

   This improvement was suggested by Charles Sandmann
   <sandmann@clio.rice.edu>.  */

unsigned short _djstat_fail_bits;

/* ----------------------------------------------------------------------- */

/* Convert file date and time to time_t value suitable for
   struct stat fields.  */

time_t
_file_time_stamp(unsigned int dos_ftime)
{
  struct tm file_tm;

  memset(&file_tm, 0, sizeof(struct tm));
  file_tm.tm_isdst = -1;    /* let mktime() determine if DST is in effect */

  file_tm.tm_sec  = (dos_ftime & 0x1f) * 2;
  file_tm.tm_min  = (dos_ftime >>  5) & 0x3f;
  file_tm.tm_hour = (dos_ftime >> 11) & 0x1f;
  file_tm.tm_mday = (dos_ftime >> 16) & 0x1f;
  file_tm.tm_mon  = ((dos_ftime >> 21) & 0x0f) - 1; /* 0 = January */
  file_tm.tm_year = (dos_ftime >> 25) + 80;

  return mktime(&file_tm);
}

/* Get time stamp of a DOS file packed as a 32-bit int.
 * This does what Borland's getftime() does, except it doesn't
 * pollute the application namespace and returns an int instead
 * of struct ftime with packed bit-fields.
 */


int
_getftime(int fhandle, unsigned int *dos_ftime)
{
  __dpmi_regs regs;

  regs.x.ax = 0x5700;
  regs.x.bx = fhandle;
  __dpmi_int(0x21, &regs);

  if (regs.x.flags & 1)
    {
      errno = __doserr_to_errno(regs.x.ax);
      return -1;
    }

  *dos_ftime = ((unsigned int)regs.x.dx << 16) + (unsigned int)regs.x.cx;

  return 0;
}

/* Cache the cluster size (aka block size) for each drive letter, so we can
 * populate the st_blksize of struct stat easily. The cluster size is
 * measured in bytes.
 *
 * ASSUMPTION: path has already been fixed by `_fixpath'.
 */

/* Comment copied from DJGPP 2.03's src/libc/compat/mntent/mntent.c:
 *
 * There may be a maximum of 32 block devices.  Novell Netware indeed
 * allows for 32 disks (A-Z plus 6 more characters from '[' to '\'')
 */
static blksize_t cache_blksize[32];
static int cache_blksize_count = -1;

blksize_t
_get_cached_blksize (const char *path)
{
  struct statfs sbuf;
  int           d; /* index into drive cache = drive_num - 1 */  
  static int    overmax_d = sizeof(cache_blksize) / sizeof(cache_blksize[0]);

  /* Force initialization in restarted programs (emacs).  */
  if (cache_blksize_count != __bss_count)
    {
      cache_blksize_count = __bss_count;
      memset(cache_blksize, 0, sizeof(cache_blksize));

      /* Default floppy drives to 512B block size, to improve performance. */
      cache_blksize[0] = cache_blksize[1] = 512;
    }

  /* Get the drive number. The fixed filename will begin with a lowercase
   * letter or a symbol. The symbols for drives > 'z:' occur straight
   * after 'Z' in ASCII.
   */
  if ((path[0] >= 'a') && (path[0] <= 'z'))
    d = path[0] - 'a';
  else
    d = path[0] - 'A';

  if ((d < 0) || (d >= overmax_d))
    {
      errno = ENODEV;
      return -1;
    }

  if (!cache_blksize[d])
    {
      if (_is_remote_drive(d + 1))
	{
	  /* Default remote drives to 4K block size, to improve performance.
	   *
	   * Also the size returned by statfs() may not be correct. Testing
	   * against files shared by Samba 2.0.10 on Linux kernel 2.2.19
	   * returned a 32K block size, even though the ext2 filesystem
	   * holding the share share had a 4K block size. */
	  cache_blksize[d] = 4096;
	}
      else
	{
	  /* No entry => retrieve cluster size */
	  if (statfs(path, &sbuf) != 0)
	    {
	      /* Failed, pass error through */
	      return -1;
	    }

	  cache_blksize[d] = sbuf.f_bsize;
	}
    }

  return cache_blksize[d];
}

/* Invent an inode number for those files which don't have valid DOS
 * cluster number.  These could be: devices like /dev/nul; empty
 * files which were not allocated disk space yet; or files on
 * networked drives, for which the redirector doesn't bring the
 * cluster number.
 *
 * To ensure proper operation of this function, you must call it
 * with a filename in some canonical form.  E.g., with a name
 * returned by truename(), or that returned by _fixpath().  The
 * point here is that the entire program MUST abide by these
 * conventions through its operation, or else you risk getting
 * different inode numbers for the same file.
 *
 * This function is due to Eric Backus and was taken with minor
 * modifications from stat.c, as included in DJGPP 1.11m5.
 * The function now returns 0 instead of -1 if it can't allocate
 * memory for a new name, so that f?stat() won't fail if the inode
 * is unavailable, but return zero inode instead.
 */

/*
  (c) Copyright 1992 Eric Backus

  This software may be used freely so long as this copyright notice is
  left intact.  There is no warranty on this software.
*/

struct name_list
{
  struct name_list *next;
  char             *name;
  unsigned          mtime;
  unsigned long     size;
  long              inode;
};

ino_t
_invent_inode(const char *name, unsigned time_stamp, unsigned long fsize)
{
  static struct name_list  *name_list[256];

  /* We count upwards from 2^28+1, which can't yield two files with
   * identical inode numbers: FAT16 uses maximum ~2^16 and FAT32 uses
   * maximum ~2^28. 
   */
#define INVENTED_INODE_START ( (1 << 28) + 1 )

  /* INODE_COUNT is declared LONG and not ino_t, because some DOS-based
   * compilers use short or unsigned short for ino_t.
   */
  static long        inode_count = INVENTED_INODE_START;

  struct name_list  *name_ptr, *prev_ptr;
  const char        *p;
  int                hash;

  /* Force initialization in restarted programs (emacs).  */
  if (xstat_count != __bss_count)
    {
      xstat_count = __bss_count;
      inode_count = INVENTED_INODE_START;
      memset (name_list, 0, sizeof name_list);
    }

  if (!name)
    return 0;

  /* Skip over device and leading slash.  This will allow for less
   * inode numbers to be used, because there is nothing bad in generating
   * identical inode for two files which belong to different drives.
   */
  if (*name && name[1] == ':' && (name[2] == '\\' || name[2] == '/'))
  {
    /* If this is a root directory, return inode = 1.  This is compatible
       with the code on stat.c which deals with root directories. */
    if (name[3] == 0)
      return (ino_t)1;

    name += 3;
  }

  /* If the passed name is empty, invent a new inode unconditionally.
   * This is for those unfortunate circumstances where we couldn't
   * get a name (e.g., fstat() under Novell).  For these we want at
   * least to ensure that no two calls will get the same inode number.
   * The lossage here is that you get different inodes even if you call
   * twice with the same file.  Sigh...
   */
  if (!*name)
    {
      ino_t retval = inode_count;

      inode_count++;
      return retval;
    }

  /* We could probably use a better hash than this */
  p = name;
  hash = 0;
  while (*p != '\0')
    hash += *p++;
  hash &= 0xff;

  /* Have we seen this string? */
  name_ptr = name_list[hash];
  prev_ptr = name_ptr;
  while (name_ptr)
    {
      if (strcmp(name, name_ptr->name) == 0 &&
          name_ptr->mtime == time_stamp &&
          name_ptr->size  == fsize)
        break;
      prev_ptr = name_ptr;
      name_ptr = name_ptr->next;
    }

  if (name_ptr)
    /* Same string, time stamp, and size, so same inode */
    return name_ptr->inode;
  else
    {
      ino_t retval;
      
      /* New string with same hash code */
      name_ptr = (struct name_list *)malloc(sizeof *name_ptr);
      if (name_ptr == 0)
        return 0;
      name_ptr->next = (struct name_list *)0;
      name_ptr->name = (char *)malloc(strlen(name)+1);
      if (name_ptr->name == 0)
      {
	free(name_ptr);
	return 0;
      }
      strcpy(name_ptr->name, name);
      name_ptr->mtime = time_stamp;
      name_ptr->size = fsize;
      name_ptr->inode = inode_count;
      if (prev_ptr)
        prev_ptr->next = name_ptr;
      else
        name_list[hash] = name_ptr;
      retval = inode_count;
      inode_count++; /* Increment for next call. */

      return retval;
    }
}
