;---------------------------------------------------------------------
;
; PPNUSE.M68 - display summary of files in AMOS PPNs
;
;---------------------------------------------------------------------
; =============== BEGINNING OF COPYRIGHT NOTICE ================
; PPNUSE.M68 is Copyright (c) Applied Systems Research, Dec 1992
; Permission is given to copy this program freely, but this
; copyright notice must be included and remain unchanged.
; You may not charge anyone for this program.
; ================== END OF COPYRIGHT NOTICE ===================
; EXTENDED DIRECTORIES:
; This program works with extended directories.
; It does a little bit of everything (MFD,UFD,files).
; Thus, it is handy when coding other extended directory programs.
; At appropriate places below, bugs in AMOS and its docs are discussed.
; Edit history:
; (100) 1994-Nov-16 - created by Bob Fowler with /E:n/D/S/T options
; (101) 1994-Nov-18 - add RMS file stats
; (102) 1994-Nov-21 - add estimate of tape backup overhead for UFDs
; Usage documentation:
; (1) Directories included in summary:
; "PPNUSE" scans the user's login account
; "PPNUSE [ppp,nnn]" scans another account
; "PPNUSE devn:[ppp,nnn]" scans an account on device devn
; "PPNUSE devn:" scans all accounts on device devn (vs one account)
; (2) Switches broaden the scan range:
; /D forces the scan range to include the entire device
; /S forces the scan range to include all devices
; (3) Details displayed (from lowest to highest level):
; Extension summary: included only by /E, suppressed by /T
; Account summary: included except when suppressed by /T
; Device summary: included whenever an entire device is scanned
; System summary: included whenever all devices are scanned
; (4) Other notes:
; /E requires an internal table be allocated for totals.
; The table size defaults to 100 extensions.
; If there are more than 100 extensions in any account, the
; 100th extension and beyond are totaled together as "etc".
; To allow for up to n extensions, use "/E:n".
; This can be used to abbreviate (/E:4) or enlarge (/E:1000).
; The absolute program limit is currently /E:6553 (=65535/10).
; Register usage:
; A3 = base of extension subtotals table
; A4 = pointer within AMOS device table
; A5 = base of impure work area
; Possible revisions:
; --- allow disk device families like "DSK"
; --- allow PPN families like "ALL:[100,100]"
; --- optionally send output to print file
; --- sort extension subtotals by extension or # of blocks
; (currently displayed in order of first occurance)
; --- change how the "etc" extensions are consolidated?
; --- breakdown totals by random/sequential files?
; --- check for overflow in xxxSSF running totals
COPY SYS
SEARCH SYSSYM ; for PHDR symbols
VMAJOR = 1
VMINOR = 0
VEDIT = 102.
TABSIZ = 10. ; size of entries in extension table
TABMAX = 65535./TABSIZ ; maximum table size
TABCNT = 100. ; default table count
PSECT
.=0
PPNUSE: PHDR -1,0,PH$REU ! PH$REE ; no privileges, reentrant, reusable
GETIMP MEMLTH,A5,PRGEND ; get (and clear) impure work area
; ===== PROCESS INPUT LINE =====
; set default scan conditions
MOV #1,DEVONE(A5) ; default to one device
CLR DEVTOT(A5) ; default to PPN totals
; clear device/drive/PPN in DDB
CLRW SCNDDB+D.DEV(A5) ; clear device
CLRW SCNDDB+D.DRV(A5) ; clear drive
CLRW SCNDDB+D.PPN(A5) ; clear PPN
; process optional device/drive/PPN from input line
BYP ; bypass blanks and tabs
LIN ; any inputs?
BEQ 10$ ; no - use login defaults
FSPEC SCNDDB(A5) ; process device/drive/PPN
10$: TSTW SCNDDB+D.DEV(A5) ; was there a device?
BNE INPDEV ; yes - use that device
; use defaults for no device input
JOBIDX A1 ; get base of user's JCB
MOVW JOBDEV(A1),SCNDDB+D.DEV(A5) ; user login device
MOVW JOBDRV(A1),SCNDDB+D.DRV(A5) ; user login drive
MOVW SCNDDB+D.PPN(A5),PPNONE(A5) ; was there a PPN?
BNE GOTDEV ; yes - use it
JOBIDX A1 ; get base of user's JCB
MOVW JOBUSR(A1),PPNONE(A5) ; user login PPN
BR GOTDEV
; device was input
INPDEV: CMPW SCNDDB+D.DRV(A5),#-1 ; was there a drive number?
BNE 10$ ; yes - use it
; if a device family (eg, "DSKn") scan is implemented, do it here
; for now, assume drive zero (eg, "DSK:" is treated as "DSK0:")
CLRW SCNDDB+D.DRV(A5) ; clear drive to zero
10$: MOVW SCNDDB+D.PPN(A5),PPNONE(A5) ; was there a PPN?
BNE 20$ ; yes - use it
CLRW PPNONE(A5) ; all PPNs
20$:
GOTDEV:
; switches
CLR EXTMAX(A5) ; default to no extension subtotals
GETOPT: BYP ; bypass blanks and tabs
LIN ; anything left on line?
JEQ GOTOPT ; no - finished with switches
CMPB (A2)+,#'/ ; "/" ?
JNE USAGE ; no - wrong syntax
; check for /E or /E:n
CMPB @A2,#'E ; "E"?
BNE NOTSWE ; no - try another
MOV #TABCNT,EXTMAX(A5) ; default to 10 extension subtotals
INC A2 ; go to next character
CMPB @A2,#': ; ":"?
BNE GETOPT ; no - use default value
INC A2 ; go to next character
GTDEC ; get number of extensions
MOV D1,EXTMAX(A5) ; maximum extensions
CMP D1,#TABMAX ; too large?
BLOS GETOPT ; no - done
TYPE <Only >
MOV #TABMAX,D1 ; maximum table siz
DCVT 0,OT$TRM ; display it
TYPECR < extensions allowed>
EXIT
NOTSWE:
; check for /S
CMPB @A2,#'S ; "S"?
BNE NOTSWS ; no - try another
INC A2 ; go to next character
CLR DEVONE(A5) ; process all devices
CLR PPNONE(A5) ; process all accounts on device
BR GETOPT ; done
NOTSWS:
; check for /D
CMPB @A2,#'D ; "D"?
BNE NOTSWD ; no - try another
INC A2 ; go to next character
CLR PPNONE(A5) ; process all accounts on device
JMP GETOPT ; done
NOTSWD:
; check for /T
CMPB @A2,#'T ; "T"?
BNE NOTSWT ; no - try another
INC A2 ; go to next character
MOV #1,DEVTOT(A5) ; device totals only
JMP GETOPT ; done
NOTSWT:
; If we are doing only one account, ignore /T
TST PPNONE(A5) ; one account only?
BEQ 10$ ; no - ok
CLR DEVTOT(A5) ; yes - clear totals only flag
10$:
; ===== GET ROOM =====
; reserve room for extension totals
TST EXTMAX(A5) ; doing totals?
BEQ GOTROM ; no - skip room
MOV EXTMAX(A5),D0 ; extensions
MUL D0,#TABSIZ ; x 10
; the GETIMP MACRO apparently fails for values in registers Dn
; GETIMP D0,A3,PRGEND ; fails (generates a 0-byte memory module)
; instead, we must expand the MACRO ourselves
PUSH D0 ; the above GETIMP generates "PUSH #0" here
PUSH
GETMEM @SP
JNE PRGEND
POP A3
POP
GOTROM:
; ===== DEVICE LOOP =====
; initialize grand totals
CLR SYSBLX(A5) ; blocks in system
CLR SYSFIL(A5) ; files in system
CLR SYSPPN(A5) ; PPNs in system
CLR SYSDEV(A5) ; devices in system
CLR SYSSSF(A5) ; sum of squares of files on system
; Scan the (undocumented) AMOS device table
; Symbols used here are from disassembly of SYSSYM.UNV[7,7]
TST DEVONE(A5) ; only one disk?
BNE BEGDEV ; yes - bypass device loop setup
MOV DEVTBL,A4 ; get base of device table
NXTDEV: TST DV.BMP(A4) ; does this device have a bitmap?
JEQ ENDDEV ; no - not a disk (could be CD Rom)
MOVW DV.DEV(A4),SCNDDB+D.DEV(A5) ; put device into DDB
MOVW DV.UNT(A4),SCNDDB+D.DRV(A5) ; put drive into DDB
BEGDEV: INC SYSDEV(A5) ; one more device in system
INIT SCNDDB(A5) ; get input buffer
ORB #<D$ERC>,SCNDDB+D.FLG(A5) ; display errors but return
; if filename is not cleared, then last filename of previous disk may appear
; on error messages relating to the new disk (eg, "not mounted")
CLR SCNDDB+D.FIL(A5) ; clear filename
CLRW SCNDDB+D.EXT(A5) ; clear extension
DIRACC SCNDDB(A5),#DA$INI ; initialize search
JNE RETDEV ; error
; initialize counts
CLR DEVBLX(A5) ; blocks in device
CLR DEVFIL(A5) ; files in device
CLR DEVPPN(A5) ; PPNs on device
CLR DEVSSF(A5) ; sum of squares of files on device
; ===== ACCOUNT LOOP =====
NXTPPN: DIRSCH SCNDDB(A5),#DS$DIR ; get next directory (PPN)
TSTB SCNDDB+D.ERR(A5) ; error? (eg, disk not mounted)
JNE RETDEV ; yes - quit device
; DSKANA uses following test for end of directory (documented):
; MOVW SCNDDB+D.DIR(A5),D0 ; result flags
; ANDW #DF$EOD,D0 ; last account?
; JNE FINDEV ; yes - finish device
; documentation example uses following test (without explanation):
TSTW D6 ; last account?
JMI FINDEV ; yes - finish device
MOVW SCNDDB+D.FIL(A5),PPNCUR(A5) ; get PPN
MOVW PPNONE(A5),D0 ; doing all accounts?
BEQ 10$ ; yes - do this one
CMPW D0,PPNCUR(A5) ; doing this account?
BNE NXTPPN ; no - skip it
10$:
; The following three PUSHes (and their corresponding POPs later on)
; save and restore the 12-byte "Directory Marker" field.
; This is necessary in order to return to the previous directory level.
; Using FIX, it was determined that DSKANA uses this method.
; The Monitor Calls (Sep 1989) do not discuss how to do this.
; Indeed, the File Service System chapter only says this:
; "This [Directory Marker] field is for internal use and it
; is not expected that it will be accessed by user programs."
; Thanks guys.
PUSH SCNDDB+D.DIR(A5) ; save to go back
PUSH SCNDDB+D.DIR+4(A5) ; save
PUSH SCNDDB+D.DIR+8.(A5) ; save
; drop one level in directory tree
DIRACC SCNDDB(A5),#DA$NEW + DA$LVL ; drop a level
; initialize counts
CLR PPNFIL(A5) ; files in account
CLR PPNBLX(A5) ; blocks in account
CLR EXTCUR(A5) ; clear extension table
; bump PPN counts
INC SYSPPN(A5) ; one more PPN in system
INC DEVPPN(A5) ; one more PPN in device
; ===== FILE LOOP =====
NXTFIL: CTRLC PRGEND
DIRSCH SCNDDB(A5),#DS$DAT ; get next file
; DSKANA uses following test for end of directory (documented):
; MOVW SCNDDB+D.DIR(A5),D0 ; result flags
; ANDW #DF$EOD,D0 ; last file?
; BNE FINPPN ; yes - finish account
; documentation example uses following test (without explanation):
TSTW D6 ; last file?
BMI FINPPN ; finish account
; add this file to totals
INC PPNFIL(A5) ; increment files in PPN
INC DEVFIL(A5) ; increment files in device
INC SYSFIL(A5) ; increment files in system
MOV SCNDDB+D.FSZ(A5),D0 ; blocks in current file
ADD D0,DEVBLX(A5) ; add to device blocks
ADD D0,PPNBLX(A5) ; add to PPN blocks
ADD D0,SYSBLX(A5) ; add to system blocks
; add this file to extension subtotals
TST EXTMAX(A5) ; do we keep extension subtotals?
BEQ NXTFIL ; no - skip it
CLR D1 ; clear upper word to be safe
MOVW SCNDDB+D.EXT(A5),D1 ; get file extension (RAD50)
MOV A3,A0 ; start at base of table
MOV EXTCUR(A5),D2 ; start search countdown
BEQ FSTEXT ; if table empty, add new entry
; Local register usage:
; D0 (blocks in file) D1 (extension) D2 (countdown) A0 (table entry)
NXTEXT: CMPW D1,@A0 ; match?
BEQ OLDEXT ; yes - add to old entry
DEC D2 ; end of table?
BEQ NEWEXT ; yes - add new entry to table
ADD #TABSIZ,A0 ; go to next entry
BR NXTEXT ; check it
NEWEXT: MOV EXTCUR(A5),D3 ; is table full?
CMP D3,EXTMAX(A5) ; ...
BLO ADDEXT ; no - add new entry
MOVW #-1,@A0 ; flag miscellaneous extension
BR OLDEXT ; add in file and blocks
ADDEXT: ADD #TABSIZ,A0 ; create new entry
FSTEXT: MOVW D1,@A0 ; put in new extension
CLR 2(A0) ; clear file count
CLR 6(A0) ; clear block count
INC EXTCUR(A5) ; one more extension
OLDEXT: ADD #1,2(A0) ; bump file count
ADD D0,6(A0) ; add in blocks
JMP NXTFIL ; go to next file
; ===== DISPLAY RESULTS FOR ONE ACCOUNT =====
FINPPN: TST DEVTOT(A5) ; totals only?
JNE ENDPPN ; yes - bypass PPN totals
; the OT$OFP flag apparently does not work (does not display PPN)
; OFILE SCNDDB(A5),OT$TRM + OT$TSP + OT$OFD + OT$OFP
OFILE SCNDDB(A5),OT$TRM + OT$TSP + OT$OFD
; "Then I'll do it myself", said the Little Red Hen.
TYPE <[>
PRPPN PPNCUR(A5) ; output current PPN
TYPE <]>
TYPE < has>
; only display extension totals if /E option selected
TST EXTMAX(A5) ; do we have extension subtotals?
BEQ 10$ ; no - skip it
MOV EXTCUR(A5),D1 ; extensions in PPN
DCVT 0,OT$TRM + OT$LSP + OT$TSP ; display it
TYPE <extensions>
10$:
MOV PPNFIL(A5),D1 ; files in PPN
DCVT 0,OT$TRM + OT$LSP + OT$TSP ; display it
TYPE <files>
MOV PPNBLX(A5),D1 ; files in PPN
DCVT 0,OT$TRM + OT$LSP + OT$TSP ; display it
TYPECR <blocks>
; display extension subtotals
MOV EXTCUR(A5),D2 ; start extension countdown
BEQ ENDPPN ; if table empty, skip display
MOV A3,A0 ; start at base of table
DSPLIN: MOV #3,D3 ; 3 extensions per display line
DSPEXT: TYPE < *.>
CMPW @A0,#-1 ; "miscellaneous" extension?
BEQ DSPMSC ; yes - special output
MOV A0,A1 ; RAD50 source
LEA A2,EXTASC(A5) ; ASCII destination
UNPACK ; unpack it
CLRB @A2 ; end with null
TTYL EXTASC(A5) ; ASCII destination
BR DSPDON
DSPMSC: TYPE <etc>
DSPDON: MOV 2(A0),D1 ; display files in PPN
; DCVT 0,OT$TRM + OT$LSP + OT$TSP
; TYPE <files>
DCVT 6,OT$TRM + OT$ZER
MOV 6(A0),D1 ; display blocks in PPN
; DCVT 0,OT$TRM + OT$LSP + OT$TSP
; TYPECR <blocks>
DCVT 7,OT$TRM + OT$ZER
ADD #TABSIZ,A0 ; go to next table entry
DEC D2 ; end of table?
BNE DSPEOL ; no - keep going
CRLF
BR ENDPPN
DSPEOL: DEC D3 ; end of line?
BNE DSPEXT ; no - stay on same line
CRLF ; finish this line
BR DSPLIN ; start new line
ENDPPN:
; add sum of squares of files into running device and system totals
MOV PPNFIL(A5),D0 ; files in this PPN
; check for >2^16 in following?
MUL D0,D0 ; square it
; check for >2^32 in following?
ADD D0,DEVSSF(A5) ; add to device sum of squares
; check for >2^32 in following?
ADD D0,SYSSSF(A5) ; add to system sum of squares
; restore markers to return to previous higher directory level
POP SCNDDB+D.DIR+8.(A5) ; return to higher directory level
POP SCNDDB+D.DIR+4(A5)
POP SCNDDB+D.DIR(A5)
JMP NXTPPN ; go to next account
; ===== DISPLAY RESULTS FOR ONE DEVICE =====
FINDEV: TSTW PPNONE(A5) ; doing all accounts?
BNE RETDEV ; no - skip device summary
OFILE SCNDDB(A5),OT$TRM + OT$TSP + OT$OFD
TYPE <has>
MOV DEVPPN(A5),D1 ; display PPNs in device
DCVT 0,OT$TRM + OT$LSP + OT$TSP
TYPE <accounts>
MOV DEVFIL(A5),D1 ; display files in device
DCVT 0,OT$TRM + OT$LSP + OT$TSP
TYPE <files>
MOV DEVBLX(A5),D1 ; display blocks in device
DCVT 0,OT$TRM + OT$LSP + OT$TSP
TYPE <blocks (>
MOV DEVSSF(A5),D0 ; sum of square of files
MOV DEVPPN(A5),D1 ; number of PPNs
CALL DIVIDE ; D2 = D0/D1
MOV D2,D0 ; D0 = D2
CALL ROOT ; D1 = SQR(D0) = R.M.S.
DCVT 0,OT$TRM + OT$TSP
TTYI
ASCII |RMS files/PPN)|
BYTE 15,0
EVEN
TST DEVTOT(A5) ; totals only?
JNE ENDDEV ; yes - bypass extra return
; apparently, repeated INIT (without DELMEM) does not allocate more memory
; DELMEM SCNDDB+D.BUF(A5) ; deallocate the buffer
; ANDB #^C<D$ERC!D$INI>,SCNDDB+D.FLG(A5) ; clear the INITed flag [110]
; ===== DONE WITH THIS DEVICE =====
RETDEV: CRLF ; blank line at end of this device
ENDDEV: TST DEVONE(A5) ; doing only one device?
BNE LSTDEV ; yes - we are done
MOV DV.NXT(A4),A4 ; address of next DEVTBL entry
CMP A4,#0 ; zero? (MOV An doesn't set Z)
JNE NXTDEV ; no - go on to next device
; display grand totals
LSTDEV: TST DEVONE(A5) ; doing all devices?
JNE PRGEND ; no - done
TYPE <System has>
MOV SYSDEV(A5),D1 ; display devices in system
DCVT 0,OT$TRM + OT$LSP + OT$TSP
TYPE <devices>
MOV SYSPPN(A5),D1 ; display PPNs in system
DCVT 0,OT$TRM + OT$LSP + OT$TSP
TYPE <accounts>
MOV SYSFIL(A5),D1 ; display files in system
DCVT 0,OT$TRM + OT$LSP + OT$TSP
TYPE <files>
MOV SYSBLX(A5),D1 ; display blocks in system
DCVT 0,OT$TRM + OT$LSP + OT$TSP
TYPE <blocks (>
MOV SYSSSF(A5),D0 ; sum of square of files
MOV SYSPPN(A5),D1 ; number
of PPNs
CALL DIVIDE ; D2 = D0/D1
MOV D2,D0 ; D0 = D2
CALL ROOT ; D1 = SQR(D0) = R.M.S.
DCVT 0,OT$TRM + OT$TSP
TTYI
ASCII |RMS files/PPN)|
BYTE 15,0
EVEN
; Overhead on AM-3000 = ( SYSSSF x .000008 ) seconds
; ~ ( SYSSSF x 573 / 2^32 ) minutes
TYPE <System tape backup UFD overhead ~>
MOV SYSSSF(A5),D1 ; sum of squares of files
CLRW D1 ; clear lower word
SWAP D1 ; cheap divide by 65536
MUL D1,#573. ; * 573
CLRW D1 ; clear lower word
SWAP D1 ; cheap divide by 65536
DCVT 0,OT$TRM + OT$TSP
TYPECR <minutes (on an AM-3000)>
; INPUT: D0 = any unsigned integer
; OUTPUT: D1 = square root of D0 (rounded)
; method: do binary search, starting with end values 0 and 2^16
; USES: D2 (power) D3 (test) D4 (square)
ROOT: MOV #32768.,D2 ; power = 2^15
CLR D1 ; root = 0
ROOT1: MOV D1,D3 ; test = root
ADD D2,D3 ; + power
MOV D3,D4 ; save test
MUL D4,D3 ; square - test * test
CMP D4,D0 ; is square too high?
BHI 10$ ; yes - skip this power
MOV D3,D1 ; no - test is a better root
10$: ASR D2,#1 ; go to next lower power
BNE ROOT1 ; if not zero, try another test
; RTN ; returns D1 = INT(SQR(D0))
; round root to nearest integer using: (root+1/2) ^ 2 = square+root + 1/4
MOV D1,D2 ; root
MUL D2,D2 ; square
ADD D1,D2 ; square + root
CMP D2,D0 ; too high?
BHI 20$ ; yes - don't round up
INC D1 ; round up
20$: RTN ; returns D1 = INT(SQR(D0)+0.5)
; The 68000 can only divide 32 bits / 16 bits ===> 16-bit quot , 16-bit rem
; The routine below divides 32 bits / 32 bits ===> 32-bit quot , 32-bit rem
; This can be done in one instruction on the 68020
; Following is the 68000 routine
CLR D2 ; D2 = quotient (clear first)
MOV #1,D3 ; D3 = current quotient bit
SHIFT1: CMP D0,D1 ; can we subtract?
BLO TEST ; no - divisor is shifted enough
ASL D1 ; shift again
BCS BACK ; we went too far!
ASL D3 ; quotient bit similiarly shifted
BR SHIFT1 ; keep going
BACK: ROXR D1 ; undo last shift
TEST: CMP D0,D1 ; can we subtract?
BLO SHIFT2 ; no - go shift
SUB D1,D0 ; subtract divisor multiple
ADD D3,D2 ; add bit to quotient
SHIFT2: LSR D3 ; quotient bit halved
BEQ ENDDIV ; if zero, we are done
LSR D1 ; divisor multiple is halved
BR TEST ; test next quotient subtract
ENDDIV: RTN
; Following is the equivalent 68020 routine
; DIVUL D2:D0,D1
; EXG D0,D2
; RTN
; ===== IMPURE WORK AREA =====
ASECT
.=0
SCNDDB: BLKB D.DDB ; directory
DEVONE: BLKL 1 ; 0 (all devices) 1 (one device)
PPNONE: BLKW 1 ; 0 (all accounts) ppn (one account)
DEVTOT: BLKL 1 ; 1 (device totals only) 0 (do accounts)
EXTMAX: BLKL 1 ; maximum # of extensions in table
EXTCUR: BLKL 1 ; current # of extensions in table
PPNCUR: BLKW 1 ; current PPN
PPNBLX: BLKL 1 ; blocks in PPN
DEVBLX: BLKL 1 ; blocks in device
SYSBLX: BLKL 1 ; blocks in system
PPNFIL: BLKL 1 ; files in PPN
DEVFIL: BLKL 1 ; files in device
SYSFIL: BLKL 1 ; files in system
DEVSSF: BLKL 1 ; sum of squares of files in device
SYSSSF: BLKL 1 ; sum of squares of files in system
DEVPPN: BLKL 1 ; accounts in device
SYSPPN: BLKL 1 ; accounts in system
SYSDEV: BLKL 1 ; devices in system
EXTASC: BLKB 4 ; work area for RAD50 to ASCII conversion
MEMLTH: