	subroutine rpl

C.. RPL - Replace File Copy.
C..
C.. Copyright (c) 1987, Richard A. Holmes (aka Holmes Compleat Computing)
C.. 4845 San Sebastian Avenue
C.. Las Vegas, Nevada 89121
C.. (702) 458-4933
C..
C.. Public domain.  Permission is hereby granted for non-commercial use
C.. and distribution of this program.  No warranties, guarantees or promises
C.. are made as to its functioning.  It works for me.  If you find it 
C.. useful, you may feel free to send $5.00 to the above address.
C..
C.. This program is used to copy a disk file to another disk file.
C.. If the destination file already exists, it will be overwritten with
C.. the new one.  It is neither first deleted and then created, nor is 
C.. any renaming performed.  This is useful to copy new versions of 
C.. programs onto a carefully laid out disk.  If the new file is longer
C.. than the old one, first (nominally) 4K of disk space is reserved
C.. (presumably on the inner tracks) and then the new sectors will
C.. come from CP/Ms standard allocation of disk space.  If the old file
C.. is longer than the new file, unused sectors will exist at the end
C.. of the file.

C.. If you modify this code, please don't use REAL variables or
C.. any sort of FORTRAN Reads or Writes.

	include rpl.dcl
	integer fcount
	include rpl.blk
	data fcount /0/, nomore /.false./

C.. Say hi.
	call strout('RPL - Replace Copy V1.0 $')
	call strend('   (c) 1987, Holmes Compleat Computing$')
C.. General program initialization.
	call inirun
C.. Copy the file(s).
100	continue
	call setfil(nomore)
	if (nomore) go to 200
	call cpyini
	call copy
	fcount = fcount + 1
	if (ambig) go to 100

C.. That's all folks.
200	continue
	call endrun
	end


	subroutine copy

C.. This feller does the copying of the file.

	integer size,inrec,outrec,maxsec,curptr,cur1,cursec
	byte rsectr,stat,wsectr
	include rpl.dcl
	include rpl.blk

C.. Open the input file.
	call setusr(iuser)
	call opin
C.. Open the output file.
	call setusr(ouser)
	call opout
C.. Initialize the counters.
	maxsec = 100
	inrec = -1
	outrec = -1
90	continue
	call setusr(iuser)
	curptr = -128
	cursec = 0
100	continue
	curptr = curptr + 128
	if (curptr.ge.12800) go to 110
	inrec = inrec + 1
	stat = rsectr(curptr,inrec)
	if (stat.lt.0) go to 900
	if (stat.gt.0) go to 800
	cursec = cursec + 1
	go to 100

C.. The buffer filled up.  Write it to disk.
110	continue
	call setusr(ouser)
	curptr = -128
	do 120 i = 1,maxsec
	outrec = outrec + 1
	curptr = curptr + 128
	stat = wsectr(curptr,outrec)
	if (stat.ne.0) go to 700
115	continue
120	continue
	go to 90

C.. Error on writing.
700	continue
	call ioerr(stat)
	call chrout(10)
	call chrout(13)
	call strout (' Unknown I/O error $')
	call decbyt(stat)
	call strout(' in writing sector $')
	call decint(outrec)
	call strend('$')
	call endrun

C.. Error on reading.
800	continue
	call ioerr(stat)
	call strout (' Unknown I/O error $')
	call decbyt(stat)
	call strout(' in reading sector $')
	call decint(outrec)
	call strend('$')
	call endrun

C.. End of file on input.  Flush out the buffer.
900	continue
	call setusr(ouser)
	inrec = inrec - 1
	if (curptr.le.0) go to 920
	curptr = -128

	do 910 i = 1,cursec
	curptr = curptr + 128
	outrec = outrec + 1
	stat = wsectr(curptr,outrec)
	if (stat.ne.0) go to 700
910	continue

C.. Close the output file.
920	continue
	call setusr(ouser)
	call clout
C.. Report on status of copy.
	if (insize.eq.osize) go to 930
	call strout('  --  $')
	if (insize.gt.osize) go to 940
	if ((osize-insize).ne.1) go to 925
	call strend('1 unused sector exists.$')
	go to 990

925	continue	
	call decint(osize - insize)
	call strend(' unused sectors exist.$')
	go to 990

C.. Both files had the same size.
930	continue
	if (insize.ne.0) go to 935
	call strend('  --  File is vacuous.$')
	go to 990

935	continue
	call strend('$')
	go to 990

940	continue
	if (insize-osize.ne.1) go to 950
	call strend('1 new sector written.$')
	go to 990

950	continue
	call decint(insize-osize)
	call strend(' new sectors written.$')
990	continue
	return
	end


	subroutine ioerr(stat)

C.. Show what an I/O error means.  End program if code is known.
C.. Return otherwise.

	byte stat

	if ((stat.lt.1).or.(stat.gt.6)) return
	call chrout(13)
	call chrout(10)
	call strout(' I/O error - $')
	if (stat.eq.1) call strend
     +	        ('reading unwritten data.$')
	if (stat.eq.2) call strend
     +		('disk is full.$')
	if (stat.eq.3) call strend
     +		('cannot close current extent.$')
	if (stat.eq.4) call strend
     +		('seek to unwritten extent.$')
	if (stat.eq.5) call strend
     +		('directory space exhaused.$')
	if (stat.eq.6) call strend
     +		('seek past physical end of disk.$')
	call endrun
	end


	subroutine inirun

C.. Parse the command string.

	include rpl.dcl
	byte blank,peek,sfirst,status,str(7),stat
	byte svfile(11)
	integer start,maxpos,ksize,fsize,reserv
	logical iambig,oambig,badopt
	common /svfile/svfile /reserv/ reserv
	include rpl.blk

	include rpl.dat
	data blank /1h /
	data str /6*1h ,1h$/
	data ambig /.false./

C.. Start picking characters from 82H in memory.
	start = x'82'

C.. Set the maximum position to be examined.
	maxpos = peek(x'80')
	maxpos = maxpos + 128
C.. Get the output file specification.
	call fspec(outfil,outdev,start,maxpos,ouser,oambig)
	if (oambig) go to 80
	if ((outdev.eq.0).and.(outfil(1).eq.blank)) go to 80
C.. Get the input file specifications.
	call fspec(infile,indev,start,maxpos,iuser,iambig)
	if (.not.iambig) go to 60
	if (outfil(1).ne.blank) go to 80
	ambig = .true.
	do 50 i = 1,11
	svfile(i) = infile(i)
50	continue

C.. The source file may not be omitted.
60	continue
	if (infile(1).ne.blank) go to 90
C.. Give mini-help on syntax type errors.
80	continue
	call help
	call endrun

C.. If the destination file was omitted, copy the input file spec.
90	continue
	if (outfil(1).ne.blank) go to 200
	do 100 i = 1,11
	outfil(i) = infile(i)
100	continue

C.. Check out the options.

200	continue
	badopt = .false.
   	call setopt(start,maxpos,badopt)
	if (badopt) go to 80
	if (ambig) call makres(reserv)
	return
	end


	subroutine cpyini

C.. Initialize for a file copy.

	byte status,sfirst
	integer fsize,reserv
	include rpl.dcl
	common /reserv/ reserv
	include rpl.blk

C.. See if the input file exists.
200	continue
	call setusr(iuser)
	status = sfirst(indev,infile)
	if (status.eq.-1) call error(' No files matched.$')

300	continue
	osize = 0
	insize = fsize(indev,infile)

C.. Determine how big the output file is now.
	call setusr(ouser)
	status = sfirst(outdev,outfil)
	if (status.eq.-1) go to 350
	osize = fsize(outdev,outfil)
350	continue
	call strout(' ($')
	call decint((insize+7)/8)
	call strout('k)$')
C.. Reserve space if necessary.
	if ((.not.ambig).and.(insize.gt.osize)) call makres(reserv)
	return
	end


	subroutine fspec(filnam,device,start,maxpos,usrnum,ambig)

C.. This routine will extract a file specification from the command line.

	byte filnam(11),device,peek,equals,blank,dot,aa,char,colon
	byte usrnum,aster,hook
	integer start,pos,maxpos
	logical ambig
	data equals,blank,dot /1h=,1h ,1h./, aa /1hA/, colon /1h:/
	data aster,hook /1h*,1h?/

	ambig = .false.
	do 50 i = 1,11
	filnam(i) = blank
50	continue

C.. POS will reflect the current position in the command line.
	pos = start
C.. Get the device and user number.
	call getdu(device,usrnum,pos,maxpos)
	if (pos.gt.maxpos) go to 600
C.. Gather up the file name.
	do 300 i = 1,9
	char = peek(pos)
C.. Check for ambiguous file name.
	if (char.eq.hook) ambig = .true.
	if (char.ne.aster) go to 70
	do 60 ii = i,8
	filnam(ii) = hook
60	continue
	ambig = .true.
	go to 80

C.. Blanks and equals terminate the file name.
70	continue
	if ((char.eq.blank).or.(char.eq.equals)) go to 500
C.. Dots start the extension.
	if (char.eq.dot) go to 400
	filnam(i) = char
80	continue
	pos = pos + 1
	if (pos.gt.maxpos) go to 600
300	continue
	call error(' File name is too long.$')
C.. Put together the file extension.
400	continue
	pos = pos + 1
	if (pos.gt.maxpos) go to 600
	do 450 i = 1,3
	char = peek(pos)
	if (char.eq.hook) ambig = .true.
	if (char.ne.aster)go to 420
	do 410 ii = i,3
	filnam(ii+8) = hook
410	continue
	ambig = .true.
	go to 430

420	continue
	if ((char.eq.blank).or.(char.eq.equals)) go to 500
	filnam(i+8) = char
430	continue
	pos = pos + 1
	if (pos.gt.maxpos) go to 600
450	continue

C.. The end of the file spec was found.
500	continue
600	continue
	pos = pos + 1
510	continue
	start = pos
	return
	end


	subroutine getdu(device,usrnum,pos,maxpos)

C.. Get device and usernumber of a file.

	byte device,usrnum,char,peek,char0,char9,chara,charp,colon
	integer pos,maxpos
	data char0,char9,chara,charp,colon /1h0,1h9,1hA,1hP,1h:/

C.. Default drive.
	call getdev(device)
C.. Get current user number.
	call getusr(usrnum)
	if (pos.gt.maxpos) go to 900
C.. We should see a colon in the first four characters.
	do 10 i = 0,3
	if (peek(pos+i).eq.colon) go to 20
10	continue
	go to 900

C.. Get the user number and device.
20	continue
	if (pos.gt.maxpos)go to 900
	char = peek(pos)
	pos = pos + 1
	if (char.eq.colon) go to 900
	if (char.lt.char0) go to 40
	if (char.gt.char9) go to 30
	usrnum = usrnum*10 + (char-char0)
	go to 20

30	continue
	if ((char.ge.chara).and.(char.le.charp)) go to 50
40	continue
	call error('Bad device/user specified.$')

50	continue
	device = char - chara + 1
	go to 20

900	continue
	if ((usrnum.gt.15).or.(usrnum.lt.0))
     +		call error (' User number is out of range.$')
	return
	end


	subroutine encdxx(string,positn,value)

C.. This routine will encode the provided VALUE into a decimal
C.. string of characters, stored in STRING, with the leftmost
C.. in column POSITN.  Leading blanks are used.  Three digitss
C.. are always used.  VALUE is treated as an unsigned, integer
C.. value.  Value are obviously limited to 0 through 999.

	byte string(1),positn,zero,blank,d1,d2,d3
	byte ascii(10)
	integer int,value
	data zero,blank /1h0,1h /,ascii/1h0,1h1,1h2,1h3,1h4,
     +		1h5,1h6,1h7,1h8,1h9/

C.. Isolate the individual digits.
	int = value
	d1 = int / 100
	d2 = (int - d1*100) / 10
	d3 = int / 10
	d3 = int - (d2*10) - (d1*100)
C.. Convert the digits to ASCII characters.
	d1 = ascii(d1+1)
	d2 = ascii(d2+1)
	d3 = ascii(d3+1)
C.. Blank fill the leading zeros.
	if (d1.ne.ascii(1)) go to 100
	d1 = blank
	if (d2.ne.ascii(1)) go to 100
	d2 = blank
100	continue
C.. Fill the characters in the user area.
	string(positn) = d1
	string(positn+1) = d2
	string(positn+2) = d3
	return

	end


	subroutine encod6(string,pos,value)

C.. This routine will convert a integer value to
C.. ASCII for displaying.  It is like ENCODE, but
C.. uses six character positions.

	byte string(1),pos,chzero,blank,pos1
	integer value,part1,part2
	data chzero,blank/1h0,1h /

C.. Isolate the two parts. (The base routine can handle
C.. exactly three digits.)
	part1 = value / 1000
	part2 = value - (part1 * 1000)
	pos1 = pos + 5
C.. Clear out the whole message area.
	do 100 i = pos,pos1
	string(i) = blank
100	continue
C.. Fill in the bottom three characters.
	call encdxx(string,pos+3,part2)
C.. If the top three digits are all zero, we are done.
	if (part1.eq.0) go to 900
C.. Change leading blanks in the bottom three positions
C.. to zeros.
	if (string(pos+4).eq.blank) string(pos+4) = chzero
	if (string(pos+3).eq.blank) string(pos+3) = chzero
C.. Now do the top three digits.
	call encdxx(string,pos,part1)
900	continue
	return
	end


	subroutine decint(value)

C.. Show integer in decimal form.

	integer value
	byte str(7),blank
	data str /6*1h ,1h$/,blank /1h /

	call encod6(str,1,value)
	do 100 i = 1,6
	if (str(i).ne.blank) go to 200
100	continue
200	continue
	call strout(str(i))
	return
	end
	

	subroutine decbyt(byte)

C.. Show byte in decimal form.

	byte byte
	integer value,mask
	data mask /x'00ff'/

	value = byte
	value = value.and.mask
	call decint(value)
	return
	end


	subroutine setfil(nomore)

C.. This routine will set up the input and output FCBs 
C.. from the next file found which matches the ambiguous
C.. specification from the user.  When no more match, 
C.. NOMORE is returned true.

	include rpl.dcl
	logical nomore,first
	integer dirptr,fcount,reserv
	byte amb1st,ambnxt,code,nofile,blank,chara,status,sfirst
	byte svfile(11),fcb(36),dirsec(128)
	include rpl.blk
	common /svfile/svfile /reserv/ reserv

	data first /.true./,nofile /x'ff'/
	data fcb /36*0/,blank /1h /,chara /1hA/

	nomore = .false.
	if (ambig) go to 10
C.. This is not an ambiguous run.  Allow only one pass through here.
	nomore = .true.
	if (.not.first) return
	nomore = .false.
	first = .false.
C.. Make sure that the file exists.
	call setusr(iuser)
	status = sfirst(indev,infile)
	if (status.ne.-1) go to 500
	call strend('Input file does not exist.$')
	call chrout(7)
	call endrun

C.. This is an ambiguous run.  The source file had wildcards.
10	continue
	if (.not.first) go to 400
C.. On the first call, scan through the directory for the specified
C.. user and build a list of matching file names.  Up to 255 are allowed.
	call setusr(iuser)
	do 50 i = 1,11
	fcb(i+1) = svfile(i)
50	continue
	fcb(1) = indev
	first = .false.
	fcount = 0
C.. Find the first file using the ambiguous specification.
	code = amb1st(fcb)
	go to 200

C.. Find the next file.
100	continue
	code = ambnxt(fcb)
200	continue
	if (code.ne.nofile) go to 250
	go to 350

C.. A file was found.  Copy the file name from the directory sector.
250	continue
	dirptr = code*32
	do 300 i = 1,11
	infile(i) = peek(x'81'+dirptr)
	dirptr = dirptr + 1
300	continue
C.. Stash the file name in the list.
	call putfil(infile)
	fcount = fcount + 1
	go to 100

C.. If more than one file matched, tell ahead of time how many
C.. files will be processed.
350	continue
	if (fcount.eq.0) go to 360
	if (fcount.eq.1) go to 370
	call strout(' $')
	call decint(fcount)
	call strend(' files will be processed.$')
	go to 400

C.. No files found in an wildcard specification.
360	continue
	call strend('No such file.$')
	call endrun

370	continue
CCC	call strend(' 1 file will be processed.$')

C.. Reserve the saved space now.
380	continue
	call makres(reserv)

C.. This run used ambiguous filespec.  The file list has been build.
C.. Extract the next file name.
400	continue
	call getfil(infile)
	do 450 i = 1,11
	outfil(i) = infile(i)
450	continue
	if (infile(1).ne.0) go to 500
	nomore = .true.
	return

C.. Make sure we don't copy back onto ourself.  This works ok but
C.. is senseless.
500	continue
	if (indev.ne.outdev) go to 550
	if (ouser.ne.iuser) go to 550
	do 520 i = 1,11
	if (outfil(i).ne.infile(i)) go to 550
520	continue
	call error (' Identity copy ignored.$')

C.. Tell what is going on.
550	continue
	call strout(' Copying $')
	call chrout(outdev + chara-1)
	call decbyt(ouser)
	call chrout(':')
	call shofil(outfil)
	call strout(' <-- $')
	call chrout(indev + chara-1)
	call decbyt(iuser)
	call strout(':$')
	call shofil(infile)
	return
	end


	subroutine shofil(name)

C.. This routine sends a file spec to the display.

	byte name(11),blank 
	data blank /1h /

	do 100 i = 1,8
	if (name(i).ne.blank) call chrout(name(i))
100	continue
	if ((name(9).eq.blank).and.(name(10).eq.blank).and.
     +		(name(11).eq.blank)) return
	call chrout('.')
	do 200 i = 9,11
	if (name(i).ne.blank) call chrout(name(i))
200	continue
	return
	end


	subroutine setopt(start,maxpos,error)

C.. This routine parses the options following the source file spec.
C.. If I knew how to do it, I would allow "T" to indicate trimming
C.. off of extra sectors.  "-T" would turn off trimming.  If you come
C.. up with a good way to do it, please let me know.

	byte ch0,ch9,cht,dash,chk,blank,dollar
	byte peek,char
	integer reserv,pos,start,maxpos,defres
	logical trim,error

	common /trim/ trim
	common /reserv/ reserv

	data ch0,ch9,chk,cht,dash,blank,dollar
     +			 /1h0,1h9,1hK,1hT,1h-,1h ,1h$/
	data trim /.true./
C.. Change DEFRES on the next line for a new default reserve size.
	data reserv /0/,defres/4/

	pos = start
	error = .false.
C.. See if we are already beyond the end of line
	if (pos.gt.maxpos) go to 800
C.. Ignore leading blanks and wait for a dollar sign.
10	continue
	if (peek(pos).ne.blank) go to 20
	pos = pos + 1
	if (pos.gt.maxpos) go to 800
20	continue
	if (peek(pos).ne.dollar) go to 900
C.. We have the dollar sign.  Check out what is after it.
100	continue
	pos = pos + 1
	if (pos.gt.maxpos) go to 800
	char = peek(pos)
CCC	if (char.eq.cht) go to 200
CCC	if (char.eq.dash) go to 300
	if ((char.ge.ch0).and.(char.le.ch9)) go to 400
	if (char.eq.chk) go to 100
	go to 900

C.. T - set trim status active (the default).
CCC200	continue
CCC	go to 100

C.. minus sign, turn off trim.
CCC300	continue
CCC	trim = .false.
CCC	go to 100

C.. Number.  Build up the reserve space amount.
400	continue
	reserv = reserv*10 + (char - ch0)
	go to 100

800	continue
	if (reserv.eq.0) reserv = defres
	return

C.. Invalid something was found.
900	continue
	error = .true.
	return
	end
