;*************************** 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]

EXIT:   MOVWL   COUNT(MEM),D1           ;                               [1.1]
       BEQ     10$                     ;                               [1.1]
       DCVT    0,OT$TRM                ;                               [1.1]
       BR      20$                     ;                               [1.1]
10$:    TYPE    %No                     ;                               [1.1]
20$:    TYPE    < file>                 ;                               [1.1]
       CMPW    D1,#1                   ;                               [1.1]
       BEQ     30$                     ;                               [1.1]
       TYPE    s                       ;                               [1.1]
30$:    TYPECR  < moved>                ;                               [1.1]
       EXIT                            ;                               [1.1]

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;MOVE - move file in DDB @A4 to account NEWPPN(MEM) on same disk

;copy file specification to work DDB DISK(MEM)
MOVE:   MOVW    D.DEV(A4),DISK+D.DEV(MEM)                               ;[1.1]
       MOVW    D.DRV(A4),DISK+D.DRV(MEM)                               ;[1.1]
       MOV     D.FIL(A4),DISK+D.FIL(MEM)                               ;[1.1]
       MOVW    D.EXT(A4),DISK+D.EXT(MEM)                               ;[1.1]
       MOVW    D.PPN(A4),DISK+D.PPN(MEM)                               ;[1.1]
       MOVW    D.PPN(A4),OLDPPN(MEM)                                   ;[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]

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;Subroutine
;Print start of error message
CANNOT: TYPESP  ?Cannot move
       PFILE   DISK(MEM)
       TYPE    < - >
       RTN

       END