/*
 *  copy.c - Copy routine for WinDosSetup
 *  Todd Laney
 *
 *  Modification History:
 *
 *  6/03/91 Vlads        Change copy process to incorporate new Install API
 *
 *  3/24/89  Toddla      Wrote it
 *
 *
 *  notes:
 *   we now use the LZCopy stuff for compression
 *   we now set the crit error handler ourselves so CHECKFLOPPY is
 *   NOT defined
 */

#include <windows.h>

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <mmsystem.h>

#include "drivers.h"
#include "sulib.h"
//#include <ver.h>


#define MAX_COPY_ATTEMPTS  15

/*
 *  Maximum number of install disks we support
 */

#define MAX_DISKS 100

/*
 *  Flags for VerInstallFile
 */

#define FORCEABLE_FLAGS  (VIF_MISMATCH + VIF_SRCOLD + VIF_DIFFLANG + VIF_DIFFTYPE + VIF_DIFFCODEPG )

/**********************************************************************
 *
 * Local function prototypes.
 *
 **********************************************************************/

 // Retrieve disk path for logical disk

 BOOL GetDiskPath(LPTSTR Disk, LPTSTR szPath);

 // Convert VIF_... to ERROR... return codes

 UINT ConvertFlagToValue(DWORD dwFlags);

 // Do the work of trying to copy a file

 LONG TryCopy(LPTSTR    szSrc,     // Full source file path
              LPTSTR    szLogSrc,  // Logical source name
              LPTSTR    szDestPath,// Destination path
              FPFNCOPY fpfnCopy); // Callback routine

 #ifdef CHECK_FLOPPY
 BOOL NEAR IsDiskInDrive(int iDisk);
 #endif

 // GLOBAL VARIABLES

 //  directory where windows will be setup to

 TCHAR szSetupPath[MAX_PATH];

 // directory where the root of the setup disks are!

 TCHAR szDiskPath[MAX_PATH];

 // Name of driver being copied (or oemsetup.inf)

 TCHAR szDrv[120];

/*
 *  global vars used by DosCopy
 */
 static LPTSTR    lpBuf = NULL;   // copy buffer
 static int      iBuf = 0;       // usage count
 static UINT     nBufSize;
 BOOL     bRetry = FALSE;
 BOOL     bQueryExist;

 extern BOOL bCopyEvenIfOlder;  // From DRIVERS.C


 BOOL DefCopyCallback(int msg, DWORD_PTR n, LPTSTR szFile)
 {
     return FC_IGNORE;
 }



/*  UINT FileCopy (szSource, szDir, fpfnCopy, UINT fCopy)
 *
 *  This function will copy a group of files to a single destination
 *
 *  ENTRY:
 *
 *  szSourc      : pointer to a SETUP.INF section
 *  szDir        : pointer to a string containing the target DIR
 *  fpfnCopy     : callback function used to notify called of copy status
 *  fCopy        : flags
 *
 *      FC_SECTION            - szSource is a section name
 *      FC_LIST               - szSource is a pointer to a char **foo;
 *      FC_LISTTYPE           - szSource is a pointer to a char *foo[];
 *      FC_FILE               - szSource is a file name.
 *      FC_QUALIFIED          - szSource is a fully qualified file name.
 *      FC_DEST_QUALIFIED     - szDir is fully qualified. Don't expand this.
 *      FC_CALLBACK_WITH_VER  - call back if file exists and report version information.
 *
 *  NOTES:
 *      if szSource points to a string of the form '#name' the section
 *      named by 'name' will be used as the source files
 *
 *      the first field of each line in the secion is used as the name of the
 *      source file.  A file name has the following form:
 *
 *          #:name
 *
 *          #       - Disk number containing file 1-9,A-Z
 *          name    - name of the file, may be a wild card expression
 *
 *  Format for copy status function
 *
 *  BOOL FAR PASCAL CopyStatus(int msg, int n, LPSTR szFile)
 *
 *      msg:
 *          COPY_ERROR          error occured while copying file(s)
 *                              n      is the DOS error number
 *                              szFile is the file that got the error
 *                              return: TRUE ok, FALSE abort copy
 *
 *          COPY_STATUS         Called each time a new file is copied
 *                              n      is the percent done
 *                              szFile is the file being copied
 *                              return: TRUE ok, FALSE abort copy
 *
 *          COPY_INSERTDISK     Please tell the user to insert a disk
 *                              n      is the disk needed ('1' - '9')
 *                              return: TRUE try again, FALSE abort copy
 *
 *          COPY_QUERYCOPY      Should this file be copied?
 *                              n      line index in SETUP.INF section (0 based)
 *                              szFile is the line from section
 *                              return: TRUE copy it, FALSE dont copy
 *
 *          COPY_START          Sent before any files are copied
 *
 *          COPY_END            Sent after all files have been copied
 *                              n   is dos error if copy failed
 *
 *          COPY_EXISTS         Sent if the FC_CALL_ON_EXIST bit was set
 *                              and the file exists at the destination
 *                              given for the filecopy.
 *
 *
 *  EXIT: returns TRUE if successful, FALSE if failure.
 *
 */

UINT FileCopy (LPTSTR szSource, LPTSTR szDir, FPFNCOPY fpfnCopy, UINT fCopy)
{
   int   err = ERROR_SUCCESS;     // Return code from this routine

   TCHAR  szPath[MAX_PATH];
   TCHAR  szLogSrc[MAX_PATH];
   TCHAR  szSrc[MAX_PATH];

   LPTSTR pFileBegin;              // First file

   LPTSTR * List;                  // Handle lists of files
   LPTSTR * ListHead;

   int   nDisk;                   // The disk we're on

   int   cntFiles = 0;            // How many files we've got to do

   if (fpfnCopy == NULL) {
      fpfnCopy = DefCopyCallback;
   }

   if (!szSource || !*szSource || !szDir || !*szDir) {
      return ERROR_FILE_NOT_FOUND;
   }


  /*
   *  fix up the drive in the destination
   */

   if ( fCopy & FC_DEST_QUALIFIED ) {
      lstrcpy(szPath, szDir);
      fCopy &= ~FC_DEST_QUALIFIED;
   } else {
      ExpandFileName(szDir, szPath);
   }

   if (szSource[0] == TEXT('#') && fCopy == FC_FILE) {
       fCopy = FC_SECTION;
       ++szSource;
   }

   switch (fCopy) {
       case FC_SECTION:
       {
           szSource = infFindSection(NULL,szSource);

          /*
           * We are called even when the section doesn't exist
           */

           if (szSource == NULL) {
               return ERROR_SUCCESS;
           }

           fCopy = FC_LIST;
       }
       // fall through to FC_LIST

       case FC_LIST:
          pFileBegin = szSource;
          cntFiles = infLineCount(szSource);
          break;

       case FC_LISTTYPE:
          ListHead = List = (LPTSTR far *)szSource;
          pFileBegin = *ListHead;
          while ( *List++ )           // Count files to be copied.
             ++cntFiles;
          break;

       case FC_FILE:
       case FC_QUALIFIED:
       default:
          pFileBegin = szSource;
          cntFiles = 1;
    }

  /*
   *  walk all files in the list and call TryCopy ....
   *
   *  NOTES:
   *      we must walk file list sorted by disk number.
   *      we should use the disk that is currently inserted.
   *      we should do a find first/find next on the files????
   *      we need to check for errors.
   *      we need to ask the user to insert disk in drive.
   *
   */

   (*fpfnCopy)(COPY_START,0,NULL);

  /*
   *  Go through all possible disks: 1 to 100 and A to Z (26)
   */

   for (nDisk = 1;
        err == ERROR_SUCCESS && (cntFiles > 0) &&
            (nDisk <= MAX_DISKS + 'Z' - 'A' + 1);
        nDisk++)
   {
      TCHAR Disk[10];              // Maximum string is "100:"
      LPTSTR pFile;
      int FileNumber;             // Which file in the list we're on
                                  // (to pass to callback)

      pFile      = pFileBegin;    // Start at first file
      List       = ListHead;      // Handled chained lists
      FileNumber = 0;             // Informational for callback - gives
                                  // which file in list we're on
     /*
      *  Work out the string representing our disk letter
      */

      if (nDisk > MAX_DISKS) {
          Disk[0] = TEXT('A') + nDisk - MAX_DISKS - 1;
          Disk[1] = TEXT('\0');
      } else {
          _itow(nDisk, Disk, 10);
      }

      wcscat(Disk, TEXT(":"));

      for (;
           err == ERROR_SUCCESS && pFile;
           FileNumber++,
           pFile = fCopy == FC_LISTTYPE ? *(++List) :
                   fCopy == FC_LIST ? infNextLine(pFile) :
                   NULL)
      {
        /*
         *  We have to reset high bit of first byte because it could be set
         *  by translating service in OEM setup to show that file name was
         *  mapped
         */

         *pFile = toascii(*pFile);


        /*
         *  should we copy this file?
         *  copy the files in disk order.
         */

         if (_wcsnicmp(pFile, Disk, wcslen(Disk)) == 0 || // File has disk
                                                         // number and we're
                                                         // on that disk
             RemoveDiskId(pFile) == pFile &&
                nDisk == 1 && *pFile ||                  // First disk and
                                                         // no disk number

             fCopy == FC_QUALIFIED) {                    // Fully qualified


            /*
             * done with a file. decrement count.
             */

             cntFiles--;

             lstrcpy(szDrv, RemoveDiskId(pFile));

             switch ((*fpfnCopy)(COPY_QUERYCOPY, FileNumber, pFile))
             {
                 case CopyCurrent:                // Skip

                         continue;

                 case CopyNeither:

                         err = ERROR_FILE_EXISTS; // File already exists

                 case CopyNew:
                         break;

                 default:
                         break;

             }

            /*
             *  Pick up bad return code from switch
             */

             if (err != ERROR_SUCCESS) {
                 break;
             }

            /*
             *  now we convert logical dest into a physical
             *    (unless FC_QUALIFIED)
             */

             infParseField(pFile, 1, szLogSrc);    // logical source

             if ( fCopy != FC_QUALIFIED ) {
                ExpandFileName(szLogSrc, szSrc); // full physical source
             } else {
                lstrcpy(szSrc,szLogSrc);
             }


            /*
             *  Attempt copy
             */

             err = TryCopy(szSrc,      // Qualified Source file
                           szLogSrc,   // Logical source file name (with disk #)
                           szPath,     // Path for directory to install in
                           fpfnCopy);  // Copy callback function

            /*
             *  If failed to find file try the windows directory
             */

             if (err != ERROR_SUCCESS) {
                 break;
             }

         } /* End if dor if DoCopy */
      }
   }

   (*fpfnCopy)(COPY_END,err,NULL);

   return err;
}

/**********************************************************************
 *
 *  TryCopy
 *
 *  Copy a single file from source to destination using the VerInstallFile
 *  API - interpreting the return code as :
 *
 *    ERROR_SUCCESS  - OK
 *    Other          - failure type
 *
 **********************************************************************/

LONG TryCopy(LPTSTR    szSrc,      // Full expanded source file path
             LPTSTR    szLogSrc,   // Logical source name
             LPTSTR    szDestPath, // Destination path
             FPFNCOPY fpfnCopy)   // Callback routine

{
    DWORD wTmpLen;
    DWORD dwRetFlags;
    TCHAR  szTempFile[MAX_PATH];
    TCHAR  szErrFile[MAX_PATH];
    TCHAR  DriversPath[MAX_PATH];
    BOOL  bRetVal;               // Return code from callback
    LPTSTR szFile;
    TCHAR  szSrcPath[MAX_PATH];
    int   iAttemptCount;
    WORD  wVerFlags;
    LONG  err;

   /*
    *  Fix up destination if file is a kernel driver
    */

    if (IsFileKernelDriver(szSrc) && szDestPath) 
    {
        wcscpy(DriversPath, szDestPath);
        wcscat(DriversPath, TEXT("\\drivers"));
        szDestPath = DriversPath;
    }

   /*
    *  Create file name from current string
    */

    szFile = FileName(szSrc);
    lstrcpy(szSrcPath, szSrc);
    StripPathName(szSrcPath);

    for(iAttemptCount = 0, wVerFlags = 0 ;
        iAttemptCount <= MAX_COPY_ATTEMPTS;
        iAttemptCount++) {

        HCURSOR  hcurPrev;             // Saved cursor state

        // Central operation - attempt to install file szFile in directory
        // pointed by szPath from directory pointed by szSrc
        // If operation will fail but with possibility to force install
        // in last parameter buffer we will have temporary file name ==>
        // therefore we can avoid excessive copying.
        // NOTE: now szFile consists of only file name and other buffers
        // only path names.

        wTmpLen = MAX_PATH;

        hcurPrev = SetCursor(LoadCursor(NULL,IDC_WAIT));
        dwRetFlags = VerInstallFile(wVerFlags,
                                    (LPTSTR) szFile,
                                    (LPTSTR) szFile,
                                    (LPTSTR) szSrcPath,
                                    (LPTSTR) szDestPath,
                                    (LPTSTR) szDestPath,
                                    (LPTSTR) szTempFile,
                                    (LPDWORD) &wTmpLen);
        SetCursor(hcurPrev);

       /*
        *  Operation failed if at least one bit of return flags is non-zero
        *  That is unusual but defined so in Version API.
        */

        if ( !dwRetFlags )
            return ERROR_SUCCESS;    // If no errors - goto next file


       /*
        *  If flag MISMATCH is set - install can be forced and we have
        *  temporary file in destination subdirectory
        */

        if ( dwRetFlags  &  VIF_MISMATCH ) {

            if ( (dwRetFlags & VIF_SRCOLD) && (!bCopyEvenIfOlder) ) {

              /*
               *  If we need not call back with question - automatically
               *  force install with same parameters.
               *  michaele, *only* if src file is *newer* than dst file
               */

               DeleteFile(szTempFile);

               return ERROR_SUCCESS;
            }

           /*
            *  If we need not call back with question - automatically
            *  force install with same parameters.
            */

            wVerFlags |= VIFF_FORCEINSTALL;
            iAttemptCount--;             // Make sure we get another go.
            continue;

        }   /* End if MISMATCH */

       /*
        *  If real error occured - call back with error file info
        *  In all dialogs we use our error codes - so I will convert
        *  flags returned from Ver API to ours.
        */

        err = ConvertFlagToValue(dwRetFlags);


       /*
        *  If source path or file is nor readable - try to change disk
        */

        if ( dwRetFlags & VIF_CANNOTREADSRC )
        {
          /*
           *  Now new path in szSrc so I deleted logic for creating it
           */

           if (RemoveDiskId(szLogSrc) == szLogSrc)

             /*
              *  if disk # not provided, default to 1
              */

              bRetVal = (*fpfnCopy)(COPY_INSERTDISK, (DWORD_PTR)"1", szSrcPath);
           else
              bRetVal = (*fpfnCopy)(COPY_INSERTDISK, (DWORD_PTR)szLogSrc, szSrcPath);


           switch (bRetVal)
              {
              case FC_RETRY:
                  continue;              // and try again...

              case FC_ABORT:
                  return ERROR_FILE_NOT_FOUND;

              case FC_IGNORE:
                  break;
              }
        }

        ExpandFileName(szLogSrc, szErrFile);

#if WINDOWSDIR

        if (!*bWindowsDir  &&
            err != FC_ERROR_LOADED_DRIVER &&
            err != ERROR_DISK_FULL)
        {
            GetWindowsDirectory(szPath, MAX_PATH);
            *bWindowsDir = TRUE;
            continue;
        }

#endif // WINDOWSDIR

        switch ((*fpfnCopy)(COPY_ERROR, err, szErrFile)) {

            case FC_IGNORE:
                return ERROR_SUCCESS;

            case FC_RETRY:
                break;

            case FC_ABORT:
                return ERROR_FILE_NOT_FOUND;
        }
    } // End of attempts

    return err;
}

/*  BOOL GetDiskPath(Disk, szPath)
 *
 *  This function will retrive the full path name for a logical disk
 *
 *  The code reads the [disks] section of SETUP.INF and looks for
 *  n = path where n is the disk char.  NOTE the disk '0' defaults to
 *  the root windows directory.
 *
 *  ENTRY:
 *
 *  cDisk        : what disk to find 0-9,A-Z
 *  szPath       : buffer to hold disk path
 *
 *  Returns :
 *     TRUE if a disk path was found
 *     FALSE if there was no disk specified (ie no ':'
 *
 */

BOOL GetDiskPath(LPTSTR Disk, LPTSTR szPath)
{
   TCHAR    ach[MAX_PATH];
   TCHAR    szBuf[MAX_PATH];
   int i;


  /*
   *  Check to see if there is actually a disk id.
   *  If not return FALSE
   */

   if (RemoveDiskId(Disk) == Disk) {
       return FALSE;
   }

  /*
   *  Create our copy of the disk id
   */

   for (i = 0; Disk[i] != TEXT(':'); i++) {
       ach[i] = Disk[i];
   }
   ach[i] = TEXT('\0');


  /*
   *  Zero disk letter means windows setup directory
   */

   if (_wcsicmp(ach, TEXT("0")) == 0) {

      /*
       * return the windows setup directory
       */

       lstrcpy(szPath,szSetupPath);
       return TRUE;
   }

  /*
   *  now look in the [disks] section for a full path name
   *
   *  This is a pretty bogus concept and is not supported
   *  in win 32 style disks section [Source Media Descriptions]
   */

   if ( !infGetProfileString(NULL,DISK_SECT,ach,szPath) &&
        !infGetProfileString(NULL,OEMDISK_SECT,ach,szPath)) {

       lstrcpy(szPath, szDiskPath);
   } else {
       infParseField(szPath,1,szPath);

      /*
       *  is the path relative? is so prepend the szDiskPath
       */

       if (szPath[0] == TEXT('.') || szPath[0] == TEXT('\0')) {
           lstrcpy(szBuf,szDiskPath);
           catpath(szBuf,szPath);
           lstrcpy(szPath,szBuf);
       }

   }

   return TRUE;
}


/*  BOOL FAR PASCAL ExpandFileName(LPSTR szFile, LPTSTR szPath)
 *
 *  This function will retrive the full path name for a file
 *  it will expand, logical disk letters to pyshical ones
 *  will use current disk and directory if non specifed.
 *
 *  if the drive specifed is 0-9, it will expand the drive into a
 *  full pathname using GetDiskPath()
 *
 *  IE  0:system ==>  c:windows\system
 *      1:foo.txt     a:\foo.txt
 *
 *  ENTRY:
 *
 *  szFile       : File name to expand
 *  szPath       : buffer to hold full file name
 *
 */
BOOL ExpandFileName(LPTSTR szFile, LPTSTR szPath)
{
   TCHAR    szBuf[MAX_PATH*2];

   if (GetDiskPath(szFile, szBuf)) {
       lstrcpy(szPath,szBuf);
       if (szFile[2])
          catpath(szPath,szFile + 2);
   } else {
       lstrcpy(szPath,szFile);
   }
   return TRUE;
}




void catpath(LPTSTR path, LPTSTR sz)
{
   //
   // Remove any drive letters from the directory to append
   //
   sz = RemoveDiskId(sz);

   //
   // Remove any current directories ".\" from directory to append
   //
   while (sz[0] == TEXT('.') && SLASH(sz[1]))
      sz += 2;

   //
   // Dont append a NULL string or a single "."
   //
   if (*sz && ! (sz[0] == TEXT('.') && sz[1] == 0))
   {
      // Add a slash separator if necessary.
      if ((! SLASH(path[lstrlen(path) - 1])) &&    // slash at end of path
          ((path[lstrlen(path) - 1]) != TEXT(':')) &&    // colon at end of path
          (! SLASH(sz[0])))                        // slash at beginning of file
         lstrcat(path, CHSEPSTR);

      lstrcat(path, sz);
   }
}

/*
 *  Return a pointer to the file name part of a string
 */

LPTSTR FileName(LPTSTR szPath)
{
   LPTSTR   sz;

   for (sz=szPath; *sz; sz++)
      ;

   for (; sz>=szPath && !SLASH(*sz) && *sz!=TEXT(':'); sz--)
      ;

   return ++sz;
}

/*
 *  Return the portion of a file name following the disk (ie anything
 *  before the colon).
 *  If there is no colon just return a pointer to the original string
 */

LPTSTR RemoveDiskId(LPTSTR szPath)
{
   LPTSTR sz;

   for (sz = szPath; *sz; sz++) {
       if (*sz == TEXT(':')) {
           return sz + 1;
       }
   }

   return szPath;
}

LPTSTR StripPathName(LPTSTR szPath)
{
    LPTSTR   sz;

    sz = FileName(szPath);

    if (sz > szPath+1 && SLASH(sz[-1]) && sz[-2] != TEXT(':'))
       sz--;

    *sz = 0;
    return szPath;
}

/*
 *  See if a file is a kernel driver.  Unfortunately the VersionInfo APIs
 *  don't seem coded up to take care of this at the moment so we just check
 *  to see if the file extension is ".SYS"
 */

 BOOL IsFileKernelDriver(LPTSTR szPath)
 {
     TCHAR drive[MAX_PATH];
     TCHAR dir[MAX_PATH];
     TCHAR fname[MAX_PATH];
     TCHAR ext[MAX_PATH];

     lsplitpath(szPath, drive, dir, fname, ext);
     return !_wcsicmp(ext, TEXT(".sys"));
 }


/**************************************************************************
 *
 * This function converts returned flags from Ver API to the numerical
 * error codes used in SETUP.
 *
 ***************************************************************************/

UINT ConvertFlagToValue(DWORD dwFlags)
{
    if ( ! dwFlags  )
       return(NO_ERROR);
    if ( dwFlags & VIF_CANNOTREADSRC )
       return(ERROR_FILE_NOT_FOUND);
    if ( dwFlags & VIF_OUTOFMEMORY )
       return(ERROR_OUTOFMEMORY);
    if ( dwFlags & VIF_ACCESSVIOLATION )
       return(ERROR_ACCESS_DENIED);
    if ( dwFlags & VIF_SHARINGVIOLATION )
       return(ERROR_SHARING_VIOLATION);
    if ( dwFlags & VIF_FILEINUSE)
       return(FC_ERROR_LOADED_DRIVER);

    return(ERROR_CANNOT_COPY);    // General error
}



#ifdef CHECK_FLOPPY
/*--------------------------------------------------------------------------

  IsValidDiskette() -

--------------------------------------------------------------------------*/

#define CBSECTORSIZE   512
#define INT13_READ   2

BOOL IsValidDiskette(int iDrive)
{
   TCHAR       buf[CBSECTORSIZE];

   iDrive |= 0x0020;   // make lower case

   iDrive -= 'a';   // A = 0, B = 1, etc. for BIOS stuff

   return MyReadWriteSector(buf, INT13_READ, iDrive, 0, 0, 1);
}



/*  BOOL IsDiskInDrive(char cDisk)
 *
 *  Is the specifed disk in the drive
 *
 *  ENTRY:
 *
 *  cDisk        : what disk required to be in the drive (logical)
 *
 *  return TRUE if the specifed disk is in the drive
 *         FALSE if the wrong disk is in the drive or disk error
 *
 */
BOOL IsDiskInDrive(int iDisk)
{

   if ((iDisk  >= 'A' && iDisk <= 'Z') ||
      (iDisk  >= 'a' && iDisk <= 'z'))
      {
      if (DosRemoveable(iDisk))
         {
         if (!IsValidDiskette(iDisk))
            return FALSE;
         }
      return TRUE;
      }
   return TRUE;   // for non drive letters assume a path
                  // and thus always in.
}

#endif