/*	 VMSTPC	Fast Tape Copy program VMS V4, native mode.

	 VMSTPC uses multiple ast  driven  QIO's  to  get  the  tape
	drive  streaming during copy operations. A sample copy of an
	ANSI D tape blocked at 8192 with serveral hundred  files  (a
	Columbia  U  Kermit  tape) took 21 CPU seconds with the TU80
	streaming about 95% of the time (done on an 11/785).


	22-MAY-1986 09:15 Brian Nelson  ( BRIAN@UOFT02.BITNET )

	Last edit: 07-May-1992 BDN  (see edit history below)


	Files:	VMSTPC.C
		VMSTPC.COM
		VMSTPC.CLD


	File TC.CLD

define verb tc
	image "sys$sysroot:[brian.c]vmstpc"
	parameter P1,label=inputarg,prompt="From",value(REQUIRED)
	parameter P2,label=outputarg,prompt="To",value(REQUIRED)
	qualifier ANSI
	qualifier APPEND
	qualifier BACKUP
	qualifier REWIND
	qualifier RT11
	qualifier DOS11
	qualifier VERIFY
	qualifier ERROR
	qualifier DENSITY    value(REQUIRED,TYPE=$NUMBER)
	qualifier ALLOCATION value(REQUIRED,TYPE=$NUMBER)
	qualifier EXTENDSIZE value(REQUIRED,TYPE=$NUMBER)
	qualifier BLOCKSIZE  value(REQUIRED,TYPE=$NUMBER)
	qualifier BUFFERS    value(REQUIRED,TYPE=$NUMBER)
	qualifier DIRECTORY, syntax=DIRECTORY
	disallow  DENSITY and NEG REWIND
	disallow  VERIFY and  NEG REWIND
	disallow  ANSI and DOS11
	disallow  BACKUP and DOS11
	qualifier JUNK,label=outputarg
define syntax DIRECTORY
	image "sys$sysroot:[brian.c]vmstpc"
	parameter P1,label=inputarg,prompt="From",value(REQUIRED)



	The qualifier /DENSITY=nnnn MAY not work. I can't test it.
	Check the function SET_DENS(LUN,DENSITY)


 Usage:

	$ set command vmstpc		! Define the TC command
	$ mou msa0:/for			! The drive must be mounted
	$ tc msa0: tape.con		! Copy the tape to TAPE.CON
	$ tc tape.con msa0:		! Copy TAPE.CON to a new tape
	$ tc/ansi msa0: ansi.con	! Allow NULL length ANSI files.
	$ tc/ver msa0: tape.con		! Copy from tape and verify it.
	$ tc/dir container.file		! Get directory of tape image
	$ tc/buf=30/blo=512 msa0: t.t	! Optimize for a DOS format tape
	$ tc/dos msa0: t.t		! Optimize for a DOS format tape

 The /ANSI qualifier is used to allow VMSTPC to avoid stopping  when  it
finds  a NULL length file on an ANSI tape. A null file is simply TWO eof
marks following the last HDR label. Since VMSTPC  normally  thinks  that
two  EOF  marks  in  a  row  signify the end of the tape, this qualifier
enables  special  checking  for  corresponding  HDR2/EOF2  marks  before
deciding about EOT.

 The  /VERIFY  qualifier will force VMSTPC to rewind the tape and verify
that was was read from (or written to) the  tape  is  identical  to  the
copy  in the disk container file. The /VERIFY option is SLOW; no attempt
is made during the verification pass to optimize throughput.

 The /APPEND qualifier is a bit unusual; if you do NOT speficy any other
qualifiers it will simply use the MTAACP IO$_SKIPFILE call to  find  the
end  of  the  tape.  Additionally,  if the tape happends to be ANSI, the
files appended may not show up later because the SEQUENCE fields of  all
the  files will be incorrect. This is not a problem for VMSBACKUP, which
bypassed RMS, but COPY and DIRECTORY will fail. Thus,  for  ANSI  tapes,
either  the /ANSI or /BACKUP qualifier should be used with /APPEND. This
will cause VMSTPC to look for HDR1 and EOF1 records and modify the  four
character SEQUENCE field. As you can imagine, this is a bit risky.
 Also  note  that the /BACKUP qualifier also uses the IO$_SKIPFILE call,
whereas the /ANSI qualifier reads records until it gets a correct END of
TAPE; ie it keeps track of HDR2 and EOF2 counts so it can detect a  NULL
length  ANSI file, which is a file composed of HDRn records, followed by
two tape marks and the EOFn records followed by a tape mark.

 The /[NO]REWIND qualifier  is dangerous when  used with /APPEND on ANSI
tapes, as the file sequence fields (described above) will be incorrect.
edits:

  The /error qalifier will cause parity etc. errors to
be ignored.


12-JUN-1986 12:54  BDN	Add /DIR, fix status checking for disk file open
			and creates.
25-JUN-1986 11:52  BDN	Add /BUFFERS=nnn/BLOCKSIZE=nnn to optimize when
			reading/writing tapes with small blocks, like RT
			tapes. Also, /RT11 implies /BUF=30/BLOCK=512  if
			those qualifiers were not present.
07-JUL-1986 11:55  BDN	/APPEND and /BACKUP
17-JUL-1986 13:50  BDN	Wait for event flag after start_tape_dump to fix
			getting the VOL1 out of sequence on 8600's (ie,
			faster CPU's
11-NOV-1986 11:13  BDN	Fix (hopefully) event flag wait bugs that show up
			on the faster cpus like 86xx's.
02-DEC-1986 09:30  BDN	Really fix it this time.
15-OCT-1987 12:56  BDN	Raise default buffer count
22-FEB-1988 08:42  BDN	Allow container files to be on a remote decnet
			node. Note: the value of the sysgen parameter
			RMS_DFNBC is important here, if too small then
			most container files being read over DECNET will
			fail.
01-Feb-1989 08:55  BDN	Add SYS$CANCEL when done reading tape

07-May-1992	   BDN  Merge in some old mods, more info displayed on
			rms write errors to the container file.
27-May-1993	   GCE  Add /error qualifier so one can use this to try
			and get good copies of tapes with bitrot.
*/




#include <stdio>
#include <dvidef>
#include <devdef>
#include <dcdef>
#include <rms>
#include <ssdef>
#include <iodef>
#include <descrip>
#include <climsgdef>

#define	then
#define	RMS$_EOF	0x1827a




union  pointer	{
		int  *intbuf ;
		char *charbuf ;
		} ;

struct itmlst	{
		unsigned short int bufferlength ;
		unsigned short int item_code ;
		union pointer  addr ;
		int  *retlength ;
		} ;

struct dsc	{
		int len ;
		char *addr ;
		} ;

struct devdsc	{
		int dev_class ;
		int dev_type ;
		int dev_lun ;
		int dev_char ;
		char dev_name[NAM$C_DVI] ;
		char dev_spec[128] ;
		} in_dev, out_dev ;


struct FAB parse_fab ;
struct NAM parse_nam ;

#define	ANSI_NULLFILE 1
#define OLD_EOF_MARK 0
#define EOF_MARK 0
#define	ANSI_HDR_SIZE 80
#define	DOS_HDR_SIZE  14

#define	BUFFER_SIZE 32768
#define	NBUFFERS 12
#define	RT_BUFFER_SIZE 512
#define RT_NBUFFERS 30
#define	DOS_BUFFER_SIZE 512
#define DOS_NBUFFERS 30

#define	MAX_NBUFFERS RT_NBUFFERS

char *buffer_list[MAX_NBUFFERS+1] ;
char *altbuf_list[MAX_NBUFFERS+1] ;

short int iosblist[MAX_NBUFFERS+1][4] ;
int eventf[MAX_NBUFFERS+1], wakeup_ef ;

int eov, eof_count ,n_files_save, n_files_skipped, rec_count ;





/*	Internal tasking dispatch table				*/

struct dispatch	{
		 int state ;
		 char *bufaddr ;
		 char *altaddr ;
		 int (*readproc) () ;
		 int (*writeproc) () ;
		 int iodone ;
		 int iopending ;
		 int efn ;
		 int param ;
		 int endoflist ;
		}
		 proc_header[MAX_NBUFFERS+1] ;


char vol_id[7] ;

char *getmsg(), *getcpu() ;
int tape_dump() , read_qio_ast() ,tape_write() ;
extern char *strcpy() , *strcat() , *malloc() ;


#define	DEF_ANSI	0
#define	DEF_RT11	0
#define DEF_DOS11	0
#define	DEF_DENSITY	1600
#define	DEF_ALLOCATION	2000
#define	DEF_EXTENDSIZE	250
#define	DEF_VERIFY	0
#define	DEF_DIR		0
#define	DEF_BUFFERS	NBUFFERS
#define	DEF_BLOCKSIZE	BUFFER_SIZE
#define	DEF_APPEND	0		/* This default should ALWAYS be 0 */
#define	DEF_BACKUP	0		/* This default should ALWAYS be 0 */
#define	DEF_REWIND	1		/* This default should ALWAYS be 1 */
#define DEF_ERROR	0		/* normal error handling */

int qual_ansi		= DEF_ANSI ;
int qual_rt11		= DEF_RT11 ;
int qual_density	= DEF_DENSITY ;
int qual_allocation	= DEF_ALLOCATION ;
int qual_extendsize 	= DEF_EXTENDSIZE ;
int qual_verify 	= DEF_VERIFY ;
int qual_dir		= DEF_DIR ;
int qual_buffers	= NBUFFERS ;
int qual_blocksize	= BUFFER_SIZE ;
int qual_dos11		= DEF_DOS11 ;
int qual_append		= DEF_APPEND ;
int qual_backup		= DEF_BACKUP ;
int qual_rewind		= DEF_REWIND ;
int qual_error		= DEF_ERROR ;

#define	SET_ANSI	1
#define	SET_RT11	2
#define	SET_DENSITY	4
#define	SET_ALLOCATION	8
#define	SET_EXTENDSIZE	16
#define	SET_VERIFY	32
#define	SET_DOS11	64
#define	SET_DIR		128
#define	SET_BUFFERS	256
#define	SET_BLOCKSIZE	512
#define	SET_APPEND	1024
#define	SET_BACKUP	2048
#define	SET_REWIND	4096
#define SET_ERROR	8192

int set_flags = 0 ;
int node_found = 0 ;
int hdr_count = 0 ;
int file_mark = 0 ;



main()
{
	$DESCRIPTOR(inputarg,  "INPUTARG") ;
	$DESCRIPTOR(outputarg, "OUTPUTARG") ;
	$DESCRIPTOR(density,   "DENSITY") ;
	$DESCRIPTOR(allocation,"ALLOCATION") ;
	$DESCRIPTOR(extendsize,"EXTENDSIZE") ;
	$DESCRIPTOR(ansitape,  "ANSI") ;
	$DESCRIPTOR(rt11tape,  "RT11") ;
	$DESCRIPTOR(verify,    "VERIFY") ;
	$DESCRIPTOR(directory, "DIRECTORY") ;
	$DESCRIPTOR(outputfile,"OUTPUT") ;
	$DESCRIPTOR(buffers,   "BUFFERS") ;
	$DESCRIPTOR(blocksize, "BLOCKSIZE") ;
	$DESCRIPTOR(dos11tape, "DOS11") ;
	$DESCRIPTOR(append,    "APPEND") ;
	$DESCRIPTOR(backup,    "BACKUP") ;
	$DESCRIPTOR(rewind,    "REWIND") ;
	$DESCRIPTOR(error,     "ERROR") ;

	int retlength , status ,temp ;
	char inarg[128], outarg[128] ;



	getparam(inarg,&inputarg) ;
	getparam(outarg,&outputarg) ;

	qual_ansi = setqual(&ansitape,DEF_ANSI) ;
	qual_dos11= setqual(&dos11tape, DEF_DOS11 ) ;
	qual_rt11 = setqual(&rt11tape,DEF_RT11) ;

	qual_append = setqual(&append,DEF_APPEND) ;
	qual_backup = setqual(&append,DEF_BACKUP) ;
	qual_verify = setqual(&verify,DEF_VERIFY) ;
	qual_dir    = setqual(&directory,DEF_DIR) ;
	qual_rewind = setqual(&rewind,DEF_REWIND) ;
	qual_error  = setqual(&error,DEF_ERROR)   ;

	if ( status = getqual_value(&allocation) ) {
		qual_allocation = status ;
		set_flags |= SET_ALLOCATION ;
	} ;

	if ( status = getqual_value(&extendsize) ) {
		qual_extendsize = status ;
		set_flags |= SET_EXTENDSIZE ;
	} ;

	if ( status = getqual_value(&density) ) {
		qual_density    = status ;
		set_flags |= SET_DENSITY ;
	} ;

	if ( status = getqual_value(&buffers) ) {
		if ( status >= 1 && status <= MAX_NBUFFERS ) {
		  qual_buffers = status ;
		  set_flags |= SET_BUFFERS ;
		}
		else
		  printf("/BUFFERS out of range 1 to %d, qualifier ignored\n",
			  MAX_NBUFFERS) ;
	} ;

	if ( status = getqual_value(&blocksize) ) {
		if ( status >= 512 && status <= BUFFER_SIZE ) {
		  qual_blocksize = status ;
		  set_flags |= SET_BLOCKSIZE ;
		}
		else
		  printf("/BLOCK out of range 512 to %d, qualifier ignored\n",
			  BUFFER_SIZE) ;
	} ;

	if ( qual_rt11 )
	  if ( ( set_flags & SET_BUFFERS ) == 0 &&
	       ( set_flags & SET_BLOCKSIZE ) == 0 ) {
		  printf("RT11 buffer count raised to %d\n",RT_NBUFFERS) ;
		  qual_buffers = RT_NBUFFERS ;
		  qual_blocksize = RT_BUFFER_SIZE ;
	  } ;

	if ( qual_dos11 )
	  if ( ( set_flags & SET_BUFFERS ) == 0 &&
	       ( set_flags & SET_BLOCKSIZE ) == 0 ) {
		  printf("DOS11 buffer count raised to %d\n",DOS_NBUFFERS) ;
		  qual_buffers = DOS_NBUFFERS ;
		  qual_blocksize = DOS_BUFFER_SIZE ;
	  } ;


	if ( init() == 0 ) exit() ;

	switch ( qual_density ) {
		case 800:
		case 1600:
		case 6250:
			break ;
		default:
			printf("Unknown density %d\n",qual_density) ;
			exit() ;
			break ;
	} ;

	if ( qual_rewind == 0 && qual_append && (qual_ansi || qual_backup))
	  printf("Ansi HDR1 and EOF1 labels may not be accessable\n\n") ;
	status = process(inarg,outarg) ;
	exit(status) ;
}






process(in,out)
char *in,*out ;
{
	int in_chan,out_chan,status ;

	if ( qual_dir ) {
		if ( *in == 0 ) return( SS$_INSFARG ) ;
		if ( ((status=parse(&in_dev,in)) & 1) == 0 ) return(status) ;
	}
	  else {
		if ( *in == 0 || *out == 0 ) return( SS$_INSFARG ) ;

		if ( ((status=parse(&in_dev,in))   & 1) == 0 ||
		     ((status=parse(&out_dev,out)) & 1) == 0 )
		  then return(status) ;
	} ;

	eov = 0 ;
	eof_count = 0 ;

	switch ( in_dev.dev_class ) {				/* Case */

	  case DC$_TAPE:
		if ( qual_dir ) {
			printf("The /DIR qualifier is only for containers\n");
			return(0) ;
		} ;
		if ( out_dev.dev_class == DC$_TAPE &&
		     strcmp(out_dev.dev_name,in_dev.dev_name) != 0 )
		  then status = tape_to_tape() ;
		  else
		   if ( out_dev.dev_class == DC$_DISK ||
			out_dev.dev_class == DC$_MISC )
		     then status = tape_to_disk() ;
		     else status = SS$_IVDEVNAM ;
		break ;

	  case DC$_MISC:
	  case DC$_DISK:
		if ( qual_dir ) then return(container_dir(in_dev.dev_spec));
		if ( out_dev.dev_class == DC$_TAPE )
		  then status = disk_to_tape() ;
		  else status = SS$_IVDEVNAM ;
		break ;

	  default:
		status = SS$_IVDEVNAM ;
		break ;

	} ;							/* end Case */

	sys$dassgn( in_dev.dev_lun ) ;
	sys$dassgn(out_dev.dev_lun ) ;
	return(status) ;
}






container_dir(f)
char *f ;
{

	int block_count = 0,size,status, total_block = 0 ;
	char *cp, dosname[20] , *r50toa() ;
	int found_dos = 0 , found_ansi = 0 ;


	if ( (( status = open_disk(f) ) & 1 ) == 0 ) return( status ) ;
	if ( ( cp = malloc( qual_blocksize ) ) == 0 )   return( 0 ) ;
	if ( (( status = read_disk(cp,&size) ) & 1 ) == 0 ) return( status ) ;


	if ( (found_dos = qual_dos11) == 0 && (found_ansi = qual_ansi) == 0 )
	  switch( size ) {
		case ANSI_HDR_SIZE:
		   if ( strncmp(cp,"VOL1",4) == 0 ) {
			printf("Container set appears to be ANSI labeled\n") ;
			found_ansi++ ;
		   } ;
		   break ;
		case DOS_HDR_SIZE:
		   printf("Container set appears to be DOS-11 labeled\n");
		   found_dos++ ;
		   break ;
		default:
		   printf("Container does not seem to be a know format\n");
		   return(0) ;
		   break ;
	  } ;

	printf("\n") ;
	block_count = -1 ;

	while ( status & 1 ) {

		switch ( size ) {

		  case ANSI_HDR_SIZE:
			eof_count = 0 ;
			if ( found_ansi && strncmp(cp,"HDR1",4) == 0 ) {
			   if ( block_count != -1 )
			     printf("     %d\n",block_count);
			   block_count = 0 ;
			   *(cp+21) = 0 ;
			   printf("%s ",cp+4) ;
			} ;
			break ;

		  case DOS_HDR_SIZE:
			eof_count = 0 ;
			if ( block_count != -1 )
			   printf("     %d\n",block_count);
			r50toa(&dosname[0],cp) ;
			r50toa(&dosname[3],cp+2) ;
			dosname[6] = '.' ;
			r50toa(&dosname[7],cp+4) ;
			printf("%s    ",dosname) ;
			block_count = 0 ;
			break ;

		  case EOF_MARK:
			if ( ++eof_count > 1 ) {
			   printf("     %d\n",block_count);
			   total_block += block_count ;
			} ;
			break ;

		  case ANSI_NULLFILE:
			eof_count = 0 ;
			break ;

		  default:
			eof_count = 0 ;
			block_count++ ;
			break ;
		} ;
		status = read_disk(cp,&size) ;
	} ;

	close_disk() ;
	if ( status == RMS$_EOF ) return(1) ; else return( status ) ;
}




/*
	VERIFY( tape_lun )

	 VERIFY makes absolutly  NO  attempt  to  optimize  transfer
	rates,  as  it  will  tend  to be cpu bound anyway comparing
	data, as well as infrequently used. It is  called  with  the
	tape  channel number passed; all other needed information is
	global already.  It  can  be  called  from  Tape_to_Disk  or
	Disk_to_Tape.

*/

verify(tape_lun,disk_file)
int tape_lun;
char *disk_file ;
{

	char *cp, *tp ;
	short int iosb[4] ;
	int kkk;
	int i,r_num,size,status,waiting ;

	if ( qual_verify == 0 ) return(1) ;
	  else	{

		r_num = 0 ;
		printf("Starting verification pass\n\n") ;
	  	close_disk() ;
	  	if (((status=open_disk(disk_file)) & 1) == 0)
		  return(status);
	  	sys$qiow(0,tape_lun,IO$_REWIND,0,0,0,0,0,0,0,0,0) ;
		cp = malloc(qual_blocksize) ;
		tp = malloc(qual_blocksize) ;
		status = read_disk(cp,&size) ;

		while ( status & 1 ) {

		  status = sys$qiow(0,tape_lun,IO$_READLBLK,&iosb,0,0,
			            tp,qual_blocksize,0,0,0,0) ;
		  if ( ( status & 1 ) == 0 ) {
			if (qual_error != 0){
			  printmsg(iosb[0]);
			  status = SS$_NORMAL;
			}
		  }
		  if ( ( status & 1 ) == 0 ) break ;
		  switch (iosb[0]) {

			case SS$_ENDOFFILE:
				if ( size > ANSI_NULLFILE )
				  printf("End of file mark mismatch\n");
				  break ;
			case SS$_NORMAL:
				r_num++ ;
				if ( size != iosb[1] ) {
				  printf("Block size mismatch #%6d, ",r_num) ;
				  printf("Expected: %5d, Got: %5d\n",
					 size,iosb[1]) ;
				 }
				  else if ( strncmp(cp,tp,size) != 0 )
				    printf("Data compare error\n") ;
				break ;
			default:
				printmsg(iosb[0]) ;
				break ;
		  } ;

		  if ( status & 1 ) status = read_disk(cp,&size) ;
		} ;
		if ( status == RMS$_EOF ) status = 1 ;
	} ;


	sys$qiow(0,tape_lun,IO$_REWIND+IO$M_NOWAIT,0,0,0,0,0,0,0,0,0) ;
	close_disk() ;
	return(status) ;
}



tape_to_tape()
{
	printf("Tape to Tape called %s %s\n",in_dev.dev_name,out_dev.dev_name);
	inistats() ;
	return(1) ;
}




/*
	This is the real work of Tape_to_Disk

 It functions by setting up a dispatch table for processing to  be  done
AFTER   I/O   completetion.   Ie,  the  AST  completion  routine  simply
'schedules' a 'task' to be run which will  process  the  result  of  the
tape  read.  Thus the copy operation is done basically done via internal
multitasking. When the ast  completion  routine  is  entered  it  simply
takes  the ast parameter and uses that to index into the process list to
make a process eligible for execution. It then sets  an  event  flag  to
get  the  scheduler  to  wake  up and scan the process table for someone
runnable. In the interests of generality, the address of the process  to
call  is  placed  into the process table by INIT(), though in reality we
always call the same routine and pass it the process number, which  thus
specifies the buffer, IOSB, and so on that it should access.

*/


tape_to_disk()
{

	short int iosb[4] ;
	int current,i,status ;

	if (((status=create_disk(out_dev.dev_spec)) & 1) == 0) return(status);
	sys$qiow(0,in_dev.dev_lun,IO$_REWIND,0,0,0,0,0,0,0,0,0) ;
	inistats() ;
	printf("Tape dump starting to %s\n",out_dev.dev_spec) ;

	current = 0 ;
	rec_count = 0 ;
	eof_count = 0 ;
	eov = 0 ;
	sys$clref( wakeup_ef ) ;
	if ( ((status=start_tape_dump()) & 1) == 0 ) return( status ) ;
	sys$waitfr( wakeup_ef ) ;


	while ( !eov && ( status & 1 ) ) {
		while ( proc_header[current].state == 0 ) {
		  sys$clref( wakeup_ef ) ;
		  sys$setast(1) ;
		  sys$waitfr(wakeup_ef ) ;
		} ;
		status = (*proc_header[current].readproc) (current) ;
		current =  ++current % qual_buffers ;
		sys$clref( wakeup_ef ) ;
		sys$setast(1) ;
	} ;


	sys$cancel(in_dev.dev_lun) ;
	printstats(file_mark,rec_count) ;
	if ( (status & 1 ) == 0 ) printmsg(status) ;
	if ( status & 1 ) status = verify(in_dev.dev_lun,out_dev.dev_spec) ;
	sys$qiow(0,in_dev.dev_lun,IO$_REWIND+IO$M_NOWAIT,0,0,0,0,0,0,0,0,0) ;
	close_disk() ;
	return(1) ;
}




tape_dump(procnum)
{

	int size , status ;
	char null_buffer[] = "" ;
	char *cp ;


	proc_header[procnum].state = 0 ;
	size = iosblist[procnum][1] ;

	switch ( iosblist[procnum][0] ) {

	  case SS$_ABORT:
	  case SS$_CANCEL:
		status = 1 ;
		break ;

	  case SS$_ENDOFFILE:
		status = 1 ;
		if (hdr_count == 0) eov = ( ++eof_count >= 2 ) ;
		if ( !eov ) {
	          status=sys$qio(eventf[procnum],in_dev.dev_lun,IO$_READLBLK,
			         &iosblist[procnum],&read_qio_ast,procnum+1,
				 proc_header[procnum].bufaddr,qual_blocksize,
				 0,0,0,0) ;
		  file_mark++ ;
		} ;
		size = ( hdr_count ) ? ANSI_NULLFILE:EOF_MARK ;
		if ( status & 1 ) status=write_disk(&null_buffer,size) ;
		break ;

	  case SS$_ENDOFTAPE:
		status = 1 ;
		eov = 1 ;
		break ;

	  case SS$_NORMAL:
		eof_count = 0 ;
		rec_count++ ;
		cp = proc_header[procnum].bufaddr ;
		proc_header[procnum].bufaddr = proc_header[procnum].altaddr ;
		proc_header[procnum].altaddr = cp ;
	        status = sys$qio(eventf[procnum],in_dev.dev_lun,IO$_READLBLK,
			         &iosblist[procnum],&read_qio_ast,procnum+1,
				 proc_header[procnum].bufaddr,qual_blocksize,
				 0,0,0,0) ;

		if ( status & 1 ) status = write_disk(cp,size) ;
		if ( ( status & 1 ) == 0 )
		  printf("%s Err: %x, Size: %d\n",getmsg(status),status,size) ;
		if ( qual_rt11 || (qual_ansi && size == ANSI_HDR_SIZE ))
		  then {
		    if ( strncmp(cp,"HDR2",4) == 0 )
		      then hdr_count++ ;
		      else if ( strncmp(cp,"EOF2",4) == 0 && hdr_count > 0 )
			then hdr_count-- ;
		        else if ( strncmp(cp,"EOV",3) == 0 ) eov = 1 ;
		} ;
		break ;

	  default:
		if(qual_error == 0){
/* normal error handling says end volume here. */
		  status = iosblist[procnum][0] ;
		  eov = 1 ;
		  break ;
		}
		if (qual_error != 0){
/* duplicate ss$_normal processing for misc. errors if error flag was
   given. This will attempt to ignore misc. errors like parity... */
		  eof_count = 0 ;
		  rec_count++ ;
		  cp = proc_header[procnum].bufaddr ;
		  proc_header[procnum].bufaddr = proc_header[procnum].altaddr ;
		  proc_header[procnum].altaddr = cp ;
/* start next $qio */
/* note this will in general reset status to 1 so all will continue. */
	          status = sys$qio(eventf[procnum],in_dev.dev_lun,IO$_READLBLK,
			         &iosblist[procnum],&read_qio_ast,procnum+1,
				 proc_header[procnum].bufaddr,qual_blocksize,
				 0,0,0,0) ;

		  if ( status & 1 ) status = write_disk(cp,size) ;
		  if ( ( status & 1 ) == 0 )
		    printf("%s Err: %x, Size: %d\n",getmsg(status),status,size) ;
		  if ( qual_rt11 || (qual_ansi && size == ANSI_HDR_SIZE ))
		    then {
		      if ( strncmp(cp,"HDR2",4) == 0 )
		        then hdr_count++ ;
		        else if ( strncmp(cp,"EOF2",4) == 0 && hdr_count > 0 )
			  then hdr_count-- ;
		          else if ( strncmp(cp,"EOV",3) == 0 ) eov = 1 ;
		  } ;
		  break ;
		}
	} ;


	if ( eov ) { sys$setast(0) ; sys$cancel( in_dev.dev_lun ) ; } ;
	return( status ) ;
}




start_tape_dump()
{
	int nqio , status ;

	for ( nqio = 0; nqio < qual_buffers; nqio++ ) {

		status = sys$qio(eventf[nqio],in_dev.dev_lun,IO$_READLBLK,
			         &iosblist[nqio],&read_qio_ast,nqio+1,
				 proc_header[nqio].bufaddr,qual_blocksize,
				 0,0,0,0) ;
		if ( ( status & 1 ) == 0 ) break ;
	} ;
	return( status ) ;
}




/*
 AST Completion, used for both tape reads and tape  writes.  Enter  with
the  QIO  number  (+1) that completed. We disable AST delivery, mark the
task table STATE entry to flag that we have something  to  process,  and
then  set  the  event flag to wake up the copy routine. The copy routine
then clears the event flag and enables further ast delivery.
*/

read_qio_ast(param)
int param ;
{
	sys$setast(0) ;
	proc_header[param-1].iopending = 0 ;
	proc_header[param-1].state = 1 ;
	sys$setef(wakeup_ef) ;
}






disk_to_tape()
{

	char *cp, *tp ;
	short int iosb[4] ;
	int i,size,save_skipped,status,waiting ;

	if (((status=open_disk(in_dev.dev_spec)) & 1) == 0) return(status);
	if ( qual_append == 0 || qual_rewind )
	  sys$qiow(0,out_dev.dev_lun,IO$_REWIND,0,0,0,0,0,0,0,0,0) ;
	if (set_flags & SET_DENSITY) set_dens(out_dev.dev_lun,qual_density) ;

	n_files_skipped = 0 ;
	if ( qual_append && qual_rewind )
	  if ( (n_files_skipped = position_eot(out_dev.dev_lun)) == 0 ) {
		printf("?Failure to position tape to logical EOT\n");
		return(0) ;
	  } ;
	save_skipped = n_files_skipped ;

	rec_count = 0 ;
	eof_count = 0 ;
	eov = 0 ;
	printf("Tape dump starting to %s\n",out_dev.dev_spec) ;
	inistats() ;
	if ( ((status=start_tape_write()) & 1) == 0 ) return( status ) ;


	while ( !eov && ( status & 1 ) ) {

		for ( i=0; i < qual_buffers; i++ )
		  if ( proc_header[i].state )
		    status = (*proc_header[i].writeproc) (i) ;

		if ( !eov && ( status & 1 ) ) {
		  sys$waitfr(wakeup_ef) ;
		  sys$clref( wakeup_ef) ;
		  sys$setast(1) ;
		} ;
	} ;

	while (1) {

		waiting = 0 ;
		for ( i=0; i < qual_buffers; i++ )
		  waiting =  waiting | proc_header[i].iopending ;
		if ( waiting == 0 ) break ;
		sys$clref( wakeup_ef) ;
		sys$setast(1) ;
		sys$waitfr(wakeup_ef) ;
	} ;


	if ( save_skipped )
	  printf("%d HDR1 and EOF1 label record SEQUENCE fields modified\n",
		 n_files_skipped-save_skipped) ;

	printstats(file_mark,rec_count) ;
	if ( (status & 1 ) == 0 ) printmsg(status) ;
	sys$qio(0,out_dev.dev_lun,IO$_WRITEOF,0,0,0,0,0,0,0,0,0) ;

	if ( qual_rewind == 0 )
	  sys$qiow(0,out_dev.dev_lun,IO$_SKIPFILE,&iosb,0,0,-2,0,0,0,0,0) ;
	else {
	  if ( status & 1 ) status = verify(out_dev.dev_lun,in_dev.dev_spec) ;
	  sys$qiow(0,out_dev.dev_lun,IO$_REWIND+IO$M_NOWAIT,0,0,0,0,0,0,0,0,0) ;
	} ;

	close_disk() ;
	return(status) ;
}




#define	HDR1_SEQ	32-1

tape_write(procnum)
int procnum ;
{

	int i, param , size , status ;
	int eof1, hdr1 ;
	char *cp ,seq[5] ;

	proc_header[procnum].state = 0 ;

	if ( eov ) return(1) ;
	if ( (status=iosblist[procnum][0]) != SS$_NORMAL ) return(status);
	cp = proc_header[procnum].bufaddr ;
	status = read_disk(cp,&size) ;

	if ( rec_count == 0 && qual_append ) then
	  if ( ansi_check(cp,"VOL1",size) )
	    status = read_disk(cp,&size) ;

	if ( status & 1 )
	  switch ( size ) {

	    case EOF_MARK:
		eov = ( ++eof_count >= 2 ) ;
		status = sys$qio(eventf[procnum],out_dev.dev_lun,IO$_WRITEOF,
				 &iosblist[procnum],&read_qio_ast,
				 procnum+1,0,0,0,0,0,0) ;
		if ( !eov ) file_mark++ ;
		break ;
	    case ANSI_NULLFILE:
		eof_count = 0 ;
		status = sys$qio(eventf[procnum],out_dev.dev_lun,IO$_WRITEOF,
				 &iosblist[procnum],&read_qio_ast,
				 procnum+1,0,0,0,0,0,0) ;
		break ;

	    default:
		eof1 = 0 ;
		hdr1 = 0 ;
		eof_count = 0 ;
		rec_count++ ;
		if ( qual_append && qual_rewind )
		  if ( (hdr1 = ansi_check(cp,"HDR1",size)) ||
		       (eof1 = ansi_check(cp,"EOF1",size)) ) {
			    sprintf(seq,"%04d",n_files_skipped+1) ;
			    for (i=0; i<4; i++) *(cp+HDR1_SEQ+i)=seq[i] ;
			    if ( eof1 ) n_files_skipped++ ;
		  } ;

		status = sys$qio(eventf[procnum],out_dev.dev_lun,IO$_WRITELBLK,
				 &iosblist[procnum],&read_qio_ast,
				 procnum+1,proc_header[procnum].bufaddr,
				 size,0,0,0,0) ;
		break ;
	   } ;

	if ( status & 1 ) proc_header[procnum].iopending = 1 ;
	return( status ) ;
}



start_tape_write()
{
	int i , status ;

	sys$setast(0) ;

	for ( i = 0; i < qual_buffers; i++ ) {
		iosblist[i][0] = SS$_NORMAL ;
		status = tape_write(i) ;
		if ( ( status & 1 ) == 0 || eov ) break ;
	} ;

	sys$setast(1) ;
	return( status ) ;
}



ansi_check(cp,s,n)
char *cp,*s ;
int n ;
{
	if ( qual_rt11 || ((qual_ansi || qual_backup) && n==ANSI_HDR_SIZE) )
	  return( strncmp(cp,s,strlen(s)) == 0 ) ;
	  else return(0) ;
}


#define	SKIPCOUNT	32766

position_eot(lun)
int lun ;
{
	short int iosb[4] ;
	int eov, i, n_files, status ;
	char *cp ;

	eov = 0 ;
	n_files = 0 ;
	hdr_count = 0 ;
	eof_count = 0 ;
	cp = malloc(qual_blocksize) ;

	vol_id[0] = 0 ;
	if ( qual_ansi || qual_backup ) {
	    sys$qiow(0,lun,IO$_READLBLK,&iosb,0,0,cp,qual_blocksize,
		     0,0,0,0) ;
	    if ( iosb[0] == SS$_NORMAL && iosb[1] == ANSI_HDR_SIZE ) {
		for (i=0; i<6; i++) vol_id[i] = *(cp+4+i) ;
		vol_id[6] = 0 ;
	    } ;
	} ;

	if ( qual_ansi == 0 && qual_rt11 == 0 ) {

	    while ( 1 ) {

		sys$qiow(0,lun,IO$_SKIPFILE,&iosb,0,0,SKIPCOUNT,0,0,0,0,0) ;
		switch ( iosb[0] ) {
		  case SS$_NORMAL:
		  case SS$_ENDOFFILE:
			n_files += iosb[1] ;
			break ;
		  case SS$_ENDOFVOLUME:
			n_files += iosb[1] ;
			if ( qual_backup ) return(n_files/3) ;
			  else return(n_files) ;
			break ;
		  default:
			return(0) ;
			break ;
		} ;
	    } ;
	}
	  else {
	    while ( !eov ) {

		sys$qiow(0,lun,IO$_READLBLK,&iosb,0,0,cp,qual_blocksize,
			 0,0,0,0) ;

		switch ( iosb[0] ) {

		  case SS$_ENDOFFILE:
			if (hdr_count == 0) {
			    eov = ( ++eof_count >= 2 ) ;
			    n_files++ ;
			} ;
			break ;

		  case SS$_ENDOFTAPE:
			eov = 1 ;
			break ;

		  case SS$_NORMAL:
			eof_count = 0 ;
			if ( ansi_check(cp,"HDR2",iosb[1]) ) hdr_count++ ;
			  else
			    if ( ansi_check(cp,"EOF2",iosb[1]) ) hdr_count-- ;
			      else if (ansi_check(cp,"EOV",iosb[1])) eov = 1 ;
			break ;

		  default:
			return(0) ;
			break ;
		} ;
	    } ;
	} ;
	sys$qiow(0,lun,IO$_SKIPFILE,&iosb,0,0,-1,0,0,0,0,0) ;
	return(n_files-1) ;
}



/*
	Someone else will have to test this. My TU80 is 1600 only, and
	my CDC 92185's density is set via the drive control panel. The
	code should work.
*/

/*
	Since SYS$LIBRARY:MTDEF.H does not exist, the following are
	taken from STARLET.MLB.
*/

#define	MT$M_DENSITY	7936
#define	MT$K_NRZI_800	3
#define	MT$K_PE_1600	4
#define	MT$K_GCR_6250	5
#define	MT$S_DENSITY	5
#define	MT$V_DENSITY	8


set_dens(lun,density)
int lun ;
{
	struct char_buffer_type { unsigned short int dummy ;
				  unsigned short int size ;
				  unsigned long  int tchars ;
				} tape_chars, sense_chars ;
	short int iosb[4] ;
	int dens,field_pos,field_size,status ;


	status = sys$qiow(0,lun,IO$_SENSEMODE,&sense_chars,0,0,0,0,0,0,0,0) ;

	switch (density) {

		case 800:  dens = MT$K_NRZI_800 ;
			   break ;
		case 1600: dens = MT$K_PE_1600 ;
			   break ;
		case 6250: dens = MT$K_GCR_6250 ;
			   break ;
		default:   return(0) ;
			   break ;
	} ;
	field_pos = MT$V_DENSITY ;
	field_size = MT$S_DENSITY ;
	lib$insv(&dens,&field_pos,&field_size,&tape_chars.tchars) ;
	tape_chars.dummy = 0 ;
	tape_chars.size  = qual_blocksize ;
	status = sys$qiow(0,lun,IO$_SETMODE,0,0,0,&tape_chars,0,0,0,0,0) ;

	printf("Status from IO$_SETMODE for density: %x\n",status) ;

	return( status ) ;
}



parse(dev,s,def_string)
struct devdsc *dev ;
char *s ;
{
	char *cp,*dp ;
	int i,status,temp1,temp2,temp3,tempchan ;
	struct dsc devname ;
	struct itmlst dvilist[4] ;
	int devtype, devclass ;

	parse_fab = cc$rms_fab ;
	parse_nam = cc$rms_nam ;
	dp = dev->dev_spec ;
	parse_fab.fab$l_nam = &parse_nam ;
	parse_fab.fab$l_fna = s ;
	parse_fab.fab$b_fns = strlen(s) ;
	parse_nam.nam$l_esa = dp ;
	parse_nam.nam$b_ess = 127 ;
	parse_nam.nam$b_nop = NAM$M_NOCONCEAL ;
	if ( ((status=sys$parse(&parse_fab)) & 1) == 0 ) return( status ) ;
	*(dp + (parse_nam.nam$b_esl&0377) ) = 0 ;

	if ( parse_nam.nam$l_fnb & NAM$M_NODE ) {
	  node_found = 1 ;
	  dev->dev_class = DC$_MISC ;
	  return(1) ;
	} ;

	cp = &parse_nam.nam$t_dvi ;
	devname.len  = *cp++ ;
	devname.addr = cp ;
	for (dp=dev->dev_name,i=0; i<devname.len; i++) *dp++ = *cp++ ;
	*dp = 0 ;
	if ( ((status=sys$assign(&devname,&tempchan,0,0)) & 1) == 0 )
	  then return( status ) ;
	dev->dev_lun = tempchan ;
	dvilist[0].bufferlength	= 4 ;
	dvilist[0].item_code	= DVI$_DEVCLASS ;
	dvilist[0].addr.intbuf	= &dev->dev_class ;
	dvilist[0].retlength	= &temp1 ;
	dvilist[1].bufferlength	= 4 ;
	dvilist[1].item_code	= DVI$_DEVTYPE ;
	dvilist[1].addr.intbuf	= &dev->dev_type ;
	dvilist[1].retlength	= &temp2 ;
	dvilist[2].bufferlength	= 4 ;
	dvilist[2].item_code	= DVI$_DEVCHAR ;
	dvilist[2].addr.intbuf	= &dev->dev_char ;
	dvilist[2].retlength	= &temp3 ;
	dvilist[3].bufferlength = 0 ;
	dvilist[3].item_code	= 0 ;
	status = sys$getdviw(0,tempchan,0,&dvilist,0,0,0,0) ;

	if ( ( status & 1 ) && dev->dev_class == DC$_TAPE )
	  then
	    if ((dev->dev_char & DEV$M_MNT) == 0 )
		then status = SS$_DEVNOTMOUNT ;
		else if ((dev->dev_char & DEV$M_FOR) == 0 )
		  then	{
			status = 0 ;
			printf("%Tape device must be mounted foreign\n") ;
			} ;

	return( status ) ;
}




printmsg(n)
int n ;
{
	printf("%s\n",getmsg(n)) ;
}


char *getmsg(n)
int n ;
{
	struct dsc msgd ;
	int mlen ;
	char junk[4] ;

	mlen = 0 ;
	msgd.len = 256 ;
	msgd.addr = malloc(256) ;
	sys$getmsg(n,&mlen,&msgd,0,&junk) ;
	*(msgd.addr + (mlen&0377)) = 0 ;
	return( msgd.addr ) ;
}




init()
{
	int i ;

	lib$get_ef( &wakeup_ef ) ;

	for (i=0; i<qual_buffers; i++) {
	  if ( (buffer_list[i] = malloc(qual_blocksize)) == 0 ||
	       (altbuf_list[i] = malloc(qual_blocksize)) == 0 )
	    then {
		printf("Allocation failure on TAPE buffers from MALLOC()\n");
		return(0) ;
		 } ;
	  proc_header[i].state = 0 ;
	  proc_header[i].bufaddr = buffer_list[i] ;
	  proc_header[i].altaddr = altbuf_list[i] ;
	  proc_header[i].readproc = &tape_dump ;
	  proc_header[i].writeproc = &tape_write ;
	  proc_header[i].iodone = 0 ;
	  proc_header[i].iopending = 0 ;
	  proc_header[i].efn = i + 1 ;
	  proc_header[i].param = i + 1 ;
	  proc_header[i].endoflist = 0 ;
	  lib$get_ef(&eventf[i]) ;
	} ;
	proc_header[qual_buffers].endoflist = -1 ;

	return(1) ;
}





/*	Interface to LIB$SHOW_TIMER	*/


static int handler ;

inistats()
{
	lib$init_timer(&handler) ;

}


cpuformat(arg,s)
char *s ;
struct dsc *arg ;
{
	arg->len &= 0377 ;
	strncpy(s,arg->addr,arg->len) ;
	*(s+arg->len) = 0 ;
}


printstats(fc,n)

int fc,n;
{
	char s[80] ;
	int code = 2 ;

	lib$show_timer(&handler,&code,&cpuformat,s) ;
	printf("File marks: %d   Records: %d   %s\n",fc,n,s) ;
}


accstats()
{
	lib$show_timer(&handler) ;
	lib$init_timer(&handler) ;

}




/*	CLI interfacing	*/

getparam(s,arg)
char *s;
struct dsc$descriptor_s *arg ;
{

	struct dsc out ;
	int retlength ;

	out.len = 128 ;
	out.addr = s ;


	*s = 0 ;
	if ( (cli$present( arg ) & 1) &&
	     (cli$get_value( arg,&out,&retlength ) & 1 ))
	  then  {
		*(s+(retlength&0377)) = 0 ;
		return(1) ;
		}
	  else  return(0) ;

}


setqual(arg,def)
struct dsc$descriptor_s *arg ;
int def ;
{
	int status ;
	status = cli$present(arg) ;
	if ( status == CLI$_PRESENT || status == CLI$_LOCPRES )
	  return(1) ;
	  else
	    if ( status == CLI$_NEGATED || status == CLI$_LOCNEG )
	      return(0) ;
	      else return(def) ;

}


getqual_value(arg)
struct dsc$descriptor_s *arg ;
{

	char valbuf[128] ;
	struct dsc out = { 128,&valbuf } ;
	int retlength,val ;


	*valbuf = 0 ;
	if ( (cli$present( arg ) & 1) &&
	     (cli$get_value( arg,&out,&retlength ) & 1 ))
	  then  {
		*(valbuf+(retlength&0377)) = 0 ;
		return( (sscanf(valbuf,"%d",&val)) ? val:0 ) ;
		}
	  else  return(0) ;

}



/*	Disk Input and Output	*/





/*	The disk image file
*/



struct FAB disk_image_fab ;
struct RAB disk_image_rab ;

int inisiz = 2000 ;
int deqsiz = 256 ;
char default_outputname[] = "SYS$LOGIN:VAX_TPC.DATA" ;


init_fab(fname)
char *fname ;
{
	disk_image_fab = cc$rms_fab ;
	disk_image_fab.fab$l_alq = (qual_allocation) ?qual_allocation:inisiz ;
	disk_image_fab.fab$w_deq = (qual_extendsize) ?qual_extendsize:deqsiz ;
	disk_image_fab.fab$l_dna = &default_outputname ;
	disk_image_fab.fab$b_dns = strlen(default_outputname) ;
	disk_image_fab.fab$l_fna = ( disk_image_fab.fab$b_fns = strlen(fname) )
				   ? fname:0 ;
	disk_image_fab.fab$w_mrs = qual_blocksize ;
	disk_image_fab.fab$b_org = FAB$C_SEQ ;
	disk_image_fab.fab$b_rfm = FAB$C_VAR ;
	disk_image_fab.fab$b_shr = FAB$M_NIL ;

	disk_image_rab = cc$rms_rab ;
	disk_image_rab.rab$l_fab = &disk_image_fab ;
	disk_image_rab.rab$b_mbc = 32 ;
	disk_image_rab.rab$b_mbf = 8 ;
	return(1) ;
}

create_disk(s)
char *s ;
{
	int sts ;

	init_fab(s) ;
	if ( ( (sts = sys$create(&disk_image_fab)) & 1 ) == 0 ) return(sts);
	return( sys$connect(&disk_image_rab) ) ;
}

open_disk(s)
char *s ;
{
	int sts ;
	init_fab(s) ;
	disk_image_fab.fab$b_shr = FAB$M_SHRGET ;
	if ( ( (sts = sys$open(&disk_image_fab)) & 1 ) == 0 ) return(sts);
	return( sys$connect(&disk_image_rab) ) ;
}


read_disk(buffer,size)
char *buffer ;
int *size ;
{
	int sts ;

	disk_image_rab.rab$l_ubf = buffer ;
	disk_image_rab.rab$w_usz = qual_blocksize ;
	sts = sys$get( &disk_image_rab ) ;
	*size = ( sts & 1 ) ? disk_image_rab.rab$w_rsz : 0 ;
	if ( sts == RMS$_RTB && node_found ) {
	  printf("Decnet buffer overrun possible. Check SYSGEN ") ;
	  printf("parameter RMS_DFNBC\n") ;
	} ;
	return( sts ) ;
}

write_disk(buffer,size)
char *buffer ;
int size ;
{
	int sts ;

	disk_image_rab.rab$l_rbf = buffer ;
	disk_image_rab.rab$w_rsz = size ;
	return( sys$put( &disk_image_rab ) ) ;
}


close_disk()
{
	int sts ;

	if ( (sts = sys$disconnect( &disk_image_rab )) & 1 )
	  then sts = sys$close( &disk_image_fab ) ;
	return( sts ) ;
}




char *r50toa(dst,r50val)
char *dst ;
unsigned short int *r50val ;
{
	char *cp ;
	char rlist[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ$.?0123456789 " ;
	unsigned short int val ;

	val = *r50val ;
	cp = dst ;
	*cp++ = rlist[ val/03100 ] ; val = val % 03100 ;
	*cp++ = rlist[ val/050 ] ;
	*cp++ = rlist[ val % 050 ] ;
	*cp++ = 0 ;
	return(dst) ;
}
