;---------------------------------------------------------------------
;
;  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:

; process other switch options here

; no more switch options
       BR      USAGE

USAGE:  TTYI
       ASCII   |Usage: PPNUSE {devn:} {[ppp,nnn]} {/E{:n}} {/D} {/S} {/T}|
       BYTE    15,0
       EVEN
       EXIT

GOTOPT:

; 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)>

PRGEND: EXIT


;---------------------;
; SQUARE ROOT ROUTINE ;
;---------------------;

; 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)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;                               ;
;  Extended Divide Routine      ;
;                               ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; 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

; Reg   Input          Output
; ---   -----------    ---------
; D0    Dividend       Remainder
; D1    Divisor        Divisor
; D2    [garbage]      Quotient
; D3    [garbage]      [zero]

DIVIDE:

; 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:

       END