delorie.com/archives/browse.cgi   search  
Mail Archives: djgpp-workers/1997/11/09/09:52:13

Date: Sun, 9 Nov 1997 16:52:06 +0200 (IST)
From: Eli Zaretskii <eliz AT is DOT elta DOT co DOT il>
To: djgpp-workers AT delorie DOT com
Subject: `fstat' lossage on Windows 95
Message-ID: <Pine.SUN.3.91.971109165102.12063C-100000@is>
MIME-Version: 1.0

I have discovered a nasty problem with the version of `fstat' which
should go into v2.02 on Windows 95.  This problem sometimes makes
`fstat' return identical inode numbers for two different files: a Very
Bad Thing.  I think I have found a solution, but since I cannot find
this documented anywhere in the references available to me, I need
some help from you to make sure it works on other systems as well.  If
you have access to Windows 9X, please help me with this testing.

I explain the problem below.  For those who aren't interested in the
gory details, but still want to help me, here's what you need to do:

     1) Save the two attached files fstat.c and xstat.h to any directory.

     2) Compile the attached source for `fstat' (it is different from
        stock v2.01 library):

	      gcc -Wall -O -g -DTEST -o fstat fstat.c

     3) Run the test program fstat.exe like this:

	      redir -o fstat.out -eo fstat 0 fstat.c xstat.h ...

	add as much file names as you can instead of the dots, but do
	NOT use wildcards (`redir' doesn't support them currently).
	The file names may be identical, but I prefer them to be
	different from one another.

     4) Send me the file fstat.out.

     5) I will analyse your output, and if it shows that the new
        `fstat' fails on your system, I will contact you and ask to
        run some more tests.

Here's the description of the problem.  When 32-bit File Access is
turned ON (as it is by default on Windows 9X), `fstat' should invent
the inode numbers, since the DOS internal structure (SFT, the System
File table) it uses on plain DOS to get hold of the starting cluster
number are bypassed by the 32BFA.  However, it seems that Windows 95
sometimes creates fake SFT entries that seem to have good info about
file's size and time stamp, but a bogus cluster number.  I could not
figure out what are the conditions under which such SFT entries are
created.  However, it seems that in all such cases, the file name part
of the SFT entry is all-blank (i.e., you have 8+3=11 blanks there).
This is illegal even under Windows 95 (try saying `dir > "        "
and see), so the new version tests this as well and doesn't use the
inode from all-blank entries like that.  I need to make sure that
Windows doesn't ever create such bigus SFT entries with non-blank file
name.

----------------------- fstat.c -------------------------------------
/* Copyright (C) 1997 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
/* This is file FSTAT.C */
/*
 *   Almost a 100% U**X-compatible fstat() substitute.
 *
 * Usage:
 *
 *   That's easy: just put this in libc.a, and then call fstat() as usual.
 *
 * Rationale:
 *
 *   Many Unix-born programs make heavy use of fstat() library
 *   function to make decisions on files' equality, size, access
 *   attributes etc.  In the MS-DOS environment, many implementations
 *   of fstat() are crippled, because DOS makes it very hard to get to
 *   certain pieces of information about files and directories.  Thus
 *   porting a program to DOS is usually an exercise in #ifdef'ing.
 *   This implementation facilitates porting Unix programs to MS-DOS
 *   by providing an fstat() which is much more Unix-compatible than
 *   those of most DOS-based C compilers (e.g., Borland's).
 *   Specifically, the following issues are taken care of:
 *
 *      1. Mode bits are returned for the actual file, files are NOT
 *         reported read-only (as in Borland's library fstat()).
 *      2. Mode bits are set for all 3 groups (user, group, other).
 *      3. Device code (st_dev, st_rdev) is correctly reported (0 = 'A',
 *         1 = 'B' etc.).
 *      4. Character device names (such as /dev/con, lpt1, aux etc.) are
 *         treated as if they were on a special drive called `@:'
 *         (st_dev = -1).  The "character special" mode bit is set
 *         for these devices.
 *      5. The inode number (st_ino) is taken from the starting cluster
 *         number of the file.  If the cluster number is unavailable, it
 *         is invented using the file's name in a manner that minimizes
 *         the possibility of inventing an inode which already belongs
 *         to another file.  See below for details.
 *      6. Executable files are found based on files' extensions and
 *         magic numbers present at their beginning, and their execute
 *         bits are set.
 *
 *   Lossage:
 *
 *      Beautiful as the above sounds, this implementation does fail
 *      under certain circumstances.  The following is a list of known
 *      problems:
 *
 *      1. Files open on networked drives mounted by Novell Netware
 *         before revision 4.x cannot be traced using DOS System File
 *         Table.  Therefore, name, extension, file attributes and the
 *         drive letter are not available for these.  Until somebody
 *         tells me how this information can be obtained under Novell,
 *         nothing could be done here.  For the time being, these files
 *         will get st_dev of -2.
 *      2. For files which reside on networked drives, the inode number
 *         is always invented, because network redirectors usually do
 *         not bring that info with them.
 *      3. Empty files do not have a starting cluster number, because
 *         DOS doesn't allocate one until you actually write something
 *         to a file.  For these the inode is also invented.
 *      4. If the st_ino field is a 16 bit number, the invented inode
 *         numbers are from 65535 and down, assuming that most disks have
 *         unised portions near their end.  Valid cluster numbers are 16-bit
 *         unisgned integers, so a possibility of a clash exists, although
 *         the last 80 or more cluster numbers are unused on all drives
 *         I've seen.  If the st_ino is 32 bit, then invented inodes are
 *         all greater than 64k, which totally eliminates a possibility
 *         of a clash with an actual cluster number.
 *      5. As this implementation relies heavily on undocumented DOS
 *         features, it will fail to get actual file info in environments
 *         other than native DOS, such as DR-DOS, OS/2 etc.  For these,
 *         the function will return whatever info is available with
 *         conventional DOS calls, which is no less than any other
 *         implementation could do.  This fstat() might also fail for
 *         future DOS versions, if the layout of DOS System File Table
 *         is changed; however, this seems unlikely.
 *
 * Copyright (c) 1994-1996 Eli Zaretskii <eliz AT is DOT elta DOT co DOT il>
 *
 * This software may be used freely so long as this copyright notice is
 * left intact.  There is no warranty on this software.
 *
 */

/*
 * Tested with DJGPP port of GNU C compiler, versions 1.11maint5 and 1.12m2,
 * under MS-DOS 3.3, 4.01, 5.0, 6.20 (with and without DoubleSpace) and
 * with networked drives under XFS 1.76, Novell Netware 3.22, and
 * TSoft NFS 0.24Beta.
 *
 */

#include <libc/stubs.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <dpmi.h>
#include <go32.h>
#include <libc/farptrgs.h>
#include <libc/bss.h>

#include "xstat.h"

#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)

/* Should we bother about any access bits?  */
#define _STAT_ACCESS        (_STAT_WRITEBIT | _STAT_EXECBIT)

/* Do we need SFT info at all? */
#define _STAT_NEEDS_SFT     (_STAT_WRITEBIT | _STAT_EXEC_EXT | _STAT_INODE)

/*
 * Lower-level assist functions to get a starting cluster of a file,
 * which will serve as an inode number.  The starting cluster is
 * found in the System File Table entry (an internal structure of
 * DOS) which belongs to our file.
 *
 * Much of the following code is derived from file H2NAME.C, which
 * came with ``Undocumented DOS'', 1st edition.
 */

/* Selectors for conventional memory and our program's memory.  */
static unsigned short dos_mem_base, our_mem_base;

/* Array of SFT entry sizes as function of DOS version. */
static size_t sft_size_list[] = {0, 0x28, 0x35, 0x3b};

/* Actual size of SFT entry for the version of DOS under
   which we are running.  */
static size_t sft_size;

/* Static array to hold a copy of SFT entry.  Should be at
   least as long as the largest number in sft_size_list[].  */
static unsigned char sft_buf[0x40];

/* Linear address of pointer to Job File Table (JFT) which
   is an array of indices into the SFT which correspond to
   open file handles.  */
static unsigned long htbl_ptr_addr;

/* True version of DOS (not the one simulated by SETVER).  */
static unsigned short dos_major, dos_minor;

/* Segment and offset of first SFT sub-table.  All searches
   start from this address.  */
static unsigned short sft_start_seg, sft_start_off;

/* This holds the failure bits from last call to fstat_init(),
   so we can return them each time fstat_assist() is called.  */
static unsigned short fstat_init_bits;

/* Holds the last seen value of __bss_count, to be safe for
   restarted programs (emacs).  */
static int fstat_count = -1;

/* The address of the PSP of the caller.  */
static unsigned long  psp_addr;

/* Initialization routine, called once per program run.
 * Finds DOS version, SFT entry size and addresses of
 * program handle table and first SFT sub-table.
 */
static int
fstat_init(void)
{
  __dpmi_regs    regs;
  int            sft_ptr_addr;
  unsigned short true_dos_version;

  /* Selectors for _farXXX() functions.  */
  dos_mem_base = _dos_ds;
  our_mem_base = _my_ds();

  /* Each DOS program has a table of file handles which are used by
   * DOS open() call.  This table holds, for each handle which is in
   * use, the index into the System File Tables' list which contains
   * data about this handle.  The pointer to that handle table is found
   * at offset 34h in the program's PSP.
   */

  /* Linear address of pointer to the handle table.  We postpone
   * dereferencing this pointer to obtain the address of the handle
   * table until we're actually called for a specific handle, because
   * somebody could in the meanwhile change that address, e.g. by
   * calling INT 21h/AX=67h to enlarge the maximum number of file
   * handles.
   */
  htbl_ptr_addr = psp_addr + 0x34;

  /*
   * Find the pointer to the first subtable in the list of SFT's.
   * It is stored at offset 4 in the DOS List-of-Lists area, a ptr
   * to which is returned by the undocumented DOS function 52h.
   * We don't check FLAGS after 52h returns because Ralph Brown's
   * Interrupt List doesn't say FLAGS are set to indicate a
   * failure.
   */
  regs.h.ah = 0x52;
  __dpmi_int(0x21, &regs);

  /* Linear addres of pointer to SFT list.  */
  sft_ptr_addr = MK_FOFF(regs.x.es, regs.x.bx + 4);
  
  /* SFT entry size depends on DOS version.
     We need exact knowledge about DOS internals, so we need the
     TRUE DOS version (not the simulated one by SETVER), if that's
     available.  */
  true_dos_version = _get_dos_version(1);
  dos_major = true_dos_version >> 8;
  dos_minor = true_dos_version & 0xff;
  sft_size = sft_size_list[dos_major > 4 ? 3 : dos_major - 1];
  if (!sft_size)        /* unsupported DOS version */
    {
      _djstat_fail_bits |= _STFAIL_OSVER;
      return 0;
    }

  /* Segment and offset of start of SFT list.  */
  sft_start_off = _farpeekw(dos_mem_base, sft_ptr_addr);
  sft_start_seg = _farpeekw(dos_mem_base, sft_ptr_addr + 2);

  return 1;
}

/* Given a handle, copy contents of System File Table entry which
 * belongs to that handle into a local buffer, and return the index
 * into the SFT array where our entry was found.  In case of failure
 * to use SFT, return -2.  If FHANDLE is illegal, return -1.
 */
static short
get_sft_entry(int fhandle)
{
  unsigned long  sft_seg;
  unsigned short sft_off;
  unsigned long  htbl_addr;
  short          sft_idx, retval;
  __dpmi_regs	 regs;

  _djstat_fail_bits = fstat_init_bits;

  /* Force initialization if we were restarted (emacs).  */
  if (fstat_count != __bss_count)
    {
      fstat_count = __bss_count;
      dos_major = 0;
    }

  /* Find the PSP address of the current process.  */
  regs.h.ah = 0x62;	/* Get PSP address */
  __dpmi_int(0x21, &regs);
  psp_addr = ( (unsigned long)regs.x.bx << 4 ) & 0xfffff;

  /* If first time called, initialize.  */
  if (!dos_major && !fstat_init())
    {
      fstat_init_bits = _djstat_fail_bits;
      return -2;
    }

  /* Test file handle for validity.
   * For DOS 3.x and later, the number of possible file handles
   * is at offset 32h in the PSP; for prior versions, it is 20.
   */
  if (fhandle < 0
      || fhandle >= (_osmajor < 3 ?
		     20
		     : _farpeekw(dos_mem_base, psp_addr + 0x32)))
    return -1;

  /* Linear address of the handle table. */
  htbl_addr = MK_FOFF(_farpeekw(dos_mem_base, htbl_ptr_addr + 2),
                      _farpeekw(dos_mem_base, htbl_ptr_addr));

  /* Index of the entry for our file handle in the SFT array.  */
  retval = sft_idx = _farpeekb(dos_mem_base, htbl_addr + fhandle);

  if (sft_idx < 0)      /* invalid file handle or bad handle table */
    {
      _djstat_fail_bits |= _STFAIL_SFTIDX;
      return -1;
    }

  /* Given the index into the SFT list, find our SFT entry.
   * The list consists of arrays (sub-tables) of entries, each sub-
   * table preceeded by a header.  The header holds a pointer to the
   * next sub-table in the list and number of entries in this sub-table.
   * The list is searched until the sub-table which contains our
   * target is found, then the sub-table entries are skipped until
   * we arrive at our target.
   */

  /* Segment (shifted 4 bits left) and offset of start of SFT list.  */
  sft_off = sft_start_off;
  sft_seg = MK_FOFF(sft_start_seg, 0);

  while (sft_off != 0xFFFF)
    {
      unsigned long entry_addr   = sft_seg + sft_off;
      short         subtable_len = _farpeekw(dos_mem_base, entry_addr + 4);

      if (sft_idx < subtable_len)
        { /* Our target is in this sub-table.  Pull in the entire
           * SFT entry for use by fstat_assist().
           */
          movedata(dos_mem_base,
                   (entry_addr + 6 + sft_idx * sft_size) & 0x000fffff,
                   our_mem_base, (unsigned int)sft_buf, sft_size);
          return retval;
        }
      /* Our target not in this subtable.
       * Subtract the number of entries in this sub-table from the
       * index of our entry, and proceed to next sub-table.
       */
      sft_idx -= subtable_len;
      sft_off  = _farpeekw(dos_mem_base, entry_addr);
      sft_seg  = MK_FOFF(_farpeekw(dos_mem_base, entry_addr + 2), 0);
    }

  /* Get here only by error, which probably means unsupported DOS version. */
  _djstat_fail_bits |= _STFAIL_SFTNF;
  return -2;
}

/* On LFN platforms, we can get all the 3 time-related fields.  */

static void
set_fstat_times (int fhandle, struct stat *stat_buf)
{
  if (_USE_LFN)
    {
      time_t access_time;
      unsigned int create_time;

      /* Access time is currently date only (time is zeroed).  */
      access_time = _file_time_stamp (_lfn_get_ftime (fhandle, _LFN_ATIME));
      if (access_time > stat_buf->st_atime)
	stat_buf->st_atime = access_time;

      /* Creation time might be zero if the file was created
	 by a DOS program which doesn't support LFN API.  */
      create_time = _lfn_get_ftime (fhandle, _LFN_CTIME);
      if (create_time)
	stat_buf->st_ctime = _file_time_stamp (create_time);
    }
}

/* fstat_assist() is where all the actual work is done.
 * It uses SFT entry, if available and its contents are verified.
 * Otherwise, it finds all the available info by conventional
 * DOS calls.
 */

static int
fstat_assist(int fhandle, struct stat *stat_buf)
{
  short          have_trusted_values = 1;
  unsigned int   dos_ftime;
  char           drv_no;
  short          dev_info;
  unsigned char  is_dev;
  unsigned char  is_remote;
  short          sft_idx = -1;
  unsigned short sft_fdate, sft_ftime;
  long           sft_fsize;
  unsigned short trusted_ftime = 0, trusted_fdate = 0;
  long           trusted_fsize = 0;

  if ((dev_info = _get_dev_info(fhandle)) == -1)
    return -1;	/* errno set by _get_dev_info() */

  _djstat_fail_bits = 0;

  /* Get pointer to an SFT entry which holds data for our handle. */
  if ( (_djstat_flags & _STAT_NEEDS_SFT) == 0 &&
       (sft_idx = get_sft_entry(fhandle)) == -1)
    {
      errno = EBADF;
      return -1;
    }

  /* Initialize buffers. */
  memset(stat_buf, 0, sizeof(struct stat));
  dos_ftime = 0;

  /* Get some info about this handle by conventional DOS calls.  These
   * will serve as verification of SFT entry contents and also as
   * fall-back in case SFT method fails.
   */
  if (_getftime(fhandle, &dos_ftime) == 0 &&
      (trusted_fsize = __filelength(fhandle)) != -1L)
    {
      trusted_ftime = dos_ftime & 0xffff;
      trusted_fdate = dos_ftime >> 16;
    }
  else
    have_trusted_values = 0;

  if (dev_info & 0x0080)
    {
      is_dev = 1;
      is_remote = 0;	/* device can't be remote */
    }
  else
    {
      is_dev = 0;
      if (dev_info & 0x8000)
	is_remote = 1;
      else
	is_remote = 0;
    }

  /* First, fill the fields which are constant under DOS. */
  stat_buf->st_uid = getuid();
  stat_buf->st_gid = getgid();
  stat_buf->st_nlink = 1;
#ifndef  NO_ST_BLKSIZE
  stat_buf->st_blksize = _go32_info_block.size_of_transfer_buffer;
#endif

  /* If SFT entry for our handle is required and available, we will use it.  */
  if ( (_djstat_flags & _STAT_NEEDS_SFT) == 0 && sft_idx >= 0)
    {
      /* Determine positions of data items in the SFT. */
      size_t fattr_ofs, name_ofs, ext_ofs, fsize_ofs, fdate_ofs,
             ftime_ofs, clust_ofs;

      switch (dos_major)
        {
          case 2:
              fattr_ofs  = 2;
              drv_no     = sft_buf[3] - 1;      /* 1 = 'A' etc. */
              name_ofs   = 4;
              ext_ofs    = 0x0b;
              fsize_ofs  = 0x13;
              fdate_ofs  = 0x17;
              ftime_ofs  = 0x19;
              clust_ofs  = 0x1c;
              is_remote  = 0;   /* DOS 2.x didn't have remote files */
              break;

          case 3:
              fattr_ofs  = 4;
              drv_no     = sft_buf[5] & 0x3f;
              if (dos_minor == 0)
                {
                  name_ofs = 0x21;
                  ext_ofs  = 0x29;
                }
              else      /* DOS 3.1 - 3.3x */
                {
                  name_ofs = 0x20;
                  ext_ofs  = 0x28;
                }
              clust_ofs  = 0x0b;
              ftime_ofs  = 0x0d;
              fdate_ofs  = 0x0f;
              fsize_ofs  = 0x11;
              break;

          default:      /* DOS 4 and up */
              fattr_ofs  = 4;
              drv_no     = sft_buf[5] & 0x3f;
              clust_ofs  = 0x0b;
              ftime_ofs  = 0x0d;
              fdate_ofs  = 0x0f;
              fsize_ofs  = 0x11;
              name_ofs   = 0x20;
              ext_ofs    = 0x28;

        }

      if (is_dev)
        {
          /* We have a character device.
           * We will pretend as if they all reside on a special
           * drive `@:', which is illegal in DOS, and just happens
           * to give a neat st_dev (= '@' - 'A') = -1.
           */

          stat_buf->st_dev = -1;
#ifdef  HAVE_ST_RDEV
          stat_buf->st_rdev = -1;
#endif

          if ( (_djstat_flags & _STAT_INODE) == 0 )
            {
              /* Character device names are all at most 8-character long. */
              short i   = 8;
              char *src = sft_buf + name_ofs;
              char dev_name[16], *dst = dev_name + 7;

              strcpy(dev_name, "@:\\dev\\        "); /* pad with 8 blanks */
              while (i-- && *src != ' ')             /* copy non-blank chars */
                *dst++ = *src++;

              stat_buf->st_ino = _invent_inode(dev_name, 0, 0);
            }

          /* Should we make printer devices write-only here? */
          stat_buf->st_mode |= (S_IFCHR | READ_ACCESS | WRITE_ACCESS);

          /* We will arrange things so that devices have current time in
           * the access-time and modified-time fields of struct stat.
           */
          stat_buf->st_atime = stat_buf->st_mtime = time(0);

          /* MS-DOS returns the time of boot when _getftime() is called
           * for character devices, but this is undocumented and
           * unsupported by DOS clones (e.g. DR-DOS).  It is also
           * inconsistent with our stat().  Therefore, we will arrange
           * things so that devices have zero (the beginning of times)
           * in creation-time field.
           */
          dos_ftime = 0;
          stat_buf->st_ctime = _file_time_stamp(dos_ftime);

          return 0;
        }

      /* Files are not allowed to fail DOS calls for their time
       * stamps and size.
       */
      else if (have_trusted_values)
        {
	  unsigned char *pname = sft_buf + name_ofs;

          /* This is a regular, existing file.  It cannot be a
           * directory, because DOS won't let us open() a directory.
           * Each file under MS-DOS is always readable by everyone.
           */
          stat_buf->st_mode |= (S_IFREG | READ_ACCESS);
          
          /* We will be extra careful in trusting SFT data: it must be
           * consistent with date, time and size of the file as known
           * from conventional DOS calls.
           */
          sft_fdate = *((unsigned short *)(sft_buf + fdate_ofs));
          sft_ftime = *((unsigned short *)(sft_buf + ftime_ofs));
          sft_fsize = *((long *)(sft_buf + fsize_ofs));

	  /* In addition, it seems that 32-bit File Access, at least
	   * in Windows 95, creates fake SFT entries for some files,
	   * which have bogus cluster numbers and all-blank file name
	   * and extension.  (It is unclear to me when exactly are
	   * these fake SFT entries created.)
	   * So, in addition, we check the file's name in the SFT to
	   * be non-blank, since file names in the FCB format cannot
	   * be all-blank, even on Windows 95.  */
	  while (pname < sft_buf + name_ofs + 8 + 3 && *pname == ' ')
	    pname++;

          if (pname < sft_buf + name_ofs + 8 + 3 &&
	      sft_ftime == trusted_ftime &&
              sft_fdate == trusted_fdate &&
              sft_fsize == trusted_fsize)
            { /* Now we are ready to get the SFT info. */
              char           sft_extension[4], *dst = sft_extension + 2;
              unsigned char *src = sft_buf + ext_ofs + 2;
              int i = 3;

              /* Get the file's extension.  It is held in the SFT entry
               * as a blank-padded 3-character string without terminating
               * zero.  Some crazy files have embedded blanks in their
               * extensions, so only TRAILING blanks are insignificant.
               */
              memset(sft_extension, 0, sizeof(sft_extension));;
              while (*src == ' ' && i--)    /* skip traling blanks */
                {
                  dst--; src--;
                }

              if (i >= 0)
                while (i--)                 /* move whatever left */
                  *dst-- = *src--;

              /* Build Unix-style file permission bits. */
              if ( !(sft_buf[fattr_ofs] & 0x07) ) /* no R, S or H bits set */
                stat_buf->st_mode |= WRITE_ACCESS;

              /* Execute permission bits.  fstat() cannot be called on
               * directories under DOS, so only executable programs/batch
               * files should be considered.
               */
              if (_is_executable((const char *)0, fhandle, sft_extension))
                stat_buf->st_mode |= EXEC_ACCESS;

              /* DOS 4.x and above seems to know about named pipes. */
              if (dos_major > 3 && (sft_buf[6] & 0x20))
                stat_buf->st_mode |= S_IFIFO;

              /* Device code. */
              stat_buf->st_dev = drv_no;
#ifdef  HAVE_ST_RDEV
              stat_buf->st_rdev = drv_no;
#endif

              /* The file's starting cluster number will serve as its
               * inode number.
               */
              if ( (_djstat_flags & _STAT_INODE) == 0 && !is_remote)
                stat_buf->st_ino = *((unsigned short *)(sft_buf + clust_ofs));

              /* If the cluster number returns zero (e.g., for empty files,
               * because DOS didn't allocate it a cluster yet) we have to
               * invent the inode using the file's name.  We will use the
               * index into the SFT as part of unique identifier for our
               * file, so a possibility of two files with the same name
               * but different paths getting the same inode number is
               * minimized.
               * If we have a remote file, we invent inode even if there
               * is a non-zero number in the SFT, because it usually is
               * bogus (a left-over from last local file handle which used
               * the same SFT entry).
               * Note that we invent the inode even if is_remote is -1
               * (i.e., IOCTL Func 0Ah failed), because that should mean
               * some network redirector grabs IOCTL functions in an
               * incompatible way.
               */
              if ( (_djstat_flags & _STAT_INODE) == 0 &&
                   (stat_buf->st_ino == 0 || is_remote))
                {
                  static char     name_pat[]   = " :sft-   \\            ";
                  char name_buf[sizeof(name_pat)];
                  unsigned char  *src_p        = sft_buf + name_ofs + 7;
                  char           *dst_p        = name_buf + 17;
                  int             j            = 8;
                  char           *name_end;
                  int             first_digit  = sft_idx / 100;
                  int             second_digit = (sft_idx - first_digit * 100) / 10;
                  int             third_digit  = sft_idx - first_digit * 100
                                                         - second_digit * 10;

                  /* Initialize the name buffer with zeroes, then
                   * put in the drive letter and ``sft-XXX'', where
                   * XXX is the index of our file entry in the SFT.
                   */
                  strcpy(name_buf, name_pat);
                  memset(name_buf + 10, 0, sizeof(name_buf) - 10);
                  name_buf[0] = drv_no + 'A';
                  name_buf[6] = first_digit  + '0';
                  name_buf[7] = second_digit + '0';
                  name_buf[8] = third_digit  + '0';

                  /* Copy filename from SFT entry to local storage.
                   * It is stored there in the infamous DOS format:
                   * both name and extension are blank-padded, and no dot.
                   * We cannot use strcpy, because the name might
                   * include embedded blanks.  Therefore we move the
                   * characters from the end towards the beginning.
                   */
                  while (*src_p == ' ' && j--)   /* skip traling blanks */
                    {
                      dst_p--;
                      src_p--;
                    }
                  name_end = dst_p + 1;

                  if (j >= 0)                  /* move whatever left */
                    while (j--)
                      *dst_p-- = *src_p--;

                  /* We've already got the extension.  If it is non-empty,
                   * insert a dot and copy the extension itself.
                   */
                  if (sft_extension[0])
                    {
                      *name_end++ = '.';
                      strcpy(name_end, sft_extension);
                    }
                  stat_buf->st_ino =
                    _invent_inode(name_buf, dos_ftime, sft_fsize);
                  _djstat_fail_bits |= _STFAIL_HASH;
                }

              /* Size, date and time. */
              stat_buf->st_size = sft_fsize;
              stat_buf->st_atime = stat_buf->st_ctime = stat_buf->st_mtime =
                _file_time_stamp(dos_ftime);

	      /* Additional time info for LFN platforms.  */
	      set_fstat_times (fhandle, stat_buf);
              return 0;
            }

          _djstat_fail_bits |= _STFAIL_BADSFT;

        }

      /* Regular file, but DOS calls to find its length and time stamp
       * failed.  This must be an illegal file handle, or something
       * else very, very funny...
       */
      else
        return -1;    /* errno set by filelength() or getftime() */

    }

  /* Can't get SFT itself or can't find SFT entry belonging to our file.
   * This is probably unsupported variety of DOS, or other (not-so-
   * compatible) OS.
   * For these we supply whatever info we can find by conventional calls.
   */
  if (have_trusted_values)
    {
      if (is_dev)
        {
          if (_djstat_flags & _STAT_INODE)
            {
              /* We need the name of the device to invent an inode for it.
               * We cannot get the REAL name, because SFT info is unavailable.
               * If IOCTL tells us this is one of the standard devices, we
               * can make an educated guess.  If not, we will invent inode
               * with no name.  This will at least ensure that no two calls
               * accidentally get the same inode number.
               * We will also pretend devices belong to a special drive
               * named `@'.
               */
              if (dev_info & 0xf)
                {
                  char dev_name[16];

                  strcpy(dev_name, "@:\\dev\\");
                  if (dev_info & 3)         /* either STDIN or STDOUT */
                    strcat(dev_name, "CON     ");
                  else if (dev_info & 4)    /* NULL device */
                    strcat(dev_name, "NUL     ");
                  else if (dev_info & 8)    /* CLOCK device */
                    strcat(dev_name, "CLOCK$  ");

                  stat_buf->st_ino = _invent_inode(dev_name, 0, 0);
                }
              else
                stat_buf->st_ino = _invent_inode("", 0, 0);

              _djstat_fail_bits |= _STFAIL_HASH;
            }

          stat_buf->st_dev = -1;
#ifdef  HAVE_ST_RDEV
          stat_buf->st_rdev = -1;
#endif

          stat_buf->st_mode |= (S_IFCHR | READ_ACCESS | WRITE_ACCESS);

          stat_buf->st_atime = stat_buf->st_mtime = time(0);
          dos_ftime = 0;
          stat_buf->st_ctime = _file_time_stamp(dos_ftime);
        }
      else
        {
          /* Regular file.  The inode will be arbitrary, as we don't have
           * this file's name.  Sigh...
           */
          if ( (_djstat_flags & _STAT_INODE) == 0 )
            {
              _djstat_fail_bits |= _STFAIL_HASH;
              stat_buf->st_ino = _invent_inode("", dos_ftime, trusted_fsize);
            }

          /* Return the minimum access bits every file has under DOS. */
          stat_buf->st_mode |= (S_IFREG | READ_ACCESS);
          if (_djstat_flags & _STAT_ACCESS)
            _djstat_fail_bits |= _STFAIL_WRITEBIT;

	  /* If we run on Windows 9X, and LFN is enabled, try harder.
	     Note that we deliberately do NOT use this call when LFN is
	     disabled, even if we are on Windows 9X, because then we
	     open the file with function 3Ch, and such handles aren't
	     supported by 71A6h call we use here.  */
	  if (dos_major >= 7 && _USE_LFN)
	    {
	      __dpmi_regs r;

	      r.x.ax = 0x71a6;	/* file info by handle */
	      r.x.bx = fhandle;
	      r.x.ds = __tb >> 4;
	      r.x.dx = 0;
	      __dpmi_int(0x21, &r);
	      if ((r.x.flags & 1) == 0
		  && (_farpeekl(dos_mem_base, __tb) & 0x07) == 0)
		stat_buf->st_mode |= WRITE_ACCESS; /* no R, S or H bits set */
	    }

          /* Executables are detected if they have magic numbers.  */
          if ( (_djstat_flags & _STAT_EXEC_MAGIC) == 0 &&
               _is_executable((const char *)0, fhandle, (const char *)0))
            stat_buf->st_mode |= EXEC_ACCESS;

          /* Lower 6 bits of IOCTL return value give the device number. */
          stat_buf->st_dev = dev_info & 0x3f;
#ifdef  HAVE_ST_RDEV
          stat_buf->st_rdev = dev_info & 0x3f;
#endif

          /* Novell Netware returns 0 drive number in the lower
           * 6 bits of dev_info.  If this is what we get, return -2
           * as drive number (it will be converted to '?' if added to 'A').
           */
          if (stat_buf->st_dev == 0)
            {
              stat_buf->st_dev = -2;
#ifdef  HAVE_ST_RDEV
              stat_buf->st_rdev = -2;
#endif
              _djstat_fail_bits |= _STFAIL_DEVNO;
            }

          stat_buf->st_size  = trusted_fsize;
          stat_buf->st_atime = stat_buf->st_ctime = stat_buf->st_mtime =
            _file_time_stamp(dos_ftime);

	  /* Additional time info for LFN platforms.  */
	  set_fstat_times (fhandle, stat_buf);
        }
      return 0;
    }

  /* Don't have even values from conventional DOS calls.
   * Give up completely on this funny handle.  ERRNO is already
   * set by filelength() and/or getftime().
   */
  else
    return -1;
}

/*
 * Main entry point.  This is a substitute for library fstat() function.
 */

int
fstat(int handle, struct stat *statbuf)
{
  int            e = errno;     /* save previous value of errno */

  if (!statbuf)
    {
      errno = EFAULT;
      return -1;
    }

  if (fstat_assist(handle, statbuf) == -1)
    {
      return -1;      /* already have ERRNO set by fstat_assist() */
    }
  else
    {
      errno = e;
      return 0;
    }
}

#ifdef  TEST

#include <stdio.h>
#include <fcntl.h>

unsigned short _djstat_flags = 0;

int main(int argc, char *argv[])
{
  struct stat stat_buf;
  int fd = -1;
  int i;
  char *endp;

  argc--; argv++;
  _djstat_flags = (unsigned short)strtoul(*argv, &endp, 0);

  /* Display 4 standard handles which are already open. */
  for (i = 0; i <= 4; i++)
    {
      fstat(i, &stat_buf);
      fprintf(stderr, "handle-%d: %d %6u %o %d %d %ld %lu %s", i,
              stat_buf.st_dev,
              (unsigned)stat_buf.st_ino,
              stat_buf.st_mode,
              stat_buf.st_nlink,
              stat_buf.st_uid,
              (long)stat_buf.st_size,
              (unsigned long)stat_buf.st_mtime,
              ctime(&stat_buf.st_mtime));
      _djstat_describe_lossage(stderr);
    }

  /* Now call fstat() for each command-line argument. */
  while (++argv, --argc)
    {
      if (fd >= 19)
        close(fd);
      fd = open(*argv, O_RDONLY);
      if (fd != -1 && !fstat(fd, &stat_buf))
        {
          fprintf(stderr, "%s (%d): %d %6u %o %d %d %ld %lu %s", *argv, fd,
                  stat_buf.st_dev,
                  (unsigned)stat_buf.st_ino,
                  stat_buf.st_mode,
                  stat_buf.st_nlink,
                  stat_buf.st_uid,
                  (long)stat_buf.st_size,
                  (unsigned long)stat_buf.st_mtime,
                  ctime(&stat_buf.st_mtime));
	  fprintf (stderr, "\t\t\tTimes: %lu %lu\n",
		   (unsigned long)stat_buf.st_atime,
		   (unsigned long)stat_buf.st_ctime);
          _djstat_describe_lossage(stderr);
        }
      else
        {
          fputs(*argv, stderr);
          perror(": failed to open/fstat");
          _djstat_describe_lossage(stderr);
        }
    }
  return 0;
}

#endif  /* TEST */

--------------------------- xstat.h --------------------------------
/* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
/*
 * Header for internal stat()/fstat() assist functions.
 *
 */

#ifndef __XSTAT_H
#define __XSTAT_H

#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dpmi.h>

/* Some errno.h headers do not define EFAULT.  Here the value is
   taken from POSIX IEEE 1003.1.  */
#include <errno.h>
#ifndef EFAULT
#define EFAULT  14
#endif

/* Under MS-DOS, file access permissions are shared by all, except for
   Write permission. */
#define READ_ACCESS     (S_IRUSR | S_IRGRP | S_IROTH)
#define WRITE_ACCESS    S_IWUSR
#define EXEC_ACCESS     (S_IXUSR | S_IXGRP | S_IXOTH)

/* Macro to convert a segment and an offset to a "far offset" suitable
   for _farxxx() functions of DJGPP.  */
#ifndef MK_FOFF
#define MK_FOFF(s,o) ((int)((((unsigned long)(s)) << 4) + (unsigned short)(o)))
#endif

#define MAX_TRUE_NAME   FILENAME_MAX

extern unsigned short   _osmajor, _osminor;
extern const    char  * _os_flavor;

/* Bits to flag f?stat() failed to use individual undocumented features. */
#define _STFAIL_SDA         1   /* Get SDA call failed */
#define _STFAIL_OSVER       2   /* Unsupported DOS version */
#define _STFAIL_BADSDA      4   /* Bad pointer to SDA */
#define _STFAIL_TRUENAME    8   /* _truename() failed */
#define _STFAIL_HASH     0x10   /* inode defaults to hashing */
#define _STFAIL_LABEL    0x20   /* Root dir, but no volume label */
#define _STFAIL_DCOUNT   0x40   /* dirent_count ridiculously large */
#define _STFAIL_WRITEBIT 0x80   /* fstat() failed to get write access bit */
#define _STFAIL_DEVNO   0x100   /* fstat() failed to get device number */
#define _STFAIL_BADSFT  0x200   /* SFT entry found, but can't be trusted */
#define _STFAIL_SFTIDX  0x400   /* bad SFT index in JFT */
#define _STFAIL_SFTNF   0x800   /* file entry not found in SFT array */

extern unsigned short   _djstat_fail_bits;

extern unsigned short   _djstat_flags;

extern time_t           _file_time_stamp(unsigned int);
extern ino_t            _invent_inode(const char *, unsigned, unsigned long);
extern unsigned short   _get_magic(const char *, int);
extern unsigned short   _get_dos_version(int);
extern char           * _truename(const char *, char *);
extern int              _is_remote_drive(int);
extern int              _is_executable(const char *, int, const char *);
extern short            _get_dev_info(int);
extern long             __filelength(int);
extern int              _is_remote_handle(int);
extern void             _djstat_describe_lossage(FILE *);
extern int              _getftime(int, unsigned int *);

#endif  /* __XSTAT_H */

- Raw text -


  webmaster     delorie software   privacy  
  Copyright © 2019   by DJ Delorie     Updated Jul 2019