	.title	recover2 recover files from damaged ODS-2 disk
	.ident	/v01/

;+
; Recover2 recovers files from a damaged ODS-2 disk, using only the
; logical block number of the file's header in what is left of the
; index file.  Another program, scan2, scans the whole disk looking
; for blocks that could be file headers and writes LBNs, FIDs, file
; names, and a few attributes in a sequential file called INDEXF.SEQ.
;
; That file is converted to an indexed file called INDEXF.IDX and
; massaged by Datatrieve to become input to this program.
;
; This program uses that file to get selected file headers from the
; damaged disk by LBN and rebuild the files on a good, mounted disk.
; 
; Anthony E. Scandora, Jr.
; Science Applications International Corp.
; October 6, 1984
;-
	.psect	data,noexe,long
;
; Parameters unique to the disk being recovered
; These should be determined at run time, but I'm in a hurry
;
dsksiz	=	891072			; size of an RA81
innam:	.ascid	/DUK1:/			; name of volume to be scanned
	.align	long
;
; Register usage:
;
; r10	number of blocks left to process in current buffer
; r9	pointer to current header block
; r8	id area in current header
; r7	map area in current header
; r6	access control area in current header
; r0-5	scratched by movc3 and used for temps
;
; r11	working VBN
; r10	working block count
; r7	current retrieval pointer
; r6	VBN of current retrieval pointer
; r5	LBN of current retrieval pointer
; r4	size of current retreival pointer
; r3	number of words of retrieval pointers left
;
; Definitions that do not have to be changed for every disk
;
	.library /fhdo2/		; define file header offsets
	.mcall	fhdo2$
	fhdo2$
	$atrdef				; define FIB attributes

idxfab:	$fab	fac=get,fnm=<INDEXF.IDX>
idxrab:	$rab	fab=idxfab,ubf=idxrec,usz=idxsiz

;outfab:	$fab	dnm=<RECOVERED:>,fac=<bio,put>,fna=hdfnam,fns=20,-
;		xab=outall
;outall:	$xaball	nxt=outdat
;outdat:	$xabdat	nxt=outpro
;outpro:	$xabpro	nxt=outrdt
;outrdt:	$xabrdt	
;outrab:	$rab	fab=outfab

outfab:	$fab	dnm=<RECOVERED:>,fac=<bio,put>,fna=hdfnam,fns=20,fop=ufo
outfib:	.word	0,0
	.address 10$
10$:	.blkb	0
outatt:	.word	atr$s_recattr,atr$c_recattr
	.address header+hdr2$_ufat
	.word	atr$s_credate,atr$c_credate
outcrd:	.address 0
	.word	atr$s_revdate,atr$c_revdate
outrvd:	.address 0
	.word	atr$s_expdate,atr$c_expdate
outexd:	.address 0
	.word	atr$s_bakdate,atr$c_bakdate
outbkd:	.address 0
	.word	atr$s_uic,atr$c_uic
	.address header+hdr2$l_fown
	.word	atr$s_fpro,atr$c_fpro
	.address header+hdr2$w_fpro
	.long	0

iniosb:	.blkw	4			; I/O status block for input channel
incnt:	.blkl				; count of bytes read

assin:	$assign	devnam=innam,chan=rdqio+qiow$_chan
rdefn	=	1			; efn for reading input disk
rdqio:	$qiow	rdefn,,io$_readlblk,iniosb,,,blocks
rdhdr:	$qiow	rdefn,,io$_readlblk,iniosb,,,header,512

ouiosb:	.blkw	4			; I/O status block for output channel
oucnt:	.blkl				; count of bytes written
outsiz:	.blkl				; allocated size of file
wrefn	=	2			; efn for writing output file
wrqio:	$qiow	wrefn,,io$_writevblk,ouiosb,,,blocks
deacou:	$qiow	wrefn,,io$_deaccess,ouiosb,,,outfib,,,,outatt

msgdsc:	.blkl				; actual size of message buffer
	.address msgbuf
msgds0:	.long	132			; msgbuf size for $fao input
	.address msgbuf
msgbuf:	.blkb	132			; output message buffer
;
; record from indexf.seq
;
idxrec:
hdlbn:	.blkl				; LBN of current header
hdfnum:	.blkl				; file number of current header
hdfseq:	.blkw				; sequence number of current header
hdfseg:	.blkw				; file segment number
hdefnu:	.blkl				; file number of extension header
hdefsq:	.blkw				; sequence number of extension header
status:	.blkb				; blank or '-' if deleted
direct:	.blkb				; blank or 'D' if directory file
hdbfnu:	.blkl				; file number of back pointer
hdbfsq:	.blkw				; file sequence number of back pointer
hdfnam:	.blkb	20			; file name
hdcrdt:	.blkw	4			; file creation date
hdefbk:	.blkl				; logical end of file block
hdhibk:	.blkl				; high allocated block number

idxsiz	=	.-idxrec		; indexf.idx record size

namdsc:	.long	20			; filename descriptor
	.address hdfnam			; for filmsg
filmsg:	.ascid	/!AS !UL/
cnterr:	.ascid	/Read !UL bytes, wrote !UL bytes for !UL block file/

	.psect	buffer,noexe,page
header:	.blkb	512			; file header block
bufsiz	=	1000			; do bufsiz blocks at a time
blocks:	.blkb	bufsiz*512		; buffer for those blocks
	.page
	.sbttl	the main program

	.psect	recover2,nowrt,nord

	.entry	recover2,^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>

	callg	assin,g^sys$assign	; assign channel to input disk
	blbs	r0,20$
	pushl	r0			; complain if no good
	calls	#1,g^lib$stop
20$:	movw	rdqio+qiow$_chan,rdhdr+qiow$_chan
	$open	idxfab			; open index file
	blbs	r0,30$
	pushl	r0
	calls	#1,g^lib$stop
30$:	$connect idxrab			; connect rab to index file
	blbs	r0,100$
	pushl	r0
	calls	#1,g^lib$stop
100$:	$get	idxrab			; get another record
	blbs	r0,110$
	ret				; exit when no more records
110$:	cmpb	direct,#^a/D/		; ignore directory files
	beqlu	100$
	cmpb	status,#^a/!/		; just do files with ! in status field
	bnequ	100$
	movl	hdlbn,rdhdr+qiow$_p3	; read header block
	callg	rdhdr,g^sys$qiow
	blbc	r0,118$			; check system status
	movzwl	iniosb,r0		; check I/O status
	blbs	r0,120$
118$:	pushl	r0			; complain about read error
	calls	#1,g^lib$stop
;
; got a header, see if it's to be recovered and read its map
;
120$:	movab	header,r9		; get header pointer
	tstw	hdr2$w_fnum(r9)		; is header deleted?
	beqlu	110$			; if so, ignore it
	movzbl	(r9),r0			; get id area pointer
	movaw	(r9)[r0],r8
	jsb	mapini			; set up map area
130$:	tstl	r3			; if map words all gone, done
	bleq	140$
	jsb	getmap			; get next pointer
	brb	130$			; and get next pointer
140$:	addl	r4,r6			; count last pointer
	decl	r6			; VBN is off the end of the file
	movl	r6,outsiz		; save because $create writes alq
	pushl	r6			; write file size
	pushaq	namdsc			; and name
	pushaq	msgds0			; push full size messsage buffer
	pushaw	msgdsc			; push pointer to actual size
	pushaq	filmsg			; push format string
	calls	#5,g^sys$fao		; format the error message
	blbc	r0,141$			; complain if FAO failed
	pushaq	msgdsc			; print file message
	calls	#1,g^lib$put_output
	blbs	r0,142$			; if put failed, die with its error
	movl	#4,r0			; otherwise, die with fatal error
141$:	pushl	r0			; push error code
	calls	#1,g^lib$stop		; and die
142$:
	.page
	.sbttl	create the file

	.if defined never		; this turned out to be a mess

; I was going to copy all the stuff from the input header to XABs
; and let RMS do a block copy.  It's too scattered, and there are
; a lot of funny cases, like there is a special xab if you really
; want to do a revision date.  I changed it to UFO access and did
; my own QIOs.

	movl	r6,outall+xab$l_alq	; put actual length of file in xab
	clrb	outall+xab$b_aop	; start with no allocation options
	bitb	#hdr2$m_ucha_con,hdr2$b_ucha(r9) ; make it contiguous?
	beqlu	143$
	bisb	#xab$m_ctg,outall+xab$b_aop
143$:	bitb	#hdr2$m_ucha_cnb,hdr2$b_ucha(r9) ; or contig. best try?
	beqlu	146$
	bisb	#xab$m_cbt,outall+xab$b_aop
146$:	movb	hdr2$_ufat+00(r9),outfab+fab$b_org ; file organization
	movb	hdr2$_ufat+01(r9),outfab+fab$b_rat ; record attributes
	movb	hdr2$_ufat+14(r9),outall+xab$b_bkz ; bucket size
	movb	hdr2$_ufat+15(r9),outfab+fab$b_fsz ; fixed control area size
	movw	hdr2$_ufat+16(r9),outfab+fab$w_mrs ; maximum record size
	movw	hdr2$_ufat+18(r9),outall+xab$w_deq ; default extend quantity
	movq	id2$w_rvno(r8),outdat+xab$w_rvn ; revision number
	movq	id2$q_crdt(r8),outdat+xab$q_cdt ; creation date
;	movq	id2$q_rvdt(r8),outdat+xab$q_rdt ; revision date
	clrq	outdat+xab$q_rdt		; revision date
	movq	id2$q_exdt(r8),outdat+xab$q_edt ; expiration date
;	movq	id2$q_bkdt(r8),outdat+xab$q_bdt ; last backup date
	clrq	outdat+xab$q_bdt		; last backup date
	movl	hdr2$_ufat+999(r9),outpro+xab$l_uic ; owner UIC
	movw	hdr2$_ufat+999(r9),outpro+xab$w_pro ; file protection

	.iff	;df never		; use ACP attribute block

; This is easier.  Just copy a few essentials like the size of the
; file and contiguous bits to the FAB and open for UFO access.

	movl	r6,outfab+fab$l_alq	; size of file from retrieval pointers
	movl	#fab$m_ufo,outfab+fab$l_fop ; start with no allocation options
	bitb	#hdr2$m_ucha_con,hdr2$b_ucha(r9) ; make it contiguous?
	beqlu	143$
	bisl	#fab$m_ctg,outfab+fab$l_fop
143$:	bitb	#hdr2$m_ucha_cnb,hdr2$b_ucha(r9) ; or contig. best try?
	beqlu	146$
	bisl	#fab$m_cbt,outfab+fab$l_fop
146$:	movaq	id2$q_crdt(r8),outcrd	; put addresses in attribute block
	movaq	id2$q_rvdt(r8),outrvd
	movaq	id2$q_exdt(r8),outexd
	movaq	id2$q_bkdt(r8),outbkd

	.endc
	clrl	incnt			; clear the byte counters
	clrl	oucnt

	$create	outfab			; create file
	blbs	r0,200$
	pushl	r0			; complain if file create error
	calls	#1,g^lib$stop
200$:	movw	outfab+fab$l_stv,wrqio+qiow$_chan ; write channel to wrqio
	movw	outfab+fab$l_stv,deacou+qiow$_chan ; write channel to deacou
	.page
	.sbttl	copy the blocks and deaccess the file

	jsb	mapini			; set up pointers for getmap
210$:	tstl	r3			; any retrieval pointers left?
	bgtr	215$
	jmp	300$
215$:	jsb	getmap			; get next retrieval pointer
	movab	blocks,rdqio+qiow$_p1	; buffer
220$:	movl	r4,r10			; number of blocks for this pointer
	cmpl	r10,#bufsiz		; will it fit?
	blequ	222$
	movl	#bufsiz,r10		; enforce maximum buffer length
222$:	ashl	#9,r10,rdqio+qiow$_p2	; byte count
	movl	r5,rdqio+qiow$_p3	; LBN
	callg	rdqio,g^sys$qiow	; read the blocks
	blbc	r0,225$			; check system status
	movzwl	iniosb,r0		; check I/O status
	blbs	r0,230$
225$:	pushl	r0			; complain about read error
	calls	#1,g^lib$stop
230$:	addl	iniosb+2,incnt		; count the bytes read
	movl	rdqio+qiow$_p2,wrqio+qiow$_p2 ; number of bytes to write
	movl	r6,wrqio+qiow$_p3	; VBN to write to
	callg	wrqio,g^sys$qiow	; write the blocks
	blbc	r0,237$			; check system status
	movzwl	ouiosb,r0		; check I/O status
	blbs	r0,240$			; do next group of blocks
237$:	pushl	r0			; complain about write error
	calls	#1,g^lib$stop
240$:	addl	ouiosb+2,oucnt		; count the bytes written
	addl	r10,r5			; bump LBN
	addl	r10,r6			; bump VBN
	subl	r10,r4			; count the blocks
	bgtr	250$			; get the rest of a big retrieval pointer
	brw	210$			; get the next retreival pointer
250$:	brw	220$
300$:	ashl	#9,outsiz,r0		; compute length of file in bytes
	cmpl	incnt,r0		; check number of bytes read
	bnequ	305$
	cmpl	oucnt,r0		; check number of bytes written
	beqlu	306$
305$:	pushl	outsiz			; push file length
	pushl	oucnt			; push out count
	pushl	incnt			; push in count
	pushaq	msgds0			; push full size messsage buffer
	pushaw	msgdsc			; push pointer to actual size
	pushaq	cnterr			; push format string
	calls	#6,g^sys$fao		; format the error message
	blbc	r0,3055$		; if FAO failed, die with its error
	pushaq	msgdsc			; print byte count error message
	calls	#1,g^lib$put_output
	blbc	r0,3055$		; if put failed, die with its error
	movl	#4,r0			; otherwise, die with fatal error
3055$:	pushl	r0			; push error code
	calls	#1,g^lib$stop		; and die
306$:	callg	deacou,g^sys$qiow	; deaccess file
	blbc	r0,307$			; check system status
	movzwl	ouiosb,r0		; check I/O status
	blbs	r0,310$
307$:	pushl	r0			; complain if deaccess error
	calls	#1,g^lib$stop
310$:	$dassgn_s wrqio+qiow$_chan	; deassign channel
	blbs	r0,320$
	pushl	r0			; complain if deassign error
	calls	#1,g^lib$stop
320$:	brw	100$			; do next one

	.page
	.sbttl	retrieval pointer routines

	.psect	mapini,nord,nowrt

mapini:	movzbl	1(r9),r1		; get map area pointer
	movaw	(r9)[r1],r7
	movzbl	hdr2$b_use(r9),r3	; number of words of pointers in use
	movl	#1,r6			; first VBN in file
	clrl	r4			; starting with no blocks
	rsb

	.psect	getmap,nord,nowrt

getmap:	addl	r4,r6			; bump VBN of current file
	tstl	r3			; any map words left?
	blequ	505$
	movzwl	(r7),r0			; get first word of retrieval pointer
	ashl	#-14,r0,r0		; get type bits
	caseb	r0,#0,#3
410$:	.word	500$-410$,510$-410$,520$-410$,530$-410$

500$:	tstw	(r7)+			; placement control, just eat the word
	decl	r3			; and count it
505$:	clrl	r4			; return nothing
	rsb

510$:	movzbl	(r7)+,r4		; format 1 - four bytes, get count
	incl	r4
	movzbl	(r7)+,r5		; get high bits of LBN
	bicb	#64,r5			; turn off format bit
	ashl	#16,r5,r5		; put high bits in high bits of r5
	bisw	(r7)+,r5		; or in low word of LBN
	subl2	#2,r3			; count two words
	rsb

520$:	movzwl	(r7)+,r4		; format 2 - six bytes, get count
	bicw	#^x8000,r4		; turn off format bit
	incl	r4
	movl	(r7)+,r5		; get LBN
	subl2	#3,r3			; count three words
	rsb

530$:	movzwl	(r7)+,r4		; format 3 - eight bytes, get high count
	bicw	#^xC000,r4		; turn off format bits
	ashl	#16,r4,r4		; put high bits up high
	bisw	(r7)+,r4		; or in low bits of LBN
	incl	r4
	movl	(r7)+,r5		; get LBN
	subl2	#4,r3			; count four wordsOM
	rsb

	.end	recover2
