/*static char *SCCSID = "@(#)discon.c	6.1 90/11/18";*/
/*static char *SCCSID = "@(#)discon.c 13.7 90/03/13";*/
/***	DISCON.C - Routines to disconnect portions of the file system
 *
 *	CHKDSK
 *	Gregory A. Jones
 *	Copyright 1988 Microsoft Corporation
 *
 *	Modification history:
 *	G.A. Jones	06/03/88     Original for Pinball 1.0.
 *	P.A. Williams	06/06/89     Changed memcmp's to chk_strnicmp's.
 */

#include "ldefs.h"
#include "ltypes.h"
#include "globals.h"
#include <pr_chk.h>
#include <pr_fsys.h>


/***	chk_delete_btree - delete a BTREE downpointer from a DIRBLK
 *
 *	This function is called by validate_structure when a BTREE
 *	downpointer is invalid or points to a DIRBLK which is corrupt.
 *	The downpointer is removed by copying the following entries
 *	over it and adjusting the length of the entry which contained
 *	it and the first-free-byte offset in the DIRBLK header.
 *
 *	So that the recovery process can recover the BTREE later,
 *	the bitmaps are left as they are, with the space marked as
 *	"in use".
 *
 *	The memory image of the DIRBLK is always updated;  the disk
 *	will only be updated if a command line parameter was given
 *	to authorize it.
 *
 *	chk_delete_btree (lsn, mem, offset)
 *
 *	ENTRY		lsn   - where the DIRBLK is on disk
 *			mem   - memory image of the DIRBLK
 *			entry - pointer to the DIRENT which has the downpointer
 *
 *	CALLS		chk_no_writing
 *			write_scratch
 *
 *	EFFECTS		Deletes a directory BTREE from the file system
 *
 *	WARNINGS	Can only delete a downpointer out of a DIRBLK,
 *			not out of an FNODE.
 */
void chk_delete_btree (lsn, mem, entry)
  ULONG lsn;
  union blk *mem;
  union dp entry;
{
#ifdef TRACE
  fprintf (stderr, "chk_delete_btree (lsn=%lx, mem, entry)\n", lsn);
  fprintf (stderr, "                  entry.d->cchThisEntry=%x\n", entry.d->cchThisEntry);
  fprintf (stderr, "                  mem->d.offulFirstFree=   %lx\n", mem->d.offulFirstFree);
  fflush (stderr);
#endif

  entry.d->fFlags &= ~DF_BTP;	      /* clear the bit on this entry */
  entry.d->cchThisEntry -= sizeof (long);	/* this entry is one dword shorter */

  /*
   *  To avoid the risk that the dirblk's offulFirstFree is wrong, we'll
   *  move the rest of the DIRBLK.  The amount to move is all
   *  of the DIRBLK beyond entry.p+entry.d->cchThisEntry + 4.
   *
   */
  memmove ( entry.p+entry.d->cchThisEntry,
	    entry.p+entry.d->cchThisEntry + sizeof (long),
	    (size_t)(DIRBLK_SIZE -
	      ((entry.p - (UCHAR *)mem) + entry.d->cchThisEntry + sizeof(long))) );

  mem->d.offulFirstFree -= 4L;

  /* can we change the disk? */
  if (!chk_no_writing (CHK_DELETE_BTREE_STR, NULL))
    write_scratch (lsn, mem, SECTORS_PER_DIRBLK);   /* save the DIRBLK */
}

/***	chk_find_name - find a name in a directory BTREE
 *
 *	This function is called to look for a particular name in a
 *	subdirectory.  It is used by chk_delete_file on each element
 *	of a pathname in turn, to eventually find the DIRBLK which
 *	contains the entry to be deleted.
 *
 *	The algorithm used is similar to that used by the filesystem
 *	to locate a file.
 *
 *	chk_find_name (lsn, name, entry)
 *
 *	ENTRY		lsn   - sector number of the FNODE to search from
 *			name  - name to look for
 *			entry - filled in with pointer to entry
 *
 *	EXIT		Returns pointer to DIRBLK (in cache), NULL if not found
 *
 *	CALLS		readbuf
 *
 *	WARNINGS	None
 *
 *	EFFECTS		Reads disk
 */
 DIRBLK *chk_find_name (lsn, name, entry)
  ULONG lsn;
  UCHAR *name;
   DIRENT **entry;
{
   BUFNODE *hdr;
   DIRBLK *db;
   FNODE *f;
  union dp dp;
  int i;
  USHORT naml;

#ifdef TRACE
  fprintf (stderr, "chk_find_name (%08lx, `%s')\n", lsn, name);
  fflush (stderr);
#endif

  f = chk_read_sectors (lsn, SECTORS_PER_FNODE);    /* read FNODE */
  if (!f)				/* can't read? */
    return (NULL);			/* just forget it */

  lsn = f->fst.a.aall [0].lsnPhys;		  /* get DIRBLK pointer */
  FREE_BLOCK (f);			/* don't need the FNODE any more */

  naml = strlen (name);
  if (!(db = ( DIRBLK *)readbuf (lsn, &hdr)))	  /* read the DIRBLK */
    return (NULL);			/* can't read -- get out now */

  dp.p = DIR_START (*db);

  while (TRUE) {
#ifndef PAWa
    i = memcmp (name, dp.d->bName, naml);
#else
    i = (dp.d->fFlags & DF_END)?-1:chk_strnicmp (name, PREMAPPED,
		      dp.d->bName, dp.d->bCodePage, naml);
#endif

    if (i > 0) {			/* new name > found name - not done */
      GETNEXT_ENTRY (dp);		   /* move to next entry */
      continue;				/* try the next name */
    }
    else if (!i) {			/* exact match! */
      if (naml > dp.d->cchName) {	/* spurious match */
	GETNEXT_ENTRY (dp);		   /* go to next entry */
	continue;
      }
      else if (naml == dp.d->cchName) { /* a real match */
	*entry = dp.d;			 /* store pointer to entry */
	return (db);			 /* return pointer to DIRBLK */
      }
    }

/*
 * It looks like the string we found was greater than our new one,
 * which means we're done looking.  However, if the string we found
 * is shorter than the new one, we may have had a spurious "greater"
 * compare, so in that case we have to keep looking.
 */

    if (((USHORT)dp.d->cchName < naml) &&
#ifndef PAWa
	(memcmp (name, dp.d->bName, dp.d->cchName) >= 0)) {
#else
	( ((dp.d->fFlags & DF_END) ?
			-1 :
			chk_strnicmp (name, PREMAPPED, dp.d->bName,
					dp.d->bCodePage, dp.d->cchName))
		>= 0 )) {
#endif
      GETNEXT_ENTRY (dp);
      continue;
    }

    if (!(dp.d->fFlags & DF_BTP))     /* no btree pointer, item missing */
      break;				/* stop searching */

    else {				/* have a btree pointer - descend */
      db = ( DIRBLK *)readbuf (DOWN_PTR (dp), &hdr);  /* read subblk */

      if ( db == NULL ) {

	  /*  Can't read the child--return not found.  */
	  return (NULL);
      }

      dp.p = DIR_START (*db);
      continue;				/* start searching child block */
    }
  }
  return (NULL);			/* entry not found! */
}

/***	chk_delete_file - delete a reference to a file which is corrupt
 *
 *	This function is called by check_system to delete a directory
 *	entry for a file which contains corrupt information or which
 *	does not point to an FNODE at all.  Because this entry may have
 *	a downpointer on it and downpointers cannot stand alone, promotion
 *	of lower-level entries may be necessary.  The algorithm used is
 *	the same one that the Pinball file system uses to delete files.
 *
 *	So that the recovery process can recover the file later,
 *	the bitmaps are left as they are, with the space marked as
 *	"in use".
 *
 *	The file system will be searched from the root directory for
 *	the given name.  This is so that any reshuffling caused by
 *	previous deletions will not cause us to lose our place.
 *
 *	chk_delete_file (name)
 *
 *	ENTRY		name  - full ASCIIZ pathname of the file
 *
 *	CALLS		read_scratch
 *			write_scratch
 *
 *	WARNINGS	Destroys contents of pathname buffer
 *
 *	EFFECTS		Deletes a file or subdirectory from the file system
 */
void chk_delete_file (name)
  UCHAR *name;
{
  UCHAR *p, *q;
   DIRBLK *blk, *nextblk;
   BUFNODE *hdr=NULL, *nexthdr=NULL;
  union dp dp, dp2, heap;
  ULONG lsec, sect;

  sect = root_fnode;			/* start looking at root FNODE */
  p = name+1;				/* point to pathname after leading \ */
  while ((q=com_strchr (p, '\\')) != NULL) {  /* while we've subdirs to find */
    *q = '\0';				/* terminate at the backslash */
    blk = chk_find_name (sect, p, &dp.d);     /* find the name in the directory */
    if (!blk) {				/* it's disappeared! */
      wipecache ();			/* forget the cache */
      return;				/* get out without deleting */
    }
    sect = dp.d->lsnFNode;		/* chain to subdir's FNODE */
    p = q+1;				/* move to next name in path */
  }

  blk = chk_find_name (sect, p, &dp.d); /* find the file in the last dir */

  if (!blk || (dp.d->fFlags & DF_END)) {      /* not found, or is end record */
#ifdef TRACE
    fprintf (stderr, "chk_delete_file: %s vanished.\n", name);
    fflush (stderr);
#endif
    wipecache ();
    return;
  }

  hdr = _adr_to_bhdr (blk);		/* get buffer header */
  hdr->cusHolds++;

  if (!(dp.d->fFlags & DF_BTP)) {     /* easy - just remove entry */
    _closeup (blk, dp.d);
    hdr->bDirtMask = TRUE;
    if (blk->offulFirstFree <= sizeof ( DIRENT) +
			((UCHAR *)&blk->bFirst - (UCHAR *)blk))
      adjust (blk->lsnThisDir);
  }
  else {					/* have downptr - must shuffle */
    dp2.p = dp.p + dp.d->cchThisEntry;		/* point to next entry */

    while (TRUE) {				/* find a leaf entry */
      nextblk = ( DIRBLK *)readbuf (DOWN_PTR (dp2), &nexthdr);

      if (nextblk == NULL) {

	  /*
	   *  Can't read child--bail out.  This case should be
	   *  very rare, since we read the child while validating
	   *  the directory.
	   */
	  wipecache();
	  return;
      }

      dp2.p = DIR_START (*nextblk);		/* point to first entry in tree */
      if (!(dp2.d->fFlags & DF_BTP))
	break;					/* got a leaf entry */
    }

    heap.p = _fmalloc (dp2.d->cchThisEntry+16);	/* place to save promotent */
    memmove (heap.p, dp2.p, dp2.d->cchThisEntry);	/* copy promotent */
    _closeup (nextblk, dp2.d);			/* remove promotent */
    lsec = (nextblk->offulFirstFree <= sizeof ( DIRENT) +
	   ((UCHAR *)&nextblk->bFirst - (UCHAR *)nextblk)) ?
	   nextblk->lsnThisDir : 0L;
    nexthdr->bDirtMask = TRUE;			/* promotent's block is dirty */

    sect = DOWN_PTR (dp);
    _closeup (blk, dp.d);			/* remove old node */
    heap.d->cchThisEntry += sizeof (ULONG);		/* promotent gets new BTP */
    heap.d->fFlags |= DF_BTP;		      /* set downpointer flag */
    DOWN_PTR (heap) = sect;			/* store new downpointer */
    /* BUGBUG - parameters 2 and 3 look reversed as in delete.c */
    insert (blk, hdr, heap.d, 0L);		/* insert promotent */

    _ffree (heap.p);				/* free heap space */
    if (lsec)					/* promotent block was emptied */
      adjust (lsec);				/* shuffle blocks at that lvl */
  }

  hdr->cusHolds = 0;				/* unhold our blocks now */
  if (nexthdr)
    nexthdr->cusHolds = 0;
  wipecache ();					/* flush all changes to disk */
}
