/**********************************************************************
*
* undel    -- a file undelete utility
*
* Written in GNU c by Trevor Blight, Jan 93
*
* Portions of this program are based (loosely) on the program RESCUE2,
* published in "The MS-DOS Developer's Guide", 2nd ed., by Howard W. Sams & Co.
*
* This software may be used freely for any purpose whatsoever.
* Please enhance and return it to the public domain.
*
***********************************************************************/

#include <osbind.h>
#include <aesbind.h>
#include <vdibind.h>
#include <gemfast.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>
#include "undel.h"


#define  MAX_CLUSTERS   4078        /* max nr clusters for 12 bit FAT */
#define  CHAIN_END      1           /* used by get_cluster() to indicate eof */

typedef unsigned char   BYTE;
typedef unsigned short  WORD;
typedef unsigned long   LONG;


typedef struct {
   char  name[8];                   /* name */
   char  ext[3];                    /* extension */
   BYTE  attrib;                    /* attribute */
   BYTE  reserved[10];
   WORD  time;                      /* time: hhhhh mmm - mmm sssss */
   WORD  date;                      /* date: yyyyyyy m - mmm ddddd */
   WORD  cluster;                   /* starting cluster (Intel format) */
   LONG  fsize;                     /* total size in bytes (Intel format) */
} DENTRY;


BYTE    *FatBuffer;                 /* FAT table buffer address */
DENTRY  *dirBuffer;                 /* directory buffer address */
DENTRY  *dirend;
int	 wi_handle;                 /* window handle */

/*** forward declarations ***/

PRIVATE void win_tidy( void );
PRIVATE BOOL fill_dta( WORD srch_att );
PRIVATE void undelete_file( const char *pathp );
PRIVATE WORD next_cluster( const WORD clust );
PRIVATE BOOL match( const char *sname, const char *fname );
PRIVATE WORD swap_word( const WORD word );
PRIVATE LONG swap_long( const LONG l );
PRIVATE void fnmcpy( char* d, const char *s );
PRIVATE int  find_subdir( WORD dnum, const char *pathp, WORD *snump );


/*** sector <--> cluster conversion macros ***/
#define  sector_of(cl)      ((cl - 2)*bpbp->clsiz + bpbp->datrec)
#define  cluster_of(s)      (2 + (s - bpbp->datrec)/bpbp->clsiz)


/********************************************************
*
* call this function at end of program to tidy up AES windows
*
*******************************************************/

PRIVATE void win_tidy( void )
{
   wind_close(wi_handle);
   wind_delete(wi_handle);
   appl_exit();
} /* win_tidy() */


/*********************************************************
*
*  main entry point
*
*********************************************************/

int main(void)                  /* ignore params */
{

#define WI_KIND		0

char      dname[80];     /* directory name buffer */
int       dnum;                  /* drive nr, A: = 0, etc */
int       r;                     /* fsel return value */
int       sextn;
char      fname[64];
int	  xdesk,ydesk,hdesk,wdesk;
char      extn3str[4];
char      extn4str[4];

   appl_init();
   wind_get(0, WF_WORKXYWH, &xdesk, &ydesk, &wdesk, &hdesk);
   wi_handle = wind_create(WI_KIND, xdesk, ydesk, wdesk, hdesk);
   if( wi_handle < 0 ) {
      form_alert(1, "[1][too many windows open][QUIT]" );
      appl_exit();
      exit(1);		    /* quit  */
   } /* if */
   wind_open(wi_handle,xdesk,ydesk,wdesk,hdesk);
   if (atexit(win_tidy) != 0 ) {
      form_alert(1, "[1][can't arrange tidy up at exit][QUIT]" );
      win_tidy();
      exit(1);
   } /* if */

   graf_mouse(ARROW, 0x0L);
   strcpy (dname, "\\");
   dnum = -1;
   sextn = 0x0001; /* select extension 1 */
   strcpy( extn3str, "c  " );
   strcpy( extn4str, "h  " );
   r = fsel("Select a file to undelete ...",       /* I  display title for box */
        dname,       /* IO initial path spec \ ... \ no drive; returns selected path */
        &dnum,       /* IO selects drive -1=default 0=A: .. 5=F: */
        &sextn,      /* IO bitmap selected extn boxes :
                      *    0x0001 = extn0    0x0002 = extn1    0x0004 = extn2
                      *    0x0008 = extn3    0x0010 = extn4
                      */
        "*  ",       /* I  extension text not including "." for each of 1st 3 extn boxes  */
        "PRG",       /*    NOTE for all 5 boxes this should be 3 chars long, even if spaces   */
        "DOC",
        extn3str,
        extn4str,    /* IO extension text for last 2 extn boxes (editable) */
        fname);      /* O  returns complete path and file */


   if (r != 0) {
      undelete_file( fname );
   } /* if */

   return 0; /* successful completion */

} /* main */


/********************************************************
*
*  replacement for Fsfirst() & Fsnext() to recognise deleted files
*
********************************************************/

struct BPB {
   short recsiz;        /* physical sector size in bytes */
   short clsiz;         /* cluster size in sectors */
   short clsizb;        /* cluster size in bytes */
   short rdlen;         /* root directory length in sectors */
   short fsiz;          /* FAT size in sectors */
   short fatrec;        /* second FAT starts at this sector */
   short datrec;        /* data sectors start here */
   short numcl;         /* nr of data clusters on disk */
   short bflags;        /* flags */
};

/*** custom dta buffer for d_First() & d_Next() ***/
struct DTA {
	WORD            dta_sector;
        DENTRY         *dta_dirBuffer;
        DENTRY         *dta_entryp;
        DENTRY         *dta_dirend;
        unsigned short  dta_srchattr;
        char		dta_buf[4];
	char		dta_drv;
	char		dta_attribute;
	unsigned short	dta_time;
	unsigned short	dta_date;
	long		dta_size;
	char		dta_name[14];
};

PRIVATE struct BPB  *bpbp;
PRIVATE struct DTA  *dtap;

short d_First(char *drvp, char *pathp, short attr)
{
WORD      dnum;                  /* drive nr, A: = 0, etc */
int       error;
WORD      snum;                  /* sector number */

   dnum = toupper(*drvp) - 'A';
   if( (Drvmap() & (1 << dnum)) == 0 ) {
      return ENXIO;
   } /* if */

   if( *pathp == '\\' ) {
      pathp++;
   } /* if */

   /*** get FAT size, root directory sector, etc from Bios Parameter Block...  ***/
   if( (bpbp = (struct BPB *)Getbpb(dnum)) == 0 ) {
      char  emsg[80];
      sprintf( emsg, "[1][can't get parameters for drive %c:][QUIT]", 'A'+dnum);
      form_alert(1, emsg );
      exit(1);		    /* quit  */
   } /* if */

   /*** allocate memory for directory buffers & FAT tables ***/

   if( ((dirBuffer = (DENTRY *)malloc(bpbp->clsizb)) == NULL
      OR (FatBuffer = (BYTE *)malloc(bpbp->fsiz * bpbp->recsiz)) == NULL) )
   {
      form_alert(1, "[1][can't allocate enough memory][QUIT]" );
      exit(1);		    /* quit  */
   } /* if */

   dirend = &dirBuffer[bpbp->clsizb/sizeof(*dirBuffer)];

   /*** read in FAT ***/

   error = Rwabs( 0 /* read mode */, FatBuffer, bpbp->fsiz,
                  bpbp->fatrec - bpbp->fsiz, /* first FAT starts here */
                  dnum );
   if (error != 0) {
      char  emsg[80];
      sprintf( emsg, "[1][%s|while reading FAT on drive %c][QUIT]",
                                     strerror(-error), dnum + 'A' );
      form_alert(1, emsg);
      exit(1);              /* quit */
   } /* if */

   if( (error = find_subdir( dnum, pathp, &snum )) != 0 )
   {
      return error;
   } /* if */

   if( (attr & FA_DIR) == 0 ) {
      char  emsg[80];
      sprintf( emsg, "[1][path is not a directory:|%s][QUIT]", pathp );
      form_alert(1, emsg);
      exit(1);		    /* quit */
   } /* if */

   /*******************************************************
   *
   * snum is the sector which has the directory entries
   *
   *******************************************************/

   error = Rwabs( 0 /* read mode */, dirBuffer, bpbp->clsiz, snum, dnum );
   if (error != 0) {
      char  emsg[80];
      sprintf( emsg, "[1][%s|while reading|directory entries on drive %c][QUIT]",
				     strerror(-error), dnum + 'A' );
      form_alert(1, emsg);
      exit(1);		    /* quit */
   } /* if */

   /*** fill the dta buffer with the first directory entry in dirBuffer ***/

   dtap = (struct DTA *)Fgetdta();

   dtap->dta_sector = snum;
   dtap->dta_dirBuffer = dirBuffer;
   dtap->dta_entryp = dirBuffer;
   dtap->dta_dirend = dirend;
   dtap->dta_drv = dnum;

   return fill_dta(0) ? 0 : -ENOENT;

} /* d_First() */


short d_Next()
{
   return fill_dta(0) ? 0 : -ENMFILES;   /* OK ? */
} /* d_Next() */


/*************************************************************
*
* copy next subdirectory entry into dta
*
*************************************************************/

PRIVATE BOOL fill_dta( WORD srch_att )
{
DENTRY     *dptr;                  /* current directory entry */
WORD        cluster;
int         error;
struct DTA *dtap;

   dtap = (struct DTA *)Fgetdta();
   dptr = dtap->dta_entryp++;

   if( dptr >= dtap->dta_dirend ) {

      /*** all entries in this cluster exhausted, get next cluster ***/

      if( dtap->dta_sector >= bpbp->datrec ) {
	 /*** this is a subdirectory ***/
	 cluster = cluster_of( dtap->dta_sector );
	 if( (cluster = next_cluster( cluster )) <= CHAIN_END ) {
	    return( FALSE ); /* no more files */
	 }
	 else {
	    dtap->dta_sector = sector_of( cluster );
	 } /* if */
      }
      else if( dtap->dta_sector >= bpbp->fatrec + bpbp->fsiz ) {
	 /*** this is the root directory ***/
	 dtap->dta_sector += bpbp->clsiz;    /* next cluster in root dir */
	 if( dtap->dta_sector >= bpbp->datrec ) {
	    return( FALSE ); /* no more files */
	 } /* if */
      }
      else {
	 /*** this shouldn't happen ***/
	 char  emsg[80];
	 sprintf( emsg, "[1][error while reading FAT on drive %c][QUIT]",
					 dtap->dta_drv + 'A' );
	 form_alert(1, emsg);
	 exit(1);	       /* quit */
      } /* if */

      error = Rwabs( 0 /* read mode */, dtap->dta_dirBuffer,
		     bpbp->clsiz, dtap->dta_sector, dtap->dta_drv );
      if (error != 0) {
	 char  emsg[80];
	 sprintf( emsg, "[1][%s|while reading|directory entries on drive %c][QUIT]",
				strerror(-error), dtap->dta_drv + 'A' );
	 form_alert(1, emsg);
	 exit(1);	       /* quit */
      } /* if */
      dtap->dta_entryp = dtap->dta_dirBuffer;
      dptr = dtap->dta_entryp++;
   } /* if */

   if( dptr->name[0] == '\0' ) {
      return( FALSE );	     /* no more files */
   } /* if */

   dtap->dta_attribute = dptr->attrib;
   dtap->dta_time = dptr->time;
   dtap->dta_date = dptr->date;
   dtap->dta_size = dptr->fsize;
   fnmcpy( dtap->dta_name, dptr->name );
   assert( strlen(dtap->dta_name) < sizeof(dtap->dta_name) ); /* in case overflow */

   return TRUE;

} /* fill_dta() */


/*************************************************************
*
* find the first sector for a pathname
* (the sector contains the subdirectory list)
*
**************************************************************/

PRIVATE int find_subdir( WORD dnum, const char *pathp, WORD *snump )
{
WORD      snum;
WORD      attr;
WORD      cluster;
int       error;
DENTRY   *dptr;                  /* current directory entry */

   /*** follow the chain of directory entries to match path name ***/

   snum = bpbp->fatrec + bpbp->fsiz;        /* root directory starts here */

   if( *pathp == '\\' ) {
         pathp++;
   } /* if */
   while( *pathp != '\0' ) {   /* loop thru all subdirectories in the path */

      error = Rwabs( 0 /* read mode */, dirBuffer, bpbp->clsiz, snum, dnum );
      if (error != 0) {
	 char  emsg[80];
	 sprintf( emsg, "[1][%s|while reading|directory entries on drive %c][QUIT]",
					strerror(-error), dnum + 'A' );
	 form_alert(1, emsg);
	 exit(1);	       /* quit */
      } /* if */
      dptr = dirBuffer;

      /*** loop thru all directory entries until the name is found ***/

      while( ((dptr->attrib & FA_DIR) == 0)         /* not a directory ? */
            OR NOT match( pathp, dptr->name ) ) {   /* names don't match ? */

	 if( ++dptr >= dirend ) {

	    /*** name not found in this cluster, get next cluster ***/
   
	    if( snum >= bpbp->datrec ) {
	       /*** this is a subdirectory ***/
	       cluster = cluster_of( snum );
	       if( (cluster = next_cluster( cluster )) <= CHAIN_END ) {
		  return( -EPATH );	/* path not found */
	       }
	       else {
		  snum = sector_of( cluster );
	       } /* if */
	    }
	    else if( snum >= bpbp->fatrec + bpbp->fsiz ) {
	       /*** this is the root directory ***/
	       snum += bpbp->clsiz;    /* next cluster in root dir */
	       if( snum >= bpbp->datrec ) {
		  return( -EPATH );	/* path not found */
	       } /* if */
	    }
	    else {
	       /*** this shouldn't happen ***/
	       char  emsg[80];
	       sprintf( emsg, "[1][error while reading|directory on drive %c][QUIT]",
					       dnum + 'A' );
	       form_alert(1, emsg);
	       exit(1); 	     /* quit */
	    } /* if */
   
	    error = Rwabs( 0 /* read mode */, dirBuffer, bpbp->clsiz, snum, dnum );
	    if (error != 0) {
	       char  emsg[80];
	       sprintf( emsg, "[1][%s|while reading|directory entries on drive %c][QUIT]",
					      strerror(-error), dnum + 'A' );
	       form_alert(1, emsg);
	       exit(1); 	     /* quit */
	    } /* if */
      
	    dptr = dirBuffer;
	 } /* if */
	 
      } /* while */

      /*** the next subdirectory name has been found ***/

      snum = sector_of( swap_word(dptr->cluster) );
      attr = dptr->attrib;
      while( (*pathp != '\0') AND (*pathp != '\\') ) {
         pathp++;
      } /* while */
      if( *pathp == '\\' ) {
         pathp++;
      } /* if */
   } /* while */

   *snump = snum;
   return 0;

} /* find_subdir() */


/**************************************************************
*
* undelete_file
*  pathp      -- points to full pathname of file
*                eg A:\..dir_path..\file_name
*
* - find the directory entry of the file
* - assume the file contents are in the empty clusters following the starting
*   cluster of the file.
* - build an image of the file in data_buffer
* - write out data_buffer to a new file
*
***************************************************************/

PRIVATE void undelete_file( const char *pathp )
{
WORD     snum;             /* sector number of directory entry */
WORD     dnum;             /* disk drive number */
WORD     cluster;
WORD     current;          /* current cluster in chain of deleted file */
WORD     previous;         /* unused ??? */
unsigned nrcl;             /* number of clusters in deleted file */
void    *data_buffer;      /* put copy of restored file here */
void    *datap;            /* points into data_buffer */
int      i;
int      error;            /* error return code for various functions */
char     fname[13];        /* name of file to be undeleted */
char     dir_path[80];     /* directory path of file to be undeleted */
long     file_size;        /* size of the file */
DENTRY  *dptr;             /* points to a copy of the directory entry of file */
int      fh;               /* file handle for restored copy of file */
char     restored_fn[80];  /* name of restored file */
char     Path[80];         /* used for fsel_input() */
int      ExitButton;       /* used for fsel_input() */
long     bytes_written;    /* returned by Fwrite() */

   assert( pathp[1] == ':' );
   dnum = toupper(*pathp) - 'A';
   if( (Drvmap() & (1 << dnum)) == 0 ) {
      char  emsg[80];
      sprintf( emsg, "[1][drive %c: doesn't exist!][QUIT]", *pathp );
      form_alert(1, emsg );
      exit(1);		    /* quit  */
   } /* if */

   /*** split pathp into directory path & filename path ***/
   strcpy (dir_path, pathp+2);
   i = strlen( dir_path );
   while( i >= 0 ) {
      if( dir_path[i] == '\\' ) {
         strcpy(fname, dir_path+i+1);
         dir_path[i+1] = '\0';
         break;
      } /* if */
      i--;
   } /* while */

   if( find_subdir( dnum, dir_path, &snum ) != 0 ) {
      char  emsg[80];
      sprintf( emsg, "[1][can't find file|%s][QUIT]", fname);
      form_alert(1, emsg );
      exit(1);		    /* quit  */
   } /* if */


   /*** look for file name in directory sector ***/

   if (Rwabs( 0 /* read mode */, dirBuffer, bpbp->clsiz, snum, dnum ) != 0) {
      char  emsg[80];
      sprintf( emsg, "[1][%s|while reading|directory entries on drive %c][QUIT]",
				strerror(-error), dtap->dta_drv + 'A' );
      form_alert(1, emsg);
      exit(1);	       /* quit */
   } /* if */
   dptr = dirBuffer;

   while( NOT match( fname, dptr->name ) ) {

      dptr++;         /* point to next name */
      if( dptr >= dirend ) {

	 /*** all entries in this cluster exhausted, get next cluster ***/

	 if( snum >= bpbp->datrec ) {
	    /*** this is a subdirectory ***/
	    cluster = cluster_of( snum );
	    if( (cluster = next_cluster( cluster )) <= CHAIN_END ) {
	       char  emsg[80];
	       sprintf( emsg, "[1][can't find file|\"%s\"][QUIT]", fname);
	       form_alert(1, emsg );
	       exit(1); 	     /* quit  */
	    }
	    else {
	       snum = sector_of( cluster );
	    } /* if */
	 }
	 else if( snum >= bpbp->fatrec + bpbp->fsiz ) {
	    /*** this is the root directory ***/
	    snum += bpbp->clsiz;	/* next cluster in root dir */
	    if( snum >= bpbp->datrec ) {
	       char  emsg[80];
	       sprintf( emsg, "[1][can't find file|\"%s\"][QUIT]", fname);
	       form_alert(1, emsg );
	       exit(1); 	     /* quit  */
	    } /* if */
	 }
	 else {
	    /*** this shouldn't happen ***/
	    char  emsg[80];
	    sprintf( emsg, "[1][error while reading|root directory on drive %c][QUIT]",
					    dnum + 'A' );
	    form_alert(1, emsg);
	    exit(1);		  /* quit */
	 } /* if */

	 if (Rwabs(0 /* read mode */, dirBuffer, bpbp->clsiz, snum, dnum) != 0) {
	    char  emsg[80];
	    sprintf( emsg, "[1][%s|while reading|directory entries on drive %c][QUIT]",
				   strerror(-error), dtap->dta_drv + 'A' );
	    form_alert(1, emsg);
	    exit(1);		  /* quit */
	 } /* if */
	 dptr = dirBuffer;
      } /* if */

      if( dptr->name[0] == '\0' ) {
	 char  emsg[80];
         sprintf( emsg, "[1][can't find file|\"%s\"][QUIT]", fname);
	 form_alert(1, emsg );
	 exit(1);	       /* quit	*/
      } /* if */
   } /* while */


   /********************************************************
   *
   * found the file to be undeleted:
   * - dptr points to a copy of the directory entry
   *
   ********************************************************/

   if( (char)dptr->name[0] != (char)0xe5) {
      char  emsg[80];
      sprintf( emsg, "[1][file is not deleted|\"%s\"][QUIT]", pathp);
      form_alert(1, emsg );
      exit(1);		    /* quit  */
   } /* if */

   current = previous = swap_word(dptr->cluster);
   if( next_cluster(current) != 0 ) {
      char  emsg[80];
      sprintf( emsg, "[1][\"%s\"|has been overwritten][QUIT]", pathp);
      form_alert(1, emsg );
      exit(1);		    /* quit  */
   } /* if */

   if( dptr->attrib == FA_DIR ) {
      nrcl = 0;                  /* directories have file size = 0 */
   }
   else {
      file_size = swap_long(dptr->fsize);
      nrcl = (file_size + bpbp->clsizb - 1)/bpbp->clsizb;
   } /* if */

   if( (datap = data_buffer = malloc( nrcl*bpbp->clsizb )) == NULL ) {
      form_alert(1, "[1][can't allocate enough memory][QUIT]" );
      exit(1);		    /* quit  */
   } /* if */

   while( nrcl > 0 ) {
      if( current > bpbp->numcl ) {
         char  emsg[80];
         sprintf( emsg, "[1][can't recover all of|\"%s\"][Carry On][QUIT]", pathp );
         if( form_alert(1, emsg ) == 2 ) {
            exit(1);		    /* quit  */
         } /* if */
         file_size = (datap-data_buffer)*sizeof(*datap);
      } /* if */
      if( next_cluster( current ) == 0 ) {
	 error = Rwabs( 0 /* read mode */, datap, bpbp->clsiz, sector_of(current), dnum );
	 if (error != 0) {
	    char  emsg[80];
	    sprintf( emsg, "[1][%s|while reading|file sectors on drive %c][QUIT]",
				   strerror(-error), dtap->dta_drv + 'A' );
	    form_alert(1, emsg);
	    exit(1);		  /* quit */
	 } /* if */
	 datap += bpbp->clsizb;
         previous = current;
         nrcl--;
      } /* if */
      current++;
   } /* while */

   /*** now write the file out ***/

   strcpy(Path, "A:\\*.*");
   Path[0] += Dgetdrv();
   if( fsel_input(Path, restored_fn, &ExitButton) == 0 )
   {
      form_alert(1, "[1]can't select a name|for restored file[oh dear]");
   } /* if */
   if( ExitButton == 0 )
   {
      /* user has cancelled !!! */
      exit(1);
   } /* if */

   /*** merge Path & restored file name ***/
   i = strlen( Path );
   while( i >= 0 ) {
      if( Path[i] == '\\' ) {
         strcpy(Path+i+1, restored_fn);
         strcpy(restored_fn, Path);
         break;
      } /* if */
      i--;
   } /* while */

   
   if( (fh = Fopen( restored_fn, 1)) < 0 ) {
      if( fh != -ENOENT ) {
	 char  emsg[80];
	 sprintf( emsg, "[1][%s|while opening %s][QUIT]",
				strerror(-fh), restored_fn );
	 form_alert(1, emsg);
	 exit(1);	       /* quit */
      } /* if */
      if( (fh = Fcreate( restored_fn, 0)) < 0 ) {
	 char  emsg[80];
	 sprintf( emsg, "[1][%s|while creating %s][QUIT]",
				strerror(-fh), restored_fn );
	 form_alert(1, emsg);
	 exit(1);	       /* quit */
      } /* if */
   } /* if */
   if( (bytes_written = Fwrite(fh, file_size, data_buffer)) < 0 ) {
      char  emsg[80];
      error = (int)bytes_written;
      sprintf( emsg, "[1][%s|while writing %s][QUIT]",
			     strerror(-error), restored_fn );
      form_alert(1, emsg);
      exit(1);		    /* quit */
   } /* if */

   if( (error = Fclose(fh)) != 0 ) {
      char  emsg[80];
      sprintf( emsg, "[1][%s|while trying to close %s][QUIT]",
			     strerror(-error), restored_fn );
      form_alert(1, emsg);
      exit(1);		    /* quit */
   } /* if */

} /* undelete_file() */


/**************************************************************
*
*  find next cluster in a chain of FAT entries ...
*
*  FAT entries:   12-bit      16-bit         meaning
*                 ------      ------         -------
*                 000         0000           free cluster
*                 001         0001           shouldn't happen
*                 002 - FEF   0002 - 7FFF    next cluster nr
*                             8000 - FFEF    shouldn't happen
*                 FF0 - FF7   FFF0 - FFF7    bad cluster
*                 FF8 - FFF   FFF8 - FFFF    end of chain
*
*  max nr clusters for 12 bit FAT = 4078
*
**************************************************************/

PRIVATE WORD next_cluster( const WORD clust )
{
int   index;      /* index into FAT */
WORD  newclust;   /* next cluster */

   /*** if nr clusters on disk > MAX_CLUSTERS use 16-bit FATs ***/

   if( bpbp->numcl > MAX_CLUSTERS ) {
      /*** 16-bit FATs ***/
      index = clust;
      newclust = FatBuffer[index] + (FatBuffer[index+1] << 8); /* byte swap */
      if( newclust >= 0xfff8 ) {
         return CHAIN_END;
      }
      else if( newclust >= 0x8000 )
      {
         /*** there is a file system problem if this happens ***/
	 char  emsg[128];
         sprintf(emsg, "[1][file system problem|bad cluster chain|"
             "at cluster %1d, value is %1d][QUIT]", (int)clust, (int)newclust );
	 form_alert(1, emsg );
         exit(1);		    /* quit  */
      } /* if */
   }
   else {
      /*** 12-bit FATs ***/
      index = clust + (clust >> 1);
      newclust = FatBuffer[index] + (FatBuffer[index+1] << 8); /* byte swap */
      if( clust & 1) {        /* is clust odd ?? */
         newclust >>= 4;
      } /* if */
      newclust &= 0x0fff;
      if( newclust >= 0xff8 ) {
         return CHAIN_END;
      }
      else if( newclust >= 0x0ff0 )
      {
         /*** there is a file system problem if this happens ***/
	 char  emsg[128];
         sprintf(emsg, "[1][file system problem|bad cluster chain|"
             "at cluster %1d, value is %1d][QUIT]", (int)clust, (int)newclust );
	 form_alert(1, emsg );
         exit(1);		    /* quit  */
      } /* if */
   } /* if */

   return newclust;

} /* next_cluster() */


/********************************************************
*
*  check names for a match.
*  fname in format 'nnnnnnnneee', ie 8 chars name + 3 chars extension
*  sname is a null terminated string.
*  match up to first directory separator (ie '\') in sname
*  return TRUE if match found, FALSE otherwise
*
********************************************************/

PRIVATE BOOL match( const char *sname,    /* search match name */
                    const char *fname     /* file or directory name */
                  )
{
const char  *fext;    /* file or dir extension */

   fext = fname + 8;
   while( fname < fext+3 ) {
      if( *fname == *sname ) {
         fname++;
         sname++;
      }
      else {
         /*** check why names differ ***/
         switch( *sname++ ) {
         case '.':
            if( (*fname == ' ') OR (fname == fext) ) {
               fname = fext;
            }
            else {
               return FALSE;
            } /* if */
            break;
         case '\\':
         case '\0':
            if( *fname == ' ' ) {
               return TRUE;
            } /* if */
            break;
         default:
            return FALSE;
         } /* switch */
      } /* if */
   } /* while */
   return TRUE;
} /* match() */


/*******************************
*
*  swap bytes the bytes in a word argument
*
*******************************/

PRIVATE WORD swap_word( const WORD word )
{
WORD  r;

   ((char *)&r)[0] = ((char *)&word)[1];
   ((char *)&r)[1] = ((char *)&word)[0];
   return r;

} /* swap_word() */


PRIVATE LONG swap_long( const LONG l )
{
LONG  r;

   ((char *)&r)[0] = ((char *)&l)[3];
   ((char *)&r)[1] = ((char *)&l)[2];
   ((char *)&r)[2] = ((char *)&l)[1];
   ((char *)&r)[3] = ((char *)&l)[0];
   return r;

} /* swap_word() */


/********************************************
*
* copy filename from directory entry to dta
*
********************************************/

PRIVATE void fnmcpy(char* d, const char *s)
{
int         i;     /* character counter */
const char *extp;  /* pointer to extension */

   extp = s+8;

   i = 8;   /* copy up to 8 chars for name */
   while( (i > 0) AND ((unsigned)*s > ' ') ) {     /* stop at '\0' or ' ' */
      *d++ = *s++;
      i--;
   } /* while */

   if( (unsigned)*extp > ' ' ) {
      *d++ = '.';
      i = 3;   /* copy up to 3 chars for extension */
      while( (i > 0) AND ((unsigned)*extp > ' ') ) {
	 *d++ = *extp++;
	 i--;
      } /* while */
   } /* if */
   *d = '\0';
} /* fnmcpy() */



/************************* end of undel.c *************************/
