#define NAME         "Copy"
#define REVISION     "15"
#define DISTRIBUTION "(Freeware) "
//#define DEBUG

/* Programmheader

        Name:           Copy
        Author:         SDI
        Distribution:   Freeware
        Description:    copies, moves, deletes and links files
        Compileropts:   -
        Linkeropts:     -gsi -l amiga

 1.0   02.08.97 : first version, only raw source
 1.1   06.08.97 : added more stuff
 1.2   08.08.97 : completed option description, added new code
 1.3   09.08.97 : and more stuff done and SILENT option
 1.4   11.08.97 : fixed problem with DELETE and ExNext function
 1.5   14.08.97 : fixed DoWork, added new stuff
 1.6   17.08.97 : added more functions, program is near completion
 1.7   24.08.97 : bug fixes in DoWork function
 1.8   25.09.97 : included missing functions, started debugging
 1.9   26.09.97 : debugging, source cleanup
 1.10  28.09.97 : and still debugging
 1.11  30.09.97 : fixed some errors
 1.12  04.10.97 : Added FORCE Option
 1.13  19.12.97 : added DIRECT option
 1.14  20.04.98 : with DIRECT now all softlinks can be deleted (Thanks to
	Magnus Holmgren for reporting this)
 1.15  06.05.98 : added automatic DIRECT detection with IsFileSystem
*/

#include <proto/dos.h>
#include <proto/exec.h>
#include <exec/memory.h>
#include "SDI_defines.h"
#define SDI_TO_ANSI
#include "SDI_ASM_STD_protos.h"

#define PARAM   "FROM/M,TO,PAT=PATTERN/K,BUF=BUFFER/K/N,ALL/S,"         \
                "DIRECT/S,CLONE/S,DATES/S,NOPRO/S,COM=COMMENT/S,"       \
                "QUIET/S,SILENT/S,NOREQ/S,ERRWARN/S,MAKEDIR/S,"         \
                "MOVE/S,DELETE/S,HARD=HARDLINK/S,SOFT=SOFTLINK/S,"      \
                "FOLNK=FORCELINK/S,FODEL=FORCEDELETE/S,"                \
                "FOOVR=FORCEOVERWRITE/S,DONTOVR=DONTOVERWRITE/S,"	\
                "FORCE/S"

#define COPYFLAG_ALL            (1<<0)
#define COPYFLAG_DATES          (1<<1)
#define COPYFLAG_NOPRO          (1<<2)
#define COPYFLAG_COMMENT        (1<<3)
#define COPYFLAG_FORCELINK      (1<<4)
#define COPYFLAG_FORCEDELETE    (1<<5)
#define COPYFLAG_FORCEOVERWRITE (1<<6)
#define COPYFLAG_DONTOVERWRITE  (1<<7)
#define COPYFLAG_QUIET          (1<<8)
#define COPYFLAG_SILENT         (1<<9)
#define COPYFLAG_ERRWARN	(1<<10)

#define COPYFLAG_SOFTLINK       (1<<20) /* produce softlinks */
#define COPYFLAG_DEST_FILE      (1<<21) /* one file mode */
#define COPYFLAG_DONE           (1<<22) /* did something in DoWork */
#define COPYFLAG_ENTERSECOND    (1<<23) /* entered directory second time */

#define COPYFLAG_SRCNOFILESYS	(1<<24) /* source is no filesystem */
#define COPYFLAG_DESNOFILESYS	(1<<25) /* destination is no filesystem */

#define COPYMODE_COPY           0
#define COPYMODE_MOVE           1
#define COPYMODE_DELETE         2
#define COPYMODE_MAKEDIR	3
#define COPYMODE_LINK           4

#define PRINTOUT_SIZE           50      /* maximum size of name printout */
#define PRINTOUT_SPACES         10      /* maximum number of spaces      */

#define FILEPATH_SIZE           300     /* maximum size of filepaths     */

/* return values */
#define TESTDEST_DIR_OK         2       /* directory exists, go in */
#define TESTDEST_DELETED        1       /* file or empty directory deleted */
#define TESTDEST_NONE           0       /* nothing existed */
#define TESTDEST_ERROR          -1      /* an error occured */
#define TESTDEST_CANTDELETE     -2      /* deletion not allowed (DONTOV) */

struct Args {
  STRPTR *      from;
  STRPTR        to;
  STRPTR        pattern;
  LONG *        buffer;
  LONG          all;
  LONG		direct;
  LONG          clone;
  LONG          dates;
  LONG          nopro;
  LONG          comment;
  LONG          quiet;
  LONG          silent;
  LONG          noreq;
  LONG		errwarn;
  LONG		makedir;
  LONG          move_mode;
  LONG          delete_mode;
  LONG          hardlink;
  LONG          softlink;
  LONG          forcelink;
  LONG          forcedelete;
  LONG          forceoverwrite;
  LONG          dontoverwrite;
  LONG		force;
};

struct CopyData {
  ULONG         Flags;
  ULONG         BufferSize;
  STRPTR        Pattern;
  ULONG         Destination;
  ULONG         CurDest;  /* Current Destination */
  ULONG		DestPathSize;
  struct FileInfoBlock *Fib;
  UBYTE         Mode;
  UBYTE         RetVal;         /* when set, error output is already done */
  UBYTE         RetVal2;        /* when set, error output must be done */
  UBYTE         Deep;
  UBYTE		FileName[FILEPATH_SIZE];
  UBYTE		DestName[FILEPATH_SIZE];
};

#define TEXT_READ		texts[0]
#define TEXT_COPIED             texts[1]
#define TEXT_MOVED              texts[2]
#define TEXT_DELETED            texts[3]
#define TEXT_LINKED             texts[4]
#define TEXT_RENAMED            texts[5]
#define TEXT_CREATED            texts[6]
#define TEXT_ENTERED            texts[7]
#define TEXT_OPENED_FOR_OUTPUT  texts[8]
#define TEXTNUM_MODE            9
#define TEXT_DIRECTORY          texts[15]
#define TEXT_NOT_DONE           texts[16]
#define TEXT_NOTHING_DONE       texts[17]
#define TEXT_ERR_FORCELINK      texts[18]
#define TEXT_ERR_DELETE_DEVICE  texts[19]
#define TEXT_ERR_DEST_DIR       texts[20]
#define TEXT_ERR_INFINITE_LOOP  texts[21]
#define TEXT_ERR_WILDCARD_DEST  texts[22]

STRPTR texts[] =
{
"read",
"copied",
"moved",
"deleted",
"linked",
"renamed",
"created",
"entered",
"opened for output",
"COPY mode\n",
"MOVE mode\n",
"DELETE mode\n",
"MAKEDIR mode\n",
"HARDLINK mode\n",
"SOFTLINK mode\n",
"%s <Dir>",				/* output of directories */
"not %s: ",
"No file was processed.\n",
"FORCELINK keyword required.\n",
"A device cannot be deleted.",
"Destination must be a directory.\n",
"Infinite loop not allowed.\n",
"Wildcard destination invalid.\n",
};

LONG  CopyFile(ULONG, ULONG, ULONG);
void  DoWork(STRPTR, struct CopyData *);
LONG  IsPattern(STRPTR); /* return 0 -> NOPATTERN, return -1 --> ERROR */
LONG  KillFile(STRPTR, ULONG);
LONG  LinkFile(ULONG, STRPTR, ULONG);
ULONG OpenDestDir(STRPTR, struct CopyData *);
void  PatCopy(STRPTR, struct CopyData *);
void  PrintName(STRPTR, ULONG, ULONG, ULONG);
void  PrintNotDone(STRPTR, STRPTR, ULONG, ULONG);
ULONG TestFileSys(STRPTR); /* returns value, when is a filesystem */
void  SetData(STRPTR, struct CopyData *);
LONG  TestDest(STRPTR, ULONG, struct CopyData *);
ULONG TestLoop(ULONG, ULONG);

struct DosLibrary *DOSBase;

ULONG start(void)
{
  struct Process *task;
  struct DosLibrary *dosbase;
  struct CopyData cd;

  /* test for WB and reply startup-message */
  if(!(task = (struct Process *) FindTask(0))->pr_CLI)
  {
    WaitPort(&task->pr_MsgPort);
    Forbid();
    ReplyMsg(GetMsg(&task->pr_MsgPort));
    return RETURN_FAIL;
  }

  memset(&cd, 0, sizeof(struct CopyData));
  cd.BufferSize = 102400;
  cd.Mode = COPYMODE_COPY;
  cd.RetVal2 = RETURN_FAIL;
  cd.Deep = 1;

  if((dosbase = (struct DosLibrary *) OpenLibrary("dos.library", 37)))
  {
    STRPTR a[2] = {"", 0};
    struct RDArgs *rda;
    struct Args args;

    DOSBase = dosbase;

    memset(&args, 0, sizeof(struct Args));

    if((rda = (struct RDArgs *) AllocDosObject(DOS_RDARGS, 0)))
    {
      rda->RDA_ExtHelp =
      "FROM     multiple input files\n"
      "TO       destination file or directory\n"
      "PATTERN  a pattern the filenames must match\n"
      "BUFFER   buffersize for copy buffer (default 200 [100K])\n"
      "ALL      deep scan into sub directories\n"
      "DIRECT   copy/delete only: work without any tests or options\n"
      "CLONE    copy comment, protection bits and date as well\n"
      "DATES    copy dates\n"
      "NOPRO    do not copy protection bits\n"
      "COMMENT  copy filecomment\n"
      "QUIET    suppress all output and requesters\n"
      "SILENT   suppress output, but not errors and requesters\n"
      "NOREQ    suppress requesters\n"
      "ERRWARN  do not proceed, when one file failed\n"
      "MAKEDIR  produce directories\n"
      "MOVE     delete source files after copying successful\n"
      "DELETE   do not copy, but delete the source files\n"
      "HARDLINK make a hardlink to source instead of copying\n"
      "SOFTLINK make a softlink to source instead of copying\n"
      "FOLNK    also makes links to directories\n"
      "FODEL    delete protected files also\n"
      "FOOVR    also overwrite protected files\n"
      "DONTOVR  do never overwrite destination\n"
      "FORCE    DO NOT USE. Call compatibility only.\n";

      if(ReadArgs(PARAM, (LONG *) &args, rda))
      {
        ULONG patbufsize = 0;
        LONG i = 0;
        APTR win = task->pr_WindowPtr;

        if(args.quiet) /* when QUIET, SILENT and NOREQ are also true! */
          args.silent = args.noreq = 1;

        if(args.buffer && *args.buffer > 0) /* minimum buffer size */
          cd.BufferSize = *args.buffer * 512;

        if(args.quiet)          cd.Flags |= COPYFLAG_QUIET;
        if(args.silent)         cd.Flags |= COPYFLAG_SILENT;
        if(args.all)            cd.Flags |= COPYFLAG_ALL;
        if(args.clone)          cd.Flags |= COPYFLAG_DATES|COPYFLAG_COMMENT;
        if(args.dates)          cd.Flags |= COPYFLAG_DATES;
        if(args.comment)        cd.Flags |= COPYFLAG_COMMENT;
        if(args.nopro)          cd.Flags |= COPYFLAG_NOPRO;
        if(args.forcelink)      cd.Flags |= COPYFLAG_FORCELINK;
        if(args.forcedelete)    cd.Flags |= COPYFLAG_FORCEDELETE;
        if(args.forceoverwrite) cd.Flags |= COPYFLAG_FORCEOVERWRITE;
        if(args.dontoverwrite)  cd.Flags |= COPYFLAG_DONTOVERWRITE;
	if(args.errwarn)	cd.Flags |= COPYFLAG_ERRWARN;

	if(args.force) /* support OS Delete and MakeLink command options */
	{
	  if(args.delete_mode)
	    cd.Flags |= COPYFLAG_FORCEDELETE;
	  if(args.hardlink || args.softlink)
	    cd.Flags |= COPYFLAG_FORCELINK;
	}

        if(!args.from)  /* no args.from means currentdir */
          args.from = a;

        if(args.noreq) /* no dos.library requests allowed */
          task->pr_WindowPtr = (APTR) -1;

        if(args.delete_mode)    { ++i; cd.Mode = COPYMODE_DELETE; }
        if(args.move_mode)      { ++i; cd.Mode = COPYMODE_MOVE; }
	if(args.makedir)	{ ++i; cd.Mode = COPYMODE_MAKEDIR; }
        if(args.hardlink)       { ++i; cd.Mode = COPYMODE_LINK; }
        if(args.softlink)
        {
          ++i;
          cd.Mode = COPYMODE_LINK;
          cd.Flags |= COPYFLAG_SOFTLINK|COPYFLAG_FORCELINK;
        }

        if(cd.Mode != COPYMODE_DELETE && cd.Mode != COPYMODE_MAKEDIR &&
        !args.to)
        {
          if(*(args.from+1)) /* when no TO is specified, the arg is last */
          {                  /* one of from. Copy this argument into */
            STRPTR *a;       /* args.to */

            a = args.from;
            while(*(++a))
              ;
            args.to = *(--a); *a = 0;
          }
        }

        /* test if more than one of the above four or any other wrong
           arguments */

	if(i > 1 ||
	(args.from == a && cd.Mode == COPYMODE_MAKEDIR) ||
	(args.direct && (args.from == a || !*args.from || cd.Pattern ||
        (cd.Flags & ~(COPYFLAG_QUIET|COPYFLAG_SILENT|COPYFLAG_ERRWARN)) ||
        (cd.Mode != COPYMODE_DELETE && (cd.Mode != COPYMODE_COPY ||
        !args.to || args.from[1])))) ||
	(args.dontoverwrite && args.forceoverwrite) ||
	(args.nopro && args.clone) ||
	(args.softlink && args.all) ||
	(!args.to && cd.Mode != COPYMODE_DELETE && cd.Mode != COPYMODE_MAKEDIR))
          SetIoErr(ERROR_TOO_MANY_ARGS);
	else if(cd.Mode == COPYMODE_MAKEDIR)
	{
	  ULONG i;
	  cd.RetVal2 = RETURN_OK;

          if(!args.silent)
            Printf(texts[TEXTNUM_MODE+COPYMODE_MAKEDIR]);

	  while(!cd.RetVal && !cd.RetVal2 && *args.from)
	  {
            if((i = IsPattern(*args.from)))
            {
              if(i != -1)
              {
                cd.RetVal = RETURN_ERROR;
                if(!args.quiet)
                  Printf(TEXT_ERR_WILDCARD_DEST);
              }
              else
                cd.RetVal2 = RETURN_FAIL;
            }
	    if((i = OpenDestDir(*args.from, &cd)))
	    {
	      UnLock(i);
	      cd.Flags |= COPYFLAG_DONE;
	    }
	    ++args.from;
	  }
	} /* cd.Mode == COPYMODE_MAKEDIR */
	else if(args.direct)
	{
	  if(cd.Mode == COPYMODE_COPY)
	  {
	    ULONG in, out;

	    if((in = Open(*args.from, MODE_OLDFILE)))
	    {
	      if((out = Open(args.to, MODE_NEWFILE)))
	      {
	        cd.RetVal2 = CopyFile(in, out, cd.BufferSize);
	        Close(out);
	      }
	      Close(in);
	    }
	  }
	  else /* COPYMODE_DELETE */
	  {
	    while(*args.from)
              KillFile(*(args.from++), cd.Flags & COPYFLAG_FORCEDELETE);
            cd.RetVal2 = RETURN_OK;
	  }
	}
        else
        {
          if(args.pattern && *args.pattern)
          {
            patbufsize = (SDI_strlen(args.pattern)<<1) + 1;
            if((cd.Pattern = (STRPTR) AllocMem(patbufsize, MEMF_PUBLIC)))
            {
              if(ParsePattern(args.pattern, cd.Pattern, patbufsize) < 0)
              {
                FreeMem(cd.Pattern, patbufsize); cd.Pattern = 0;
              }
            }
          }

          if((cd.Fib = (struct FileInfoBlock *) AllocDosObject(DOS_FIB, 0)))
          {
            if(!args.silent)
              Printf(texts[TEXTNUM_MODE+cd.Mode+
              (cd.Flags & COPYFLAG_SOFTLINK ? 1 : 0)]);

            if(args.pattern && !cd.Pattern)
            {
              if(!*args.pattern)
                SetIoErr(ERROR_BAD_TEMPLATE);
            }
            else if(cd.Mode == COPYMODE_DELETE)
            {
              cd.RetVal2 = RETURN_OK;

              while(cd.RetVal <= (args.errwarn ? RETURN_OK : RETURN_WARN)
              && *args.from)
                PatCopy(*(args.from++), &cd);

            }
            else if((i = IsPattern(args.to)))
            {
              if(i != -1)
              {
                cd.RetVal = RETURN_ERROR;
                if(!args.quiet)
                  Printf(TEXT_ERR_WILDCARD_DEST);
              }
            }
            else
            {
              STRPTR path;

              if(*(path = PathPart(args.to)) == '/')
                ++path; /* is destination a path description ? */

              if(*path && !*(args.from+1) && !(i = IsPattern(*args.from)))
              {
                ULONG lock;

                /* is destination an existing directory */
                if((lock = Lock(args.to, SHARED_LOCK)))
                {
                  if(Examine(lock, cd.Fib))
                  {
                    if(cd.Fib->fib_DirEntryType > 0)
                      cd.RetVal2 = RETURN_OK;
                    /* indicate dir-mode for next if */
                  }
                  else
                    i = 1;
                  UnLock(lock);
                }

                /* is source a directory */
                if(!i && cd.RetVal2 && (lock = Lock(*args.from, SHARED_LOCK)))
                {
                  if(Examine(lock, cd.Fib))
                  {
                    cd.RetVal2 = RETURN_OK;
                    if(cd.Mode != COPYMODE_COPY ||
                    cd.Fib->fib_DirEntryType < 0)
                    {
                      UBYTE sep;

                      cd.Flags |= COPYFLAG_DEST_FILE;
                      /* produce missing destination directories */
                      sep = *path; *path = 0;
                      if((cd.CurDest = OpenDestDir(args.to, &cd)))
                      {
                        *path = sep;
                        /* do the job */
			UnLock(lock); lock = 0;
			CopyMem(*args.from, cd.FileName, 1+strlen(*args.from));
                        DoWork(FilePart(args.to), &cd); /* on file call */
			UnLock(cd.CurDest);
                      }
                    }
                  }
                  if(lock)
                    UnLock(lock);
                }
                else if(cd.Mode == COPYMODE_COPY && !TestFileSys(*args.from))
                {
                  UBYTE sep;
                  cd.Flags |= COPYFLAG_DEST_FILE|COPYFLAG_SRCNOFILESYS;
                  cd.RetVal2 = RETURN_OK;
                  /* produce missing destination directories */
                  sep = *path; *path = 0;
                  if((cd.CurDest = OpenDestDir(args.to, &cd)))
                  {
                    *path = sep;
                    /* do the job */
		    CopyMem(*args.from, cd.FileName, 1+strlen(*args.from));
                    DoWork(FilePart(args.to), &cd); /* on file call */
		    UnLock(cd.CurDest);
                  }
                }
              }
              else if(i != -1)
                cd.RetVal2 = RETURN_OK;

              if(!cd.RetVal && !cd.RetVal2 && !(cd.Flags &
              COPYFLAG_DEST_FILE) && (cd.Destination =
              OpenDestDir(args.to, &cd)))
              {
                while(cd.RetVal <= (args.errwarn ? RETURN_OK : RETURN_WARN)
                && *args.from && !CTRL_C)
                  PatCopy(*(args.from++), &cd);

                UnLock(cd.Destination);
              }
            } /* else */

            if(!(cd.Flags & COPYFLAG_DONE) && !args.silent && !cd.RetVal
            && !cd.RetVal2)
              Printf(TEXT_NOTHING_DONE);

            FreeDosObject(DOS_FIB, cd.Fib);
          } /* if((cd.Fib = ... )) */

          if(cd.Pattern)
            FreeMem(cd.Pattern, patbufsize);
        } /* else */

	task->pr_WindowPtr = win;

        FreeArgs(rda);
      } /* ReadArgs */
      FreeDosObject(DOS_RDARGS, rda);
    } /* AllocDosObject */

    if(!cd.RetVal2 && CTRL_C)
    {
      SetIoErr(ERROR_BREAK);
      cd.RetVal2 = RETURN_WARN;
    }

    if(cd.RetVal2 && !args.quiet && !cd.RetVal)
      PrintFault(IoErr(),0);

    if(cd.RetVal)
      cd.RetVal2 = cd.RetVal;

    CloseLibrary((struct Library *) dosbase);

    if(args.errwarn && cd.RetVal2 == RETURN_WARN)
      cd.RetVal2 = RETURN_ERROR;
  }

  return cd.RetVal2;
}

void PatCopy(STRPTR name, struct CopyData *cd)
{
  struct AnchorPath *APath;
  ULONG retval, doit = 0, deep = 0, failval = RETURN_WARN, first = 0;

#ifdef DEBUG
  Printf("PatCopy(%s, .)\n", name);
#endif

  if((cd->Mode == COPYMODE_COPY || (cd->Flags & COPYFLAG_ALL)) &&
  !IsPattern(name))
    first = 1; /* enter first directory (support of old copy style) */

  if(cd->Flags & COPYFLAG_ERRWARN)
    failval = RETURN_OK;

  cd->CurDest = cd->Destination;
  cd->DestPathSize = 0;

  if(cd->Mode == COPYMODE_COPY && !TestFileSys(name))
  {
    cd->Flags |= COPYFLAG_SRCNOFILESYS;
    CopyMem(name, cd->FileName, 1+strlen(name));
    DoWork(FilePart(name), cd);
    cd->Flags &= ~COPYFLAG_SRCNOFILESYS;
    return;
  }

  if((APath = (struct AnchorPath *) AllocMem(sizeof(struct AnchorPath) + FILEPATH_SIZE,
  MEMF_PUBLIC|MEMF_CLEAR)))
  {
    APath->ap_BreakBits = SIGBREAKF_CTRL_C;
    APath->ap_Strlen = FILEPATH_SIZE;

    for(retval = MatchFirst(name, APath); !retval && cd->RetVal <=
    failval && !cd->RetVal2; retval = MatchNext(APath))
    {
      if(doit)
      {
        DoWork(cd->Fib->fib_FileName, cd); doit = 0;
      }

      if(deep)         /* used for Deep checking */
      {
        ++cd->Deep; deep = 0;
      }

      cd->Flags &= ~COPYFLAG_ENTERSECOND;

      CopyMem(APath->ap_Buf, cd->FileName, FILEPATH_SIZE);
      CopyMem(&APath->ap_Info, cd->Fib, sizeof(struct FileInfoBlock));

      if(first && APath->ap_Info.fib_DirEntryType > 0)
        APath->ap_Flags |= APF_DODIR;
      else if(APath->ap_Flags & APF_DIDDIR)
      {
	ULONG i;
        cd->Flags |= COPYFLAG_ENTERSECOND;
        APath->ap_Flags &= ~APF_DIDDIR;
        --cd->Deep;
        if(cd->Mode == COPYMODE_DELETE || cd->Mode == COPYMODE_MOVE)
	  doit = 1;
	if((i = cd->CurDest))
	{
          cd->CurDest = ParentDir(i);
	  cd->DestPathSize = 0;
	  if(i != cd->Destination)
            UnLock(i);
          if(!cd->CurDest)
            break;
        }
      }
      else if(APath->ap_Info.fib_DirEntryType > 0)
      {
	doit = 1;
        if(cd->Flags & COPYFLAG_ALL)
        {
          APath->ap_Flags |= APF_DODIR;
          deep = 1;
        }
      }
      else if(!cd->Pattern || MatchPatternNoCase(cd->Pattern,
      APath->ap_Info.fib_FileName))
        doit = 1;
      first = 0;
    }
    MatchEnd(APath);

    if(retval != ERROR_NO_MORE_ENTRIES)
      cd->RetVal2 = RETURN_FAIL;

    if(doit)
      DoWork(cd->Fib->fib_FileName, cd);

/* No need to clear the flags here, as they are cleared on next PatJoin
   call (DoWork is not called first round, as lock is zero!). */

    FreeMem(APath, sizeof(struct AnchorPath) + FILEPATH_SIZE);
  }
  else
  {
    cd->RetVal = RETURN_FAIL;
    if(!cd->Flags & COPYFLAG_QUIET)
      PrintFault(ERROR_NO_FREE_STORE, 0);
  }

  if(cd->CurDest && cd->CurDest != cd->Destination)
    UnLock(cd->CurDest);
}

LONG IsPattern(STRPTR name)
{
  LONG a, ret = -1;
  STRPTR buffer;

  a = (strlen(name)<<1) + 10;

  if((buffer = (STRPTR) AllocMem(a, MEMF_ANY)))
  {
    ret = ParsePattern(name, buffer, a);
    FreeMem(buffer, a);
  }

  if(ret == -1)
    SetIoErr(ERROR_NO_FREE_STORE);

  return ret;
}

LONG KillFile(STRPTR name, ULONG doit)
{
  if(doit)
    SetProtection(name, 0);
  return DeleteFile(name);
}

ULONG OpenDestDir(STRPTR name, struct CopyData *cd)
{
  LONG a, err = 0, cr = 0;
  STRPTR ptr = name;
  UBYTE as;

  if((cd->Mode == COPYMODE_COPY || cd->Mode == COPYMODE_MOVE) &&
  !TestFileSys(name))
  {
    cd->Flags |= COPYFLAG_DESNOFILESYS;
    CopyMem(name, cd->DestName, 1+strlen(name));
    return (ULONG) Lock("", SHARED_LOCK);
  }

  while(!err && *ptr != 0)
  {
    while(*ptr && *ptr != '/')
      ++ptr;
    as = *ptr;
    *ptr = 0;

    if((a = TestDest(name, 1, cd)) == TESTDEST_CANTDELETE)
    {
      if(!(cd->Flags & COPYFLAG_QUIET))
        Printf(TEXT_ERR_DEST_DIR);
      err = 2;
    }
    else if(a < 0)
      err = 1;
    else if(a != TESTDEST_DIR_OK)
    {
      if((a = CreateDir(name)))
      {
	++cr;
        if(!(cd->Flags & COPYFLAG_SILENT))
        {
          PrintName(name, 1, 1, 1);
          Printf("%s\n", TEXT_CREATED);
        }
        UnLock(a);
      }
      else
      {
        if(!(cd->Flags & COPYFLAG_QUIET))
          PrintNotDone(name, TEXT_CREATED, 1, 1);
        err = 2;
      }
    }
    
    *(ptr++) = as;
  }

  if(err)
  {
    cd->RetVal = RETURN_ERROR;

    if(!(cd->Flags & COPYFLAG_QUIET) && err == 1)
      PrintNotDone(name, TEXT_OPENED_FOR_OUTPUT, 1, 1);
    return 0;
  }

  if(cd->Mode == COPYMODE_MAKEDIR && !cr && !(cd->Flags & COPYFLAG_QUIET))
  {
    SetIoErr(ERROR_OBJECT_EXISTS);
    PrintNotDone(name, TEXT_CREATED, 1, 1);
  }

  return (ULONG) Lock(name, SHARED_LOCK);
}

void PrintName(STRPTR name, ULONG deep, ULONG dir, ULONG txt)
{
  deep %= PRINTOUT_SPACES; /* reduce number of spaces */
  /* This produces an error with MaxonC++ */

  while(deep--)
    Printf(" ");

  if((deep = strlen(name)) > PRINTOUT_SIZE) /* reduce name size */
  {
    name += deep-PRINTOUT_SIZE;
    Printf("...");
  }

  Printf((dir ? TEXT_DIRECTORY : "%s"), name);
  if(txt)
    Printf(" ..");
  Flush(Output());
}

void PrintNotDone(STRPTR name, STRPTR txt, ULONG deep, ULONG dir)
{
  if(name)
    PrintName(name, deep, dir, 1);
  Printf(TEXT_NOT_DONE, txt);
  PrintFault(IoErr(),0);
}

/* returns value, when it seems to be a filesystem */
ULONG TestFileSys(STRPTR name)
{
  STRPTR n = name;
  ULONG ret = 1;

  while(*n && *n != ':')
   ++n;

  if(*(n++) == ':')
  {
    UBYTE a;

    a = *n; *n = 0;
    ret = IsFileSystem(name);
    *n = a;
  }  
  return ret;
}

void DoWork(STRPTR name, struct CopyData *cd)
{
  ULONG pdir, lock = 0;
  STRPTR printerr = 0, printok = "";

#ifdef DEBUG
  Printf("DoWork(%s, .)\n", name);
#endif

  if(cd->RetVal > (cd->Flags & COPYFLAG_ERRWARN ? RETURN_OK : RETURN_WARN)
  || cd->RetVal2)
    return;

  if(cd->Mode != COPYMODE_DELETE && !(cd->Flags & COPYFLAG_DESNOFILESYS))
  {
    if(!cd->DestPathSize)
    {
      if(!NameFromLock(cd->CurDest, cd->DestName, FILEPATH_SIZE))
      {
        cd->RetVal2 = RETURN_FAIL;
        UnLock(lock);
        return;
      }
      cd->DestPathSize = strlen(cd->DestName);
    }
    cd->DestName[cd->DestPathSize] = 0;
    AddPart(cd->DestName, name, FILEPATH_SIZE);
  }

  if(cd->Flags & (COPYFLAG_SRCNOFILESYS|COPYFLAG_DESNOFILESYS))
  {
    ULONG in, out, res = 0, kill = 1;
    STRPTR txt = TEXT_OPENED_FOR_OUTPUT;

#ifdef DEBUG
    Printf("Partly DIRECT mode active now (%s - %s)\n", cd->FileName,
    cd->DestName);
#endif

    if((out = Open(cd->DestName, MODE_NEWFILE)))
    {
      txt = cd->Mode == COPYMODE_MOVE ? TEXT_MOVED : TEXT_COPIED;
      if((in = Open(cd->FileName, MODE_OLDFILE)))
      {
        ULONG h;

	h = CopyFile(in, out, cd->BufferSize);
	Close(in);
	if(!h)
	{
	  kill = 0;
	  if(cd->Mode == COPYMODE_MOVE)
	  {
	    if(KillFile(cd->FileName, cd->Flags & COPYFLAG_FORCEDELETE))
              res = 1;
	  }
	  else
	    res = 1;
	}
      }
      Close(out);

      if(kill)
	KillFile(cd->DestName, 0);
    }
    if(!res && !(cd->Flags & COPYFLAG_QUIET))
    PrintNotDone(cd->Flags & COPYFLAG_SILENT ? name : 0,
    txt, cd->Deep, cd->Fib->fib_DirEntryType > 0);
    else
    {
      cd->Flags |= COPYFLAG_DONE;
      if(!(cd->Flags & COPYFLAG_SILENT))
        Printf("%s\n", txt);
    }
    return;
  }

  if(!(lock = Lock(cd->FileName, SHARED_LOCK)))
  {
    cd->RetVal = RETURN_WARN;
    if(!(cd->Flags & COPYFLAG_QUIET))
    {
      PrintNotDone(cd->Fib->fib_FileName, TEXT_READ, cd->Deep,
      cd->Fib->fib_DirEntryType > 0);
    }
    return;
  }

  if(!(pdir = ParentDir(lock)))
  {
    cd->RetVal = RETURN_ERROR;
    if(cd->Mode == COPYMODE_DELETE)
    {
      if(!(cd->Flags & COPYFLAG_QUIET))
      {
        Printf(" %s ", cd->FileName);
        Printf(TEXT_NOT_DONE, TEXT_DELETED);
        Printf("%s\n", TEXT_ERR_DELETE_DEVICE);
      }
    }
    UnLock(lock);
    return;
  }
  UnLock(pdir);

  if(!(cd->Flags & COPYFLAG_SILENT))
    PrintName(name, cd->Deep, cd->Fib->fib_DirEntryType > 0,
    cd->Fib->fib_DirEntryType < 0 || (cd->Flags & COPYFLAG_ALL ?
    cd->Mode != COPYMODE_DELETE : cd->Mode != COPYMODE_COPY) ||
    cd->Flags & COPYFLAG_ENTERSECOND);

  if((cd->Flags & COPYFLAG_ENTERSECOND) || (cd->Mode == COPYMODE_DELETE
  && (!(cd->Flags & COPYFLAG_ALL) || cd->Fib->fib_DirEntryType < 0)))
  {
    UnLock(lock); lock = 0;

    if(KillFile(cd->FileName, cd->Flags & COPYFLAG_FORCEDELETE))
      printok = TEXT_DELETED;
    else
    {
      cd->RetVal = RETURN_WARN;
      printerr = TEXT_DELETED;
    }
  }
  else if(cd->Mode == COPYMODE_DELETE)
    ;
  else if(cd->Fib->fib_DirEntryType > 0)
  {
    ULONG a;

    if((cd->Flags & COPYFLAG_ALL || cd->Mode == COPYMODE_LINK ||
    cd->Mode == COPYMODE_MOVE) && TestLoop(lock, cd->CurDest))
    {
      printok = 0;
      cd->RetVal = RETURN_ERROR;
      if(!(cd->Flags & COPYFLAG_QUIET))
      {
        if(cd->Flags & COPYFLAG_SILENT)
          PrintName(name, cd->Deep, 1, 1);
        Printf(TEXT_NOT_DONE, TEXT_ENTERED);
        Printf(TEXT_ERR_INFINITE_LOOP);
      }
    }
    else if((a = TestDest(cd->DestName, 1, cd)) < 0)
    {
      printerr = TEXT_CREATED; cd->RetVal = RETURN_ERROR;
    }
    else if(cd->Flags & COPYFLAG_ALL)
    {
      ULONG i;

      i = cd->CurDest;
      cd->DestPathSize = 0;

      if(a == TESTDEST_DIR_OK)
      {
        if(!(cd->CurDest = Lock(cd->DestName, SHARED_LOCK)))
        {
          printerr = TEXT_ENTERED; cd->RetVal = RETURN_ERROR;
        }
        else
          printok  = TEXT_ENTERED;
      }
      else if((cd->CurDest = CreateDir(cd->DestName)))
      {
        UnLock(cd->CurDest);
        if((cd->CurDest = Lock(cd->DestName, SHARED_LOCK)))
          printok = TEXT_CREATED;
        else
        {
          printerr = TEXT_ENTERED; cd->RetVal = RETURN_ERROR;
        }
      }
      else
      {
        printerr = TEXT_CREATED; cd->RetVal = RETURN_ERROR;
      }
      if(!cd->CurDest)
        cd->CurDest = i;
      else if(i != cd->Destination)
        UnLock(i);
    }
    else if(cd->Mode == COPYMODE_MOVE)
    {
      if(Rename(cd->FileName, cd->DestName))
        printok = TEXT_RENAMED;
      else
      {
        printerr = TEXT_RENAMED; cd->RetVal = RETURN_WARN;
      }
    }
    else if(cd->Mode == COPYMODE_LINK)
    {
      if(!(cd->Flags & COPYFLAG_FORCELINK))
      {
        printok = 0;
        cd->RetVal = RETURN_WARN;
        if(!(cd->Flags & COPYFLAG_QUIET))
        {
          if(cd->Flags & COPYFLAG_SILENT)
            PrintName(name, cd->Deep, 1, 1);
          Printf(TEXT_NOT_DONE, TEXT_LINKED);
          Printf(TEXT_ERR_FORCELINK);
        }
      }
      else if(LinkFile(lock, cd->DestName, cd->Flags & COPYFLAG_SOFTLINK))
        printok = TEXT_LINKED;
      else
      {
        printerr = TEXT_LINKED; cd->RetVal = RETURN_WARN;
      }
    }
    else /* COPY mode only displays directories, when not ALL */
    {
      printok = 0;
      if(!(cd->Flags & COPYFLAG_SILENT))
        Printf("\n");
    }
  }
  else
  {
    /* test for existing destination file */
    if(TestDest(cd->DestName, 0, cd) < 0)
      printerr = TEXT_OPENED_FOR_OUTPUT;
    else if(cd->Mode == COPYMODE_MOVE && Rename(cd->FileName, cd->DestName))
      printok = TEXT_RENAMED;
    else if(cd->Mode == COPYMODE_LINK)
    {
      if(!(cd->Flags & COPYFLAG_SOFTLINK) && LinkFile(lock, cd->DestName, 0))
        printok = TEXT_LINKED;
      else
      {
        printerr = TEXT_LINKED; cd->RetVal = RETURN_WARN;
        if(cd->Flags & COPYFLAG_SOFTLINK)
          SetIoErr(ERROR_OBJECT_WRONG_TYPE);
      }
    }
    else
    {
      ULONG in, out, res = 0, h;
      STRPTR txt = TEXT_OPENED_FOR_OUTPUT;

      if((out = Open(cd->DestName, MODE_NEWFILE)))
      {
        ULONG kill = 1;

	txt = cd->Mode == COPYMODE_MOVE ? TEXT_MOVED : TEXT_COPIED;
	UnLock(lock); lock = 0;
	if((in = Open(cd->FileName, MODE_OLDFILE)))
	{
	  h = CopyFile(in, out, cd->BufferSize);
	  Close(in);
	  if(!h)
	  {
	    kill = 0;
	    if(cd->Mode == COPYMODE_MOVE)
	    {
	      if(KillFile(cd->FileName, cd->Flags & COPYFLAG_FORCEDELETE))
                res = 1;
	    }
	    else
	      res = 1;
	  }
        }
	Close(out);

	if(kill)
	  KillFile(cd->DestName, 0);
      }
      if(!res)
      {
	printerr = txt; cd->RetVal = RETURN_WARN;
      }
      else
	printok = txt;
    }
  }
      
  if(printerr && !(cd->Flags & COPYFLAG_QUIET))
    PrintNotDone(cd->Flags & COPYFLAG_SILENT ? name : 0,
    printerr, cd->Deep, cd->Fib->fib_DirEntryType > 0);
  else if(printok)
  {
    cd->Flags |= COPYFLAG_DONE;
    if(!(cd->Flags & COPYFLAG_SILENT))
      Printf("%s\n", printok);
    SetData(cd->DestName, cd);
  }

  if(lock)
    UnLock(lock);
}

LONG CopyFile(ULONG from, ULONG to, ULONG bufsize)
{
  STRPTR buffer;
  LONG s, err = 0;

  if((buffer = (STRPTR) AllocMem(bufsize, MEMF_ANY)))
  {
    do
    {
      if((s = Read(from, buffer, bufsize)) == -1 ||
      Write(to, buffer, s) == -1)
        err = RETURN_FAIL;
    } while(s == bufsize && !err);
    FreeMem(buffer, bufsize);
  }
  else
    err = RETURN_FAIL;

  return err;
}

/* Softlink's path starts always with device name! f.e. "Ram Disk:T/..." */
LONG LinkFile(ULONG from, STRPTR to, ULONG soft)
{
  if(soft)
  {
    UBYTE name[FILEPATH_SIZE];
    NameFromLock(from, name, FILEPATH_SIZE);
    return MakeLink(to, (ULONG) name, LINK_SOFT);
  }
  else
    return MakeLink(to, from, LINK_HARD);
}

/* return 0 means no loop, return != 0 means loop found */
ULONG TestLoop(ULONG srcdir, ULONG destdir)
{
  ULONG par, lock, loop = 0;

  lock = destdir;

  if(SameDevice(srcdir, destdir))
  {
    do
    {
      if(!SameLock(srcdir, lock))
        loop = 1;
      else
      {
        par = ParentDir(lock);
        if(lock != destdir)
          UnLock(lock);
        lock = par;
      }
    } while(!loop && lock);
  }

  if(lock != destdir)
    UnLock(lock);

  return loop;
}

void SetData(STRPTR name, struct CopyData *cd)
{
  if(cd->Flags & COPYFLAG_NOPRO)
    SetProtection(name, 0);
  else
    SetProtection(name, cd->Fib->fib_Protection);
  if(cd->Flags & COPYFLAG_DATES)
    SetFileDate(name, &cd->Fib->fib_Date);
  if(cd->Flags & COPYFLAG_COMMENT)
    SetComment(name, cd->Fib->fib_Comment);
}

LONG TestDest(STRPTR name, ULONG type, struct CopyData *cd)
{
  LONG ret = TESTDEST_ERROR;
  ULONG lock;

  if((lock = Lock(name, SHARED_LOCK)))
  {
    struct FileInfoBlock *fib;

    if((fib = (struct FileInfoBlock *) AllocDosObject(DOS_FIB, 0)))
    {
      if(Examine(lock, fib))
      {
        UnLock(lock); lock = 0;
        if(type)
        {
          if(fib->fib_DirEntryType > 0)
            ret = TESTDEST_DIR_OK;
          else if(!(cd->Flags & COPYFLAG_DONTOVERWRITE))
          {
            if(KillFile(name, cd->Flags & COPYFLAG_FORCEOVERWRITE))
              ret = TESTDEST_DELETED;
          }
          else
            ret = TESTDEST_CANTDELETE;
        }
        else if(cd->Flags & COPYFLAG_DONTOVERWRITE)
          ret = TESTDEST_CANTDELETE;
        else if(KillFile(name, cd->Flags & COPYFLAG_FORCEOVERWRITE))
          ret = TESTDEST_DELETED;
      }
      FreeDosObject(DOS_FIB, fib);
    }
    if(lock)
      UnLock(lock);
  }
  else
    ret = TESTDEST_NONE;

  if(ret == TESTDEST_CANTDELETE)
    SetIoErr(ERROR_OBJECT_EXISTS);

  return ret;
}

