// FastCopy.bcpl created by Steve Putz
// last edited by Steve Putz February 13, 1985  10:40 PM

//		to load:		BLDR FastCopy BFSInit GP Password
//		to run:		FastCopy newFile[/partition] ← oldFile[/partition]

get "AltoFileSys.d"
get "Streams.d"

external
[	CallSwat		// from OS
	DayTime
	OpenFile
	ReadBlock
	WriteBlock
	FilePos
	SetFilePos
	PositionPtr
	TruncateDiskStream
	ReadLeaderPage
	WriteLeaderPage
	Closes
	BFSClose
	MoveBlock
	Gets
	Puts
	Ws
	Wns
	dsp
	keys
	sysZone
	lvUserFinishProc
	UserPassword
	BFSInit				// from BFSInit.br
	ReadParam		// from GP.br
	SetupReadParam
	Password			// from Password.br
]

manifest
[	pageSize = 256
	bufferPages = 127
	bufferSize = bufferPages*pageSize
]

static
[	savedUFP
	oldPart
]

structure String:
[	length		byte
	char↑1,255	byte
]

let FastCopy() be
[	Ws("FastCopy.run -- Fast cross-partition file copy program of February 13, 1985*n")

	// define special Alto call procedures
	let AltoVersion = table [ 61014b; 1401b ]
	let ChangePartition = table [ 61037b; 1401b ]

	if AltoVersion() rshift 12 ls 4 then
	[	Ws("No partitions on this machine.")
		finish
	]

	oldPart = ChangePartition(0)
	savedUFP = @lvUserFinishProc
	@lvUserFinishProc = MyCleanUp

	let buffer = vec bufferSize		// allocate large disk buffer
	let destName = vec 50
	let sourceName = vec 50
	let switches = vec 50		// unpacked string
	SetupReadParam(0, switches)
	let noAccessCheck = switches!0 eq 1 & switches!1 eq $P

	ReadParam($P, "destination file: ", destName)	// packed BCPL string
	let destPart = 0
	for i = 1 to switches!0 do destPart = destPart*10 + (switches!i) - $0
	if destPart eq 0 then destPart = oldPart

	if ReadParam($P, -1, sourceName) ne -1 & sourceName!0 ne "←"!0
		then SyntaxError()

	ReadParam($P, "source file: ", sourceName)	// packed BCPL string
	let sourcePart = 0
	for i = 1 to switches!0 do sourcePart = sourcePart*10 + (switches!i) - $0
	if sourcePart eq 0 then sourcePart = oldPart

	if ChangePartition(sourcePart) eq 0 then
	[	Ws("unable to access BFS"); Wns(dsp, sourcePart)
		finish
	]

	let sourceDisk = 0		// i.e. default to sysDisk
	if sourcePart ne oldPart then
	[	sourceDisk = BFSInit(sysZone, false)	// (false = no allocation)
		if sourceDisk eq 0 then
		[	Ws("unable to access BFS"); Wns(dsp, sourcePart)
			finish
		]
	]

	unless noAccessCheck % CheckAccess(sourcePart, sourceDisk) do
	[	Ws("Password incorrect for BFS"); Wns(dsp, sourcePart)
		if sourcePart ne oldPart then BFSClose(sourceDisk)
		finish
	]

	let source = OpenFile(sourceName, ksTypeReadOnly, charItem, 0, 0, 0, 0, 0, sourceDisk)
	if source eq 0 then
	[	Ws("BFS"); Wns(dsp, sourcePart); Ws(":")
		Ws(sourceName); Ws(" not found")
		finish
	]

	if ChangePartition(destPart) eq 0 then
	[	Ws("unable to access BFS"); Wns(dsp, destPart)
		finish
	]

	let destDisk = 0		// i.e. default to sysDisk
	if destPart ne oldPart then
	[	test destPart eq sourcePart
			ifso destDisk = sourceDisk
			ifnot
			[	destDisk = BFSInit(sysZone, true)	// (true = allow allocation)
				if destDisk eq 0 then
				[	Ws("unable to access BFS"); Wns(dsp, destPart)
					ChangePartition(sourcePart)
					Closes(source)
					if sourcePart ne oldPart then BFSClose(sourceDisk)
					finish
				]
			]
	]

	unless noAccessCheck % CheckAccess(destPart, destDisk) do
	[	Ws("Password required for BFS"); Wns(dsp, destPart)
		ChangePartition(sourcePart)
		Closes(source)
		if sourcePart ne oldPart then BFSClose(sourceDisk)
		finish
	]

	let dest = OpenFile(destName, ksTypeWriteOnly, charItem, 0, 0, 0, 0, 0, destDisk)
	if dest eq 0 then
	[	Ws("Error opening ")
		Ws("BFS"); Wns(dsp, destPart); Ws(":"); Ws(destName)
		ChangePartition(sourcePart)
		Closes(source)
		if sourcePart ne oldPart then BFSClose(sourceDisk)
		finish
	]

	Ws("copying ")
	Ws("BFS"); Wns(dsp, sourcePart); Ws(":"); Ws(sourceName)
	Ws(" to ")
	Ws("BFS"); Wns(dsp, destPart); Ws(":"); Ws(destName)
	Ws(" (buffer size = "); Wns(dsp, bufferPages); Ws(" pages)")

	let readCount = nil
	let writeCount = nil
	let transfers = 0
	let startTime = vec 2
	DayTime(startTime)
	[	ChangePartition(sourcePart)
		readCount = ReadBlock(source, buffer, bufferSize)		// number of words read
		ChangePartition(destPart)
		writeCount = WriteBlock(dest, buffer, readCount)
		if readCount ne writeCount
			then [ ChangePartition(oldPart); CallSwat("Error Writing") ]
		Ws(".")
		transfers = transfers + 1
	] repeatuntil readCount ls bufferSize

	ChangePartition(sourcePart)
	let bytePos = FilePos(source) & (pageSize*2-1)	// chars in last page
	ChangePartition(destPart)
	PositionPtr(dest, bytePos)		// back up if length is odd
	TruncateDiskStream(dest)

	let leader = vec pageSize
	ReadLeaderPage(dest, leader)
	ChangePartition(sourcePart)
	ReadLeaderPage(source, buffer)
	MoveBlock(lv leader>>LD.created, lv buffer>>LD.created, lTIME)
	ChangePartition(destPart)
	WriteLeaderPage(dest, leader)		// copy source creation date

	Closes(dest)
	if destPart ne oldPart then BFSClose(destDisk)
	ChangePartition(sourcePart)
	Closes(source)
	if sourcePart ne oldPart & destPart ne sourcePart then BFSClose(sourceDisk)
	ChangePartition(oldPart)

	let stopTime = vec 2
	DayTime(stopTime)
	let elapsedTime = stopTime!1 - startTime!1
	Ws("done.*n")
	Wns(dsp, (transfers-1)*bufferPages+(readCount+pageSize-1)/pageSize)
	Ws(" pages copied in "); Wns(dsp, elapsedTime); Ws(" seconds.")
	finish
]

and SyntaxError() be
[
	Ws("Syntax is:*n*t>FastCopy newFile[/partition] ← oldFile[/partition]")
	finish
]

and CheckAccess(diskPart, disk) = valof
[	if disk eq 0 then resultis true		// disk is current partition
	let sysBoot = OpenFile("Sys.boot", ksTypeReadOnly, wordItem, 0, 0, 0, 0, 0, disk)
	if sysBoot eq 0 then
	[	Ws("Unable to access BFS"); Wns(dsp, diskPart)
		Ws(":Sys.boot -- cannot determine if ") // "Password required..."
		resultis false
	]
	SetFilePos(sysBoot, 0, 1400b)  //see Password.bcpl in OS
	let diskPsw = vec 9
	ReadBlock(sysBoot, diskPsw, 9)
	Closes(sysBoot)
	if diskPsw!0 eq 0 then resultis true	// no disk password
	let userPsw = vec 50
	let char = 0
	MoveBlock(userPsw, UserPassword, UserPassword!-1)
	until Password(userPsw, diskPsw, false) do
	[	if char ne 0 then Ws("Incorrect.  ")
		Ws("Please enter password for BFS"); Wns(dsp, diskPart); Ws(": ")
		userPsw>>String.length = 0
		[	char = Gets(keys)
			if char eq $*n then break
			if char eq 127 then [ Ws("XXX*n"); resultis false ]	// DEL aborts
			userPsw>>String.length = (userPsw>>String.length)+1
			userPsw>>String.char↑(userPsw>>String.length) = char
			Puts(dsp, $**)		// echo *
		] repeat
		Puts(dsp, $*n)		// new line
	]
	resultis true
]

and MyCleanUp(code) be
[
	let ChangePartition = table [ 61037b; 1401b ]
	ChangePartition(oldPart)
	@lvUserFinishProc = savedUFP
]