/***************************************************************************
 *                                                                         *
 *   FIOAPI.C                                                              *
 *                                                                         *
 *   Copyright (c) 1996-1997 Galacticomm, Inc.      All Rights Reserved.   *
 *                                                                         *
 *   File I/O API                                                          *
 *                                                                         *
 *                                            - R. Stein  5/19/91          *
 *   Unix additions                           - Mahesh Neelakanta 10/26/94 *
 *   Modified for Unity and NT version        - Ilya Minkin 01/17/96       *
 *                                                                         *
 ***************************************************************************/

#ifdef GCWINNT
#include <windows.h>
#endif // GCWINNT
#include <utime.h>
#include <direct.h>
#include "gcomm.h"

#define FILREV "$Revision: 37 $"

LONG ztzone=0;                     /* Seconds between GMT and local time   */

                                   /* global vars set by cntdir()/cntdirs()*/
LONG numfils=0;                    /* total number of files                */
LONG numbyts=0;                    /* total number of bytes                */
LONG numbytp=0;                    /* # bytes + wasted part of last cluster*/
LONG numdirs=0;                    /* total number of subdirectories       */

CHAR deldirerr[GCMAXPTH+40];       /* error message when deldir() != 0     */

#ifdef GCDOS
#define USE2152                    /* keep to use INT21/52/+22,            */
                                   /*   delete to use INT2F/122C           */
#ifdef GCDOSP
static VOID *rtosel(VOID *realptr,INT nbytes);
static VOID unrsel(VOID *selptr);
#else
#define rtosel(realptr,nbytes) realptr
#define unrsel(dp)
#endif // GCDOSP
static struct devhdr *firstdev(VOID);

CHAR *cpybuf=NULL;
CHAR (*devs)[9]=NULL;
INT numdevs;
INT walkdevs(INT count);
#endif // GCDOS

static VOID adddirs(INT clsiz,const CHAR *path);
static GBOOL morcfile(const CHAR *src,const CHAR *dst,GBOOL movfil);
static GBOOL copyutil(const CHAR *src,const CHAR *dst);

CHAR *                             /*   returns pointer to dstbuf or ""    */
fileparts(                         /* grab required part of a full filename*/
SHORT filpart,                     /*   part to be returned                */
const CHAR *fullname,              /*   full filename to be worked on      */
CHAR *dstbuf,                      /*   destination buffer for data        */
SHORT dstsiz)                      /*   destination buffer size            */
{
     static CHAR *empty="";
     CHAR tmpbuf[MAXPATH];
     CHAR tmpbuf1[MAXPATH];
     INT retval;

     switch (filpart) {
     case GCPART_PATH:
          retval=fnsplit(fullname,tmpbuf,tmpbuf1,NULL,NULL);
          stzcpy(dstbuf,empty,dstsiz);
          if (retval&DRIVE) {
               stzcat(dstbuf,tmpbuf,dstsiz);
          }
          if (retval&DIRECTORY) {
               stzcat(dstbuf,tmpbuf1,dstsiz);
          }
          break;
     case GCPART_FNAM:
          retval=fnsplit(fullname,NULL,NULL,tmpbuf,tmpbuf1);
          if (retval&FILENAME) {
               stzcpy(dstbuf,tmpbuf,dstsiz);
#ifdef GCDOS
                    if (strlen(dstbuf) > 8) {
                         dstbuf[8]='\0';
                    }
# endif // GCDOS
               if (retval&EXTENSION) {
#ifdef GCDOS
                         if (strlen(tmpbuf1) > 4) {
                              tmpbuf1[4]='\0';
                         }
#endif // GCDOS
                    stzcat(dstbuf,tmpbuf1,dstsiz);
               }
          }
          else {
               stzcpy(dstbuf,empty,dstsiz);
          }
          break;
     case GCPART_FILE:
          if (fnsplit(fullname,NULL,NULL,tmpbuf,NULL)&FILENAME) {
               stzcpy(dstbuf,tmpbuf,dstsiz);
#ifdef GCDOS
                    if (strlen(dstbuf) > 8) {
                         dstbuf[8]='\0';
                    }
#endif // GCDOS
          }
          else {
               stzcpy(dstbuf,empty,dstsiz);
          }
          break;
     case GCPART_EXTN:
          if (fnsplit(fullname,NULL,NULL,NULL,tmpbuf)&EXTENSION) {
#ifdef GCDOS
                    if (strlen(tmpbuf) > 4) {
                         tmpbuf[4]='\0';
                    }
#endif // GCDOS
               stzcpy(dstbuf,tmpbuf+1,dstsiz);
          }
          else {
               stzcpy(dstbuf,empty,dstsiz);
          }
          break;
     case GCPART_DRVS:
          if (fnsplit(fullname,tmpbuf,NULL,NULL,NULL)&DRIVE) {
               stzcpy(dstbuf,tmpbuf,dstsiz);
          }
          else {
               stzcpy(dstbuf,empty,dstsiz);
          }
          break;
     default:
          break;
     }
     return(dstbuf);
}

GBOOL                              /*   returns TRUE=copy successful       */
cpyutl(                            /* copy file utility                    */
const CHAR *src,                   /*   source file name                   */
const CHAR *dst,                   /*   destination file names             */
const CHAR *srcmod,                /*   source fopen() mode                */
const CHAR *dstmod)                /*   destination fopen() mode           */
{
     INT nbyts;
     FILE *sfp,*dfp;
#ifndef GCDOS
     CHAR cpybuf[CPBSIZ];
#endif // GCDOS

#ifdef GCDOS
     if (cpybuf == NULL) {
          cpybuf=alcmem(CPBSIZ);
     }
#endif // GCDOS
     if ((sfp=fopen(src,srcmod)) == NULL) {
          return(FALSE);
     }
     if ((dfp=fopen(dst,dstmod)) == NULL) {
          fclose(sfp);
          return(FALSE);
     }
     while ((nbyts=fread(cpybuf,1,CPBSIZ,sfp)) > 0) {
          if (fwrite(cpybuf,1,nbyts,dfp) != nbyts) {
               fclose(sfp);
               fclose(dfp);
               return(FALSE);
          }
     }
     fclose(sfp);
     fclose(dfp);
     if ((dfp=fopen(dst,FOPRB)) == NULL) {
          return(FALSE);
     }
     fclose(dfp);
     setFileGMT(dst,getFileGMT(src));
     return(TRUE);
}

GBOOL                              /*   returns TRUE if one found          */
filspclst(                         /* any files in the first filespec?     */
struct filblklst *flp)             /*   path and file list to search       */
{
     CHAR *fp,*tp;
     static CHAR buff[GCMAXPTH];

     strcpy(buff,flp->pathpf);
     for (fp=flp->list,tp=buff+strlen(buff)
          ; *fp != '\0' && *fp != ' ' && tp < buff+sizeof(buff)-1
          ; fp++,tp++) {
          *tp=*fp;
     }
     *tp='\0';
     return(fnd1st(&flp->fb,buff,0));
}

GBOOL                              /*   returns TRUE if found              */
fil1stlst(                         /* find first file in a list            */
struct filblklst *flp,             /*   struct for continuing w/ filnxtlst */
const CHAR *prefix,                /*   path prefix                        */
const CHAR *list)                  /*   list of files/path                 */
{
     CHAR *np;

     stzcpy(flp->list,skpwht(list),MAXFLL+1);
     stzcpy(flp->pathpf,prefix,MAXFLP+1);
     while (1) {
          if (flp->list[0] == '\0') {
               return(FALSE);
          }
          if (filspclst(flp)) {
               return(TRUE);
          }
          np=skpwht(skpwrd(flp->list));
          movmem(np,flp->list,strlen(np)+1);
     }
}

GBOOL                              /*   returns TRUE if one found          */
filnxtlst(                         /* find next file in list               */
struct filblklst *flp)             /*   previous filblklst to cont. search */
{
     CHAR *np;

     if (fndnxt(&flp->fb)) {
          return(TRUE);
     }
     while (1) {
          np=skpwht(skpwrd(flp->list));
          movmem(np,flp->list,strlen(np)+1);
          if (flp->list[0] == '\0') {
               return(FALSE);
          }
          if (filspclst(flp)) {
               return(TRUE);
          }
     }
}

VOID
cntdir(                            /* count bytes and files in a directory */
const CHAR *path)                  /*   path name of the directory contents*/
{
     struct ffblk fb;
     USHORT clsiz;

     clsiz=clsize(path);
     numfils=0L;
     numbyts=0L;
     numbytp=0L;
     if (fnd1st(&fb,path,0)) {
          do {
               numfils++;
               numbyts+=fb.ff_fsize;
               numbytp+=clfit(fb.ff_fsize,clsiz);
          } while (fndnxt(&fb));
     }
}

VOID
cntdirs(                           /* count bytes/files in dir & its subdir*/
const CHAR *path)                 /*   path name of the directory contents*/
{
     numfils=0L;
     numbyts=0L;
     numbytp=0L;
     numdirs=0L;
     adddirs(clsize(path),path);
}

LONG                               /*  returns total file size             */
clfit(                             /* figure size of file including waste  */
LONG size,                         /*   file size in bytes                 */
USHORT clsiz)                      /*   size of clusters on this drive     */
{
     return((clsiz == 0 || size == 0L) ? 0L : ((size-1)/clsiz+1)*clsiz);
}

UINT                               /*   returns size or 0 for error        */
clsize(                            /* get cluster size in bytes            */
const CHAR *path)                  /*   path to drive to check             */
{
     CHAR drivebuf[GCMAXPTH];
#ifdef GCWINNT
     DWORD secPerClust;
     DWORD bytesPerSec;
     DWORD numOfFreeClust;
     DWORD totalNumOfClust;
     CHAR *rootPathName;
#else
     INT drive;
     union REGS regs;
#endif // GCWINNT

     fileparts(GCPART_DRVS,path,drivebuf,GCMAXPTH);
#ifdef GCWINNT
     if (drivebuf[0] == '\0') {    /* use current drive                    */
          rootPathName=NULL;
     }
     else {
          rootPathName=drivebuf;
     }
     if (GetDiskFreeSpace(rootPathName,
                          &secPerClust,
                          &bytesPerSec,
                          &numOfFreeClust,
                          &totalNumOfClust)) {
          return((UINT)(bytesPerSec*secPerClust));
     }
     return(0);
#else
     drive=0;
     if (drivebuf[0] != '\0') {
          drive=toupper(drivebuf[0])-64; /* 'A'->1, 'B'->2, 'C'->3, etc.   */
     }
     regs.h.ah=0x1C;
     regs.h.dl=drive;
     intdos(&regs,&regs);
     return(regs.h.al*regs.x.cx);
#endif // GCWINNT
}

GBOOL                              /*   returns TRUE=file updated          */
setFileTm(                         /* set file time & date                 */
const CHAR *fname,                 /*   file name                          */
USHORT dtim,                       /*   time to set                        */
USHORT ddat)                       /*   date to set                        */
{
#ifdef GCWINNT
     WIN32_FIND_DATA fb;
     FILETIME localFileTime,fileTime;
     HANDLE hFile;
     GBOOL retval;
     HANDLE hFF;

     if ((hFF=FindFirstFile(fname,&fb)) == INVALID_HANDLE_VALUE) {
          return(FALSE);
     }
     FindClose(hFF);
     if (!DosDateTimeToFileTime(ddat,dtim,&localFileTime)) {
          return(FALSE);
     }
     if (!LocalFileTimeToFileTime(&localFileTime,&fileTime)) {
          return(FALSE);
     }
     hFile=CreateFile(fname,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,
                      NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
     if (hFile == INVALID_HANDLE_VALUE) {
          return(FALSE);
     }
     retval=SetFileTime(hFile,&fb.ftCreationTime,&fileTime,&fileTime);
     CloseHandle(hFile);
     return(retval);
#else
     struct tm tim;
     struct utimbuf utim;
     struct stat st;
     time_t caltim;

     if (stat(fname,&st) < 0) {
          return(FALSE);
     }
     memcpy(&tim,localtime(&st.st_mtime),sizeof(struct tm));
     tim.tm_year=ddyear(ddat)-1900;
     tim.tm_mon=ddmon(ddat)-1;
     tim.tm_mday=ddday(ddat);
     tim.tm_hour=dthour(dtim);
     tim.tm_min=dtmin(dtim);
     tim.tm_sec=dtsec(dtim);
     if ((caltim=mktime(&tim)) < 0) {
          return(FALSE);
     }
     utim.actime=caltim;
     utim.modtime=caltim;
     if (utime((CHAR *)fname,&utim) < 0) {
          return(FALSE);
     }
     return(TRUE);
#endif // GCWINNT
}

GBOOL                              /*   returns TRUE=file updated          */
setFileGMT(                        /* set file time and date               */
const CHAR *fname,                 /*   file name                          */
ULONG gmt70)                       /*   time in sec since 00:00 1/1/70 GMT */
{
     USHORT day,hour,minsec;
     LONG gmtmp;

     gmtmp=gmt70-315532800L-ztzone;  /* conv to secs since 00:00 1/1/80 lcl */
     if (gmtmp < 0L) {
          gmt70=0L;
     }
     else {
          gmt70=gmtmp;
     }
     day=(USHORT)(gmt70/86400L);                       /* days since 1/1/80 */
     hour=(USHORT)((gmt70/3600L)%24L);                 /* hour (local time) */
     minsec=(USHORT)(gmt70%3600L);                    /* seconds since hour */
     return(setFileTm(fname,dttime(hour,minsec/60,minsec%60),datofc(day)));
}


VOID
getFileTm(                         /* get time and date for file           */
const CHAR *fname,                 /*   file name                          */
USHORT *dtim,                      /*   time to get                        */
USHORT *ddat)                      /*   date to get                        */
{
#ifdef GCWINNT
     WIN32_FIND_DATA fb;
     FILETIME localFTime;
     HANDLE hFF;

     *ddat=*dtim=0;
     if ((hFF=FindFirstFile(fname,&fb)) == INVALID_HANDLE_VALUE) {
          return;
     }
     FindClose(hFF);
     FileTimeToLocalFileTime(&fb.ftLastWriteTime,&localFTime);
     FileTimeToDosDateTime(&localFTime,ddat,dtim);
#else
     struct stat st;
     struct tm *tim;

     *ddat=*dtim=0;
     if (stat(fname,&st) < 0) {
          return;
     }
     tim=localtime(&st.st_mtime);
     *ddat=(USHORT)dddate((tim->tm_mon+1),tim->tm_mday,((tim->tm_year)+1900));
     *dtim=(USHORT)dttime(tim->tm_hour,tim->tm_min,tim->tm_sec);
#endif // GCWINNT
}

ULONG                              /*   returns time and date in GMT format*/
getFileGMT(                        /* get GMT file time and date           */
const CHAR *fname)                 /*   file name                          */
{
     ULONG mtime;
     USHORT dat,tim,cof;

     getFileTm(fname,&tim,&dat);
     /* The following line is because of an assertion in difDate (which is */
     /* called by cofdat()).  getFileTm() returns dat == 0 if the file     */
     /* doesn't exist, which is not a valid date; cofdat() barfs on this.  */
     /* 22615 is what cofdat(0) used to return.                            */
     cof=dat == 0 ? 22615 : cofdat(dat);
     mtime=315532800L+                  /* 00:00 1/1/70 to 00:00 1/1/80    */
           cof*86400L+                  /* 00:00 1/1/80 to 00:00 fileday   */
           dthour(tim)*3600L+           /*  \                              */
           dtmin(tim)*60L+              /*   > compute filetime since      */
           dtsec(tim)+                  /*  /  00:00 on fileday            */
           ztzone;                      /* convert from local time to GMT  */
     return(mtime);
}

LONG                               /*   returns kbytes free or -1 for error*/
dskfre(                            /* number of kbytes free on a drive     */
const CHAR *path)                  /*   path on drive to check,NULL=default*/
{
     CHAR drivebuf[GCMAXPTH];
#ifdef GCWINNT
     DWORD secPerClust;
     DWORD bytesPerSec;
     DWORD numOfFreeClust;
     DWORD totalNumOfClust;
     CHAR *rootPathName;
#else
     INT drive;
     union REGS regs;
#endif // GCWINNT

#ifdef GCWINNT
     if (path != NULL) {
          fileparts(GCPART_DRVS,path,drivebuf,GCMAXPTH);
          if (drivebuf[0] == '\0') {
               rootPathName=NULL;       // use current drive
          }
          else {
               rootPathName=drivebuf;
          }
     }
     else {
          rootPathName=NULL;            // use current drive
     }
     if (GetDiskFreeSpace(rootPathName,
                          &secPerClust,
                          &bytesPerSec,
                          &numOfFreeClust,
                          &totalNumOfClust)) {
          return((LONG)((bytesPerSec/256)*secPerClust*(numOfFreeClust/4)));
     }
     return(-1L);
#else
     if (path == NULL) {
          drive=0;
     }
     else {
          fileparts(GCPART_DRVS,path,drivebuf,GCMAXPTH);
          if ( drivebuf[0] == '\0') {
               drive=0;
          }
          else {
               drive=toupper(drivebuf[0])+1-'A'; /* 'A'=1,'B'=2,'C'=3 etc.*/
          }
     }
     ASSERT(drive >= 0 && drive <= 26);
     regs.h.ah=0x36;
     regs.h.dl=drive;
     intdos(&regs,&regs);
     return(((LONG)regs.x.bx/4L)*(LONG)regs.x.ax*((LONG)regs.x.cx/256L));
#endif // GCWINNT
}

GBOOL                              /*   returns TRUE=found a file          */
fnd1st(                            /* find file matching filspc and attr   */
struct ffblk *fbptr,               /*   ptr to data storage area           */
const CHAR *filspc,                /*   file spec to match                 */
INT attr)                          /*   attribute to match                 */
{
     return(findfirst(filspc,(struct ffblk *)fbptr,attr) == 0);
}

GBOOL                              /*   returns TRUE=found a file          */
fndnxt(                            /* find next file matching filspc & attr*/
struct ffblk *fbptr)               /*   ptr to data storage area           */
{
     return(findnext((struct ffblk *)fbptr) == 0);
}


GBOOL                              /*   TRUE if dir matching dname exists  */
isdir(                             /* check whether a directory exists     */
const CHAR *dname)                 /*   directory name to search for       */
{
     struct ffblk fb;

     return(fndfile(&fb,dname,FAMDIR) && (fb.ff_attrib & FAMDIR));
}


GBOOL                              /*   TRUE if matching file exists       */
isfile(                            /* check for existence of file(s)       */
const CHAR *fname)                 /*   filename or wildcard to check      */
{
     struct ffblk fb;

     return(fndfile(&fb,fname,FAMSYS|FAMRON|FAMHID));
}


#define SPECIAL_BITS (FAMDIR|FAMHID|FAMSYS)


GBOOL                              /*   TRUE if matching file found        */
fndfile(                           /* check file's existence and stats     */
struct ffblk *fb,                  /*   file info (CLOSED after findfirst) */
const CHAR *fname,                 /*   filespec we're searching for       */
INT attr)                          /*   attribute(s) to match              */
{
     GBOOL     found;              /* TRUE if we found a match             */

#ifdef GCDOS
     found=fnd1st(fb,fname,attr);  /* no special care needed under DOS     */
#else
     /*
      *   Most of this is per the logic in Borland's RTL.
      *
      *   The difference is that we close the search handle before
      *   exiting -- Borland leaves the handle open, and we end up
      *   leaking handles like a sieve.
      */
     HANDLE    fhand=INVALID_HANDLE_VALUE;  /* our search handle           */
     WIN32_FIND_DATA ff;           /* WIN32 search info                    */
     FILETIME local;               /* filetime translated to local time    */

     for (;;)
     {
          if (fhand == INVALID_HANDLE_VALUE) {
               fhand=FindFirstFile(fname,&ff);
               found=(fhand != INVALID_HANDLE_VALUE);
          }
          else {
               found=FindNextFile(fhand,&ff);
          }

          if (!found) {
               break;
          }
          /* If requested attribute word includes hidden, system, or
           * subdirectory bits, return normal files AND those with
           * any of the requested attributes.
           */
          else if ((ff.dwFileAttributes & SPECIAL_BITS) == 0
                || (attr & ff.dwFileAttributes & SPECIAL_BITS) != 0) {
               break;
          }
     }

     if (fhand != INVALID_HANDLE_VALUE) {
          FindClose(fhand);
     }

     if (found) {
          /* Fill in the find_t structure from the NT structure. */
          fb->ff_attrib = ff.dwFileAttributes;

          /* Convert times from GM to Local */
          FileTimeToLocalFileTime(&ff.ftLastWriteTime, &local);
          FileTimeToDosDateTime(&local, &fb->ff_fdate, &fb->ff_ftime);

          fb->ff_fsize = ff.nFileSizeLow;
          strcpy(fb->ff_name, ff.cFileName);
     }
     fb->ff_reserved=0;
#endif
     return(found);
}

GBOOL                              /*   TRUE if found file & converted name*/
getShortName(                      /* get DOS 8.3 version of file name     */
CHAR *shortBuf,                    /*   buffer to receive short name       */
const CHAR *filePath,              /*   complete path of file to get for   */
size_t bufSiz)                     /*   size of buffer                     */
{
#if defined(GCWINNT)

     HANDLE    hFind;              /* our search handle                    */
     WIN32_FIND_DATA ff;           /* WIN32 search info                    */

     if ((hFind=FindFirstFile(filePath,&ff)) != INVALID_HANDLE_VALUE) {
          FindClose(hFind);
          stlcpy(shortBuf
                ,*ff.cAlternateFileName == '\0' ? ff.cFileName
                                                : ff.cAlternateFileName
                ,bufSiz);
          return(TRUE);
     }

#elif defined(GCDOS)

     struct ffblk fb;

     /* NT version doesn't screen attribs, so get all files for DOS, too */
     if (fnd1st(&fb,filePath,FAMDIR|FAMHID|FAMSYS)) {
          stlcpy(shortBuf,fb.ff_name,bufSiz);
          return(TRUE);
     }

#else
#error Unsupported operating system in getShortName
#endif // GCWINNT
     return(FALSE);
}

CHAR *                             /*   returns converted file name        */
mkdosn(                            /* convert string to DOS file name      */
const CHAR *string)                /*   file name to convert to DOS format */
{
     CHAR *cp,*up,c;
     static CHAR buffer[GCMAXFNM];

     stzcpy(buffer,string,8+1);
     if ((cp=strchr(buffer,'.')) == NULL) {
          strcat(buffer,".");
          if ((cp=strchr(string,'.')) != NULL) {
               stzcpy(up=buffer+strlen(buffer),cp+1,3+1);
               if ((cp=strchr(up,'.')) != NULL) {
                    *cp='\0';
               }
          }
     }
     else {
          stzcpy(cp+1,string+((INT)(cp-buffer))+1,3+1);
          if ((up=strchr(cp+1,'.')) != NULL) {
               *cp='\0';
          }
     }
     for (cp=buffer ; (c=*cp) != '\0' ; cp++) {
          if (c < ' ' || '\177' <= c || strchr(" \\:/\"*?,;=[]",c) != NULL) {
               *cp='-';
          }
     }
     return(buffer);
}

VOID
fixpth(                            /* add a backslash to string if needed  */
CHAR *path)                        /*   path to append                     */
{
     SHORT length;

     if ((length=strlen(path)) > 0) {
          if (!isvalds(path[length-1])) {
               strcat(path,SLS);
          }
     }
}

CHAR *                             /*   copy of pointer to destination     */
makePath(                          /* combine directory and file name      */
CHAR *dst,                         /*   destination buffer                 */
const CHAR *dir,                   /*   directory name                     */
const CHAR *file,                  /*   file name                          */
size_t dstSiz)                     /*   size of destination buffer         */
{
     if (dir != NULL && dir != dst) {
          stlcpy(dst,dir,dstSiz);
     }
     if (dst[0] != '\0' && !isvalds(dst[strlen(dst)-1])) {
          stlcat(dst,SLS,dstSiz);
     }
     return(stlcat(dst,file,dstSiz));
}

GBOOL                              /*   TRUE=ok FALSE=err, see deldirerr[] */
deldir(                            /* delete a subdirectory & all contents */
const CHAR *dirpath)               /*   name of a directory                */
{
     struct ffblk fb;
     CHAR path[GCMAXPTH],fptmp[GCMAXFNM];
     CHAR *cp;

     strcpy(deldirerr,dirpath);
     if (!fndfile(&fb,dirpath,FAMDIR)) {
          strcat(deldirerr," does not exist");
          return(FALSE);
     }
     if (!(fb.ff_attrib&FAMDIR)) {
          strcat(deldirerr," is not a directory");
          return(FALSE);
     }
     if (!sameas(fb.ff_name,fileparts(GCPART_FNAM,dirpath,fptmp,GCMAXFNM))) {
          strcat(deldirerr," contains wildcards");
          return(FALSE);
     }
     if (strlen(dirpath) > GCMAXPTH-1-12-1) { /* room for "\" file name NUL */
          strcat(deldirerr," is too long");
          return(FALSE);
     }
     strcpy(path,dirpath);
     strcat(path,SLS);
     strcpy(cp=path+strlen(path),STAR);
     if (fnd1st(&fb,path,FAMDIR)) {
          do {
               strcpy(cp,fb.ff_name);
               if (fb.ff_attrib&FAMDIR) {
                    if (!sameas(fb.ff_name,".") && !sameas(fb.ff_name,"..")
                     && rmdir(path) != 0 && !deldir(path)) {
                         return(FALSE);
                    }
               }
               else {
                    if (unlink(path) != 0) {
                         sprintf(deldirerr,"%s cannot be deleted",path);
                         return(FALSE);
                    }
               }
          } while (fndnxt(&fb));
          fnd1st(&fb,"\\",FAMDIR); // helps to avoid "Access Violation" error
                                   // under NT
          if (rmdir(dirpath) != 0) {
               strcat(deldirerr," cannot be removed");
               return(FALSE);
          }
     }
     return(TRUE);
}

CHAR *                             /*   returns unique file name           */
uniqfn(                            /* create a unique filespec             */
CHAR *fspec)                       /*   original file specification        */
{
     return(ll_uniqfn(TRUE,fspec));
}

CHAR *                             /*   returns unique file name           */
ll_uniqfn(                         /* low-level routine for unique fspec   */
GBOOL cataonerr,                   /*   catastro() on errors?              */
CHAR *fspec)                       /*   original file specification        */
{
     SHORT ci,si,di,ai,n,width;
     CHAR c;
     struct ffblk fb;
     static USHORT i,limit[]={1,10,100,1000,10000,65535U};

     n=strlen(fspec);
     ci=stgidx(fspec,':');
     si=stgidx(fspec,SL);
     di=stgidx(fspec,'.');
     ai=stgidx(fspec,'*');
     if (si == -1) {
          si=ci;
     }
     else {
          si=(SHORT)(strrchr(fspec,SL)-fspec);
          if (cataonerr) {
               ASSERT(0 <= si && si < n);
          }
     }
     if (di == -1) {
          di=n;
     }
     if (ai > di) {
          width=5-n+di;
     }
     else {
          width=10-di+si;
     }
     if (width > 5) {
          width=5;
     }
     movmem(fspec+ai+1,fspec+ai+width,n-ai);
     c=fspec[ai+width];
     for (i=0 ; i < limit[width] ; i++) {
          sprintf(fspec+ai,"%0*u",width,i);
          fspec[ai+width]=c;
          if (!fndfile(&fb,fspec,FAMDIR+FAMSYS+FAMHID) || fb.ff_fsize == 0L) {
               return(fspec);
          }
     }
     if (cataonerr) {
          catastro("Cannot make a unique file name with %s",fspec);
     }
     return("");
}

CHAR *                             /*   copy of pointer to dest            */
normspec(                          /* normalize filespec to complete path  */
CHAR *dest,                        /*   dest buffer (must be GCMAXPTH long)*/
const CHAR *src)                   /*   path+file name to normalize        */
{
#ifdef GCWINNT
     CHAR szBuf[GCMAXPTH];

     if (src[0] == '\0') { // Win32 version of fullpath does not like empty stg
          if (GetCurrentDirectory(sizeof(szBuf),szBuf) == 0) {
               dest[0]='\0';
          }
          else {
               strcpy(dest,szBuf);
          }
          return(dest);
     }
#endif // GCWINNT
     if (_fullpath(dest,src,GCMAXPTH-1) == NULL) {
          dest[0]='\0';
     }
     else {
          fnmcse(dest);
     }
     return(dest);
}

VOID
gmkdir(                            /* recursive version of mkdir()         */
const CHAR *dirnam)                /*   directory name to create           */
{
     CHAR *cp,buf[GCMAXPTH];

     stlcpy(buf,dirnam,GCMAXPTH);
     if ((cp=strchr(buf,':')) != NULL) {
          cp++;
     }
     else {
          cp=buf;
     }
     while (strlen(cp) > 0 && (cp=strchr(cp+1,SL)) != NULL) {
          *cp='\0';
          MKDIR(buf);
          *cp=SL;
     }
     MKDIR(buf);
}

GBOOL                              /*   returns TRUE=file moved            */
movefile(                          /* move file(s) from src to dest        */
const CHAR *src,                   /*   source file spec                   */
const CHAR *dest)                  /*   destination file spec              */
{
     return(morcfile(src,dest,TRUE));
}

GBOOL                              /*   returns TRUE=file copied           */
copyfile(                          /* copy file(s) from src to dest        */
const CHAR *src,                   /*   source file spec                   */
const CHAR *dest)                  /*   destination file spec              */
{
     return(morcfile(src,dest,FALSE));
}


FILE *                             /*   returns FILE pointer or NULL       */
gcfsopen(                          /* opens a file in shared mode          */
const CHAR *filename,              /*   file name to open                  */
const CHAR *mode,                  /*   mode to open with                  */
USHORT shflag)                     /*   shared mode flags                  */
{
     return(_fsopen(filename,mode,shflag));
}

GBOOL                              /*   return TRUE if separator character */
isvalds(                           /* is this char. a file path separator  */
CHAR c)                            /*   character to test                  */
{
     if (c == SL || c == ':') {
          return(TRUE);
     }
     return(FALSE);
}

GBOOL                              /*   returns TRUE=valid file name char  */
isvalfc(                           /* is this a valid file name character? */
CHAR c)                            /*   character to check                 */
{
     if (c < ' ' || c == '.' || c == '"' || c == '/' || c == '\\'
      || c == '[' || c == ']' || c == ':' || c == ';' || c == '|'
      || c == '<' || c == '>' || c == '+' || c == '=' || c == ',') {
          return(FALSE);
     }
     return(TRUE);
}

GBOOL                              /*   returns TRUE=valid file name       */
isvalfn(                           /* check for valid file name            */
const CHAR *filnam)                /*   string to check                    */
{
     const CHAR *cp;
     INT i;

     for (i=1,cp=filnam ; *cp != '\0' && *cp != '.' ; ++i,++cp) {
          if (i > GCMAXFILE || !isvalfc(*cp)) {
               return(FALSE);
          }
     }
     if (*cp++ == '.') {
          for (i=1 ; *cp != '\0' ; ++i,++cp) {
               if (i > GCMAXEXT || *cp == '.' || !isvalfc(*cp)) {
                    return(FALSE);
               }
          }
     }
     if (rsvnam(filnam)) {
          return(FALSE);
     }
     return(TRUE);
}

GBOOL                              /*   returns TRUE=valid directory       */
isvaldn(                           /* verify directory name is ok for OS   */
const CHAR *dirnam)                /*   directory name to check            */
{
     CHAR *cp,tmpdir[GCMAXPTH];
     INT pos;

     if (strlen(dirnam) >= GCMAXPTH) {
          return(FALSE);
     }
     normspec(tmpdir,dirnam);
     if (tmpdir[pos=strlen(tmpdir)-1] == SL) {
          tmpdir[pos]='\0';
     }
     while (strlen(tmpdir) > 2) {  /* e.g. "C:"                            */
          cp=strrchr(tmpdir,SL);
          if (cp == NULL || !isvalfn(cp+1)) {
               return(FALSE);
          }
          *cp='\0';
     }
     return(strlen(tmpdir) == 2 && isalpha(tmpdir[0]) && tmpdir[1] == ':');
}

CHAR *                             /*   returns ptr to cvt'd fname         */
fnmcse(                            /* handles filenames before use in code */
CHAR *fname)                       /*   filename to work on                */
{
#ifdef GCWINNT
     return(fname);
#else
     return(strupr(fname));
#endif // GCWINNT
}

GBOOL                              /*   return TRUE=reserved               */
rsvnam(                            /* is a name reserved by OS for a device*/
const CHAR *name)                  /*   name to check                      */
{
#if defined(GCWINNT)

     INT  cpos;
     CHAR *up,*dp,*cp,*p;
     CHAR fname[GCMAXFILE];

     cp=strrchr(name,':');
     up=strrchr(name,'/');
     dp=strrchr(name,'\\');
     p=max(cp,up);
     p=max(p,dp);
     if (p == NULL) {
          p=(CHAR*)name;
     }
     else {
          p++;
     }
     stlcpy(fname,p,sizeof(fname));
     p=strchr(fname,'.');
     if (p != NULL) {
          *p='\0';
     }

     if (sameas(fname,"CON")
      || sameas(fname,"AUX")
      || sameas(fname,"PRN")
      || sameas(fname,"NUL")) {
          return(TRUE);
     }
     if (sameto("COM",fname)
      || sameto("LPT",fname)) {
          if (fname[3] == '\0') {
               return(FALSE);
          }
          cpos=strlen(fname)-1;
          if (fname[cpos] == ':') {
               fname[cpos]='\0';
          }
          return(alldgs(&fname[3]));
     }
     return(FALSE);

#elif defined(GCDOS)

     INT loop;
     CHAR fnptr[MAXFILE];

     if (devs == NULL) {
          walkdevs(walkdevs(0));
     }
     fileparts(GCPART_FILE,name,fnptr,MAXFILE);
     for (loop=0 ; loop < numdevs ; loop++) {
          if (stricmp(devs[loop],fnptr) == 0) {
               return(TRUE);
          }
     }
     return(FALSE);

#endif // GCWINNT
}

#ifdef GCDOS

INT
walkdevs(                          /* walk device chain, counting/storing  */
INT count)                         /* ... 0 == count devs & alc memory     */
{
     struct devhdr *dp,*dpnew;

     if (count != 0) {
          strcpy(devs[0],"NUL");
     }
     else if (devs != NULL) {
          return(numdevs);
     }
     if ((dp=firstdev()) == NULL) {
          catastro("Incompatible version of DOS (can't find device chain)");
     }
     numdevs=1;
     while (dp != NULL && (unsigned)dp != 0xFFFFL) {
          dp=rtosel(dp,sizeof(struct devhdr));
          if (dp->dh_attr&0x8000) {
               if (count != 0) {
                    stzcpy(devs[numdevs],dp->dh_name,9);
                    unpad(devs[numdevs]);
               }
               numdevs++;
          }
          dpnew=(struct devhdr *)dp->dh_next;
          unrsel(dp);
          dp=dpnew;
     }
     if (count == 0) {
          devs=(CHAR (*)[])alczer(numdevs*sizeof(*devs));
     }
     return(numdevs);
}
#endif // GCDOS

LONG                               /*   returns # of bytes copied          */
xfrfil(                            /* copy from one open file to another   */
FILE *fromfp,                      /*   source file pointer                */
FILE *tofp,                        /*   destination file pointer           */
LONG nbytes)                       /*   max bytes to transfer              */
{
     LONG wbytes;
     INT n;
     CHAR block[BLKSIZ];
     INT fwsiz;

     for (wbytes=0L ; nbytes >= BLKSIZ ; nbytes-=BLKSIZ) {
          if ((n=(INT)fread(block,1,BLKSIZ,fromfp)) > 0) {
               if ((fwsiz=(INT)fwrite(block,1,n,tofp)) > 0) {
                    wbytes+=fwsiz;
               }
          }
          if (n < BLKSIZ) {
               return(wbytes);
          }
     }
     if (nbytes > 0L && (n=fread(block,1,(INT)nbytes,fromfp)) > 0) {
          wbytes+=fwrite(block,1,n,tofp);
     }
     return(wbytes);
}

static VOID
adddirs(                           /* recursive workhorse for cntdirs()    */
INT clsiz,                         /*   cluster size on the drive          */
const CHAR *path)                  /*   wildcard path of files & subdirs   */
{
     struct ffblk fb;
     CHAR subpath[GCMAXPTH],fptmp[GCMAXPTH];
     SHORT rootln;

     if (fnd1st(&fb,path,FAMDIR)) {
          do {
               if (fb.ff_attrib&FAMDIR) {
                    if (!sameas(fb.ff_name,".") && !sameas(fb.ff_name,"..")) {
                         rootln=strlen(fileparts(GCPART_PATH,path,fptmp,
                                                 GCMAXPTH));
                         if (rootln+strlen(fb.ff_name)+1+strlen(STAR)
                             < GCMAXPTH) {
                              sprintf(subpath,"%s%s"SLS STAR,fptmp,fb.ff_name);
                              adddirs(clsiz,subpath);
                         }
                         numdirs++;
                    }
               }
               else {
                    numfils++;
                    numbyts+=fb.ff_fsize;
                    numbytp+=clfit(fb.ff_fsize,clsiz);
               }
          } while (fndnxt(&fb));
     }
}

static GBOOL                       /*   returns TRUE=file moved/copied     */
morcfile(                          /* move or copy file utility            */
const CHAR *src,                   /*   source file spec                   */
const CHAR *dest,                  /*   destination file spec              */
GBOOL movfil)                      /*   move (TRUE) or copy (FALSE) file   */
{
     struct ffblk fb;
     INT error;

     error=0;
     if (fnd1st(&fb,src,0)) {
          do {
               if ((error=!copyutil(src,dest)) == 0 && movfil) {
                    unlink(src);
               }
          } while (!error && fndnxt(&fb));
     }
     return(!error);
}

static GBOOL                       /*   returns TRUE=file copied           */
copyutil(                          /* our own copy file utility            */
const CHAR *src,                   /*   source file spec                   */
const CHAR *dst)                   /*   destination file spec              */
{
#ifdef GCDOS
     INT nbytes;
     FILE *srcfp,*dstfp;
#endif // GCDOS

#ifdef GCWINNT
     if (CopyFile(src,dst,FALSE)) {
          setFileGMT(dst,getFileGMT(src));
          return(TRUE);
     }
     return(FALSE);
#endif // GCWINNT
#ifdef GCDOS
     if (cpybuf == NULL) {
          cpybuf=alcmem(CPBSIZ);
     }
     if ((srcfp=fopen(src,FOPRB)) == NULL) {
          return(FALSE);
     }
     if ((dstfp=fopen(dst,FOPWB)) == NULL) {
          fclose(srcfp);
          return(FALSE);
     }
     while ((nbytes=fread(cpybuf,1,CPBSIZ,srcfp)) > 0) {
          if (fwrite(cpybuf,1,nbytes,dstfp) != nbytes) {
               fclose(srcfp);
               fclose(dstfp);
               unlink(dst);
               return(FALSE);
          }
     }
     fclose(srcfp);
     fclose(dstfp);
     if (setFileGMT(dst,getFileGMT(src))) {
          return(TRUE);
     }
     return(FALSE);
#endif // GCDOS
}

#if defined(GCDOSP)                /* DOS protected mode routines          */
static VOID *
rtosel(                            /* converted real mode ptr to prot mode */
VOID *realptr,                     /*   real mode pointer                  */
INT nbytes)                        /*   number of bytes (sizeof(*realptr)) */
{                                  /*   return value should pass through   */
                                   /*   unrsel() when finished             */
     USHORT rmseg,rmoff,sel;

     rmoff=FP_OFF(realptr);
     rmseg=FP_SEG(realptr);
     DosMapRealSeg(rmseg+(rmoff>>4),nbytes+15,&sel);
     return(MK_FP(sel,rmoff&0x0F));
}

static VOID
unrsel(                            /* free selector created by rtosel()    */
VOID *selptr)
{
     DosFreeSeg(FP_SEG(selptr));
}

static struct devhdr *             /*   return ptr to first device header  */
firstdev(VOID)                     /* get address of first device driver   */
{
     REGS16 r;

#ifdef USE2152
     struct devhdr *realp,*protp;

     setmem(&r,sizeof(REGS16),0);
     r.ax=0x5200;
     if (DosRealIntr(0x21,&r,0L,0) != 0) {
          return(NULL);
     }
     realp=(struct devhdr *)MK_FP(r.es,r.bx+0x22);
     protp=rtosel(realp,sizeof(struct devhdr));
     if (memcmp(protp->dh_name,"NUL     ",8) != 0) {
          unrsel(protp);
          return(NULL);
     }
     unrsel(protp);
     return(realp);
#else
     setmem(&r,sizeof(REGS16),0);
     r.ax=0x122C;
     if (DosRealIntr(0x2F,&r,0L,0) != 0) {
          return(NULL);
     }
     return((struct devhdr *)MK_FP(r.bx,r.ax));
#endif // USE2152
}

#elif defined(GCDOSL)              /* DOS large model routines             */

static struct devhdr *             /*   return ptr to first device header  */
firstdev(void)                     /* get address of first device driver   */
{
#ifdef USE2152
     union REGS regs;
     struct SREGS sregs;
     struct devhdr *devptr;

     segread(&sregs);
     regs.h.ah=0x52;
     intdosx(&regs,&regs,&sregs);
     devptr=(struct devhdr *)(MK_FP(sregs.es,regs.x.bx+0x22));
     if (memcmp(devptr->dh_name,"NUL     ",8) != 0) {
          return(NULL);
     }
     return(devptr);
#else
     union REGS regs;

     regs.x.ax=0x122C;
     int86(0x2F,&regs,&regs);
     return((struct devhdr *)(MK_FP(regs.x.bx,regs.x.ax)));
#endif // USE2152
}
#endif //GCDOSP
