#define VN "1.0 02/12/83"
/* UNERASE - This does the inverse of ERASE. It revives the specified files,
   by changing the first byte of their directory entries from 0xE5
   back to the specified user number. */

#include <LIBRARY.H>

struct _dirpb dir_pb;		/* directory management parameter block */
struct _dir *dir_entry;		/* pointer to directory entry */
struct _scb scb;		/* search control block */
struct _scb scba;		/* scb setup to match all files */
struct _dpb dpb;		/* CP/M's disk parameter block */
struct _bv inuse_bv;		/* bit vector for blocks in use */
struct _bv file_bv;		/* bit vector for file to be unerased */
struct _bv extents;		/* bit vector for those extents unerased */

char file_name[20];		/* formatted for display : un/d:FILENAME.TYP */

short cur_disk;			/* current logical disk at start of program
		   NZ = show map of number of files */
int count;			/* used to access the allocation block numbers
		   in each directory entry */
int user;			/* user in which the file is to be revived */


main(argc,argv)
short argc;		/* argument count */
char *argv[];		/* argument vector (pointer to an array of chars) */
{

printf("\nUNERASE Version %s (Library %s)",VN,LIBVN);
chk_use(argc);			/* check usage */
cur_disk = bdos(GETDISK);	/* get current default disk */


	/* using a special version of set search control block,
	   set the disk, name, type (no ambiguous names), the user number
	   to match only erased entries, and the length to compare
	   the user, name and type.
	   This special version also returns the disk_id taken from
	   the file name on the command line.  */
if ((dir_pb.dp_disk = ssetscb(scb,argv[1],0xE5,12)) == 0)
	{	/* use default disk */
	dir_pb.dp_disk = cur_disk;
	}
else
	{	/* make disk A = 0, B = 1 (for SELDSK) */
	dir_pb.dp_disk--;
	}
printf("\nSearching disk %d.",dir_pb.dp_disk);

if(strscn(scb,"?"))	/* check if ambiguous name */
	{
	printf("\nError - UNERASE can only revive a single file at a time.");
	exit();
	}


	/* set up a special search control block that will match with
	   all existing files. */
ssetscb(scba,"*.*",'?',12);	/* set file name and initialize scb */


if (argc == 2)			/* no user number specified */
	user = bdos(GETUSER,0xFF);	/* get current user number */
else
{
	user = atoi(argv[2]);		/* get specified number */
	if (user > 15)
		{
		printf("\nUser number can only be between 0 - 15.");
		exit();
		}
}

/* build a bit vector that shows the allocation blocks
   currently in use. scba has been set up to match all
   active directory entries on the disk. */
build_bv(inuse_bv,scba);

/* build a bit vector for the file to be restored showing
   which allocation blocks will be needed for the file. */
if (!build_bv(file_bv,scb))
	{
	printf("\nNo directory entries found for file %s.",
		argv[1]);
	exit();
	}
/* perform a boolean AND of the two bit vectors. */
bv_and(file_bv,inuse_bv,file_bv);

/* check if the result is non-zero - if so, then one or more
   of the allocation blocks required by the erased file is
   already in use for an existing file and the file cannot
   be restored. */
if (bv_nz(file_bv))
{
	printf("\n--- This file cannot be restored as some parts of it");
	printf("\n    have been re-used for other files! ---");
	exit();
}

/* continue on to restore the file by changing all the entries
   in the directory to have the specified user number.
   Note : the problem is complicated by the fact that there
   may be several entries in the directory that are for the
   same file name and type, and even have the same extent
   number. For this reason, a bit map is kept of the extent
   numbers unerased - duplicate extent numbers will not be
   unerased. */

/* set up the bit vector for up to 127 unerased extents */
bv_make(extents,16);		/* 16 * 8 bits */


/* set the directory to "closed", and force the get_nde
   function to open it. */
dir_pb.dp_open = 0;

/* while not at the end of the directory, return a pointer to
   the next entry in the directory. */
while(dir_entry = get_nde(dir_pb))
{

	/* check if user = 0xE5 and name, type match */
if (comp_fname(scb,dir_entry) == NAME_EQ)
	{
		/* test if this extent has already been
		   unerased */
	if (bv_test(extents,dir_entry -> de_extent))
		{		/* yes it has */
		printf("\n\t\tExtent #%d of %s ignored.",
			dir_entry -> de_extent,argv[1]);
		continue;	/* do not unerase this one */
		}
	else			/* indicate this extent unerased */
		{
		bv_set(extents,dir_entry -> de_extent);
		dir_entry -> de_userno = user; /* unerase entry */
		dir_pb.dp_write = 1;	/* need to write sector back */
		printf("\n\tExtent #%d of %s unerased.",
			dir_entry -> de_extent,argv[1]);
		}
	}
}

printf("\n\nFile %s unerased in User Number %d.",
argv[1],user);

bdos(SETDISK,cur_disk);	/* reset to current disk */
}


build_bv(bv,scb)	/* build bit vector (from directory) */
/* This function scans the directory of the disk specified in
   the directory parameter block (declared as a global variable),
   and builds the specified bit vector showing all the allocation
   blocks used by files matching the name in the search control
   block. */

/* Entry Parameters */
struct _bv *bv;		/* pointer to the bit vector */
struct _scb *scb;	/* pointer to search control block */
/* also uses : directory parameter block (dir_pb) */

/* Exit Parameters
   The specified bit vector will be created, and will have 1-bits
   set wherever an allocation block is found in a directory
   entry that matches the search control block.
   It also returns the number of directory entries matched. */
{
unsigned abno;		/* allocation block number */
struct _dpb *dpb;	/* pointer to the disk parameter block in the BIOS */
int mcount;		/* match count of dir. entries matched */

mcount = 0;		/* initialize match count */
dpb = get_dpb(dir_pb.dp_disk);	/* get disk parameter block address */

/* make the bit vector with one byte for each 8 allocation
   blocks + 1 */
if (!(bv_make(bv,(dpb -> dpb_maxabn >>3)+1)))
	{
	printf("\nError - Insufficient memory to make a bit vector.");
	exit();
	}


/* set directory to "closed" to force the get_nde
   function to open it. */
dir_pb.dp_open = 0;

/* now scan the directory building the bit vector */
while(dir_entry = get_nde(dir_pb))
	{
		/* compare user number (which can legitimately be
		   0xE5), the file name and the type). */
	if (comp_fname(scb,dir_entry) == NAME_EQ)
		{
		++mcount;		/* update match count */
		for (count = 0;		/* start with the first alloc. block */
		    count < dir_pb.dp_nabpde;	/* for number of alloc. blks per dir. entry */
		    count++)
			{
				/* set the appropriate bit number for
				   each non-zero allocation block number */
			if (dir_pb.dp_nabpde == 8)	/* assume 8 2-byte numbers */
				{
				abno = dir_entry -> _dirab.de_long[count];
				}
			else	/* assume 16 1-byte numbers */
				{
				abno = dir_entry -> _dirab.de_short[count];
				}
			if (abno) bv_set(bv,abno); /* set the bit */
			}
		}
	}	
return mcount;		/* return number of dir entries matched */
}



chk_use(argc)		/* check usage */
/* This function checks that the correct number of
   parameters has been specified, outputting instructions
   if not. */

/* Entry Parameter */
int argc;	/* Count of the number of arguments on the command line */
{

/* The minimum value of argc is 1 (for the program name itself),
   so argc is always one greater than the number of parameters
   on the command line */

if (argc == 1 || argc > 3)
	{
	printf("\nUsage :");
	printf("\n\tUNERASE {d:}filename.typ {user}");
		printf("\n\tOnly a single unambiguous file name can be used.)");
	exit();
	}
} /* end chk_use */


ssetscb(scb,fname,user,length)	/* special version of set search control block */
/* This function sets up a search control block according
   to the file name, type, user number and number of bytes to
   compare. 
   The file name can take the following forms :

	filename
	filename.typ
	d:filename.typ

   It sets the bit map according to which disks should be searched.
   For each selected disk, it checks to see if an error is generated
   when selecting the disk (i.e. if there are disk tables in the BIOS
   for the disk). */

/* Entry Parameters */
struct _scb *scb;	/* pointer to search control block */
char *fname;		/* pointer to the file name */
short user;		/* user number to be matched */
int length;		/* number of bytes to compare */

/* Exit Parameters
   Disk number to be searched. (A = 1, B = 2...)
*/
{
short disk_id;		/* disk number to search */

setfcb(scb,fname);	/* set search control block as though it
			   is a file control block. */
disk_id = scb -> scb_userno;	/* set disk_id before it gets overwritten
				   by the user number */
scb -> scb_userno = user;	/* set user number */
scb -> scb_length = length;	/* set number of bytes to compare */
return disk_id;
} /* end setscb */

