/* 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() and fstat().
 *
 *
 * 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 <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <limits.h>
#include <time.h>
#include <errno.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>.  */

#define _STAT_INODE         1   /* should we bother getting inode numbers? */
#define _STAT_EXEC_EXT      2   /* get execute bits from file extension? */
#define _STAT_EXEC_MAGIC    4   /* get execute bits from magic signature? */
#define _STAT_DIRSIZE       8   /* compute directory size? */
#define _STAT_ROOT_TIME  0x10   /* try to get root dir time stamp? */
#define _STAT_WRITEBIT   0x20   /* fstat() needs write bit? */

/* 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;
}

/* 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];

  /* If the st_inode is wider than a short int, we will count up
   * from USHRT_MAX+1 and thus ensure there will be no clashes with
   * actual cluster numbers.
   * Otherwise, we must count downward from USHRT_MAX, which could
   * yield two files with identical inode numbers: one from actual
   * DOS cluster number, and another from this function.  In the
   * latter case, we still have at least 80 inode numbers before
   * we step into potentially used numbers, because some FAT entries
   * are reserved to mean EOF, unused entry and other special codes,
   * and the FAT itself uses up some clusters which aren't counted.
   */
  static int         dir = (sizeof(ino_t) > 2 ? 1 : -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 = (sizeof(ino_t) > 2
                                    ? (long)USHRT_MAX + 1L
                                    : USHRT_MAX);

  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 = (sizeof(ino_t) > 2 ? (long)USHRT_MAX + 1L : USHRT_MAX);
      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 += dir;
      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 += dir; /* increment or decrement as appropriate */

      return retval;
    }
}
