;*************************** AMUS Program Label ******************************
; Filename: MOVE.M68 Date: 10/23/91
; Category: UTIL Hash Code: 335-634-657-433 Version: 1.2
; Initials: ULTR/US Name: DAVID PALLMANN
; Company: ULTRASOFT CORPORATION Telephone #: 5163484848
; Related Files:
; Min. Op. Sys.: AMOSL 1.3B/AMOS32 1.0 Expertise Level: INT
; Special: after assembly: .LNKLIT MOVE,WLDSCN (needs WLDSCN.OBJ)
; Description: Moves files between PPNs (on same disk) super-fast
;*****************************************************************************
;MOVE.M68
;Moves a file to a different user file directory
;A freeware product from UltraSoft; use the program but don't sell it
;
;Written by: David Pallmann
;
;Edit History:
;1.0 19-Feb-86 created. /DFP
;1.0A 23-Feb-86 correct handling of invalid account numbers (thanks Brad). /DFP
;1.1 08-Sep-86 added wildcarding support. /DFP
;1.2 23-Oct-91 - add ersatz name support (example: .MOVE *.* TO TEMP:). /DFP
; - add warnings about traditional format disks only.
;
;MOVE transfers the file descriptor information from one User File Directory
;to another, effectively changing the file's "home" from one PPN to another.
;Restrictions: (1) the source and destination account must be on the same
;disk; (2) the source and destination accounts must both exist (MOVE does
;not create PPNs); (3) both source and destination PPNs must be in the user's
;project (for security reasons) unless logged into [1,2], in which case
;anything goes.
;
;In addition to this source file, you will also need WLDSCN.OBJ, available on
;the AMUS network.
;
;How to assemble this program: (1) .M68 MOVE
; (2) .LNKLIT MOVE,WLDSCN
;
;This program is for traditional format disks only. It should not be used
;on extended format disks.
VMAJOR =1
VMINOR =2
EXTERN $FNPPN
EXTERN W.INIT,W.SPEC,W.NEXT ;[1.1]
SEARCH SYS
SEARCH SYSSYM
;the following is a definition of a Master File Directory. Every disk or
;other block-structured device has one or more Master File Directory blocks.
;The first MFD is always block 1. Near the end of the MFD is a link word.
;If this word is non-zero, it contains the link to the next MFD block. The
;2nd MFD block may in turn also link to another block. Finally, the link
;word will be zero at some point, indicating the end of the MFD chain.
;
;A Master File Directory block contains 63 PPN entries, each of which
;describe a PPN on the disk. The information contained in a PPN entry is
;(1) the account number (PPN); (2) the block number of the corresponding
;UFD (see next set of comments), and (3) the LOG password for the account.
;These elements are symbolically defined here:
.OFINI
.OFDEF MF.PPN,2 ; project-programmer number
.OFDEF MF.LNK,2 ; link to 1st UFD block
.OFDEF MF.PSW,4 ; password packed RAD50
.OFSIZ MF.SIZ ; (size of a PPN entry)
;The directory for an account is maintained in one or more User File
;Directory (UFD) blocks. Like MFD blocks, UFDs contain link words. Unlike
;the MFD, the UFD blocks contain their link word at the very beginning of
;the block. After the link word there are 42 file entries. File entries
;contain the following information: (1) filename; (2) file extension; (3)
;size of the file (in blocks); (4) number of bytes in the last block of the
;file (if this is a negative number then the file is contiguous); (5) the
;first block number of the file. The following symbols define a file entry:
.OFINI
.OFDEF UF.FIL,4 ; filename packed RAD50
.OFDEF UF.EXT,2 ; file extension packed RAD50
.OFDEF UF.SIZ,2 ; file size in blocks
.OFDEF UF.ACT,2 ; active count
.OFDEF UF.LNK,2 ; link to first block of file
.OFSIZ UF.ESZ
;The following symbols define local memory used by MOVE
.OFINI
.OFDEF DISK,D.DDB ; DDB used for disk I/O
.OFDEF OLDPPN,2 ; old PPN of file
.OFDEF NEWPPN,2 ; new PPN of file
.OFDEF OLDUFD,2 ; block number of old PPN UFD
.OFDEF NEWUFD,2 ; block number of new PPN UFD
.OFDEF FILSIZ,2 ; file size in blocks
.OFDEF FILACT,2 ; active count
.OFDEF FILLNK,2 ; link to 1st block of file
.OFDEF COUNT,2 ; number of files MOVEd [1.1]
.OFDEF CVTBUF,4 ; RAD50 conversion buffer [1.2]
.OFSIZ MEMSIZ ; (size of this memory area)
;the following symbols define registers by name for ease of code reading
MEM=A5 ; A5 points to local memory
;the following macro moves a WORD value to a LONGWORD destination
DEFINE MOVWL SRC,DST
CLR D7
MOVW SRC,D7
MOV D7,DST
ENDM
;The following macros define entry points to the wildcard scanner
; WINIT initializes the scanner and returns A4 pointing to work memory
; WSPEC processes a command line wildcard specification
; WSCAN returns the a matching file specification
;
;The normal calling sequence goes something like this:
;
; WINIT ; initialized - must preserve A4 from now on
; ........
; LEA A2,addr ; point A2 to file specification
; WSPEC XYZ ; process spec - use .XYZ as default extension
; ........
; LOOP: WSCAN ; get next file spec, return in DDB @A4
; BNE exit ; branch if no more files
; ........
; JMP LOOP
;
DEFINE WINIT
IF NDF,W.INIT,EXTERN W.INIT
CALL W.INIT
ENDM
DEFINE WSPEC EXT
IF NDF,W.SPEC,EXTERN W.SPEC
CALL W.SPEC
IF B,EXT,ASCII /???/
IF NB,EXT,ASCII /'EXT/
BYTE 0
ENDM
DEFINE WSCAN
IF NDF,W.SCAN,EXTERN W.SCAN
CALL W.SCAN
ENDM
;Start of code
;Program HeaDeR indicates attributes of this program and stores version number
;GET IMPure area allocates local memory and points MEM (A5) to it.
;Lastly, call the WINIT routine, which initializes the wildcard scanner, and
;points A4 to the base of a work area. A4 must be preserved throughout the
;code in order for other wildcard calls (W.SPEC and W.SCAN) to work. [1.1]
START: PHDR -1,PV$RPD!PV$WPD,PH$REE!PH$REU
GETIMP MEMSIZ,MEM
WINIT ;[1.1]
INIT DISK(MEM) ; allocate an I/O buffer for DDB
;Command line processing
;The proper command line syntax is: .MOVE {file} TO [a,b]
;(the "TO" is optional, and so are the left and right brackets)
;If the user has entered a blank command line, or an invalid one, we set
;him (or her) straight by branching to HELP: below.
CMDLIN: BYP ; bypass any spaces/tabs
LIN ; end of command line?
BEQ HELP ; yes - empty line - give help
;[1.1] FSPEC DISK(MEM),DAT ; load DDB with file specification
WSPEC ; process wildcard file spec [1.1]
JNE EXIT ; exit on bad spec [1.1]
BYP ; bypass more white space
CMMB @A2,#'T ; is the "T" of "TO" present?
BNE 10$ ; no
INC A2 ; bypass the "T"
CMMB (A2)+,#'O ; is the "O" of "TO" present?
BNE HELP ; no - error - give help
BYP ; bypass more white space
10$: CMMB @A2,#'[ ; is left bracket present?
BNE 20$ ; no - branch
INC A2 ; bypass bracket
20$: NUM ; is a PPN present?
JEQ GETPPN ; yes - branch [1.1]
ALF ; ersatz name? [1.2]
JEQ GETERZ ; yes - branch [1.2]
HELP: TYPECR The MOVE command transfers a file to a different account.
TYPECR <The format of the command is: .MOVE {file} TO [p,pn]>
CRLF ;
TYPECR <Examples: .MOVE *.BAS TO [100,1]> ;[1.1]
TYPECR < .MOVE *.* TO MYACCT:> ;[1.2]
CRLF ;
TYPECR Note: this command is for traditional format disks only.
CRLF ;
EXIT
;Command line format seems reasonably valid. Get the new PPN from the command
;line and store it in NEWPPN(MEM). Store the old PPN in OLDPPN(MEM). If
;the user specified a PPN in the {file} parameter of the command line, the
;old PPN will be at DISK+D.PPN(MEM). If the user did not specify a source
;PPN, we default to where he/she is logged into, and we get the PPN by
;pointing to the user's Job Control Block and copying JOBUSR(JCB).
GETPPN: GTPPN ; get new PPN
JNE BADPPN ; [1.0A]
MOVW D1,NEWPPN(MEM) ; store it in memory
MOVW DISK+D.PPN(MEM),OLDPPN(MEM) ; store old PPN
BNE 10$ ; branch if one was there
JOBIDX A0 ; otherwise point to JCB
MOVW JOBUSR(A0),OLDPPN(MEM) ; and get old PPN from there
10$: BR GETNXT ; [1.2]
GETERZ: LEA A1,CVTBUF(MEM) ; convert ersatz spec [1.2]
PACK ; from ASCII to [1.2]
PACK ; two RAD50 words [1.2]
CMPB (A2)+,#': ; colon present (expected)? [1.2]
JNE BADPPN ; no error [1.2]
MOV CVTBUF(MEM),D0 ; get ersatz name RAD50 [1.2]
MOV ERSATZ,A0 ; index ersatz table [1.2]
10$: TST @A0 ; end of ersatz table? [1.2]
JEQ BADPPN ; yes - error [1.2]
CMP D0,EZ.NAM(A0) ; is this a match? [1.2]
BEQ 20$ ; yes - get PPN [1.2]
ADD #EZ.SIZ,A0 ; advance to next entry [1.2]
BR 10$ ; loop [1.2]
20$: MOVW EZ.PPN(A0),NEWPPN(MEM) ; store new PPN [1.2]
JEQ BADPPN ; no PPN - error [1.2]
;get next file from wildcard scanner and call the MOVE routine, which does
;the actual work of moving the file from one PPN to another [1.1]
;if no more files, we are done [1.1]
GETNXT: CTRLC EXIT ; branch on ^C [1.1]
WSCAN ; get next file [1.1]
JNE EXIT ; no more files [1.1]
CALL MOVE ; move the file [1.1]
BR GETNXT ; on to next file [1.1]
;See if the file actually exists
;If not, we cannot proceed
CHKOFL: LOOKUP DISK(MEM) ; does file exist?
BEQ CHKNFL ; yes
CALL CANNOT
TYPECR file not found
;[1.1] EXIT
RTN ;[1.1]
;See if the file exists in the new PPN; if so, we can't copy this file there
CHKNFL: MOVW NEWPPN(MEM),DISK+D.PPN(MEM)
LOOKUP DISK(MEM)
BNE 10$
CALL CANNOT
TYPE <file already exists in [>
PRPPN NEWPPN(MEM)
TYPECR ]
;[1.1] EXIT
RTN ;[1.1]
10$: MOVW OLDPPN(MEM),DISK+D.PPN(MEM)
;The next step is to see if the old and new PPNs are really out there on
;the device specified. Fortunately, there is a subroutine provided with
;AMOS/L, called $FNPPN, which figures out which MFD block a particular PPN
;is defined in. If $FNPPN fails (which will happen if the user specified
;a non-existent account number), an appropriate (scary) error message will
;be output and the user will be exited to the all-too-familiar dot.
CHKOLD: MOVW OLDPPN(MEM),D1 ; load the old PPN into D1
LEA A2,DISK(MEM) ; point A2 to the DDB
CALL $FNPPN ; look-up the PPN
BNE BADPPN ; branch on error
MOVW MF.LNK(A1),OLDUFD(MEM) ; store block number of new UFD
CHKNEW: MOVW NEWPPN(MEM),D1 ; load the new PPN into D1
CALL $FNPPN ; look-up the PPN
BNE BADPPN ; branch on error
MOVW MF.LNK(A1),NEWUFD(MEM) ; store block number of new UFD
BR CHKPRV ; and branch
BADPPN: TYPECR ?Account Specification Error
RTN
;make sure user isn't violating his privileges. A [1,2] user can copy to
;and from any account; any other user has to stay within his project.
CHKPRV: JOBIDX A0 ; index Job Control Block
CMMW JOBUSR(A0),#402 ; logged into [1,2]?
BEQ LOKDIR ; yes - anything goes
CMMB OLDPPN+1(MEM),JOBUSR+1(A0) ; is old PPN within project?
BNE PRTERR ; no, branch
CMMB NEWPPN+1(MEM),JOBUSR+1(A0) ; is new PPN within project?
BEQ SCNOLD ; yes, branch
PRTERR: CALL CANNOT
TYPECR Protection Violation
;[1.1] EXIT
RTN ;[1.1]
LOKDIR: DSKDRL DISK(MEM) ; lock the device's directory
;Scan through the UFD blocks of the old PPN, looking for the file descriptor
;information for the {file} specified by the user.
SCNOLD: MOVWL OLDUFD(MEM),DISK+D.REC(MEM) ; set block number to old PPN UFD
BEQ UFDERR ; branch if end of UFD (should never happen)
READ DISK(MEM) ; read UFD block
MOV DISK+D.BUF(MEM),A0 ; index buffer
MOVW (A0)+,OLDUFD(MEM) ; store next block link in memory
MOV #42.,D0 ; get ready to scan 42 file entries
FILOLD: CMM UF.FIL(A0),DISK+D.FIL(MEM) ; right file?
BNE NXTOLD ; no
CMMW UF.EXT(A0),DISK+D.EXT(MEM) ; right extension?
BEQ FOUND ; yes
NXTOLD: ADD #UF.ESZ,A0 ; advance to next file entry
SOB D0,FILOLD ; loop till entire UFD scanned
BR SCNOLD ; scan next UFD block
UFDERR: TYPECR ?File not in UFD - can't continue
;[1.1] EXIT
RTN ;[1.1]
;found file descriptor information (yea!)
;store size, link, and active count
FOUND: MOVW UF.SIZ(A0),FILSIZ(MEM)
MOVW UF.ACT(A0),FILACT(MEM)
MOVW UF.LNK(A0),FILLNK(MEM)
;delete the file descriptor in the old UFD
DELETE: MOVW #-1,UF.FIL(A0)
WRITE DISK(MEM)
;Scan through the UFD blocks of the new PPN, looking for a delete file entry
;or the end of the directory.
SCNNEW: MOVWL NEWUFD(MEM),DISK+D.REC(MEM) ; set block number to new PPN UFD
BEQ UFDEND ; branch if end of UFD
READ DISK(MEM) ; read UFD block
MOV DISK+D.BUF(MEM),A0 ; index buffer
MOVW (A0)+,NEWUFD(MEM) ; store next block link in memory
MOV #42.,D0 ; get ready to scan 42 file entries
FILNEW: CMMW UF.FIL(A0),#-1 ; delete file entry?
BEQ ADDFIL ; yes
TSTW UF.FIL(A0) ; end of UFD?
BEQ INSFIL ; yes
NXTNEW: ADD #UF.ESZ,A0 ; advance to next file entry
SOB D0,FILNEW ; loop till entire UFD scanned
BR SCNNEW ; scan next UFD block
;end of UFD reached, which means there aren't any free (unused or deleted)
;file entries. Rather than go through the hassle of allocating another
;directory block; updating the bitmap; and so forth, we take the easy way
;out by creating and immediately erasing a file in the new PPN area. This
;has the net effect of forcing AMOS/L to create another UFD block for us.
UFDEND: DSKDRU DISK(MEM) ; unlock directories
MOVW NEWPPN(MEM),DISK+D.PPN(MEM)
OPENO DISK(MEM)
CLOSE DISK(MEM)
DSKDEL DISK(MEM)
MOVW OLDPPN(MEM),DISK+D.PPN(MEM)
MOVW NEWPPN(MEM),D1 ; load the new PPN into D1
CALL $FNPPN ; look-up the PPN
MOVW MF.LNK(A1),NEWUFD(MEM) ; store block number of new UFD
DSKDRL DISK(MEM) ; re-lock directories
BR SCNNEW ; and start the scan over again
;insert a file entry into the UFD
INSFIL: CMPW D0,#1 ; is this the last entry in UFD?
BEQ ADDFIL ; yes
CLRW UF.ESZ(A0) ; mark following entry as the last
;A0 now points to an available file entry in the UFD; write file name,
;extension, size, active count, and link into it.
ADDFIL: MOV DISK+D.FIL(MEM),UF.FIL(A0) ; set filename
MOVW DISK+D.EXT(MEM),UF.EXT(A0) ; set extension
MOVW FILSIZ(MEM),UF.SIZ(A0) ; set size
MOVW FILACT(MEM),UF.ACT(A0) ; set active count
MOVW FILLNK(MEM),UF.LNK(A0) ; set link to first block
WRITE DISK(MEM) ; rewrite UFD block
DSKDRU DISK(MEM) ; unlock disk's directory
;Tell user what we did
DONE: PFILE DISK(MEM)
;[1.1] TYPE < transferred to [>
TYPE < to [> ;[1.1]
PRPPN NEWPPN(MEM)
TYPECR ]
INCW COUNT(MEM) ;[1.1]
;[1.1] EXIT
RTN ;[1.1]