#define NAME         "Copy"
#define VERSION      "1"
#define REVISION     "18"
#define DATE         "23.06.2001"
#define DISTRIBUTION "(Freeware) "
#define AUTHOR       "by Dirk Stöcker <stoecker@epost.de>"
//#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
 1.16  21.08.99 : fixed PATTERN Problem
 1.17  12.03.00 : fixed problem with dir link deletion and ALL
 1.18  23.06.01 : fixed Delete problem, when files did not exist, removed all
	RELOC and DATA hunks
*/

#include <proto/dos.h>
#include <proto/exec.h>
#include <exec/memory.h>

#define version "$VER: " NAME " " VERSION "." REVISION " (" DATE ") " DISTRIBUTION AUTHOR
#define CTRL_C (SetSignal(0L,0L) & SIGBREAKF_CTRL_C)

#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_LINK           3
#define COPYMODE_MAKEDIR	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               "read"
#define TEXT_COPIED             "copied"
#define TEXT_MOVED              "moved"
#define TEXT_DELETED            "deleted"
#define TEXT_LINKED             "linked"
#define TEXT_RENAMED            "renamed"
#define TEXT_CREATED            "created"
#define TEXT_ENTERED            "entered"
#define TEXT_OPENED_FOR_OUTPUT  "opened for output"
#define TEXT_DIRECTORY          "%s <Dir>"
#define TEXT_NOT_DONE           "not %s: "
#define TEXT_NOTHING_DONE       "No file was processed.\n"
#define TEXT_ERR_FORCELINK      "FORCELINK keyword required.\n"
#define TEXT_ERR_DELETE_DEVICE  "A device cannot be deleted."
#define TEXT_ERR_DEST_DIR       "Destination must be a directory.\n"
#define TEXT_ERR_INFINITE_LOOP  "Infinite loop not allowed.\n"
#define TEXT_ERR_WILDCARD_DEST  "Wildcard destination invalid.\n"
#define TEXTMODE_COPY           "COPY mode\n"
#define TEXTMODE_MOVE           "MOVE mode\n"
#define TEXTMODE_DELETE         "DELETE mode\n"
#define TEXTMODE_HARDLINK       "HARDLINK mode\n"
#define TEXTMODE_SOFTLINK       "SOFTLINK mode\n"
#define TEXTMODE_MAKEDIR        "MAKEDIR mode\n"

#define DB   struct DosLibrary *DOSBase,
#define EB   struct ExecBase *SysBase,
#define DBA  DOSBase,
#define EBA  SysBase,

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

ULONG start(void)
{
  struct Process *task;
  struct ExecBase *SysBase;
  struct DosLibrary *DOSBase;
  struct CopyData cd;
  ULONG i;

  SysBase = (*((struct ExecBase **) 4));
  /* 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;
  }

  for(i = 0; i < sizeof(struct CopyData); ++i)
    ((UBYTE *)(&cd))[i] = 0;
  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];
    struct RDArgs *rda;
    struct Args args;

    a[0] = version; /* only to have the version string in code, assignment is removed by optimizer */
    a[0] = "";
    a[1] = 0;

    for(i = 0; i < (sizeof(struct Args)/4); ++i)
      ((ULONG *)(&args))[i] = 0;

    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 || args.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(TEXTMODE_MAKEDIR);

	  while(!cd.RetVal && !cd.RetVal2 && *args.from)
	  {
            if((i = IsPattern(DBA EBA *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(DBA EBA *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(DBA EBA in, out, cd.BufferSize);
	        Close(out);
	      }
	      Close(in);
	    }
	  }
	  else /* COPYMODE_DELETE */
	  {
	    while(*args.from)
              KillFile(DBA *(args.from++), cd.Flags & COPYFLAG_FORCEDELETE);
            cd.RetVal2 = RETURN_OK;
	  }
	}
        else
        {
          if(args.pattern && *args.pattern)
          {
            patbufsize = (strlen(args.pattern)<<1) + 1;
            if((cd.Pattern = (STRPTR) AllocMem(patbufsize, MEMF_PUBLIC)))
            {
              if(ParsePatternNoCase(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)
            {
	      STRPTR t;

	      switch(cd.Mode)
	      {
	      case COPYMODE_COPY: t = TEXTMODE_COPY; break;
	      case COPYMODE_MOVE: t = TEXTMODE_MOVE; break;
	      case COPYMODE_DELETE: t = TEXTMODE_DELETE; break;
	      case COPYMODE_LINK: t = (cd.Flags & COPYFLAG_SOFTLINK ? TEXTMODE_SOFTLINK : TEXTMODE_HARDLINK); break;
	      }

              Printf(t);
            }

            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(DBA EBA *(args.from++), &cd); cd.RetVal = cd.RetVal2 = 0;
              }
            }
            else if((i = IsPattern(DBA EBA 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(DBA EBA *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(DBA EBA args.to, &cd)))
                      {
                        *path = sep;
                        /* do the job */
			UnLock(lock); lock = 0;
			CopyMem(*args.from, cd.FileName, 1+strlen(*args.from));
                        DoWork(DBA EBA FilePart(args.to), &cd); /* on file call */
			UnLock(cd.CurDest);
                      }
                    }
                  }
                  if(lock)
                    UnLock(lock);
                }
                else if(cd.Mode == COPYMODE_COPY && !TestFileSys(DBA *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(DBA EBA args.to, &cd)))
                  {
                    *path = sep;
                    /* do the job */
		    CopyMem(*args.from, cd.FileName, 1+strlen(*args.from));
                    DoWork(DBA EBA 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(DBA EBA args.to, &cd)))
              {
                while(cd.RetVal <= (args.errwarn ? RETURN_OK : RETURN_WARN)
                && *args.from && !CTRL_C)
                  PatCopy(DBA EBA *(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;
}

static ULONG strlen(STRPTR str)
{
  ULONG i = 0;
  while(*(str++)) ++i;
  return i;
}

static void PatCopy(DB EB 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(DBA EBA 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(DBA name))
  {
    cd->Flags |= COPYFLAG_SRCNOFILESYS;
    CopyMem(name, cd->FileName, 1+strlen(name));
    DoWork(DBA EBA 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(DBA EBA 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) && (cd->Mode != COPYMODE_DELETE || APath->ap_Info.fib_DirEntryType < 3))
        {
          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(DBA EBA 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);
}

static LONG IsPattern(DB EB 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;
}

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

static ULONG OpenDestDir(DB EB 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(DBA 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(DBA 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(DBA name, 1, 1, 1);
          Printf("%s\n", TEXT_CREATED);
        }
        UnLock(a);
      }
      else
      {
        if(!(cd->Flags & COPYFLAG_QUIET))
          PrintNotDone(DBA name, TEXT_CREATED, 1, 1);
        err = 2;
      }
    }
    
    *(ptr++) = as;
  }

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

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

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

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

static void PrintName(DB STRPTR name, ULONG deep, ULONG dir, ULONG txt)
{
  if(deep > PRINTOUT_SPACES)
    deep = PRINTOUT_SPACES; /* reduce number of spaces */

  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());
}

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

/* returns value, when it seems to be a filesystem */
static ULONG TestFileSys(DB 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;
}

static void DoWork(DB EB 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(DBA EBA in, out, cd->BufferSize);
	Close(in);
	if(!h)
	{
	  kill = 0;
	  if(cd->Mode == COPYMODE_MOVE)
	  {
	    if(KillFile(DBA cd->FileName, cd->Flags & COPYFLAG_FORCEDELETE))
              res = 1;
	  }
	  else
	    res = 1;
	}
      }
      Close(out);

      if(kill)
	KillFile(DBA cd->DestName, 0);
    }
    if(!res && !(cd->Flags & COPYFLAG_QUIET))
    PrintNotDone(DBA 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(DBA 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(DBA name, cd->Deep, cd->Fib->fib_DirEntryType > 0,
    cd->Fib->fib_DirEntryType < 0 || (cd->Flags & COPYFLAG_ALL ?
    (cd->Mode != COPYMODE_DELETE || cd->Fib->fib_DirEntryType > 2) : 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 || cd->Fib->fib_DirEntryType > 2)))
  {
    UnLock(lock); lock = 0;

    if(KillFile(DBA 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(DBA lock, cd->CurDest))
    {
      printok = 0;
      cd->RetVal = RETURN_ERROR;
      if(!(cd->Flags & COPYFLAG_QUIET))
      {
        if(cd->Flags & COPYFLAG_SILENT)
          PrintName(DBA name, cd->Deep, 1, 1);
        Printf(TEXT_NOT_DONE, TEXT_ENTERED);
        Printf(TEXT_ERR_INFINITE_LOOP);
      }
    }
    else if((a = TestDest(DBA 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(DBA name, cd->Deep, 1, 1);
          Printf(TEXT_NOT_DONE, TEXT_LINKED);
          Printf(TEXT_ERR_FORCELINK);
        }
      }
      else if(LinkFile(DBA 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(DBA 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(DBA 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(DBA EBA in, out, cd->BufferSize);
	  Close(in);
	  if(!h)
	  {
	    kill = 0;
	    if(cd->Mode == COPYMODE_MOVE)
	    {
	      if(KillFile(DBA cd->FileName, cd->Flags & COPYFLAG_FORCEDELETE))
                res = 1;
	    }
	    else
	      res = 1;
	  }
        }
	Close(out);

	if(kill)
	  KillFile(DBA cd->DestName, 0);
      }
      if(!res)
      {
	printerr = txt; cd->RetVal = RETURN_WARN;
      }
      else
	printok = txt;
    }
  }
      
  if(printerr && !(cd->Flags & COPYFLAG_QUIET))
    PrintNotDone(DBA 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);
    if(cd->Flags & COPYFLAG_NOPRO)
      SetProtection(cd->DestName, 0);
    else
      SetProtection(cd->DestName, cd->Fib->fib_Protection);
    if(cd->Flags & COPYFLAG_DATES)
      SetFileDate(cd->DestName, &cd->Fib->fib_Date);
    if(cd->Flags & COPYFLAG_COMMENT)
      SetComment(cd->DestName, cd->Fib->fib_Comment);
  }

  if(lock)
    UnLock(lock);
}

static LONG CopyFile(DB EB 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/..." */
static LONG LinkFile(DB 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 */
static ULONG TestLoop(DB 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;
}

static LONG TestDest(DB 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(DBA name, cd->Flags & COPYFLAG_FORCEOVERWRITE))
              ret = TESTDEST_DELETED;
          }
          else
            ret = TESTDEST_CANTDELETE;
        }
        else if(cd->Flags & COPYFLAG_DONTOVERWRITE)
          ret = TESTDEST_CANTDELETE;
        else if(KillFile(DBA 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;
}

