/*
 * mv v1.0 - Unix-like move file utility
 *
 * Copyright 1989 Edwin Hoogerbeets
 *
 * This code may be freely redistributed as long as no charges other than
 * reasonable copying fees are levied for it.
 *
 * Manx version by Edwin Hoogerbeets
 * usenet: edwin@watcsc.waterloo.edu
 * CIS:    72647,3675
 *
 * Works mostly like the Unix move.
 *
 * Usage: mv [-cfix]    [-] file1 file2
 *        mv [-cfix]    [-] path1 [path2 ...] dir
 *        cp [-fimnx]   [-] file1 file2
 *        cp [-fimnxrR] [-] path1 [path2 ...] dir
 *        rm [-cdfimrR] [-] path  [path ...]
 *
 * Where path is either a file or a directory.
 *
 *  -c act like cp instead (as in "mv -c" means do a cp instead of mv)
 *  -d remove directories only if they are empty (as in AmigaDOS Delete)
 *  -f force quiet mode, overwriting destination files if necessary.
 *  -i force interactive mode
 *  -m act like mv instead
 *  -n do not copy file dates, comments and protections (use "n"ew dates..)
 *  -R same as -r
 *  -r recursively do directories as well (mv is always recursive)
 *  -x act like rm instead
 *  -  end of options (useful to remove a file whose name starts with
 *     a dash eg. "-d")
 *
 * Moves, etc. across devices are supported.
 *
 */

#include <fcntl.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#ifdef ARP
#include <libraries/arpbase.h>
#include <libraries/arpfunc.h>
#endif
#include <exec/memory.h>
#include <ctype.h>

#define FIBSIZE (long)sizeof(struct FileInfoBlock)
#define BUFSIZE 256

typedef struct fl {
  char name[BUFSIZE];
  struct fl *next;
} filenode;

#define FNSIZE (long)sizeof(filenode)

struct FileLock          *lock;
struct FileInfoBlock     *fib;

int mvflag = 0, /* is this a move command? */
    cpflag = 0, /* is this a copy command? */
    rmflag = 0, /* is this a remove command? */
    rflag  = 0, /* is this command recursive? */
    fflag  = 0, /* don't ask if it should overwrite, just do it */
    iflag  = 0, /* do interactive mode */
    nflag  = 1, /* copy file dates, comments and protections */
    dflag  = 0; /* delete directories only if they are empty */

char commandname[32] = "";

long ofile;     /* output file handle */
long ifile;     /* input file handle  */

#ifdef ARP

typedef struct BAP {
  struct AnchorPath bap_ap;
  char padding[BUFSIZE];
} BigAnchorPath;

#define APSIZE (long)sizeof(BigAnchorPath)

int arpflag = 0;

#endif

/* these are so Manx won't complain about ptr/int conversions, etc. */
extern struct FileLock   *ParentDir();
extern struct FileLock   *CreateDir();
extern struct FileLock   *Lock();
extern struct FileLock   *CurrentDir();
extern int                Examine();
extern char              *AllocMem();
extern struct FileHandle *Open();
extern struct MsgPort    *DeviceProc();
extern struct Library    *OpenLibrary();
extern struct _dev       *_devtab;

#ifdef ARP
struct ArpBase       *ArpBase;
struct IntuitionBase *IntuitionBase;
struct GfxBase       *GfxBase;
#endif

#include <exec/alerts.h>
#include <workbench/startup.h>

extern long _savsp, _stkbase;

extern int errno;
extern int Enable_Abort;

extern int _argc, _arg_len;
extern char **_argv, *_arg_lin;

_main(alen, aptr)
long alen;
char *aptr;
{
        struct Process *pp, *FindTask();

        _stkbase = _savsp - *((long *)_savsp+1) + 8;
        *(long *)_stkbase = 0x4d414e58L;

        pp = FindTask(0L);
        _cli_parse(pp, alen, aptr);
        Enable_Abort = 1;

        exit(main(_argc, _argv));
}

/*
 * The following few routines were taken from my edlib1.1 source. They
 * are included here so that anyone can recompile this source without
 * the library. (I'll be happy to send you edlib if you want it.)
 */

char *strrpbrk(str, charset)
register char *str, *charset;
{
  register char *temp;
  extern char *index();

  temp = str + strlen(str) - 1;

  while ( temp != (str - 1)  && !index(charset, *temp) )
    --temp;

  return( (temp != (str - 1)) ? temp : NULL);
}

int stricmp(str1,str2)
register char *str1,*str2;
{
    register int index = 0;

    while ( str1[index] && str2[index] &&
            tolower(str1[index]) == tolower(str2[index]) )
        ++index;

    return( (tolower(str1[index]) < tolower(str2[index])) ? -1 :
          ( (tolower(str1[index]) > tolower(str2[index])) ?  1 : 0) );
}


/* return a pointer to the first character of a file name in a path name */
char *basename(buf)
register char *buf;
{
  register char *foo = strrpbrk(buf,":/");

  return( foo ? (foo + 1) : buf );
}

/* end of edlib routines */

/* write out a string */
int emit(file,str)
long file;
char *str;
{
  Write(file,str,strlen(str));
}

/*
 * return the length of the largest piece of memory that is possibly
 * contiguous
 */
long mem()
{
   long chip, fast;
   extern long AvailMem();

   Forbid();
   chip = AvailMem(MEMF_CHIP);
   fast = AvailMem(MEMF_FAST);
   Permit();

   return(chip>fast ? chip : fast);
}

/* make a new file info block and return a pointer to it */
struct FileInfoBlock *newfib()
{
  struct FileInfoBlock *fib;

  fib = (struct FileInfoBlock *) AllocMem(FIBSIZE, MEMF_CLEAR);

  if ( !fib ) {
    if ( !fflag ) {
      emit(ofile,commandname);
      emit(ofile,": Out of memory!\n");
    }
    return(NULL);
  }

  return(fib);
}

/* get rid of a used file info block */
int freefib(fib)
{
  if ( fib )
    FreeMem(fib,FIBSIZE);
}

/* make a new buffer and return a pointer to it */
char *newbuf()
{
  register char *temp = AllocMem(BUFSIZE,MEMF_CLEAR|MEMF_PUBLIC);

  if ( !temp ) {
    if ( !fflag ) {
      emit(ofile,commandname);
      emit(ofile,": Out of memory!\n");
    }
    return(NULL);
  }

  return(temp);
}

/* free a buffer previously allocated with newbuf() */
int freebuf(buf)
char *buf;
{
  if ( buf ) {
    FreeMem(buf,BUFSIZE);
  }
}

/* make a new filenode structure and return a pointer to it */
filenode *newfilenode()
{
  filenode *new = (filenode *)
    AllocMem(FNSIZE,MEMF_PUBLIC|MEMF_CLEAR);

  if ( new ) {
    new->name[0] = '\0';
    new->next = NULL;
  }

  return(new);
}

/* free a list of filenode structures */
int freefilenodes(file)
filenode *file;
{
  if ( file ) {
    freefilenodes(file->next);

    FreeMem(file,FNSIZE);
  }
}

/* return a pointer to the last element of a filenode list */
filenode *end(file)
filenode *file;
{
  if ( file ) {
    if ( file->next ) {
      return(end(file->next));
    } else {
      return(file);
    }
  } else {
    return(NULL);
  }
}

/* return the length of a list of filenodes */
int arglength(file)
filenode *file;
{
  if ( file->next ) {
    return(arglength(file->next)+1);
  } else {
    return(1);
  }
}

/* Does the input string contain a wildcard? */
int haswild(name)
char *name;
{
  register int foo = 0;

  while ( name[foo] && name[foo] != '*' && name[foo] != '?' &&
          name[foo] != '#' )
    ++foo;

  return ( name[foo] );
}


/*
 * This routine takes a string with possibly a wildcard in it and expands
 * it to a list of filenode structures. If arp isn't opened or if it
 * wasn't compiled with arp, then it creates a list of 1 filenode containing
 * the argument it was passed. The pointer to nomem is where it puts the
 * error code for "low on available memory" errors.
 */
filenode *expand(name,nomem)
char *name;
int *nomem;
{
  filenode *file;

# ifdef ARP
  if ( arpflag && haswild(name) ) {
    filenode *temp;
    int error;
    BigAnchorPath *anchor;

    *nomem = 0;

    anchor = (BigAnchorPath *)
        AllocMem(APSIZE,MEMF_CLEAR|MEMF_PUBLIC);

    anchor->bap_ap.ap_Length = BUFSIZE;

    if ( FindFirst(name,anchor) ) {
      FreeAnchorChain(anchor);
      return(NULL);
    }

    if ( file = newfilenode() ) {
      strcat(file->name,anchor->bap_ap.ap_Buf);
    } else {
      *nomem = 1;
      FreeAnchorChain(anchor);
      return(NULL);
    }
#   ifdef DEBUG
    printf("First matched file: %s\n",file->name);
#   endif

    temp = file;

    while ( !(error = FindNext(anchor)) ) {

      if ( !(temp->next = newfilenode()) ) {
        *nomem = 1;
        FreeAnchorChain(anchor);
        freefilenodes(file);
        return(NULL);
      }

      temp = temp->next;

      strcat(temp->name,anchor->bap_ap.ap_Buf);

#   ifdef DEBUG
    printf("Next matched file: %s\n",temp->name);
#   endif
    }

    FreeAnchorChain(anchor);
  } else {
# endif
    file = newfilenode();

    if ( file ) {
      *nomem = 0;
      strcat(file->name,name);
    } else {
      *nomem = 1;
      return(NULL);
    }

# ifdef ARP
  }
# endif

  return(file);
}

/* well, I guess this is a pro-choice program. ;-) */
_abort()
{
  if ( lock )
    UnLock(lock);

  exit(-1);
}

/*
 * make a string containing the path part of a full AmigaDOS path name.
 * return a pointer to this string.
 */
char *parent(name)
char *name;
{
  register char *foo;
  char *temp = AllocMem((long)strlen(name)+1,MEMF_CLEAR);

  strcat(temp,name);

  /* get a pointer to the filename part */
  foo = basename(temp);

  /*
   * lop off the file name part -- the length of the whole original
   * string must still be freed when freeing what temp points to.
   */
  *foo = '\0';

  return(temp);
}

/* are the two files on the same volume? If you can't tell, guess */
int samedev(src,dst)
char *src, *dst;
{
  char  srcbuf[40], dstbuf[40], *temp;
  struct FileLock *lock;

  /* Simultaneous get a lock and convert BPTR to a C pointer */
  lock = (struct FileLock *)BADDR(Lock(src,ACCESS_READ));

  if (lock == NULL) {
    return(-1);
  }

  temp = (char *)
    BADDR(((struct DeviceList *)BADDR(lock->fl_Volume))->dl_Name);

  strncpy(srcbuf,&temp[1],temp[0]+1);
  srcbuf[temp[0]+2] = '\0';

  UnLock(((long)lock) >> 2);  /* You must UnLock or the GURU visits */

  temp = parent(dst);

  lock = (struct FileLock *)BADDR(Lock(temp,ACCESS_READ));

  FreeMem(temp,strlen(dst)+1);

  if (lock == NULL) {
    return(-1);
  }

  temp = (char *)
    BADDR(((struct DeviceList *)BADDR(lock->fl_Volume))->dl_Name);

  strncpy(dstbuf,&temp[1],temp[0]+1);
  dstbuf[temp[0]+2] = '\0';

  UnLock(((long)lock) >> 2);  /* You must UnLock or the GURU visits */

# ifdef DEBUG
  printf("%s and %s are %son the same volume\n",src,dst,
        (!stricmp(srcbuf,dstbuf))?"":"not ");
# endif
  return( !stricmp(srcbuf,dstbuf) );
}

/*
 * Is the named file a directory? Get a File Info Block and point fib to
 * it and return the results.
 *
 * return   0  for not a dir (ie. file)
 * return   1  for a dir
 * return   2  for no access to file
 * return   3  for not being able to examine file
 *
 */
int isdir(path,fib)
char *path;
struct FileInfoBlock **fib;
{
  struct FileLock *lock;
  register int result;

  /* allocate a word aligned memory block to hold our info */
  *fib = newfib();

  if ( !(lock = Lock(path,ACCESS_READ)) ) {
    return(2);
  }

  if ( *fib ) {
    if ( Examine(lock,*fib) ) {

      /* if the source is not a directory .. */
      result = (*fib)->fib_DirEntryType > 0 ? 1 : 0;

    } else {
      /* 3 for could not examine */
      result = 3;
    }

  } else {
    if ( !fflag ) {
      emit(ofile,commandname);
      emit(ofile,": Out of memory!\n");
    }
    UnLock(lock);
    return(-1);
  }

  UnLock(lock);

  return(result);
}

#ifdef ARP
void closethings()
{
  if ( ArpBase ) {
    CloseLibrary(ArpBase);
  }

  if ( IntuitionBase ) {
    CloseLibrary(IntuitionBase);
  }

  if ( GfxBase ) {
    CloseLibrary(GfxBase);
  }

}
#endif

void usage()
{
  if ( !fflag ) {
    emit(ofile,"Usage: mv [-cfix]    [-] file1 file2\n");
    emit(ofile,"       mv [-cfix]    [-] path1 [path2 ...] dir\n");
    emit(ofile,"       cp [-fimnx]   [-] file1 file2\n");
    emit(ofile,"       cp [-fimnxrR] [-] path1 [path2 ...] dir\n");
    emit(ofile,"       rm [-cdfimrR] [-] path  [path ...]\n");
    emit(ofile,"\nWhere path is either a file or a directory.\n");
  }

# ifdef ARP
  closethings();
# endif

  exit(1);
}

/* the following is a mess. brace yourself. */
main(argc,argv)
int argc;
char *argv[];
{
  register int index, c;
  struct FileInfoBlock *startfib = NULL, *endfib = NULL;
  filenode *start = NULL, *temp = NULL, *endnode = NULL;
  int args, result, nomem;

  ofile = (long) Open("*",MODE_NEWFILE); /* open new file for stderr */
  ifile = Input();

# ifdef ARP
  ArpBase = (struct ArpBase *) OpenLibrary(ArpName,0L);

  if ( ArpBase ) {
#   ifdef DEBUG
    printf("opened arp.library okay\n");
#   endif

    if ( !(IntuitionBase = (struct IntuitionBase *)
           OpenLibrary("intuition.library",0L)) ) {
      emit(ofile,"Could not open intuition.library\n");
#     ifdef ARP
      closethings();
#     endif
      exit(-1);
    }

    if ( !(GfxBase = (struct GfxBase *)
           OpenLibrary("graphics.library",0L)) ) {
      emit(ofile,"Could not open graphics.library\n");
#     ifdef ARP
      closethings();
#     endif
      exit(-1);
    }
    arpflag = 1;
  } else {
#   ifdef DEBUG
    printf("Arp.library not opened.\n");
#   endif
    arpflag = 0;
  }
# endif

  if ( !stricmp(basename(argv[0]),MVNAME) ) {

    ++mvflag;
    ++rflag;

  } else if ( !stricmp(basename(argv[0]),RMNAME) ) {

    ++rmflag;

  } else {

    /*
     * default to the copy command so that if the user renames the
     * executable to something we don't understand, we don't do
     * anything really destructive.
     */
    ++cpflag;
  }

  index = 1;

  /* simplistic argument processing */

  while ( argv[index][0] == '-' ) {
    c = 1;

    /* - option was specified to end other options */
    if ( !argv[index][c] ) {
      ++index;
      break;
    }

    while ( argv[index][c] ) {
      switch ( argv[index][c] ) {
        case 'c':
          cpflag = 1;
          rflag = mvflag = rmflag = 0;
          break;

        case 'd':
          dflag = 1;
          break;

        case 'f':
          fflag = 1;
          iflag = 0;
          break;

        case 'i':
          fflag = 0;
          iflag = 1;
          break;

        case 'm':
          rflag = mvflag = 1;
          cpflag = rmflag = 0;
          break;

        case 'n':
          nflag = 0;
          break;

        case 'R':
        case 'r':
          rflag = 1;
          break;

        case 'x':
          rmflag = 1;
          rflag = mvflag = cpflag = 0;
          break;

        default:
          emit(ofile,"invalid option ");
          Write(ofile,&argv[index][c],1);
          emit(ofile,"\n");
          usage();
      }
      c++;
    }
    ++index;
  }

  /* if there are no file names left after the options were processed ... */
  if ( (argc - index) < (rmflag ? 1 : 2) ) {
    usage();
  }

  if ( mvflag ) {

    strcat(commandname,MVNAME);
#   ifdef DEBUG
    printf("mv command executing\n");
#   endif

  } else if ( rmflag ) {

    strcat(commandname,RMNAME);
#   ifdef DEBUG
    printf("rm command executing\n");
#   endif

  } else {

    strcat(commandname,CPNAME);
#   ifdef DEBUG
    printf("cp command executing\n");
#   endif

  }

  /*
   * expand the first argument. index contains the number of the first
   * file name argument at this point, because it is updated by the options
   * parsing piece of code above.
   */
  start = expand(argv[index++],&nomem);

  if ( nomem ) {
    if ( !fflag ) {
      emit(ofile,commandname);
      emit(ofile,": Out of memory!\n");
    }
#   ifdef ARP
    closethings();
#   endif
    exit(-1);
  }

  temp = end(start);

  for ( ;index < argc; index++) {

    /*
     * it is possible that previous arguments had a wildcard and didn't
     * match anything, so temp would be NULL at this point, otherwise
     * attach the new file list from expand onto the end of the list.
     */
    if ( temp ) {
      temp->next = expand(argv[index],&nomem);
    } else {
      temp = expand(argv[index],&nomem);
      start = temp;
    }

    if ( nomem ) {
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": Out of memory!\n");
      }
#     ifdef ARP
      closethings();
#     endif
      freefilenodes(start);
      exit(-1);
    }

    temp = end(temp);
  }

  endnode = end(start);
  args = arglength(start);

# ifdef DEBUG
  printf("argc: %d\n",argc);

  for ( temp = start; temp; temp = temp->next ) {
    printf("argument \"%s\"\n", temp->name );
  }
# endif

  if ( !rmflag ) {

    /*
     * main case statement for the program to find out what to do with
     * its life. (You gotta fight, for your right,
     * to PPPPPAAAAAAARRRRRRRRIIIIIIIITTTTTTTYYYYYY!!!!!!
     */

    /*
     * check last argument (ie. the destination file) to make sure it
     * is a directory
     */
    switch ( isdir(endnode->name,&endfib) ) {

      /*
       * destination is a file, check that there are only 2 arguments
       * and that the first one is also a file, or inform the user
       * of his (or her) silliness.
       */
      case 0:
        if ( args == 2 ) {
          if ( isdir(start->name,&startfib) ) {
            if ( !fflag ) {
              emit(ofile,commandname);
              emit(ofile,": cannot move a directory onto a file\n");
            }

            freefib(startfib);
            freefib(endfib);
            freefilenodes(start);
            usage();

          } else {

            result = mv2f(start->name,startfib,endnode->name,endfib);

            if ( result == 2 && mvflag ) {
              rm(start->name,fflag,iflag);
            }

            freefib(startfib);
            freefib(endfib);
            freefilenodes(start);
            exit(0);
          }
        } else {
          if ( !fflag ) {
            emit(ofile,commandname);
            emit(ofile,": cannot move a directory onto a file\n");
          }
          freefib(endfib);
          freefilenodes(start);
          usage();
        }
        break;

      /* destination is a directory */
      case 1:
        freefib(endfib);
        break;

      /* destination doesn't exist */
      case 2:
        if ( args == 2 && !isdir(start->name,&startfib) ) {

          /*
           * move file specified in start->name to a new
           * file in endnode->name
           */
          result = mv2f(start->name,startfib,endnode->name,NULL);

          if ( result == 2 && mvflag ) {
            rm(start->name,fflag,iflag);
          }

          freefib(endfib);
          freefib(startfib);
          freefilenodes(start);
          exit(0);
        } else {
          if ( args > 2 ) {
            struct FileLock *lock;
            char *buf = newbuf();

            freefib(startfib);
            freefib(endfib);

            if ( !buf ) {
              freefilenodes(start);
              exit(-1);
            }

            if ( iflag ) {
              buf[0] = '\0';
              emit(ofile,commandname);
              emit(ofile,": create directory ");
              emit(ofile,endnode->name);

              Read(ifile,buf,BUFSIZE);

              if ( buf[0] != 'y' && buf[0] != 'Y' ) {
                break;
              }
            }

            if ( !(lock = CreateDir(endnode->name)) ) {
              if ( !fflag ) {
                emit(ofile,commandname);
                emit(ofile,": unable to create directory ");
                emit(ofile,endnode->name);
                emit(ofile,"\n");
              }
#             ifdef ARP
              closethings();
#             endif
              freefilenodes(start);
              freebuf(buf);
              exit(-1);
            } else {
              UnLock(lock);
            }
            freebuf(buf);
          }
        }
        break;

      case 3:
        if ( !fflag ) {
          emit(ofile,commandname);
          emit(ofile,": could not examine file ");
          emit(ofile,endnode->name);
          emit(ofile,"\n");
        }

        freefilenodes(start);
        freefib(endfib);
        exit(3);
        break;
    }

# ifdef DEBUG
  printf("Move or copy files to directory %s\n",endnode->name);
# endif
  }

  /*
   * For each source argument, move it to the correct directory.
   */
  for ( temp = start; temp; temp = temp->next ) {

    Chk_Abort();

    if ( (temp != endnode) && (mvflag || cpflag) ) {
      result = mv(temp->name,endnode->name);

      switch ( result ) {

        /* if there was an error, get out now */
        case -1:
#         ifdef ARP
          closethings();
#         endif
          freefilenodes(start);
          exit(-1);

        /* if there was a successful copy, then remove the source */
        case 2:
          if ( !cpflag )
            rm(temp->name,fflag,iflag);
          break;

        /*
         * if there was a successful move, then do nothing, 'cause the
         * source is gone already.
         */
        default:
          break;
      }
    } else if ( rmflag ) {
      rm(temp->name,fflag,iflag);
    }
  }

# ifdef ARP
  closethings();
# endif

  freefilenodes(start);

  exit(0);
}

int rm_file(file,fflag,iflag)
char *file;
int fflag, iflag;
{
  register int result;
  char *buf = newbuf();

  if ( !buf ) {
    return(-1);
  }

# ifdef DEBUG
  printf("Deleting file %s\n",file);
# endif

  switch ( isdeletable(file) ) {
    case 2:
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": could not find file ");
        emit(ofile,file);
        emit(ofile,"\n");
      }
      result = -1;
      break;

    case 1:
      if ( iflag ) {
        buf[0] = '\0';

        emit(ofile,commandname);
        emit(ofile,": remove ");
        emit(ofile,file);
        emit(ofile,"? ");

        Read(ifile,buf,BUFSIZE);

        if ( buf[0] != 'y' && buf[0] != 'Y' ) {
          result = -1;
          break;
        }
      }

      SetProtection(file,0);

      if ( !DeleteFile(file) ) {
        if ( !fflag ) {
          emit(ofile,commandname);
          emit(ofile,": could not remove ");
          emit(ofile,file);
          emit(ofile,"\n");
        }
        result = -1;
      } else {
        result = 1;
      }
      break;

    case 0:
      if ( !fflag ) {
        buf[0] = '\0';

        emit(ofile,commandname);
        emit(ofile,": overide delete protection for file ");
        emit(ofile,file);
        emit(ofile,"? ");

        Read(ifile,buf,BUFSIZE);

        if ( buf[0] == 'y' || buf[0] == 'Y' ) {

          SetProtection(file,0);

          if ( !DeleteFile(file) ) {
            if ( !fflag ) {
              emit(ofile,commandname);
              emit(ofile,": could not remove ");
              emit(ofile,file);
              emit(ofile,"\n");
            }
            result = -1;
          } else {
            result = 1;
          }
        } else {
          result = -1;
        }
      } else {

        SetProtection(file,0);

        if ( !DeleteFile(file) ) {
          if ( !fflag ) {
            emit(ofile,commandname);
            emit(ofile,": could not remove ");
            emit(ofile,file);
            emit(ofile,"\n");
          }
          result = -1;
        } else {
          result = 1;
        }
      }
      break;
  }

  freebuf(buf);
  return(result);
}


/* recursively remove a directory or a remove a file */
int rm_dir(name,fflag,iflag)
char *name;
int fflag, iflag;
{
   register struct FileLock *lock, *cwd;
   register struct FileInfoBlock *fib;
   register char *buf;
   register int result = 1;

# ifdef DEBUG
  printf("Recursively deleting directory %s\n",name);
# endif

   buf = newbuf();
   fib = (struct FileInfoBlock *)AllocMem(FIBSIZE,MEMF_CLEAR);

   if (lock = Lock(name, ACCESS_READ)) {

      cwd = CurrentDir(lock);

      if (Examine(lock, fib)) {

         buf[0] = '\0';

         while (result && ExNext(lock, fib)) {

            if ( fib->fib_DirEntryType > 0 )
               result = rm_dir(fib->fib_FileName,fflag,iflag);

            if (buf[0]) {
               rm_file(buf,fflag,iflag);
            }

            strcpy(buf, fib->fib_FileName);
         }

         if ( buf[0] ) {
            rm_file(buf,fflag,iflag);
         }
      }

      UnLock(CurrentDir(cwd));

   } else {
     if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": could not get a lock on ");
        emit(ofile,name);
        emit(ofile,"\n");
      }
      result = -1;
   }

   FreeMem(fib, FIBSIZE);
   freebuf(buf);

   return(result);
}

rm(name,fflag,iflag)
char *name;
int fflag,iflag;
{
  int result;
  struct FileInfoBlock *fib;

  switch ( isdir(name,&fib) ) {
    case 0:
      result = rm_file(name,fflag,iflag);
      break;

    case 1:
      if ( rflag ) {
        /* recursively delete the directory */
        result = rm_dir(name,fflag,iflag);
        if ( result ) {
          rm_file(name,fflag,iflag);
        }
      } else if ( dflag ) {

        /* only deletes directory if it is empty, as in AmigaDOS Delete */
        result = rm_file(name,fflag,iflag);

      } else {
        if ( !fflag ) {
          emit(ofile,commandname);
          emit(ofile,": ");
          emit(ofile,name);
          emit(ofile," is a directory (not removed)\n");
        }
        result = -1;
      }
      break;

    case 2:
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": could not access file ");
        emit(ofile,name);
        emit(ofile,"\n");
      }
      break;


    case 3:
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": could not examine file ");
        emit(ofile,name);
        emit(ofile,"\n");
      }
      break;
  }

  if ( fib )
    freefib(fib);

  return(result);
}


/* mv a file or directory _to_a_directory_ */
int mv(src,dst)
char *src, *dst;
{
  register int result = 0;
  struct FileInfoBlock *srcfib;

  switch ( isdir(src,&srcfib) ) {

    /* source is a file */
    case 0:
      result = mvfile(src,srcfib,dst);
      break;

    /* source is a directory */
    case 1:
      result = mvdir(src,srcfib,dst);
      break;

    /* no access to source */
    case 2:
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": could not access file ");
        emit(ofile,src);
        emit(ofile,"\n");
      }
      result = 0;
      break;

    /* not able to examine source */
    case 3:
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": could not examine file ");
        emit(ofile,src);
        emit(ofile,"\n");
      }
      result = 0;
      break;
  }

  freefib(srcfib);
  return(result);
}

/* mv to a destination dir _from_ a directory source */
int mvdir(src,srcfib,dst)
char *src, *dst;
struct FileInfoBlock *srcfib;
{
  register int result = 1, temp, onsame;
  struct FileInfoBlock *dstfib;

  if ( (onsame = samedev(src,dst)) == -1 )
    return(0);

  temp = isdir(dst,&dstfib);

  /* if they are on the same device, treat the src dir as a file */
  if ( onsame && mvflag ) {

    /* if the destination dir doesn't exist, then move onto it... */
    if ( temp == 2 ) {

      result = mv2f(src,srcfib,dst,NULL);
    } else {

      /* ...else move into it */
      result = mv2d(src,srcfib,dst);
    }

  } else if ( mvflag || (cpflag && rflag) ) {

    /* ugh. We have to copy the source dir to the destination dir */

    register int success, len;
    register char c;
    register struct FileLock *oldlock, *newlock, *dstlock;
    char *buf = newbuf();
    char *dstbuf = newbuf();
    register struct FileInfoBlock *fib = newfib();

    if ( !fib || !buf || !dstbuf ) {
      result = -1;
    } else {

      dstbuf[0] = '\0';

      strcat(dstbuf,dst);

      if ( temp != 2 ) {
        /*
         * build the name of the destination from the directory name and
         * later, the file name
         */

        len = strlen(dstbuf);

        /*
         * only append a slash if the file name is not the current directory,
         * (ie. "") the last character is not null, and the last character
         * is neither of ':' or '/'. This part is so much easier under Unix
         * path naming conventions, but hey, Amigoids gotta be different!
         */
        if ( len && (c = dstbuf[len - 1] ) && c != ':' && c != '/' )
          strcat(dstbuf,"/");

        /*
         * add only the file name onto the destination directory to
         * build the name of the file we want to move to
         */
        strcat(dstbuf,basename(src));
      }

      /* see if the directory exists */
      if ( !(dstlock = Lock(dstbuf,ACCESS_READ)) ) {
        struct DateStamp ds;

        /* if no lock, try creating it */

        if ( iflag ) {
          buf[0] = '\0';

          emit(ofile,commandname);
          emit(ofile,": create directory ");
          emit(ofile,dstbuf);
          emit(ofile,"? ");

          Read(ifile,buf,BUFSIZE);

          if ( buf[0] != 'y' && buf[0] != 'Y' ) {
            freefib(fib);
            freefib(dstfib);

            return(0);
          }
        }

        if ( !(dstlock = CreateDir(dstbuf)) ) {
          if ( !fflag ) {
            emit(ofile,commandname);
            emit(ofile,": unable to create directory ");
            emit(ofile,dstbuf);
            emit(ofile,"\n");
          }
          return(0);
        }
#       ifdef DEBUG
        printf("Created directory %s\n",dstbuf);
#       endif

        setdate(&srcfib->fib_Date,dstbuf);

      }

      UnLock(dstlock);

      /*
       * get a lock on the source directory, since we have to copy it
       * recursively
       */
      newlock = Lock(src,ACCESS_READ);

      /* Take a look at the directory */
      success = Examine(newlock,fib);

      /*
       * while the examination of the source directory worked and the
       * last move worked...
       */
      while ( ExNext(newlock,fib) && result > 0 ) {

        /* build the source file name */
        buf[0] = '\0';

        strcat(buf,src);

        len = strlen(buf);

        if ( len && (c = buf[len - 1] ) && c != ':' && c != '/' )
          strcat(buf,"/");

        strcat(buf,&fib->fib_FileName[0]);

        result = mv(buf,dstbuf);
      }

      UnLock(newlock);
    }

    freefib(fib);
    freebuf(buf);
    freebuf(dstbuf);

  } else {
    if ( !fflag ) {
      emit(ofile,commandname);
      emit(ofile,": ");
      emit(ofile,src);
      emit(ofile," is a directory (not copied)\n");
    }
  }

  freefib(dstfib);

  return(result);
}

/* mv to a destination file or directory _from_ a file */
int mvfile(src,srcfib,dst)
char *src, *dst;
struct FileInfoBlock *srcfib;
{
  register int result = 0;
  struct FileInfoBlock *dstfib;
  char *buf = newbuf();

  if ( !buf ) {
    return(-1);
  }

  switch ( isdir(dst,&dstfib) ) {

    /* destination is a file */
    case 0:
      result = mv2f(src,srcfib,dst,dstfib);
      break;

    /* destination is a directory */
    case 1:
      result = mv2d(src,srcfib,dst);
      break;

    case 2:
      if ( iflag ) {
        buf[0] = '\0';

        emit(ofile,commandname);
        emit(ofile,": create directory ");
        emit(ofile,dst);
        emit(ofile,"? ");

        Read(ifile,buf,BUFSIZE);

        if ( buf[0] != 'y' && buf[0] != 'Y' ) {
          result = 0;
          break;
        }
      }
#     ifdef DEBUG
      printf("Creating dir %s\n",dst);
#     endif

      lock = CreateDir(dst);

      if ( !lock ) {
        if ( !fflag ) {
          emit(ofile,commandname);
          emit(ofile,": could not create destination directory ");
          emit(ofile,dst);
          emit(ofile,"\n");
        }
        result = 0;
      } else {

        UnLock(lock);
        result = mv2d(src,srcfib,dst);
      }

      break;

    case 3:
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": could not examine directory ");
        emit(ofile,dst);
        emit(ofile,"\n");
      }
      result = 0;
      break;
  }

  freefib(dstfib);
  freebuf(buf);
  return(result);
}

/* mv a file _to_ a directory dst */
int mv2d(src,srcfib,dst)
char *src, *dst;
struct FileInfoBlock *srcfib;
{
  char *buf = newbuf();
  register char c;
  register int result = 0;
  register int len = strlen(dst);
  struct FileInfoBlock *dstfib;

  if ( !buf ) {
    return(-1);
  }

  buf[0] = '\0';

  /* build the file name in the destination directory */
  strcat(buf,dst);

  if ( len && (c = buf[len - 1] ) && c != ':' && c != '/' )
    strcat(buf,"/");

  strcat(buf,basename(src));

  switch ( isdir(buf,&dstfib) ) {

    case 0:
      result = mv2f(src,srcfib,buf,dstfib);
      break;

    case 1:
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": could not move file ");
        emit(ofile,src);
        emit(ofile," onto directory ");
        emit(ofile,buf);
        emit(ofile,"\n");
      }
      result = 0;
      break;

    case 2:
      result = mv2f(src,srcfib,buf,NULL);
      break;

    case 3:
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": something is awry with file ");
        emit(ofile,buf);
        emit(ofile,"\n");
      }
      result = 0;
      break;
  }

  freefib(dstfib);
  freebuf(buf);
  return(result);
}

/* mv a file _to_ a file */
int mv2f(src,srcfib,dst,dstfib)
char *src, *dst;
struct FileInfoBlock *srcfib, *dstfib;
{
  register int result = 0, cleanup = 0;
  char *inputbuf;
  register int onsame;

  /* are they on the same volume? */
  if ( (onsame = samedev(src,dst)) == -1) {
    freebuf(inputbuf);
    return(0);
  }

  if ( !(inputbuf = newbuf()) ) {
    return(-1);
  }

  if ( dstfib ) {
    if ( onsame && srcfib->fib_DiskKey == dstfib->fib_DiskKey ) {

      if ( !mvflag ) {
        if ( !fflag ) {
          emit(ofile,commandname);
          emit(ofile,": cannot copy ");
          emit(ofile,src);
          emit(ofile," to itself!\n");
        }
        freebuf(inputbuf);
        return(1);
      }

    } else {

      if ( iflag ) {
        inputbuf[0] = '\0';

        emit(ofile,commandname);
        emit(ofile,": overwrite ");
        emit(ofile,dst);
        emit(ofile,"? ");

        Read(ifile,inputbuf,BUFSIZE);

        if ( inputbuf[0] != 'y' && inputbuf[0] != 'Y' ) {
          freebuf(inputbuf);
          return(1);
        }

        /* if remove unsuccessful, return an error */
        if ( rm_file(dst,1,0) == -1 ) {
          freebuf(inputbuf);
          return(0);
        }

      } else {

        /* if remove unsuccessful, return an error */
        if ( rm_file(dst,fflag,0) == -1 ) {
          freebuf(inputbuf);
          return(0);
        }

      }
    }
  }

  result = (onsame && mvflag) ? move(src,dst) : cp(src,srcfib,dst);

  freebuf(inputbuf);

  return(result);
}

/* copy a source file to a destination file */
int cp(src,srcfib,dst)
char *src, *dst;
struct FileInfoBlock *srcfib;
{
  register int num;
  register long size;
  register char *buf;
  struct FileHandle *srchandle, *dsthandle;

# ifdef DEBUG
  printf("copy file %s to %s\n",src,dst);
# endif

  size = mem() - 4;

  if ( size < 128 ) {
    if ( !fflag ) {
      emit(ofile,commandname);
      emit(ofile,": Out of memory!\n");
    }
    return(-1);
  }

  /*
   * allocate a maximum of "size" continous bytes, else source file
   * size worth of bytes. This allows one read and one write if there
   * is enough memory for it. The 8 is added in case there is a
   * zero size file we want to copy and we still want to allocate
   * a buffer anyways.
   */
  size = ( srcfib->fib_Size + 8 > size ) ? size : srcfib->fib_Size + 8;

# ifdef DEBUG
  printf("Allocating %d bytes for the copy\n",size);
# endif

  if ( !(buf = AllocMem(size,MEMF_CLEAR|MEMF_PUBLIC)) ) {
    /* didn't work, try again with a smaller size */
    size = size / 4;

    if ( !(buf = AllocMem(size,MEMF_CLEAR|MEMF_PUBLIC)) ) {
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": Out of memory!\n");
      }
      return(-1);
    }
  }

  /* if we can't open the file, return an error */
  if ( !(dsthandle = Open(dst,MODE_NEWFILE)) ) {
    if ( !fflag ) {
      emit(ofile,commandname);
      emit(ofile,": cannot Open file ");
      emit(ofile,dst);
      emit(ofile,"\n");
    }
    FreeMem(buf,size);

    /* returning 0 indicates an error to the above routines */
    return(0);
  }

  if ( !(srchandle = Open(src,MODE_OLDFILE)) ) {
    if ( !fflag ) {
      emit(ofile,commandname);
      emit(ofile,": cannot Open file ");
      emit(ofile,src);
      emit(ofile,"\n");
    }
    Close(dsthandle);
    FreeMem(buf,size);
    return(0);
  }

  /* copy bytes back and forth. Read returns -1 for a read error */
  while ( (num = Read(srchandle,buf,size)) > 0 ) {
    if ( Write(dsthandle,buf,num) == -1 ) {
      if ( !fflag ) {
        emit(ofile,commandname);
        emit(ofile,": error writing to file ");
        emit(ofile,dst);
        emit(ofile,". Disk may be full.\n");
      }
      Close(srchandle);
      Close(dsthandle);
      FreeMem(buf,size);
      return(-1);
    }
  }

  Close(srchandle);
  Close(dsthandle);

  if ( num == -1 ) {
    if ( !fflag ) {
      emit(ofile,commandname);
      emit(ofile,": error reading from file ");
      emit(ofile,src);
      emit(ofile,"\n");
    }
    FreeMem(buf,size);
    return(0);
  }

  if ( nflag ) {
    /*
     * copy the protection bits, the comment and the datestamp to the
     * new file if the n flag is set (ie. -n was NOT specified in the
     * commandline)
     */
    SetProtection(dst,srcfib->fib_Protection);

    SetComment(dst,srcfib->fib_Comment);

    setdate(&srcfib->fib_Date,dst);

    FreeMem(buf,size);
  }

  /* return 2 for successful copy (not 1, because 1 is a move) */
  return(2);
}

/* rename a source file to a destination file */
int move(src,dst)
char *src, *dst;
{
  register int foo;

# ifdef DEBUG
  printf("move file %s to %s\n",src,dst);
# endif

  /* return 1 for successful move */
  foo =  Rename(src,dst) ? 1 : 0;

  if ( !foo && !fflag ) {
    emit(ofile,commandname);
    emit(ofile,": could not move file ");
    emit(ofile,src);
    emit(ofile," to ");
    emit(ofile,dst);
    emit(ofile,"\n");
  }

  return(foo);
}

int setdate(date,name)
struct DateStamp *date;
char *name;
{
  register UBYTE *ptr;
  struct MsgPort *task;
  register struct FileLock *lock, *parent;
  register struct FileInfoBlock *fib;
  int stat, result, dos_packet();

  if ( !(task = DeviceProc(name)) ) {
    return(0);
  }

  if ( !(lock = Lock(name,ACCESS_READ)) ) {
    return(0);
  }

  fib = newfib();

  /*
   * thanks to Matt Dillon for this routine. I really don't know how
   * it does what it does, but it works, so no comments here.
   */

  /*
   * Well, I mean other than these comments...
   */

  /*
   * Oh, forget it.
   */
  if ( fib ) {
    if ( Examine(lock,fib) ) {

      parent = ParentDir(lock);

      UnLock(lock);

      ptr = (UBYTE *) AllocMem(256L,MEMF_CLEAR|MEMF_PUBLIC);

      strcpy((ptr + 1),fib->fib_FileName);

      *ptr = (UBYTE) strlen(fib->fib_FileName);

      result = dos_packet(task,34L,NULL,parent,(ULONG)&ptr[0] >> 2L,date);

      FreeMem(ptr,256L);

      UnLock(parent);
    }

    freefib(fib);

  } else {
    UnLock(lock);
  }
}

/* is a file delete protected? */
int isdeletable(file)
char *file;
{
  register struct FileLock *lock;
  register struct FileInfoBlock *fib;
  register int result = 0;

  if ( !(lock = Lock(file,ACCESS_READ)) ) {
    return(0);
  }

  /* allocate a word aligned memory block to hold our info */
  fib = newfib();

  if ( fib ) {
    if ( Examine(lock,fib) ) {

      result = fib->fib_Protection;
      freefib(fib);

    } else {

      freefib(fib);
      return(2);
    }

  }


  UnLock(lock);

  return( !(result & FIBF_DELETE) );
}


