home *** CD-ROM | disk | FTP | other *** search
/ PC Extra Super CD 1998 January / PCPLUS131.iso / DJGPP / V2 / DJLSR201.ZIP / src / libc / posix / sys / stat / fstat.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-09-26  |  32.4 KB  |  885 lines

  1. /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */
  2. /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */
  3. /* This is file FSTAT.C */
  4. /*
  5.  *   Almost a 100% U**X-compatible fstat() substitute.
  6.  *
  7.  * Usage:
  8.  *
  9.  *   That's easy: just put this in libc.a, and then call fstat() as usual.
  10.  *
  11.  * Rationale:
  12.  *
  13.  *   Many Unix-born programs make heavy use of fstat() library
  14.  *   function to make decisions on files' equality, size, access
  15.  *   attributes etc.  In the MS-DOS environment, many implementations
  16.  *   of fstat() are crippled, because DOS makes it very hard to get to
  17.  *   certain pieces of information about files and directories.  Thus
  18.  *   porting a program to DOS is usually an exercise in #ifdef'ing.
  19.  *   This implementation facilitates porting Unix programs to MS-DOS
  20.  *   by providing an fstat() which is much more Unix-compatible than
  21.  *   those of most DOS-based C compilers (e.g., Borland's).
  22.  *   Specifically, the following issues are taken care of:
  23.  *
  24.  *      1. Mode bits are returned for the actual file, files are NOT
  25.  *         reported read-only (as in Borland's library fstat()).
  26.  *      2. Mode bits are set for all 3 groups (user, group, other).
  27.  *      3. Device code (st_dev, st_rdev) is correctly reported (0 = 'A',
  28.  *         1 = 'B' etc.).
  29.  *      4. Character device names (such as /dev/con, lpt1, aux etc.) are
  30.  *         treated as if they were on a special drive called `@:'
  31.  *         (st_dev = -1).  The "character special" mode bit is set
  32.  *         for these devices.
  33.  *      5. The inode number (st_ino) is taken from the starting cluster
  34.  *         number of the file.  If the cluster number is unavailable, it
  35.  *         is invented using the file's name in a manner that minimizes
  36.  *         the possibility of inventing an inode which already belongs
  37.  *         to another file.  See below for details.
  38.  *      6. Executable files are found based on files' extensions and
  39.  *         magic numbers present at their beginning, and their execute
  40.  *         bits are set.
  41.  *
  42.  *   Lossage:
  43.  *
  44.  *      Beautiful as the above sounds, this implementation does fail
  45.  *      under certain circumstances.  The following is a list of known
  46.  *      problems:
  47.  *
  48.  *      1. Files open on networked drives mounted by Novell Netware
  49.  *         before revision 4.x cannot be traced using DOS System File
  50.  *         Table.  Therefore, name, extension, file attributes and the
  51.  *         drive letter are not available for these.  Until somebody
  52.  *         tells me how this information can be obtained under Novell,
  53.  *         nothing could be done here.  For the time being, these files
  54.  *         will get st_dev of -2.
  55.  *      2. For files which reside on networked drives, the inode number
  56.  *         is always invented, because network redirectors usually do
  57.  *         not bring that info with them.
  58.  *      3. Empty files do not have a starting cluster number, because
  59.  *         DOS doesn't allocate one until you actually write something
  60.  *         to a file.  For these the inode is also invented.
  61.  *      4. If the st_ino field is a 16 bit number, the invented inode
  62.  *         numbers are from 65535 and down, assuming that most disks have
  63.  *         unised portions near their end.  Valid cluster numbers are 16-bit
  64.  *         unisgned integers, so a possibility of a clash exists, although
  65.  *         the last 80 or more cluster numbers are unused on all drives
  66.  *         I've seen.  If the st_ino is 32 bit, then invented inodes are
  67.  *         all greater than 64k, which totally eliminates a possibility
  68.  *         of a clash with an actual cluster number.
  69.  *      5. As this implementation relies heavily on undocumented DOS
  70.  *         features, it will fail to get actual file info in environments
  71.  *         other than native DOS, such as DR-DOS, OS/2 etc.  For these,
  72.  *         the function will return whatever info is available with
  73.  *         conventional DOS calls, which is no less than any other
  74.  *         implementation could do.  This fstat() might also fail for
  75.  *         future DOS versions, if the layout of DOS System File Table
  76.  *         is changed; however, this seems unlikely.
  77.  *
  78.  * Copyright (c) 1994-1996 Eli Zaretskii <eliz@is.elta.co.il>
  79.  *
  80.  * This software may be used freely so long as this copyright notice is
  81.  * left intact.  There is no warranty on this software.
  82.  *
  83.  */
  84.  
  85. /*
  86.  * Tested with DJGPP port of GNU C compiler, versions 1.11maint5 and 1.12m2,
  87.  * under MS-DOS 3.3, 4.01, 5.0, 6.20 (with and without DoubleSpace) and
  88.  * with networked drives under XFS 1.76, Novell Netware 3.22, and
  89.  * TSoft NFS 0.24Beta.
  90.  *
  91.  */
  92.  
  93. #include <libc/stubs.h>
  94. #include <string.h>
  95. #include <stdlib.h>
  96. #include <stdio.h>
  97. #include <errno.h>
  98. #include <time.h>
  99. #include <unistd.h>
  100. #include <fcntl.h>
  101. #include <sys/types.h>
  102. #include <sys/stat.h>
  103.  
  104. #include <dpmi.h>
  105. #include <go32.h>
  106. #include <libc/farptrgs.h>
  107. #include <libc/bss.h>
  108.  
  109. #include "xstat.h"
  110.  
  111. #define _STAT_INODE         1   /* should we bother getting inode numbers? */
  112. #define _STAT_EXEC_EXT      2   /* get execute bits from file extension? */
  113. #define _STAT_EXEC_MAGIC    4   /* get execute bits from magic signature? */
  114. #define _STAT_DIRSIZE       8   /* compute directory size? */
  115. #define _STAT_ROOT_TIME  0x10   /* try to get root dir time stamp? */
  116. #define _STAT_WRITEBIT   0x20   /* fstat() needs write bit? */
  117.  
  118. /* Should we bother about executables at all? */
  119. #define _STAT_EXECBIT       (_STAT_EXEC_EXT | _STAT_EXEC_MAGIC)
  120.  
  121. /* Should we bother about any access bits?  */
  122. #define _STAT_ACCESS        (_STAT_WRITEBIT | _STAT_EXECBIT)
  123.  
  124. /* Do we need SFT info at all? */
  125. #define _STAT_NEEDS_SFT     (_STAT_WRITEBIT | _STAT_EXEC_EXT | _STAT_INODE)
  126.  
  127. /*
  128.  * Lower-level assist functions to get a starting cluster of a file,
  129.  * which will serve as an inode number.  The starting cluster is
  130.  * found in the System File Table entry (an internal structure of
  131.  * DOS) which belongs to our file.
  132.  *
  133.  * Much of the following code is derived from file H2NAME.C, which
  134.  * came with ``Undocumented DOS'', 1st edition.
  135.  */
  136.  
  137. /* Selectors for conventional memory and our program's memory.  */
  138. static unsigned short dos_mem_base, our_mem_base;
  139.  
  140. /* Array of SFT entry sizes as function of DOS version. */
  141. static size_t sft_size_list[] = {0, 0x28, 0x35, 0x3b};
  142.  
  143. /* Actual size of SFT entry for the version of DOS under
  144.    which we are running.  */
  145. static size_t sft_size;
  146.  
  147. /* Static array to hold a copy of SFT entry.  Should be at
  148.    least as long as the largest number in sft_size_list[].  */
  149. static unsigned char sft_buf[0x40];
  150.  
  151. /* Linear address of pointer to Job File Table (JFT) which
  152.    is an array of indices into the SFT which correspond to
  153.    open file handles.  */
  154. static unsigned long htbl_ptr_addr;
  155.  
  156. /* True version of DOS (not the one simulated by SETVER).  */
  157. static unsigned short dos_major, dos_minor;
  158.  
  159. /* Segment and offset of first SFT sub-table.  All searches
  160.    start from this address.  */
  161. static unsigned short sft_start_seg, sft_start_off;
  162.  
  163. /* This holds the failure bits from last call to fstat_init(),
  164.    so we can return them each time fstat_assist() is called.  */
  165. static unsigned short fstat_init_bits;
  166.  
  167. /* Holds the last seen value of __bss_count, to be safe for
  168.    restarted programs (emacs).  */
  169. static int fstat_count = -1;
  170.  
  171. /* Initialization routine, called once per program run.
  172.  * Finds DOS version, SFT entry size and addresses of
  173.  * program handle table and first SFT sub-table.
  174.  */
  175. static int
  176. fstat_init(void)
  177. {
  178.   __dpmi_regs    regs;
  179.   int            sft_ptr_addr;
  180.   unsigned short true_dos_version;
  181.  
  182.   /* Selectors for _farXXX() functions.  */
  183.   dos_mem_base = _dos_ds;
  184.   our_mem_base = _my_ds();
  185.  
  186.   /* Each DOS program has a table of file handles which are used by
  187.    * DOS open() call.  This table holds, for each handle which is in
  188.    * use, the index into the System File Tables' list which contains
  189.    * data about this handle.  The pointer to that handle table is found
  190.    * at offset 34h in the program's PSP.
  191.    */
  192.  
  193.   /* Linear address of pointer to the handle table.  We postpone
  194.    * dereferencing this pointer to obtain the address of the handle
  195.    * table until we're actually called for a specific handle, because
  196.    * somebody could in the meanwhile change that address, e.g. by
  197.    * calling INT 21h/AX=67h to enlarge the maximum number of file
  198.    * handles.
  199.    */
  200.   htbl_ptr_addr = (_go32_info_block.linear_address_of_original_psp & 0xfffff)
  201.                   + 0x34;
  202.  
  203.   /*
  204.    * Find the pointer to the first subtable in the list of SFT's.
  205.    * It is stored at offset 4 in the DOS List-of-Lists area, a ptr
  206.    * to which is returned by the undocumented DOS function 52h.
  207.    * We don't check FLAGS after 52h returns because Ralph Brown's
  208.    * Interrupt List doesn't say FLAGS are set to indicate a
  209.    * failure.
  210.    */
  211.   regs.h.ah = 0x52;
  212.   __dpmi_int(0x21, ®s);
  213.  
  214.   /* Linear addres of pointer to SFT list.  */
  215.   sft_ptr_addr = MK_FOFF(regs.x.es, regs.x.bx + 4);
  216.   
  217.   /* SFT entry size depends on DOS version.
  218.      We need exact knowledge about DOS internals, so we need the
  219.      TRUE DOS version (not the simulated one by SETVER), if that's
  220.      available.  */
  221.   true_dos_version = _get_dos_version(1);
  222.   dos_major = true_dos_version >> 8;
  223.   dos_minor = true_dos_version & 0xff;
  224.   sft_size = sft_size_list[dos_major > 4 ? 3 : dos_major - 1];
  225.   if (!sft_size)        /* unsupported DOS version */
  226.     {
  227.       _djstat_fail_bits |= _STFAIL_OSVER;
  228.       return 0;
  229.     }
  230.  
  231.   /* Segment and offset of start of SFT list.  */
  232.   sft_start_off = _farpeekw(dos_mem_base, sft_ptr_addr);
  233.   sft_start_seg = _farpeekw(dos_mem_base, sft_ptr_addr + 2);
  234.  
  235.   return 1;
  236. }
  237.  
  238. /* Given a handle, copy contents of System File Table entry which
  239.  * belongs to that handle into a local buffer, and return the index
  240.  * into the SFT array where our entry was found.  In case of failure
  241.  * to use SFT, return -2.  If FHANDLE is illegal, return -1.
  242.  */
  243. static short
  244. get_sft_entry(int fhandle)
  245. {
  246.   unsigned long  sft_seg;
  247.   unsigned short sft_off;
  248.   unsigned long  htbl_addr;
  249.   short          sft_idx, retval;
  250.  
  251.   _djstat_fail_bits = fstat_init_bits;
  252.  
  253.   /* Force initialization if we were restarted (emacs).  */
  254.   if (fstat_count != __bss_count)
  255.     {
  256.       fstat_count = __bss_count;
  257.       dos_major = 0;
  258.     }
  259.  
  260.   /* If first time called, initialize.  */
  261.   if (!dos_major && !fstat_init())
  262.     {
  263.       fstat_init_bits = _djstat_fail_bits;
  264.       return -2;
  265.     }
  266.  
  267.   /* Test file handle for validity.
  268.    * For DOS 3.x and later, the number of possible file handles
  269.    * is at offset 32h in the PSP; for prior versions, it is 20.
  270.    */
  271.   if (fhandle < 0 ||
  272.       fhandle >=
  273.         (_osmajor < 3 ?
  274.          20 :
  275.          _farpeekw(dos_mem_base,
  276.                    (_go32_info_block.linear_address_of_original_psp & 0xfffff)
  277.                    + 0x32)))
  278.       return -1;
  279.  
  280.   /* Linear address of the handle table. */
  281.   htbl_addr = MK_FOFF(_farpeekw(dos_mem_base, htbl_ptr_addr + 2),
  282.                       _farpeekw(dos_mem_base, htbl_ptr_addr));
  283.  
  284.   /* Index of the entry for our file handle in the SFT array.  */
  285.   retval = sft_idx = _farpeekb(dos_mem_base, htbl_addr + fhandle);
  286.  
  287.   if (sft_idx < 0)      /* invalid file handle or bad handle table */
  288.     {
  289.       _djstat_fail_bits |= _STFAIL_SFTIDX;
  290.       return -1;
  291.     }
  292.  
  293.   /* Given the index into the SFT list, find our SFT entry.
  294.    * The list consists of arrays (sub-tables) of entries, each sub-
  295.    * table preceeded by a header.  The header holds a pointer to the
  296.    * next sub-table in the list and number of entries in this sub-table.
  297.    * The list is searched until the sub-table which contains our
  298.    * target is found, then the sub-table entries are skipped until
  299.    * we arrive at our target.
  300.    */
  301.  
  302.   /* Segment (shifted 4 bits left) and offset of start of SFT list.  */
  303.   sft_off = sft_start_off;
  304.   sft_seg = MK_FOFF(sft_start_seg, 0);
  305.  
  306.   while (sft_off != 0xFFFF)
  307.     {
  308.       unsigned long entry_addr   = sft_seg + sft_off;
  309.       short         subtable_len = _farpeekw(dos_mem_base, entry_addr + 4);
  310.  
  311.       if (sft_idx < subtable_len)
  312.         { /* Our target is in this sub-table.  Pull in the entire
  313.            * SFT entry for use by fstat_assist().
  314.            */
  315.           movedata(dos_mem_base,
  316.                    (entry_addr + 6 + sft_idx * sft_size) & 0x000fffff,
  317.                    our_mem_base, (unsigned int)sft_buf, sft_size);
  318.           return retval;
  319.         }
  320.       /* Our target not in this subtable.
  321.        * Subtract the number of entries in this sub-table from the
  322.        * index of our entry, and proceed to next sub-table.
  323.        */
  324.       sft_idx -= subtable_len;
  325.       sft_off  = _farpeekw(dos_mem_base, entry_addr);
  326.       sft_seg  = MK_FOFF(_farpeekw(dos_mem_base, entry_addr + 2), 0);
  327.     }
  328.  
  329.   /* Get here only by error, which probably means unsupported DOS version. */
  330.   _djstat_fail_bits |= _STFAIL_SFTNF;
  331.   return -2;
  332. }
  333.  
  334. /* On LFN platforms, we can get all the 3 time-related fields.  */
  335.  
  336. static void
  337. set_fstat_times (int fhandle, struct stat *stat_buf)
  338. {
  339.   if (_USE_LFN)
  340.     {
  341.       time_t access_time;
  342.       unsigned int create_time;
  343.  
  344.       /* Access time is currently date only (time is zeroed).  */
  345.       access_time = _file_time_stamp (_lfn_get_ftime (fhandle, _LFN_ATIME));
  346.       if (access_time > stat_buf->st_atime)
  347.     stat_buf->st_atime = access_time;
  348.  
  349.       /* Creation time might be zero if the file was created
  350.      by a DOS program which doesn't support LFN API.  */
  351.       create_time = _lfn_get_ftime (fhandle, _LFN_CTIME);
  352.       if (create_time)
  353.     stat_buf->st_ctime = _file_time_stamp (create_time);
  354.     }
  355. }
  356.  
  357. /* fstat_assist() is where all the actual work is done.
  358.  * It uses SFT entry, if available and its contents are verified.
  359.  * Otherwise, it finds all the available info by conventional
  360.  * DOS calls.
  361.  */
  362.  
  363. static int
  364. fstat_assist(int fhandle, struct stat *stat_buf)
  365. {
  366.   short          have_trusted_values = 1;
  367.   unsigned int   dos_ftime;
  368.   char           drv_no;
  369.   unsigned char  is_dev;
  370.   unsigned char  is_remote;
  371.   short          sft_idx = -1;
  372.   unsigned short sft_fdate, sft_ftime;
  373.   long           sft_fsize;
  374.   unsigned short trusted_ftime = 0, trusted_fdate = 0;
  375.   long           trusted_fsize = 0;
  376.  
  377.   _djstat_fail_bits = 0;
  378.  
  379.   /* Get pointer to an SFT entry which holds data for our handle. */
  380.   if ( (_djstat_flags & _STAT_NEEDS_SFT) == 0 &&
  381.        (sft_idx = get_sft_entry(fhandle)) == -1)
  382.     {
  383.       errno = EBADF;
  384.       return -1;
  385.     }
  386.  
  387.   /* Initialize buffers. */
  388.   memset(stat_buf, 0, sizeof(struct stat));
  389.   dos_ftime = 0;
  390.  
  391.   /* Get some info about this handle by conventional DOS calls.  These
  392.    * will serve as verification of SFT entry contents and also as
  393.    * fall-back in case SFT method fails.
  394.    */
  395.   if (_getftime(fhandle, &dos_ftime) == 0 &&
  396.       (trusted_fsize = __filelength(fhandle)) != -1L)
  397.     {
  398.       trusted_ftime = dos_ftime & 0xffff;
  399.       trusted_fdate = dos_ftime >> 16;
  400.     }
  401.   else
  402.     have_trusted_values = 0;
  403.  
  404.   /* First, fill the fields which are constant under DOS. */
  405.   stat_buf->st_uid = getuid();
  406.   stat_buf->st_gid = getgid();
  407.   stat_buf->st_nlink = 1;
  408. #ifndef  NO_ST_BLKSIZE
  409.   stat_buf->st_blksize = _go32_info_block.size_of_transfer_buffer;
  410. #endif
  411.  
  412.   /* If SFT entry for our handle is required and available, we will use it.  */
  413.   if ( (_djstat_flags & _STAT_NEEDS_SFT) == 0 && sft_idx >= 0)
  414.     {
  415.       /* Determine positions of data items in the SFT. */
  416.       size_t fattr_ofs, name_ofs, ext_ofs, fsize_ofs, fdate_ofs,
  417.              ftime_ofs, clust_ofs;
  418.  
  419.       switch (dos_major)
  420.         {
  421.           case 2:
  422.               fattr_ofs  = 2;
  423.               drv_no     = sft_buf[3] - 1;      /* 1 = 'A' etc. */
  424.               is_dev     = drv_no < 0;          /* sft_buf[3] = 0 */
  425.               name_ofs   = 4;
  426.               ext_ofs    = 0x0b;
  427.               fsize_ofs  = 0x13;
  428.               fdate_ofs  = 0x17;
  429.               ftime_ofs  = 0x19;
  430.               clust_ofs  = 0x1c;
  431.               is_remote  = 0;   /* DOS 2.x didn't have remote files */
  432.               break;
  433.  
  434.           case 3:
  435.               fattr_ofs  = 4;
  436.               drv_no     = sft_buf[5] & 0x3f;
  437.               is_dev     = sft_buf[5] & 0x80;
  438.               is_remote  = sft_buf[6] & 0x80;
  439.               if (dos_minor == 0)
  440.                 {
  441.                   name_ofs = 0x21;
  442.                   ext_ofs  = 0x29;
  443.                 }
  444.               else      /* DOS 3.1 - 3.3x */
  445.                 {
  446.                   name_ofs = 0x20;
  447.                   ext_ofs  = 0x28;
  448.                 }
  449.               clust_ofs  = 0x0b;
  450.               ftime_ofs  = 0x0d;
  451.               fdate_ofs  = 0x0f;
  452.               fsize_ofs  = 0x11;
  453.               break;
  454.  
  455.           default:      /* DOS 4 and up */
  456.               fattr_ofs  = 4;
  457.               drv_no     = sft_buf[5] & 0x3f;
  458.               is_dev     = sft_buf[5] & 0x80;
  459.               is_remote  = sft_buf[6] & 0x80;
  460.               clust_ofs  = 0x0b;
  461.               ftime_ofs  = 0x0d;
  462.               fdate_ofs  = 0x0f;
  463.               fsize_ofs  = 0x11;
  464.               name_ofs   = 0x20;
  465.               ext_ofs    = 0x28;
  466.  
  467.         }
  468.  
  469.       if (is_dev)
  470.         {
  471.           /* We have a character device.
  472.            * We will pretend as if they all reside on a special
  473.            * drive `@:', which is illegal in DOS, and just happens
  474.            * to give a neat st_dev (= '@' - 'A') = -1.
  475.            */
  476.  
  477.           stat_buf->st_dev = -1;
  478. #ifdef  HAVE_ST_RDEV
  479.           stat_buf->st_rdev = -1;
  480. #endif
  481.  
  482.           if ( (_djstat_flags & _STAT_INODE) == 0 )
  483.             {
  484.               /* Character device names are all at most 8-character long. */
  485.               short i   = 8;
  486.               char *src = sft_buf + name_ofs;
  487.               char dev_name[16], *dst = dev_name + 7;
  488.  
  489.               strcpy(dev_name, "@:\\dev\\        "); /* pad with 8 blanks */
  490.               while (i-- && *src != ' ')             /* copy non-blank chars */
  491.                 *dst++ = *src++;
  492.  
  493.               stat_buf->st_ino = _invent_inode(dev_name, 0, 0);
  494.             }
  495.  
  496.           /* Should we make printer devices write-only here? */
  497.           stat_buf->st_mode |= (S_IFCHR | READ_ACCESS | WRITE_ACCESS);
  498.  
  499.           /* We will arrange things so that devices have current time in
  500.            * the access-time and modified-time fields of struct stat.
  501.            */
  502.           stat_buf->st_atime = stat_buf->st_mtime = time(0);
  503.  
  504.           /* MS-DOS returns the time of boot when _getftime() is called
  505.            * for character devices, but this is undocumented and
  506.            * unsupported by DOS clones (e.g. DR-DOS).  It is also
  507.            * inconsistent with our stat().  Therefore, we will arrange
  508.            * things so that devices have zero (the beginning of times)
  509.            * in creation-time field.
  510.            */
  511.           dos_ftime = 0;
  512.           stat_buf->st_ctime = _file_time_stamp(dos_ftime);
  513.  
  514.           return 0;
  515.         }
  516.  
  517.       /* Files are not allowed to fail DOS calls for their time
  518.        * stamps and size.
  519.        */
  520.       else if (have_trusted_values)
  521.         {
  522.           /* This is a regular, existing file.  It cannot be a
  523.            * directory, because DOS won't let us open() a directory.
  524.            * Each file under MS-DOS is always readable by everyone.
  525.            */
  526.           stat_buf->st_mode |= (S_IFREG | READ_ACCESS);
  527.           
  528.           /* We will be extra careful in trusting SFT data: it must be
  529.            * consistent with date, time and size of the file as known
  530.            * from conventional DOS calls.
  531.            */
  532.           sft_fdate = *((unsigned short *)(sft_buf + fdate_ofs));
  533.           sft_ftime = *((unsigned short *)(sft_buf + ftime_ofs));
  534.           sft_fsize = *((long *)(sft_buf + fsize_ofs));
  535.           if (sft_ftime == trusted_ftime &&
  536.               sft_fdate == trusted_fdate &&
  537.               sft_fsize == trusted_fsize)
  538.             { /* Now we are ready to get the SFT info. */
  539.               char           sft_extension[4], *dst = sft_extension + 2;
  540.               unsigned char *src = sft_buf + ext_ofs + 2;
  541.               int i = 3;
  542.  
  543.               /* Get the file's extension.  It is held in the SFT entry
  544.                * as a blank-padded 3-character string without terminating
  545.                * zero.  Some crazy files have embedded blanks in their
  546.                * extensions, so only TRAILING blanks are insignificant.
  547.                */
  548.               memset(sft_extension, 0, sizeof(sft_extension));;
  549.               while (*src == ' ' && i--)    /* skip traling blanks */
  550.                 {
  551.                   dst--; src--;
  552.                 }
  553.  
  554.               if (i >= 0)
  555.                 while (i--)                 /* move whatever left */
  556.                   *dst-- = *src--;
  557.  
  558.               /* Build Unix-style file permission bits. */
  559.               if ( !(sft_buf[fattr_ofs] & 0x07) ) /* no R, S or H bits set */
  560.                 stat_buf->st_mode |= WRITE_ACCESS;
  561.  
  562.               /* Execute permission bits.  fstat() cannot be called on
  563.                * directories under DOS, so only executable programs/batch
  564.                * files should be considered.
  565.                */
  566.               if (_is_executable((const char *)0, fhandle, sft_extension))
  567.                 stat_buf->st_mode |= EXEC_ACCESS;
  568.  
  569.               /* DOS 4.x and above seems to know about named pipes. */
  570.               if (dos_major > 3 && (sft_buf[6] & 0x20))
  571.                 stat_buf->st_mode |= S_IFIFO;
  572.  
  573.               /* Device code. */
  574.               stat_buf->st_dev = drv_no;
  575. #ifdef  HAVE_ST_RDEV
  576.               stat_buf->st_rdev = drv_no;
  577. #endif
  578.  
  579.               /* The file's starting cluster number will serve as its
  580.                * inode number.
  581.                */
  582.               if ( (_djstat_flags & _STAT_INODE) == 0 && !is_remote)
  583.                 stat_buf->st_ino = *((unsigned short *)(sft_buf + clust_ofs));
  584.  
  585.               /* If the cluster number returns zero (e.g., for empty files,
  586.                * because DOS didn't allocate it a cluster yet) we have to
  587.                * invent the inode using the file's name.  We will use the
  588.                * index into the SFT as part of unique identifier for our
  589.                * file, so a possibility of two files with the same name
  590.                * but different paths getting the same inode number is
  591.                * minimized.
  592.                * If we have a remote file, we invent inode even if there
  593.                * is a non-zero number in the SFT, because it usually is
  594.                * bogus (a left-over from last local file handle which used
  595.                * the same SFT entry).
  596.                * Note that we invent the inode even if is_remote is -1
  597.                * (i.e., IOCTL Func 0Ah failed), because that should mean
  598.                * some network redirector grabs IOCTL functions in an
  599.                * incompatible way.
  600.                */
  601.               if ( (_djstat_flags & _STAT_INODE) == 0 &&
  602.                    (stat_buf->st_ino == 0 || is_remote))
  603.                 {
  604.                   static char     name_pat[]   = " :sft-   \\            ";
  605.                   char name_buf[sizeof(name_pat)];
  606.                   unsigned char  *src_p        = sft_buf + name_ofs + 7;
  607.                   char           *dst_p        = name_buf + 17;
  608.                   int             j            = 8;
  609.                   char           *name_end;
  610.                   int             first_digit  = sft_idx / 100;
  611.                   int             second_digit = (sft_idx - first_digit * 100) / 10;
  612.                   int             third_digit  = sft_idx - first_digit * 100
  613.                                                          - second_digit * 10;
  614.  
  615.                   /* Initialize the name buffer with zeroes, then
  616.                    * put in the drive letter and ``sft-XXX'', where
  617.                    * XXX is the index of our file entry in the SFT.
  618.                    */
  619.                   strcpy(name_buf, name_pat);
  620.                   memset(name_buf + 10, 0, sizeof(name_buf) - 10);
  621.                   name_buf[0] = drv_no + 'A';
  622.                   name_buf[6] = first_digit  + '0';
  623.                   name_buf[7] = second_digit + '0';
  624.                   name_buf[8] = third_digit  + '0';
  625.  
  626.                   /* Copy filename from SFT entry to local storage.
  627.                    * It is stored there in the infamous DOS format:
  628.                    * both name and extension are blank-padded, and no dot.
  629.                    * We cannot use strcpy, because the name might
  630.                    * include embedded blanks.  Therefore we move the
  631.                    * characters from the end towards the beginning.
  632.                    */
  633.                   while (*src_p == ' ' && j--)   /* skip traling blanks */
  634.                     {
  635.                       dst_p--;
  636.                       src_p--;
  637.                     }
  638.                   name_end = dst_p + 1;
  639.  
  640.                   if (j >= 0)                  /* move whatever left */
  641.                     while (j--)
  642.                       *dst_p-- = *src_p--;
  643.  
  644.                   /* We've already got the extension.  If it is non-empty,
  645.                    * insert a dot and copy the extension itself.
  646.                    */
  647.                   if (sft_extension[0])
  648.                     {
  649.                       *name_end++ = '.';
  650.                       strcpy(name_end, sft_extension);
  651.                     }
  652.                   stat_buf->st_ino =
  653.                     _invent_inode(name_buf, dos_ftime, sft_fsize);
  654.                   _djstat_fail_bits |= _STFAIL_HASH;
  655.                 }
  656.  
  657.               /* Size, date and time. */
  658.               stat_buf->st_size = sft_fsize;
  659.               stat_buf->st_atime = stat_buf->st_ctime = stat_buf->st_mtime =
  660.                 _file_time_stamp(dos_ftime);
  661.  
  662.           /* Additional time info for LFN platforms.  */
  663.           set_fstat_times (fhandle, stat_buf);
  664.               return 0;
  665.             }
  666.  
  667.           _djstat_fail_bits |= _STFAIL_BADSFT;
  668.  
  669.         }
  670.  
  671.       /* Regular file, but DOS calls to find its length and time stamp
  672.        * failed.  This must be an illegal file handle, or something
  673.        * else very, very funny...
  674.        */
  675.       else
  676.         return -1;    /* errno set by filelength() or getftime() */
  677.  
  678.     }
  679.  
  680.   /* Can't get SFT itself or can't find SFT entry belonging to our file.
  681.    * This is probably unsupported variety of DOS, or other (not-so-
  682.    * compatible) OS.
  683.    * For these we supply whatever info we can find by conventional calls.
  684.    */
  685.   if (have_trusted_values)
  686.     {
  687.       short dev_info = _get_dev_info(fhandle);   /* IOCTL Function 0 */
  688.       if (dev_info == -1)
  689.         return -1;    /* errno set by get_dev_info() */
  690.  
  691.       if (dev_info & 0x80)      /* it's a device */
  692.         {
  693.           if (_djstat_flags & _STAT_INODE)
  694.             {
  695.               /* We need the name of the device to invent an inode for it.
  696.                * We cannot get the REAL name, because SFT info is unavailable.
  697.                * If IOCTL tells us this is one of the standard devices, we
  698.                * can make an educated guess.  If not, we will invent inode
  699.                * with no name.  This will at least ensure that no two calls
  700.                * accidentally get the same inode number.
  701.                * We will also pretend devices belong to a special drive
  702.                * named `@'.
  703.                */
  704.               if (dev_info & 0xf)
  705.                 {
  706.                   char dev_name[16];
  707.  
  708.                   strcpy(dev_name, "@:\\dev\\");
  709.                   if (dev_info & 3)         /* either STDIN or STDOUT */
  710.                     strcat(dev_name, "CON     ");
  711.                   else if (dev_info & 4)    /* NULL device */
  712.                     strcat(dev_name, "NUL     ");
  713.                   else if (dev_info & 8)    /* CLOCK device */
  714.                     strcat(dev_name, "CLOCK$  ");
  715.  
  716.                   stat_buf->st_ino = _invent_inode(dev_name, 0, 0);
  717.                 }
  718.               else
  719.                 stat_buf->st_ino = _invent_inode("", 0, 0);
  720.  
  721.               _djstat_fail_bits |= _STFAIL_HASH;
  722.             }
  723.  
  724.           stat_buf->st_dev = -1;
  725. #ifdef  HAVE_ST_RDEV
  726.           stat_buf->st_rdev = -1;
  727. #endif
  728.  
  729.           stat_buf->st_mode |= (S_IFCHR | READ_ACCESS | WRITE_ACCESS);
  730.  
  731.           stat_buf->st_atime = stat_buf->st_mtime = time(0);
  732.           dos_ftime = 0;
  733.           stat_buf->st_ctime = _file_time_stamp(dos_ftime);
  734.         }
  735.       else
  736.         {
  737.           /* Regular file.  The inode will be arbitrary, as we don't have
  738.            * this file's name.  Sigh...
  739.            */
  740.           if ( (_djstat_flags & _STAT_INODE) == 0 )
  741.             {
  742.               _djstat_fail_bits |= _STFAIL_HASH;
  743.               stat_buf->st_ino = _invent_inode("", dos_ftime, trusted_fsize);
  744.             }
  745.  
  746.           /* Return the minimum access bits every file has under DOS. */
  747.           stat_buf->st_mode |= (S_IFREG | READ_ACCESS);
  748.           if (_djstat_flags & _STAT_ACCESS)
  749.             _djstat_fail_bits |= _STFAIL_WRITEBIT;
  750.  
  751.           /* Executables are detected if they have magic numbers.  */
  752.           if ( (_djstat_flags & _STAT_EXECBIT) &&
  753.                _is_executable((const char *)0, fhandle, (const char *)0))
  754.             stat_buf->st_mode |= EXEC_ACCESS;
  755.  
  756.           /* Lower 6 bits of IOCTL return value give the device number. */
  757.           stat_buf->st_dev = dev_info & 0x3f;
  758. #ifdef  HAVE_ST_RDEV
  759.           stat_buf->st_rdev = dev_info & 0x3f;
  760. #endif
  761.  
  762.           /* Novell Netware returns 0 drive number in the lower
  763.            * 6 bits of dev_info.  If this is what we get, return -2
  764.            * as drive number (it will be converted to '?' if added to 'A').
  765.            */
  766.           if (stat_buf->st_dev == 0)
  767.             {
  768.               stat_buf->st_dev = -2;
  769. #ifdef  HAVE_ST_RDEV
  770.               stat_buf->st_rdev = -2;
  771. #endif
  772.               _djstat_fail_bits |= _STFAIL_DEVNO;
  773.             }
  774.  
  775.           stat_buf->st_size  = trusted_fsize;
  776.           stat_buf->st_atime = stat_buf->st_ctime = stat_buf->st_mtime =
  777.             _file_time_stamp(dos_ftime);
  778.  
  779.       /* Additional time info for LFN platforms.  */
  780.       set_fstat_times (fhandle, stat_buf);
  781.         }
  782.       return 0;
  783.     }
  784.  
  785.   /* Don't have even values from conventional DOS calls.
  786.    * Give up completely on this funny handle.  ERRNO is already
  787.    * set by filelength() and/or getftime().
  788.    */
  789.   else
  790.     return -1;
  791. }
  792.  
  793. /*
  794.  * Main entry point.  This is a substitute for library fstat() function.
  795.  */
  796.  
  797. int
  798. fstat(int handle, struct stat *statbuf)
  799. {
  800.   int            e = errno;     /* save previous value of errno */
  801.  
  802.   if (!statbuf)
  803.     {
  804.       errno = EFAULT;
  805.       return -1;
  806.     }
  807.  
  808.   if (fstat_assist(handle, statbuf) == -1)
  809.     {
  810.       return -1;      /* already have ERRNO set by fstat_assist() */
  811.     }
  812.   else
  813.     {
  814.       errno = e;
  815.       return 0;
  816.     }
  817. }
  818.  
  819. #ifdef  TEST
  820.  
  821. #include <stdio.h>
  822. #include <fcntl.h>
  823.  
  824. unsigned short _djstat_flags = 0;
  825.  
  826. int main(int argc, char *argv[])
  827. {
  828.   struct stat stat_buf;
  829.   int fd = -1;
  830.   int i;
  831.   char *endp;
  832.  
  833.   argc--; argv++;
  834.   _djstat_flags = (unsigned short)strtoul(*argv, &endp, 0);
  835.  
  836.   /* Display 4 standard handles which are already open. */
  837.   for (i = 0; i <= 4; i++)
  838.     {
  839.       fstat(i, &stat_buf);
  840.       fprintf(stderr, "handle-%d: %d %6u %o %d %d %ld %lu %s", i,
  841.               stat_buf.st_dev,
  842.               (unsigned)stat_buf.st_ino,
  843.               stat_buf.st_mode,
  844.               stat_buf.st_nlink,
  845.               stat_buf.st_uid,
  846.               (long)stat_buf.st_size,
  847.               (unsigned long)stat_buf.st_mtime,
  848.               ctime(&stat_buf.st_mtime));
  849.       _djstat_describe_lossage(stderr);
  850.     }
  851.  
  852.   /* Now call fstat() for each command-line argument. */
  853.   while (++argv, --argc)
  854.     {
  855.       if (fd >= 19)
  856.         close(fd);
  857.       fd = open(*argv, O_RDONLY);
  858.       if (fd != -1 && !fstat(fd, &stat_buf))
  859.         {
  860.           fprintf(stderr, "%s (%d): %d %6u %o %d %d %ld %lu %s", *argv, fd,
  861.                   stat_buf.st_dev,
  862.                   (unsigned)stat_buf.st_ino,
  863.                   stat_buf.st_mode,
  864.                   stat_buf.st_nlink,
  865.                   stat_buf.st_uid,
  866.                   (long)stat_buf.st_size,
  867.                   (unsigned long)stat_buf.st_mtime,
  868.                   ctime(&stat_buf.st_mtime));
  869.       fprintf (stderr, "\t\t\tTimes: %lu %lu\n",
  870.            (unsigned long)stat_buf.st_atime,
  871.            (unsigned long)stat_buf.st_ctime);
  872.           _djstat_describe_lossage(stderr);
  873.         }
  874.       else
  875.         {
  876.           fputs(*argv, stderr);
  877.           perror(": failed to open/fstat");
  878.           _djstat_describe_lossage(stderr);
  879.         }
  880.     }
  881.   return 0;
  882. }
  883.  
  884. #endif  /* TEST */
  885.