/*
	RT-11 Adapter Package for CP/M

	Modification history (most recent first)

	Rev. 1.2  -- August 1983 added Copy command to copy all files
			from an RT-11 disk to a CP/M disk.
						Tony Nicholson

	Rev. 1.1c -- June 1983 fixed r50toa routine so it translates a
			'Z' correctly.		Tony Nicholson

	Rev. 1.1b -- December 1982 fixed initial disk selection of the
			RT11 disk drive by modifying the BIOS(SEL_DSK)
			call to include an extra parameter to force
			disk type selection.	Tony Nicholson

	Rev. 1.1a -- November 1982 added conditional for Godbout DISK1
			DMA Floppy disk controller in the header file
			so that sector numbering is correct.
						Tony Nicholson

	Rev. 1.1 -- March 1981 consisting of adding a valid system date
			word to all files placed on the RT-11 disk and
			putting the volume ID on a disk when the directory
			is initialized.  This will keep RT-11 versions
			later than V02C from choking.

	Rev. 1.0 -- July 1980


	copyright (c) 1980, William C. Colley, III

This group of functions implements enough of RT-11 to allow the rest of
the package to work.  The functions are built and named as per the
RT-11 Software Support Manual for version 2C of RT-11.
*/

#include "RT11.H"

/*
Routine to look up an RT-11 file.  The routine accepts a file name as an
array of int and returns 0 if the file was not found, 1 otherwise.
The length of the file can be extracted thru dir_pointer, and the
starting block of the file appears in file_start.
*/

lookup(file_name)
int *file_name;
{
	usrcom();
	return dleet(file_name);
}

/*
Routine to rename an RT-11 file.  Pass the old and new names.  The routine
returns 0 if the file doesn't exist, 1 otherwise.
*/

rename(old_file,new_file)
int *old_file, *new_file;
{
	char i;
	usrcom();
	if (!dleet(old_file)) return 0;
	for (i = 0; i < 3; i++) _putword(dir_pointer,i+1,new_file[i]);
	*dir_pointer = 0;
	*(dir_pointer + 1) = TENTAT;
	clocom(new_file);
	return 1;
}

/*
Routine to delete an RT-11 file.  Pass the routine the file name as an
array of int.  If the file does not exist, the routine returns 0, else
it returns 1.
*/

delete(file_name)
int *file_name;
{
	usrcom();
	if (!dleet(file_name)) return 0;
	*dir_pointer = 0;
	*(dir_pointer + 1) = EMPTY;
	consol(0);
	segrw(WRITE,current_segment);
	return 1;
}

/*
Routine to build a tentative entry in the directory.  Routine requires
a file name in an array of int, and the size of the file required.  The
routine returns 0 if the entry is not possible, or the first block of
the file if it is.  If the entry fails, the appropriate diagnostic is printed.
*/

enter(file_name,size)
int *file_name, size;
{
	char *save_pntr, i;
	unsigned ret_value;
	usrcom();
	do
	{
retry:		consol(0);
		while (entry(EMPTY))
		{
			if (size <= _getword(dir_pointer,4))
			{
				save_pntr = dir_pointer;
				ret_value = file_start;
				while (entry(EMPTY)) incr1();
				if (dir_pointer > &directory.entries[1000] -
					directory.extra_bytes)
				{
					ret_value = current_segment;
					if (extend())
					{
						blkchk(ret_value);
						goto retry;
					}
					puts("\nError -- directory full\n");
					return 0;
				}
				dir_pointer = save_pntr;
				_expand(dir_pointer);
				*dir_pointer++ = 0;
				*dir_pointer++ = TENTAT;
				for (i = 0; i < 3; i++)
				{
					_putword(dir_pointer,i,file_name[i]);
				}
				_putword(dir_pointer,3,size);
				_putword(dir_pointer,5,sysdate);
				incr1();
				_putword(dir_pointer,3,
					_getword(dir_pointer,3) - size);
				segrw(WRITE,current_segment);
				return ret_value;
			}
			incr1();
		}
	}
	while (nxblk());
	puts("\nError -- no large enough gaps\n");
	return 0;
}

/*
Routine to extend an RT-11 directory by splitting a directory segment.
The routine returns 0 if no directory segment is available, 1 otherwise.
*/

extend()
{
	struct dirseg temp_seg;
	int t, newseg;
	emt_375(READ,0,2,temp_seg);
	if (temp_seg.highest_segment >= temp_seg.total_segments) return 0;
	newseg = temp_seg.highest_segment;
	temp_seg.highest_segment++;
	emt_375(WRITE,0,2,temp_seg);
	movmem(directory,temp_seg,1024);
	blkchk(current_segment);
	for (t = (1024 - 10) / (14 + directory.extra_bytes);
		t > 0; t--) incr1();
	*dir_pointer = 0;
	*(dir_pointer + 1) = EMPTY;
	temp_seg.next_segment = directory.next_segment;
	directory.next_segment = newseg;
	segrw(WRITE,current_segment);
	temp_seg.first_block = file_start;
	movmem(temp_seg,directory,10);
	movmem(temp_seg.entries + (dir_pointer - directory.entries),
		directory.entries, directory.entries + 1024 - dir_pointer);
	segrw(WRITE,newseg);
	current_segment = newseg;
	blkchk(newseg);
	return 1;
}

/*
Routine to close an RT-11 file.  The file name comes over in radix 50 in
the array file_name.  The routine returns 0 if no file by that name was
around to close, 1 otherwise.
*/

klose(file_name)
int *file_name;
{
	char i;
	blkchk(1);
	do
	{
		while(entry(TENTAT))
		{
			for (i = 0; i < 3; i++)
			{
				if (_getword(dir_pointer,i+1) !=
					file_name[i]) break;
			}
			if (i == 3)
			{
				clocom(file_name);
				return 1;
			}
			incr1();
		}
	}
	while (nxblk());
	return 0;
}
	
/*
Routine that continues close and rename operations.
*/

clocom(file_name)
int *file_name;
{
	char *tdptr;
	int tseg;
	tdptr = dir_pointer;
	tseg = current_segment;
	if (dleet(file_name))
	{
		*dir_pointer = 0;
		*(dir_pointer + 1) = EMPTY;
		if (tseg != current_segment)
		{
			consol(0);
			segrw(WRITE,current_segment);
		}
	}
	blkchk(tseg);
	dir_pointer = tdptr;
	*tdptr++ = 0;
	*tdptr = PERM;
	consol(0);
	segrw(WRITE,current_segment);
}	

/*
Routine to get an RT-11 file name from the console.  The name is packed
into the int array file_name in radix 50.  The routine returns 0
if the file name is not legal, 1 otherwise.
*/

getfd(file_name)
int *file_name;
{
	char c, i, j, name[20], *pntr;
	pntr = gets(name);
	file_name[0] = file_name[1] = file_name[2] = 0;
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
		{
			c = *pntr;
			if (c == '.' || c == '\0') c = ' ';
			else pntr++;
			if ((c = ator50(c)) == 255) return 0;
			file_name[i] = file_name[i] * 050 + c;
		}
	}
	if (*pntr == '.') pntr++;
	for (i = 0; i < 3; i++)
	{
		c = *pntr;
		if (c == '\0') c = ' ';
		else pntr++;
		if ((c = ator50(c)) == 255) return 0;
		file_name[2] = file_name[2] * 050 + c;
	}
	return 1;
}

/*
Routine to set up an RT-11 file I/O operation.
*/

usrcom()
{
	current_segment = 0;
	blkchk(1);
}

/*
Routine to scan the directory for a file of a specified name.  The routine
is passed the file name in radix 50 as an array of int.  The routine returns
0 if the file is not found, 1 otherwise.
*/

dleet(file_name)
int *file_name;
{
	char i;
	blkchk(1);
	do
	{
		while(entry(PERM))
		{
			for (i = 0; i < 3; i++)
			{
				if (_getword(dir_pointer,i+1) !=
					file_name[i]) break;
			}
			if (i == 3) return 1;
			incr1();
		}
	}
	while (nxblk());
	return 0;
}

/*
Routine to get the next directory segment into core.  The routine returns
0 if no next segment exists, 1 otherwise.
*/

nxblk()
{
	if (directory.next_segment == 0) return 0;
	blkchk(directory.next_segment);
	return 1;
}

/*
Routine to find out if the requested directory segment is in core.  If it
isn't, the segment is read in.
*/

blkchk(segment)
int segment;
{
	if (segment != current_segment)
	{
		current_segment = segment;
		segrw(READ,segment);
	}
	dir_pointer = directory.entries;
	file_start = directory.first_block;
}

/*
Function to read/write a directory segment.  Parameters passed are the
directory segment desired, and a read/write flag as per emt_375.
*/

segrw(read_write,segment)
int segment;
char read_write;
{
	emt_375(read_write,segment * 2 + 4,2,directory);
}

/*
Routine to find a specified file type in a directory segment.
The routine starts from the current value of dir_pointer, and
returns either 0 if the type isn't found or 1 if it is.
*/

entry(type)
char type;
{
	char t;
	while (1)
	{
		if ((t = *(dir_pointer + 1)) == type) return 1;
		if (t == ENDSEG) return 0;
		incr1();
	}
}

/*
Routine to increment the directory pointer to the next entry in the
directory.
*/

incr1()
{
	file_start += _getword(dir_pointer,4);
	dir_pointer += 14 + directory.extra_bytes;
}

/*
Routine to compress the stray tentatives and empties out of a directory
segment.  The file name file_name gives a tentative file that is to be
exempt from the compression (i. e., there is only one active channel).
If no files are to be exempt, pass 0 for file_name.
*/

consol(file_name)
int *file_name;
{
	char i, *next;
	dir_pointer = directory.entries;
	while (entry(TENTAT))
	{
		if (file_name != 0)
		{
			for (i = 0; i < 3; i++)
			{
				if (_getword(dir_pointer,i+1) !=
					file_name[i]) break;
			}
		}
		else i = 0;
		if (i != 3)
		{
			*dir_pointer = 0;
			*(dir_pointer + 1) = EMPTY;
		}
		incr1();
	}
	dir_pointer = directory.entries;
	while (entry(EMPTY))
	{
		next = dir_pointer + 14 + directory.extra_bytes;
		if (*(next + 1) == EMPTY)
		{
			_putword(dir_pointer,4,_getword(next,4) +
				_getword(dir_pointer,4));
			_squeez(next);
			continue;
		}
		if (_getword(dir_pointer,4) == 0)
		{
			if (*(dir_pointer - 13 -
				directory.extra_bytes) == PERM)
			{
				_squeez(dir_pointer);
				continue;
			}
		}
		incr1();
	}
	blkchk(current_segment);
}

/*
This routine emulates the block read/write EMT call (#375).  You pass the
routine a starting block number, a block count, and an appropriate core
buffer.  The routine reads or writes the blocks and returns.  A read is
done if the read/write flag has value READ, and a write is done if the
read/write flag is WRITE.  Each block is 512 bytes.
*/

emt_375(read_write,start_block,block_count,core_buffer)
int start_block, block_count;
char read_write, *core_buffer;
{
	char i;
	while (--block_count >= 0)
	{
		for (i = 0; i < 4; i++)
		{
			biosh(SEL_DSK,1,sel_flag);
			bdos(SET_DMA,core_buffer);
			_find(start_block,i);
			bios(read_write,0);
			bdos(SET_DMA,DMA_ADDR);
			biosh(SEL_DSK,0,sel_flag);
			core_buffer += 128;
			sel_flag |= 1;
		}
	start_block++;
	}
}

/*
Internal function to map an RT-11 block number and sector number inside the
block into a physical sector number.  The sectors within a block are numbered
from 0 to 3.
*/

_find(block,sector)
int block;
char sector;
{
	int r, s, t;
	r = (block - 6) * 2 + (sector >> 1);
	sector &= 001;
	t = (r + 12) / 13;
	r = (r + 12) % 13;
	s = t * 6 + r * 4 + (r > 6 ? 1 : 0);
	if (sector) s += (r == 6 ? 3 : 2);
	t++;
	bios(SET_TRK,t);
#ifndef DISK1
	bios(SET_SEC,s % 26 + 1);
#endif
#ifdef	DISK1
	bios(SET_SEC,s % 26);
#endif
}

/*
Internal routine to extract a word from a core buffer that is an array of
char.  The array is passed, as is the word number desired.  The function
returns the word.
*/

_getword(buffer,word_number)
unsigned word_number;
char *buffer;
{
	buffer += word_number << 1;
	return *buffer++ + (*buffer << 8);
}

/*
Internal routine to place a word into the core buffer that is an array of char.
The array is passed, as is the word number desired, and the word itself.
*/

_putword(buffer,word_number,word)
unsigned word_number, word;
char *buffer;
{
	buffer += word_number << 1;
	*buffer++ = word & 0xff;
	*buffer = word >> 8;
}

/*
Internal routine to compress an entry out of a directory segment.
The address of the entry is passed.
*/

_squeez(entry)
char *entry;
{
	char *next;
	next = entry + 14 + directory.extra_bytes;
	movmem(next,entry,&directory.entries[1014] - next);
}		

/*
Internal routine to splice an entry into a directory segment.
The address of the disired entry is passed.
*/

_expand(entry)
char *entry;
{
	char *next;
	next = entry + 14 + directory.extra_bytes;
	movmem(entry,next,&directory.entries[1014] - next);
}

/*
Routine to map a character into its radix 50 representation.  The function
returns the rad 50 version or 255 if no rad 50 version exists.
*/

ator50(ascii)
char ascii;
{
	switch(ascii)
	{
		case ' ':	return 0;
		case '$':	return 033;
		case '.':	return 034;
	}
	ascii = toupper(ascii);
	if ((ascii -= '0') <= 9) return ascii + 036;
	if ((ascii -= ('A' - '0')) <= ('Z' - 'A')) return ascii + 1;
	return 255;
}

biosh(vector,c,de)
int vector,c,de;
{
	return call((peek(2) << 8) | peek(1)+(vector-1)*3,0,0,c,de);
}
