/* move subdirectories
** Written by Peter Wu; July 86.
** compile with cc mv /ze
*/
#define LINT_ARGS

#define ALLOC 26	/* starting cluster number in directory entry */
#define PLEN 200	/* max path len */

#include <dos.h>
#include "dta.h"
#include "peek.h"
#include <conio.h>

unsigned char func32h();
unsigned short findir();
unsigned short clus2sec();
char *readsec();
char lastc(char *);

/* external var */
extern int
  num_sec,  /* number of sectors buffered */
  brk_st;  /* orginal break status */

/* source & dest must be normalized
** mydta1 contains dta of source
*/
mvdir(mydta1,source,dest)  /* move subdirectory */
union dtbuf mydta1;
char *source, *dest;
{
  unsigned short sector1, sector2, offset1, offset2, clus1, clus2,
		d1, d2, status, *p1, *p2, ds1, ds2, bps, parent, drv;
  char *secbuf1, *secbuf2, *secbuf3, cpath[PLEN];
  union dtbuf mydta2;

  /* make sure source is not a root directory */
  if (lastc(source) == '\\') {
    cputs("move root directory? You're joking!\n\015");
    exit(1);
  }

  /* make sure source is not predesessor of destination */
  if (apreb(source, dest)) {
    /* this will create a directory loop if allowed to go on or
    ** it's a redundant rename to itself (e.g. mv \ha\. \)
    */
    cputs("not moved to avoid loop or redundancy\n\015");
    return -1;
  }

  /* see if source is a predesessor of current directory */
  drv = toupper(source[0]) - '@';
  current(drv,cpath);
  if ( apreb(source,cpath) ) {
    cputs("not moved to preserve current directory\n\015");
    return -2;
  }

  /* there should be a way to test if source is a predesessor of
  ** a 'subst' drive's current directory
  */

  bset(0);  /* do not allow user to break during this portion */

  status = mkdir(dest);
  if (status) {
    cputs("can't; file exists already/disk write protected\n\015");
    exit(1);
  }

  status = ffmf(dest, A_DIR, &mydta2);
  if (status) {
    cputs("error on ffmf after mkdir\n\015");
    error("mvdir", 0);
  }

  num_sec = 0;	/* clear sector buffers */

  sector1 = findir(mydta1, &offset1, &d1, &secbuf1);
  if (sector1 < 0) {
    cputs("cannot find source directory\n\015");
    error("mvdir", 0);
  }

  sector2 = findir(mydta2, &offset2, &d2, &secbuf2);
  if (sector2 < 0) {
    cputs("cannot get info on destination directory\n\015");
    error("mvdir", 0);
  }

  if (d1 != d2) {  /* this should have been detected earlier */
    cputs("source and destination has to be on the same drive\n\015");
    exit(1);
  }

  /* now switch the starting cluster of the two directories */
  p1 =	(unsigned short *) (secbuf1 + offset1 + ALLOC);
  p2 =	(unsigned short *) (secbuf2 + offset2 + ALLOC);
  clus1 = *p1;
  clus2 = *p2;
#ifdef debug
  printf("clus1=%4x  clus2=%4x\n", clus1, clus2);
#endif
  *p1 = clus2;
  *p2 = clus1;

  status = flirt(d1-1, sector1);  /* mark sector dirty */
  if (status) {
    cputs("error in flirt\n\015");
    error("mvdir", 0);
  }

  status = flirt(d1-1, sector2);  /* mark sectors as dirty */
  if (status) {
    cputs("error in flirt\n\015");
    error("mvdir", 0);
  }

  /* now we must find the cluster# of the parent directory of the
  ** destination directory (slot number two in clus2) so we can put
  ** it in the source directory (which will become the new destination
  ** directory.
  */
  ds1 = clus2sec(d1, clus1, &bps);  /* sector# of cluster 1 */
  ds2 = clus2sec(d1, clus2, &bps);  /* sector# of cluster 2 */
  secbuf3 = readsec(d1-1,ds2);
  if (secbuf3 == (char *) 0) {
    cputs("error in calling readsec\n\015");
    error("mvdir", 0);
  }

  parent = * (unsigned short *) (secbuf3 + 32 + ALLOC);
#ifdef debug
  printf("parent is %4x\n", parent );
#endif

  /* now write this into source dir */

  secbuf3 = readsec(d1-1,ds1);
  if (secbuf3 == (char *) 0) {
    cputs("error in calling readsec\n\015");
    error("mvdir", 0);
  }

  *(unsigned short *) (secbuf3 + 32 + ALLOC) = parent;
  status = flirt(d1-1,ds1);
  if (status) {
    cputs("error in flirt\n\015");
    error("mvdir", 0);
  }

  /* now write back all three (or less) modified sectors */
  status = writesec(d1-1,ds1);	/* the cluster containing parent pointer */
  if (status) {  /* what could cause this to happen? */
    cputs("error in writing first modified sector\n\015");
    error("mvdir", 1);
  }

  status = writesec(d1-1,sector2);
  if (status) {  /* it's impossible for this to happen */
    cputs("error in writing second modified sector\n\015");
    error("mvdir", 1);
  }

  status = writesec(d1-1,sector1);
  if (status) {  /* this is also impossible */
    cputs("error in writing third modified sector\n\015");
    error("mvdir", 1);
  }

  bdos(0xd,0,0);  /* reset disk - neccessary for rmdir to work since DOS'
		  ** buffer now contains invalid information (it didn't
		  ** know that the disk was modified).
		  */
  status = rmdir(source);  /* this dir should be empty now */
  if (status) {
    putn(
"Oops!\n\015",
"cannot remove old directory \"", source, "\"\n\015",
"Maybe one of your `subst' disk is using this directory. If this is\n\015",
"the case, remove the subst disk and then remove this directory.\n\015", 0);
    exit(1);
  }

  bset(brk_st);  /* restore break status */
  return 0;  /* no error */
}

unsigned short findir(mydta, offsetp, drv, secbuf)
char **secbuf;
unsigned short *offsetp;
unsigned int *drv;
union dtbuf mydta;
{
  unsigned int status, i, j, offset, sector, cluster, tmp, bps;
  union REGS inregs, outregs;
  struct SREGS segregs;
  char c, dir[13];  /* directory entry formatted like X.Y not "X       Y  " */

  if (mydta.dos.attr != A_DIR) {  /* guard against programmer's error */
    putn(mydta.dos.fn, " is not a directory\n\015",0);
    error("findir", 0);
  }

  /* now find the cluster where the searched directory is */
  cluster = mydta.dos.clusl + (mydta.dos.clush << 8);

  /* now convert the cluster number to sector number */
  sector = clus2sec(mydta.dos.drv_no, cluster, &bps);

  /* calculate directory entry offset in the sector */
  offset = ((mydta.dos.sloth << 8) + mydta.dos.slotl) * 32;

  sector += offset / bps;  /* normalize sector & offset */
  offset %= bps;

#ifdef debug
  printf("dir at sector %d\n", sector);
#endif

  *secbuf = readsec(mydta.dos.drv_no - 1, sector);
  if (*secbuf == (char *) 0) {
    cputs("error in calling readsec\n\015");
    error("findir", 0);
  }

#ifdef debug
  printf("First 11 bytes in sector: %11.11s\n", *secbuf+offset);
  printf("Fn returned by ffmf: %11.11s\n", mydta.fn);
#endif

  /* redundant check to see if we have the correct directory entry */
  /* first format the directory entry name in this form "*.*" instead of
  ** "???????????"
  */
  i=0;
  c = (*secbuf)[offset];
  while ((c != ' ') && (i < 8)) {  /* copy the first 8 characters */
    dir[i] = c;
    i++;
    c = (*secbuf)[offset+i];
  }

  j = 8;
  c = (*secbuf)[offset+j];
  if (c != ' ') {  /* sub-dir name has extension */
    /* now add '.' and copy the extension */
    dir[i] = '.';
    i++;
    while ((c != ' ') && (j < 11)) {
      dir[i] = c;
      i++;
      j++;
      c = (*secbuf)[offset + j];
    }
  }
  dir[i] = '\0';  /* terminate string */
#ifdef debug
  printf("formatted directory entry string: %s\n", dir);
#endif

  if (strcmp(mydta.dos.fn, dir) == 0) {
#ifdef debug
    printf("Directory entry found!\n");
#endif
  } else {
    cputs("cannot find directory entry\n\015");
    error("findir", 0);
  }

  /* redundant check to make sure file size of directory is zero */
  if (*(unsigned long *)(*secbuf+offset+28) != 0L) {
    cputs("found directory with size > 0\n\015");
    error("findir", 0);
  }

  *offsetp = offset;
  *drv = mydta.dos.drv_no;
  return sector;
}

unsigned short clus2sec(drv, clus_no, pbps)  /* convert cluster to sector */
unsigned short clus_no, *pbps, drv;
{
  unsigned char status;
  static unsigned short spc=0, ss, bps, tabseg, taboff;
  unsigned short sector;

  if (spc == 0) {  /* first time function is called */
    status = func32h(drv, &tabseg, &taboff);  /* See PC Tech Journal */
    if (status == 0xff) {
      cputs("func32h: invalid drive: "); putch(drv+'A'); cputs("\n\015");
      error("clus2sec", 0);
    }

    spc = peekb(tabseg,taboff+4)+1;  /* sector per cluster */
    ss =  peekw(tabseg,taboff+11);  /* starting data sector */
    bps = peekw(tabseg,taboff+2);  /* bytes per sector */

#ifdef debug
    printf("drive #: %d\n", drv);
    printf("sectors per cluster: %d\n", spc);
    printf("# allocation units: %d\n",  peekw(tabseg,taboff+13)-1 );
    printf("sector size: %d\n", bps);
    printf("starting sector: %d\n", ss);
#endif
  }
  *pbps = bps;	/* return this value */

  if (clus_no == 0) {
#ifdef debug
    printf("parent directory is root! - special case\n");
#endif
    sector = peekw(tabseg,taboff+16);  /* first sector of root directory */
  } else {
    sector = (clus_no - 2) * spc + ss;	/* see DOS Tech. Ref */
  }

  return sector;
}

/* apreb test to see if patha is equal to or is a predessesor of pathb
** This is used to test whether moving a directory would result
** in a directory loop condition and also whether the source
** directory is a predessor of the current path (can't delete
** source directory in this case, so don't move)
**
** patha and pathb must be normalized paths
** E.g.
**	   apreb("A:\DEF", "A:\DEF")    is true
**	   apreb("A:\DEF", "A:\DEF\GHI")   is also true
**	   apreb("A:\DEF", "A:\DEFG")    is false
*/
apreb(patha, pathb)
char *patha, *pathb;
{
  int i, lena, lenb;
  char c;

  lena = strlen(patha);
  lenb = strlen(pathb);
  if (lena > lenb) {  /* if patha is longer, it can't be a predessesor */
    return 0;
  }

  c = pathb[lena];  /* if patha is a predessesor, c should be '\\' or '\0' */
  if ((c != '\\') && (c != '\0')) {
    return 0;
  }

  if (strncmp(patha, pathb, lena)) {  /* not equal */
    return 0;
  }

  return 1;
}
