/* Program to move files and/or subdirectories - like mv on unix
** Written by Peter Wu July, 86 @ Faculty Support Center @ UW-Madison
** Compile with IBM C version 1.00 (or Microsoft C 3.00).
** Link with fstat, mv, normal, sector, absdr, func32h, break, putn /stack:5000
**
** This is module "front.c", the front end of mv. It looks at the sources
** and destination supplied by the user and determines whether to call
** mvdir to move/rename sub-directories or call rename to move/rename files.
*/

/* programmer's notes:
-- won't work on network disk
--
-- things to work on:
--
-- be interactive:
--   ask if user wants to replace existing file/directory
--   verbose mode where user must confirm each move
-- better error handling
-- make it work on network disks
-- detect and report write-protected disk if possible
-- allow /f option on destination?
--
-- bugs:
+--------------------------------+
|  c:				 |
|  subst p: /user/peter/mv/test  |
|  mv /usr/peter/mv/test .	 |
+--------------------------------+
-- The above sequence will prevent mv from removing the source directory after
-- the content is moved to the destination directory. I don't know any way to
-- detect this BEFORE moving the content. So my solution is to print a message
-- telling the user to remove the source directory (now empty) by hand after
-- he got rid of the 'subst drive'.
*/
#define LINT_ARGS

#define UNK_TYP 0

#define A_INT 0x80  /* interactive file attribute */

#define LASTDRV 26  /* drive# larger than this is network share disk */
#define NULL (char *) 0
#define PLEN 200	/* max. path length; hope it's long enough */

#include "dta.h"
#include "date.h"
#include <conio.h>
#include <string.h>
#include <stdlib.h>

char lastc(char *);
char getkey(char *);

int brk_st;  /* original break status: 0=break off, 1=break on */

  /* When break is on, user can break the program when any DOS function is
  ** called. When break is off, user can only break the program when a DOS
  ** I/O function is called (if the user presses break before an I/O function,
  ** DOS will remember the break but let the program keep running until the
  ** program calls an IO function). It is undesirable to have break on
  ** all the time since for instance if the user breaks the program after I
  ** wrote one sector to disk (I need to write three sectors per sub-directory
  ** moved), he'll end up with an inconsistent directory. The solution is to
  ** make sure break is off when I'm doing disk writes, and restore it
  ** to its orginal status when I'm done writing the disk.
  ** If the user really wants to mess up his disk, he can still do it with
  ** ctrl-alt-del or switching the power off in the middle of running mv.
  */

main(argc,argv)
int argc;
char *argv[];
{
  unsigned int status, dtype, stype, status2, smask, dmask, i, drv, plimit,
	       doit;
  union dtbuf mydta1, mydta2, tmpdta;
  char fsource[PLEN], fdest[PLEN], *source, *dest, *tmp, fdest2[PLEN];

  if (argc < 3) {  /* print help */
    putn(

"Usage: MV <source1> <source2> .. <sourceN> <dest>\n\15",
"  MV renames/moves the source files/directories to the destination.\n\15",
"  Wildcards ok. Specify source type with /d, /f, /h, and /i.\n\15",
"  /d=sub-directories, /f=files, /h=search hidden, /i=interactive.\n\15",
"    \"*.*\\.\" or \"*.*/d\" specifies all sub-directories only\n\15",
"    \"*.*/f\" specifies all visible files only\n\15",
"    \"*.*/hf\" specifies all visible and hidden files\n\15",
"    \"*.*/fi\" will prompt you (move or not) for every file found\n\15",
"  Version 1.20 made ", date, ". For DOS 2.xx and 3.xx.\n\n\15",
"Please send comments/bug reports to one of the following addresses:\n\15",
"  Arpanet: pwu@unix.macc.wisc.edu\n\15",
"  Bitnet: WU at WISVMACC\n\15",
"  CompuServe: 76377,1332\n\15",
"  UUCP: {akgua|ihnp4|seismo|harvard|allegra|ucbvax}!uwvax!uwmacc!pwu\n\15",
NULL);

    exit(0);
  }

  /* test DOS version */
  switch (_osmajor) {
    case 2:  /* dos 2.xx */
      break;
    case 3:  /* dos 3.xx */
      if (_osminor <= 20) {  /* make sure it's no later than version 3.20 */
	break;
      }
    default:
      cputs("need DOS between version 2.00 and 3.20\n\15");
      exit(1);
  }

  plimit = PLEN - 80;  /* limit on user supplied path name */

  /* process destination first */
  dest = argv[argc-1];
  if (strlen(dest) > plimit) {
    cputs("destination path too long\n\15");
    exit(1);
  }

  strcpy(fdest,dest);
  dtype = normal(fdest);  /* normalize destination path */

  if (dtype == 0) {  /* no specified type for destination */
    dmask = A_FIL | A_DIR;
  } else {
    dmask = dtype;
  }

  status2 = ffmf(fdest, A_MASK, &mydta2);  /* find info on dest */
#ifdef debug
  printf("fdest is %s, dmask=%d, status2 = %d\n", fdest, dmask, status2);
#endif

  if (!status2) {  /* if destination exists  */
#ifdef debug
    printf("destination exists and has attr: %x\n", mydta2.dos.attr);
#endif
    /* check if destination is ambigious here */
    if (lastc(fdest) != '\\') { /* root would cause error, so don't check it */
      tmpdta = mydta2;	/* don't disturb mydta2, we need it later */
      status = fnmf(&tmpdta);
      if (!status) {  /* ha, there's more than one destination! */
	cputs("destination is ambigious\n\15");
	exit(1);
      }
    }

    /* if destination is an existing file, report error */
    if ((mydta2.dos.attr & A_DIR) == 0) {
      cputs("destination is an existing file!\n\15");
      exit(1);
    }

    /* fix fdest so that a destination of "*\." will have the expanded name
    ** by removing the * and appending the directory name found by ffmf.
    ** This will cause an error if fdest is the root of a 'subst' disk,
    ** so we must make sure fdest is not a root.
    */
    if (lastc(fdest) != '\\') {  /* if not root then */
      tmp = strrchr(fdest,'\\');  /* find last '\' */
      if (tmp == NULL) {  /* no '\' ????!!!! */
	cputs("error after strrchr: cannot find \\\n\15");
	error("front",0);
      }
      strcpy(tmp+1, mydta2.dos.fn);  /* dest with wild cards expanded */
    }

  } else {  /* destination not found */

#ifdef debug
    printf("destination not exists\n");
#endif

    if (dtype == A_DIR) {  /* specified directory not exist */
      cputs("can't find destination directory\n\15");
      exit(1);
    } else {  /* destination type not specified or of file type */
      /* check: source better be unambigious */
      if (argc > 3) {
	cputs("can't rename more than one source\n\15");
	exit(1);
      }
      /* destination is not found, let's lookup destination's parent.
      ** This has to exist (e.g. if C:\X\Y is destination and doesn't
      ** exist, C:\X should still exists). This lookup also allows us
      ** to compare the drive number between the destination and the
      ** source to detect cross device move.
      */
      strcpy(fdest2, fdest);
      chopath(fdest2);	/* remove last portion of path */
      status = ffmf(fdest2, A_MASK, &mydta2);
      if (status) {  /* if destination's parent doesn't exist */
	putn("path \"", fdest2, "\" not exist!\n\15", 0);
	exit(1);
      } else if ((mydta2.dos.attr & A_DIR) == 0) { /* not a directory */
	putn("\"", fdest2, "\" is not a directory!\n\15", 0);
	exit(1);
      }
    }
  }

  brk_st = bstat();  /* get current break status (on or off) */

  /***********************************************************/
  /* now process SOURCE **************************************/
  /***********************************************************/
  for (i=1; i < argc - 1; i++) {  /* for all source specification */
    source = argv[i];
    if (strlen(source) > plimit) {  /* rare user error but just in case */
      cputs("source path too long! skipped\n\15");
      continue;  /* next argument */
    }

    strcpy(fsource,source);
    stype = extype(fsource);  /* extract type */
    stype |= normal(fsource);  /* normalize source path */

#ifdef debug
    printf("normalized source path: %s\n", fsource);
#endif

    if ((stype & (A_DIR | A_FIL)) == 0) {  /* no specified type, use default */
      smask = stype | A_FIL | A_DIR;  /* look for file or directory */
    } else {
      smask = stype;
    }

    status = ffmf(fsource, smask, &mydta1);  /* find info on source */
    if (status) {
      putn(source, " not found\n\15", 0);
      continue;
    }

    /* see if source is on a network disk */
    if ((mydta1.dos.drv_no < 0) || (mydta1.dos.drv_no >= LASTDRV)) {
      cputs("sorry, mv doesn't work on network disks\n\15");
      exit(1);
    }

    /* is source and destination on same drive? */
    if (mydta1.dos.drv_no != mydta2.dos.drv_no) {
      cputs("source and destination must be on same physical drive\n\15");
      exit(1);
    }

    /* check: if destination does not exist, source better be unique */
    if (status2) {  /* destination does not exist */
      if (lastc(fsource) != '\\') { /* don't call fnmf on root */
	tmpdta = mydta1;  /* do not change mydta1, mess with a copy */
	status = fnmf(&tmpdta);  /* see if there's more than one source */
	if (!status) {	/* source is ambigious */
	  cputs("can't rename more than one source\n\15");
	  exit(1);
	}
      }
    }

    do {  /* repeat for all source wildcard */

#ifdef debug
      printf("source found, fn: %s\n", mydta1.dos.fn);
      printf("found attribute is %2xh\n", mydta1.dos.attr);
#endif

      /* form source name with wild cards expanded */
      chopath(fsource);  /* chop off wild cards */
      catpath(fsource, mydta1.dos.fn);	/* append expanded name */

      if (status2) {  /* destination not exist */

	if (stype & A_INT) {  /* interactive mode */
	  doit = prompt(fsource,fdest);
	} else {  /* in batch mode, always do it */
	  putn(fsource, " ÄÄ ", fdest, "  ", 0);
	  doit = 1;  /* do it! */
	}

	if (doit) {

	  if (mydta1.dos.attr & A_DIR) {  /* rename or move directory */

	    status =  mvdir(mydta1, fsource, fdest);
	    if (!status) {
	      cputs("OK.\n\15");
	    }

	  } else {  /* rename or move file */

	    /* don't know what rename does when user presses break key,
	    ** so set break off to be safe
	    */
	    bset(0);  /* turn break off (disable break key) */
	    status = rename(fdest,fsource);  /* use fdest or dest? */
	    bset(brk_st);  /* restore break status to orginal value */

	    if (status) {
	      putn("can't\n\15",
		   "invalid file name/disk write protected\n\15", 0);
	    } else {
	      cputs("ok\n\15");
	    }
	    /* fall thru to fnmf */
	  }

	} else {  /* doit = 0, because the user press 'n' */

	  cputs("not moved\15\n");

	}

      } else {	/* now handle the case when destination do exists */

#ifdef debug
	printf("dest exists, let's see...\n");
#endif

	if (mydta2.dos.attr & A_DIR) {	/* destination is a directory */
#ifdef debug
	  printf("before strcat fdest is %s\n", fdest);
	  printf("now cat with %13.13s\n", mydta1.dos.fn);
#endif

	  /* create destination + tail of source in fdest2
	  ** e.g.      source = \user\peter\cat
	  **	  destination = \junk\haha
	  **	       fdest2 = \junk\haha\cat
	  */
	  strcpy(fdest2, fdest);  /* make a work copy */
	  catpath(fdest2, mydta1.dos.fn);

	  if (stype & A_INT) {	/* interactive mode */
	    doit = prompt(fsource,fdest2);
	  } else {  /* in batch mode, always do it */
	    putn(fsource, " ÄÄ ", fdest2, "  ", 0);
	    doit = 1;  /* do it! */
	  }

	  if (doit) {

#ifdef debug
	    printf("dest+tail of source: %s\n", fdest2);
#endif

	    if (mydta1.dos.attr & A_DIR) {  /* source is also a directory */

	      status = mvdir(mydta1, fsource, fdest2);
	      if (!status) {
		cputs("OK.\n\15");
	      }
	      /* fall down to fnmf */

	    } else {  /* move a file into a directory */

	      bset(0);	/* turn break off (disable break key) */
	      status = rename(fdest2, fsource);
	      bset(brk_st);  /* set break status to orginal value */

	      if (status) {
		cputs("can't; file exists already/disk write-protected\n\15");
	      } else {
		cputs("ok.\n\15");
	      }
	    }
	  } else {  /* doit = 0 */
	    cputs("not moved\15\n");
	  }

	} else {  /* dest is an existing file! */

	  putn(fsource, " ÄÄ ", fdest,
	       "   can't rename: file exists already\n\15", 0);

	}

      }  /* if dest exist or not */

      status = fnmf(&mydta1);  /* find info on next matching source */

    } while (status == 0);  /* while there are matching files */
  }  /* for different sources */

  exit(0);  /* no error */
}

prompt(s,d)  /* return 1 for 'y'; 0 for 'n' */
char *s, *d; /* part of the prompt */
{
  char c;

  do {
    putn(s, " ÄÄ ", d, " (y/n/q; ?=help): ", 0);
    c = getkey("yYnNqQ?");
    putch(' ');
    switch (c) {

      case '?':
	putn("\15\n",
	     "y - yes, move it\15\n",
	     "n - no, skip it\15\n",
	     "q - quit to DOS\15\n",
	     "? - this help message\15\n\n",
	     0);
	break;

      case 'y':
      case 'Y':
	return 1;

      case 'n':
      case 'N':
	return 0;

      case 'q':
      case 'Q':
	cputs("Quit.\15\n");
	exit(0);

      default:
	cputs("\15\nHow do I get here?\15\n");
	error("prompt",0);

    }  /* end switch */
  } while (1);
}

char getkey(valid)  /* wait for valid key and echo it */
char *valid;
{
  char c;
  int i;

  do {
    c = getch();
    i = index(valid,c);
    if (i > -1) {
      putch(c);  /* echo valid key */
      return c;
    } else {  /* beep at invalid key */
      putch(7);
    }
  } while (1);
}
