/*******************************************************************************
*  Program:    COPYDISK.C  C 5.1 and MASM 5.1
*
*  Purpose:    Copies volume label, subdirectory structure and all files
*	       regardless of attribute type from one disk medium to another.
*
*  Author:		Gordon Harris
*			3349 Humboldt Ave S
*			Minneapolis, MN 55408
*
*			Comments can be addressed to my
*			CompuSurve address: [72611,620]
*
*
*
* Description: COPYDISK is an XCOPY like utility which allows you to copy
*	       an entire disk to a drive of differing type, e.g. copy
*	       the contents of a 1.2 m floppy to a 1.44 m floppy, etc.
*
*	       Unlike XCOPY, COPYDISK will copy the volume label from the
*	       source disk to the target, as well as copying all
*	       subdirectories and files including hidden, system or read-
*	       only files and directories.  All files on the target disk
*	       created by COPYDISK will have identical attributes (dates,
*	       times, etc) as the files on the source disk.  If the
*	       source disk is bootable, so will the resulting target disk.
*
*  Syntax:     The syntax for using COPYDISK is:
*
*	       COPYDISK sourcedrive: targetdrive: [-n] [-x] [-f]
*
*	       where "sourcedrive:" and "targetdrive:" are valid dos drives
*	       and [-n], [-x] and [-f] are optional parameters.
*
*  Operation:  Given valid parameters, COPYDISK (1) performs a media check
*	       on the indicated drives, (2) prompts the user for permission
*	       to delete all existing data from the target drive, (3) copies
*	       the volume label from the source drive to the target and then
*	       (4) proceeds to copy all files and directories from the source
*	       to the target.
*
*	       COPYDISK will abort if its check of the media type of the
*	       target disk reveals that it is a fixed disk.  This protects
*	       you from inadvertently deleting the contents of a hard disk
*	       either by using an incorrect parameter for the target drive
*	       or by using a virtual drive name created by ASSIGN or SUBST
*	       which represents a fixed disk drive or subdirectory on a
*	       hard disk.
*
*	       During the media check, COPYDISK installs its own critical
*	       error handler.  If a error is detected reading either the
*	       source or target drives, COPYDISK will prompt you to retry
*	       access to the disk.  If you choose not to retry access to
*	       the target disk, COPYDISK will prompt you as to whether you
*	       wish to format the target.
*
*	       COPYDISK will also abort if the data on the source disk is
*	       too large to fit on the empty target disk, or if any errors
*	       occur reading data from the source or writing data to the
*	       target disks.
*
*  Optional Parameters:
*	    -n (no prompt).  This is useful when using COPYDISK in batch
*	       files.  With the "-n" parameter, COPYDISK will not prompt you
*	       for permission to delete all data from the target disk.
*
*	    -x (relaxed media checking).  With this parameter, the target
*	       disk may be a hard disk and the source data may be larger
*	       than the capacity of the target disk.
*
*	    -f (format target automatically if media check failure).  With
*	       this parameter, the DOS FORMAT.COM command will be spawned
*	       without prompting if the target disk fails the media check.
*
*  Credits:    Ray Duncan, Advanced MS DOS Programming, 1988, Microsoft Press
*	       Kevin P. Welch, "WFINDER"
*
*
********************************************************************************/
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <malloc.h>
#include <process.h>
#include "COPYDISK.h"

char	    *pSource;
size_t	    nBufSize;
int	    errno;
int	    nNumFiles;
char	    szSyntaxMsg [] = "Syntax: COPYDISK sourcedrive: targetdrive: -NoPrompt \n\n",
	    szMsg[80] = "\n";

union REGS inregs, outregs;
struct SREGS segregs;
unsigned _osversion;

main(int argc, char * argv[] )
   {
   int	    bPrompt, bCheck, bFormat, n;
   struct   drvinfo_t drv1, drv2;
   int	    crterror = 0;
   char     szDrive1 [10], szDrive2 [10];

   errno = EINVAL;

   strcpy (szDrive1, " :\\*.*");
   strcpy (szDrive2, " :");
   if (argc < 3)
      ErrExit("");
   szDrive1 [0] = toupper(*argv [1]);
   szDrive2 [0] = toupper(*argv [2]);

   /* check for same source & target drives */
   if (szDrive1 [0] == szDrive2 [0])
      ErrExit("Sourcedrive = Targetdrive");

   /* check remaining parameters */
   bPrompt = TRUE;
   bCheck  = TRUE;
   bFormat = FALSE;
   for (n = 3; n < argc; n++)
      {
      if (argv [n] [0] == '-' || argv [n] [0] == '/')
	 switch ( toupper(argv [n] [1]) )
	    {
	    case 'N':
	       bPrompt = FALSE;
	       break;
	    case 'X':
	       bCheck = FALSE;
	       break;
	    case 'F':
	       bFormat = TRUE;
	    }
      }

   /* install critical error handler */
   setint24 (&crterror);

   /* perform media check on source drive */
   while ( !drvinfo(szDrive1 [0] - '@', &drv1))
      {
      if (!crterror)
	 {
	 sprintf (szMsg, "Source drive %c: not valid", *szDrive1);
	 ErrExit (szMsg);
	 }
      crterror = 0;
      printf("\r\7Error reading source disk %c:  Retry? (Y/N)  \b", *szDrive1);
      if (toupper (getche()) != 'Y')
	 ErrExit ("");
      }

   /* perform media check on target drive */
   while ( !drvinfo(szDrive2 [0] - '@', &drv2))
      {
      if (!crterror)
	 {
	 sprintf (szMsg, "Target drive %c: not valid", *szDrive2);
	 ErrExit (szMsg);
	 }

      if (!bFormat)
	 {
	 printf("\r\7Error reading target disk %c:  Retry? (Y/N)  \b", *szDrive2);
	 if (toupper (getche()) == 'Y')
	    continue;
	 printf ("\rDo you wish to format disk in drive %c: ? (Y/N) ",*szDrive2);
	 bFormat = (toupper (getche()) == 'Y');
	 }

      if (crterror && bFormat)
	 {
	 switch (LOBYTE(_osversion))
	    {
	    case 3:
	       strcpy(szMsg, "/H");
	       break;
	    case 4:
	       strcpy(szMsg, "/AUTOTEST");
	       break;
	    default:
	       szMsg[0] = '\0';
	    }
	 printf("\rFormatting %s                                     \n", szDrive2);
	 spawnlp(P_WAIT,"format.com", "format.com", szDrive2, szMsg, NULL);
	 }
      else
	 {
	 ErrExit("");
	 }
      crterror = 0;
      bFormat = FALSE;
      }

   /* de-install critical error handler here */
   restint24 ();

   /* check results of previous media check */
   if (bCheck)
      {
      if (drv2.type == 0x0f8)
	 {
	 errno = EACCES;
	 sprintf(szMsg, "Targetdrive %c: is a fixed disk", *szDrive2);
	 ErrExit(szMsg);
	 }

      if (drv1.lDataSize > drv2.lDiskSpace)
	 {
	 sprintf (szMsg, "Source drive %c: data too large for target drive %c",
			  *szDrive1, *szDrive2);
	 ErrExit(szMsg);
	 }
      }

   if (bPrompt)
      {
      printf ("\rWARNING: all data on drive %c will be deleted.  Do you wish to continue? (Y/N) ", *szDrive2);
      if (toupper (getche()) != 'Y')
	 exit(1);
      }

   volcopy (szDrive1[0] - '@', szDrive2 [0] - '@');

   printf ("\rDeleting files and directories on drive %c:"
	   "                                     \r", *szDrive2);
   strcat (szDrive2, "\\");
   chdir (szDrive2);
   strcat (szDrive2, "*.*");
   DelAll(szDrive2, _A_NORMAL | _A_RDONLY | _A_HIDDEN | _A_SYSTEM);

   /* Allocate file buffer */
   nBufSize = _memmax();
   pSource = malloc (nBufSize);

   if (pSource == NULL)
      {
      ErrExit ("Insufficient memory available");
      }

   nNumFiles = 0;
   szDrive1 [2] = '\0';
   szDrive2 [2] = '\0';
   printf ("Copying files and directories from drive %c: to %c:\n%s\\\n", *szDrive1, *szDrive2, szDrive2);
   strcat (szDrive1, "\\*.*");
   strcat (szDrive2, "\\*.*");
   CopyAll(szDrive1, szDrive2, _A_NORMAL | _A_RDONLY | _A_HIDDEN | _A_SYSTEM);
   free(pSource);
   printf(" %d file(s) copied                            \n", nNumFiles);
   }

void ErrExit (char *msg)
   {
   fprintf(stderr, "\nError %d--%s%s", errno, _strerror(msg), szSyntaxMsg);
   exit(errno);
   }


BOOL DelAll(
   PSTR     szFileSpec,
   WORD     wAttributes)
   {
   BOOL     bContinue;
   WORD     wEntry, wDirEntries;
   struct find_t     DirEntry;
   char     szPath[64], szSpec[64], szEntry[64],
	    szCurFile [64], szCurDir [64];

   /* initialization */
   bContinue = TRUE;
   wDirEntries = 0;

   /* separate file spec into path and wildcards */
   for (wEntry=strlen(szFileSpec)-1; szFileSpec[wEntry]!='\\'; wEntry-- );

   strcpy( szPath, szFileSpec );
   szPath[wEntry] = 0;

   strcpy( szCurFile, szFileSpec );
   szCurFile[wEntry + 1] = 0;

   strcpy( szSpec, &szFileSpec[wEntry+1] );

   /* perform search for normal files */
   if ( _dos_findfirst(szFileSpec,wAttributes,&DirEntry) == 0 )
      {
      /* repeat until all entries exhausted */
      do {
         /* output current file name */
	 szCurFile[wEntry + 1] = 0;
	 strcat (szCurFile, DirEntry.name);
	 if (DirEntry.attrib != _A_NORMAL || DirEntry.attrib != _A_ARCH)
	    _dos_setfileattr (szCurFile, _A_NORMAL);
	 unlink (szCurFile);
	 } while ( _dos_findnext(&DirEntry) == 0 );
      }

   /* perform search for sub-directories */
   sprintf( szEntry, "%s\\*.*", szPath );
   if ( _dos_findfirst(szEntry,_A_SUBDIR | _A_NORMAL | _A_RDONLY | _A_HIDDEN | _A_SYSTEM, &DirEntry) == 0 )
      {
      /* repeat until all entries exhausted */
      do {
         /* eliminate special directory entries */
	 if ( (DirEntry.attrib & _A_SUBDIR) && (DirEntry.name[0]!='.') )
	    {
	    sprintf( szEntry, "%s\\%s\\%s", szPath, DirEntry.name, szSpec );
	    sprintf( szCurDir, "%s\\%s", szPath, DirEntry.name);
	    bContinue = DelAll( szEntry, wAttributes );
	    rmdir (szCurDir);
	    }
	 } while ( (bContinue)&&(_dos_findnext(&DirEntry) == 0) );
      }

   /* return final result */
   return( bContinue );
   }


BOOL CopyAll(
   PSTR     szFileSpec1,
   PSTR     szFileSpec2,
   WORD     wAttributes)
   {
   BOOL     bContinue;
   WORD     wEntry, wDirEntries;

   struct find_t  DirEntry;
   struct find_t near *pDirEntry;
   char near	  *pPos;
   int		  hSource, hTarget;
   size_t	  nRead, nWrite, nNumRead, nNumWrite;
   long 	  lTotRead, lTotWrite;
   int		  n, nFiles;

   char     szPath[64], szSpec[64], szEntry[64],
	    szCurFile [68], szNewFile [68], szNewDir [64];


   /* initialization */
   bContinue = TRUE;
   wDirEntries = 0;

   /* separate file spec into path and wildcards */
   for (wEntry=strlen(szFileSpec1)-1; szFileSpec1[wEntry]!='\\'; wEntry-- );

   strcpy( szPath, szFileSpec1 );
   szPath[wEntry] = 0;

   strcpy( szCurFile, szFileSpec1 );
   szCurFile[wEntry + 1] = 0;

   strcpy( szNewFile, szFileSpec1 );
   szNewFile[wEntry + 1] = 0;
   szNewFile[0] = szFileSpec2[0];

   strcpy( szSpec, &szFileSpec1[wEntry+1] );

   lTotRead = 0L;
   lTotWrite = 0;

   /* perform search for normal files */
   if ( _dos_findfirst(szFileSpec1,wAttributes,&DirEntry) == 0 )
      {
      /* repeat until all entries exhausted */
      do {
	 nFiles = 0;
	 pPos = pSource;

	 while (pPos + (sizeof (DirEntry) * 2) < pSource + nBufSize)
	    {
	    /* copy DirEntry into buffer */
	    memcpy (pPos, &DirEntry, sizeof (DirEntry) );
	    pDirEntry = pPos;
	    pPos += sizeof (DirEntry);

	    /* Open source file */
	    szCurFile[wEntry + 1] = '\0';
	    strcat (szCurFile, DirEntry.name);
	    if (lTotRead == 0L)
	       {
	       if (_dos_open (szCurFile, O_RDONLY, &hSource) != 0)
		  {
		  sprintf (szMsg, "Could not open file %s", szCurFile);
		  ErrExit(szMsg);
		  }
	       }

	    /* read as much of file into buffer as possible */
	    if(_dos_read(hSource, pPos, nBufSize - (pPos - pSource), &nNumRead) != 0)
	       {
	       sprintf (szMsg, "Error reading file %s", szCurFile);
	       ErrExit (szMsg);
	       }

	    pPos += nNumRead;
	    lTotRead += (long) nNumRead;
	    nFiles++;
	    printf(" %-12s %7ld bytes read   \r",
			pDirEntry->name,
			lTotRead);

	    if (lTotRead == DirEntry.size)
	       {
	       _dos_close (hSource);
	       lTotRead = 0L;
	       if (_dos_findnext(&DirEntry) != 0)
		  break;
	       }
	    else
	       break;
	    }

	 /* Write new files */
	 /* position of first file */
	 pDirEntry = pSource;
	 pPos = pSource + sizeof (DirEntry);
	 for (n = 0; n < nFiles; n++)
	    {
	    /* check to see if we are finishing a file.. */
	    if (lTotWrite == 0)
	       {
	       szNewFile[wEntry + 1] = '\0';
	       strcat (szNewFile, pDirEntry->name);

	       if (_dos_creat (szNewFile, pDirEntry->attrib, &hTarget) != 0)
		  {
		  sprintf (szMsg, "Could not create file %s", szNewFile);
		  ErrExit (szMsg);
		  }
	       }

	    if (pDirEntry->size > nBufSize - (pPos - pSource))
	       nWrite = nBufSize - (pPos - pSource);
	    else
	       nWrite = pDirEntry->size;

	    if ( (pDirEntry->size - lTotWrite) < nWrite)
	       nWrite = (unsigned int) (pDirEntry->size - lTotWrite);

	    if ( _dos_write (hTarget, pPos, nWrite, &nNumWrite) != 0 )
	       {
	       sprintf (szMsg, "Error writing file %s", szNewFile);
	       ErrExit (szMsg);
	       }
	    if (nNumWrite != nWrite)
	       {
	       sprintf (szMsg, "Error writing file %s", szNewFile);
	       ErrExit (szMsg);
	       }

	    lTotWrite += (long) nNumWrite;

	    pPos += (unsigned long) nWrite;

	    printf(" %-12s %7ld bytes written\r",
			pDirEntry->name,
			lTotWrite);

	    if (lTotWrite == pDirEntry->size)
	       {
	       lTotWrite = 0L;
	       _dos_setftime( hTarget, pDirEntry->wr_date, pDirEntry->wr_time);
	       _dos_close (hTarget);
	       nNumFiles++;
	       }

	    pDirEntry = pPos;
	    pPos += sizeof (DirEntry);
	    }

	 } while ( lTotRead != 0L || _dos_findnext(&DirEntry) == 0 );
      }

   /* perform search for sub-directories */
   sprintf( szEntry, "%s\\*.*", szPath );
   if ( _dos_findfirst(szEntry,_A_SUBDIR | _A_NORMAL | _A_RDONLY | _A_HIDDEN | _A_SYSTEM, &DirEntry) == 0 )
      {
      /* repeat until all entries exhausted */
      do {
         /* eliminate special directory entries */
	 if ( (DirEntry.attrib & _A_SUBDIR) && (DirEntry.name[0]!='.') )
	    {
	    sprintf( szEntry, "%s\\%s\\%s", szPath, DirEntry.name, szSpec );
	    sprintf( szNewDir, "%s\\%s", szPath, DirEntry.name);

	    szNewDir [0] = szFileSpec2 [0];
	    mkdir (szNewDir);
	    _dos_setfileattr(szNewDir, DirEntry.attrib);

	    printf("%s%s\n", szNewDir, "                                          ");
	    bContinue = CopyAll( szEntry,szFileSpec2, wAttributes );
	    }
	 } while ( (bContinue)&&(_dos_findnext(&DirEntry) == 0) );
      }

   /* return final result */
   return( bContinue );
   }
