[comp.sources.misc] Disk Usage for VAX/VMS

allbery@ncoast.UUCP (09/29/87)

This is a program for VAX/VMS users interested in finding out not only
where their disk space is going, but also where the most significant
changes are occuring.

--------------------------  Cut Here  --------------------------------

                .title  DISK_USAGE  Provides information on disk usage
;------------------------------------------------------------------------
;       
;       DISK_USAGE                              8/87  D.A. Munroe
;
;
;       Overview:
;       ---------
;       This program shows disk usage in terms of the directory structure.
;       By the use of comparison files, shows where the most significant
;       changes are occurring.
;
;       As explained below, this program makes use of a data file produced
;       by the ANALYZE/DISK_STRUCTURE command and as a result produces two
;       files: a listing file and a comparison file.
;
;
;       The listing file:
;       -----------------
;       This file shows how many files and blocks are in use by each directory
;       and subdirectory.  The entries are organized in both an alphabetical
;       and a heirarchical order which reflects the directory structure.  The
;       file count and block usage information for a parent directory includes
;       all the files and blocks of its subdirectories.  In addition, the
;       listing file also provides such information as the volume name, owner
;       name, and the time when the usage data file (from ANALYZE/DISK_STRUCTURE)
;       was created.
;
;       If a comparison file (described below) was provided as input to this
;       program, then the listing will also show how much increase or decrease
;       there was per directory, in terms of blocks allocated.  The comparison
;       file provides a way of showing how much change there has been between
;       the last time this program has been run and the current run.
;
;       Notes:
;               1. If a directory is empty (has no files) it will not appear
;                  in the listing because ANALYZE/DISK_USAGE will not generate
;                  an entry for it in the data file.
;
;               2. The value for blocks allocated includes those for the file
;                  header, thus this value is larger than what you see from the
;                  DIRECTORY command.
;
;               3. Files marked for deletion, but which have not actually been
;                  deleted yet, will appear at the top of the listing with no
;                  parent directory.


;       The comparison file:
;       --------------------
;       Each run of this program generates a comparison file as output which
;       is intended to be used as input for the next run of this program.  If
;       no input comparison file is available, the program will run normally,
;       but will not be able to show changes on the listing.  This will be the
;       case on the first run of this program.
;
;       This program will check that the comparison file corresponding to one
;       disk cannot be used as comparison for an entirely different disk; the
;       program is able to handle and does take into account addition or deletion
;       of directories when doing comparisons.
;
;       In order to better keep track of listing and comparison files on systems
;       with more than one disk, it is recommended that the user of this program
;       provide descriptive names which override the default file specifications
;       used by this program.
;
;
;       Invoking the program:
;       ---------------------
;       In order to run this program you first need to issue the command:
;
;               $ ANALYZE/DISK_STRUCTURE device:/USAGE=filespec
;
;       which will produce a data file file for input to this program (the
;       default filespec is USAGE.DAT).
;
;       This program is intended to be invoked as a foreign command.  If the
;       program resides in sys$system, you would first enter the definition:
;
;               $ DISK_USAGE := "$SYS$SYSTEM:DISK_USAGE"
;
;       then to run the program, issue the command:
;
;               $ DISK_USAGE  [filespec]
;
;       where "USAGE.DAT" is the default filename of a data file created
;       by ANALYZE/DISK_STRUCTURE.  A typical approach in using this
;       program would be to first do:
;
;               $ ANALYZE/DISK_STRUCTURE _DUA0:/USAGE=DUA0_USAGE
;               $ DISK_USAGE DUA0_USAGE
;
;       then use a similar sequence with DUA1 and so on.  The default
;       filespec is SYS$MANAGER:USAGE.DAT but may be overridden by a
;       user provided filespec on the command line.


;       Files used (defaults):
;       ----------------------
;       inputs:
;
;       sys$manager:usage.dat   usage data from ANALYZE/DISK_STRUCTURE
;       sys$manager:usage.cmp   (optional) comparison file, from a prior
;                               run of this program
;
;       outputs:
;
;       sys$manager:usage.lis   listing of disk usage information
;       sys$manager:usage.cmp   for use in comparing one run of this
;                               program with the next
;
;
;       Additional information:
;       -----------------------
;       A complete description of the ANALYZE/DISK_STRUCTURE command and
;       the format of the USAGE.DAT file is given in the VMS documentation
;       of the VERIFY utility and in the $USGDEF module of STARLET.MLB .
;
;       Comments, corrections, and suggestions should be sent to:
;
;                       David A. Munroe
;                       Capital Cities Communications/ABC
;                       7818 SE Stark Ave
;                       Portland, Oregon  97215
;                       (503) 251-7533
;
;               uucp:   ...tektronix!reed!omen!safari!dave
;                       ...ptsfa!safari!dave
;                       ...ihnp4!safari!dave
;
;------------------------------------------------------------------------


                $RMSDEF
                $USGDEF

;       primary definitions

version         =               212.                    ; see .ident and version_msg

cmp_field_size  =               8                       ; for showing usage difference
cmp_size        =               512.                    ; record size for USAGE.CMP files
indent_value    =               4                       ; spacing before each subdirectory
lis_size        =               80.                     ; max record size for USAGE.LIS
max_name_size   =               39.                     ; for file and directory names
max_nodes       =               2000.                   ; depends on number of .DIR entries
q_stack_size    =               64.                     ; for comparison tree traversal


;--------------------------------------------------------------------------------
;               Macros
;--------------------------------------------------------------------------------

; generate general purpose string descriptor

                .macro  string_dsc      buffer,size
                .word           size                    ; output buffer size
                .byte           DSC$K_DTYPE_T           ; text data type
                .byte           DSC$K_CLASS_S           ; string descriptor class
                .address        buffer                  ; address of output buffer
                .endm

; format a value using FA0 and write it to the output file

                .macro  write_fmt       item,fao_p1,fao_p2=#0,?ok
                $fao_s  -
                        ctrstr  =       'item'_fmt,-    ; descriptor for format control string
                        outlen  =       fao_outlen,-    ; where to store length of formatted output
                        outbuf  =       fao_outbuf,-    ; descriptor for formatted output buffer
                        p1      =       fao_p1,-
                        p2      =       fao_p2
                blbs    r0,ok                           ; branch if converted ok
                movab   'item'_cvt_msg,r0               ; else set up to write error message
                bsbw    write_msg                       ;
                brw     done                            ;

ok:             cvtwb   fao_outlen,lis_outlen           ; set length of formatted output
                movab   lis_outlen,r0                   ; .ASCIC format
                bsbw    write_msg                       ; write it
                .endm


                .ident  \v2.12\                         ; 10-Sep-1987  D.A. Munroe
                .psect  CODE,nowrt,exe
                .entry  DISK_USAGE,^m<r2,r3,r4,r5,r6,r7,r8,r9,r10,r11>

;----------------------------------------------------------------
;       Look at the command line to see if the user provided
;       anything to override the default file name. If this
;       program is invoked with a line such as:
;
;                       $ DISK_USAGE DUA0
;
;       then the effect of calling lib$get_foreign is to put
;       "DUA0" in fname_buff (via fname_dsc).  This will then
;       override the "USAGE" default so that we will expect
;       to work with:
;
;       sys$manager:dua0.dat    (the usage data file)
;       sys$manager:dua0.cmp    (optional input comparison file)
;
;       sys$manager:dua0.lis    (usage listing file)
;       sys$manager:dua0.cmp    (new output comparison file)
;
;       Similarly, the user could have specified something like:
;
;               $ DISK_USAGE SYS$USER:[DAVE]
;
;       to redirect where the files are placed.
;
;----------------------------------------------------------------

gcml:           pushaw  fname_length            ; where to put resulting length
                clrl    -(sp)                   ; no prompt string
                pushaq  fname_dsc               ; descriptor for default name
                calls   #3,g^lib$get_foreign    ; look at command line
                blbs    r0,10$                  ; continue if ok
                ret                             ; else exit w/status in r0
10$:
;--------------------------------------------------------------------------------
;  At this point if the user has provided a name, it will be in fname_buff
;  and r2 will be its length.  Otherwise, r2 will be zero and the default
;  of sys$manager:usage.dat will be used.
;--------------------------------------------------------------------------------


;--------------------------------------------------------------------------------
;               Open files and connect streams.
;
;               The fna field of the data fab is a pointer to the filename
;               buffer (which may or may not contain "usage" at this time).
;--------------------------------------------------------------------------------

open:           cvtwb   fname_length,data_fab+fab$b_fns ; set filename length
                $open   fab = data_fab                  ; open USAGE.DAT
                blbs    r0,5$                           ; branch if ok
                brw     file_error                      ;

5$:             $create  fab = lis_fab                  ; create USAGE.LIS
                blbs    r0,10$                          ; branch if ok
                brw     file_error                      ;

10$:            movb    #1,r3                           ; assume USAGE.CMP (input) exists
                $open   fab = icmp_fab                  ; try to open USAGE.CMP
                blbs    r0,15$                          ; branch if it exists
                clrb    r3                              ; nope
                cmpl    r0,#RMS$_FNF                    ; skip if file not found
                beql    15$                             ;
                brw     file_error                      ;

15$:            movb    r3,icmp_state                   ; USAGE.CMP (input) status
                $create  fab = ocmp_fab                 ; create USAGE.CMP (output)
                blbs    r0,20$                          ; branch if ok
                brw     file_error                      ;

20$:            $connect  rab = data_rab                ; connect rab for USAGE.DAT
                blbs    r0,25$                          ; branch if ok
                brw     record_error                    ;

25$:            $connect  rab = lis_rab                 ; connect rab for USAGE.LIS
                blbs    r0,30$                          ; branch if ok
                brw     record_error                    ;

30$:            tstb    r3                              ; USAGE.CMP (input) exists?
                beql    35$                             ; branch if not
                $connect  rab = icmp_rab                ; connect rab for USAGE.CMP (input)
                blbs    r0,35$                          ; branch if ok
                brw     record_error                    ;

35$:            $connect  rab = ocmp_rab                ; connect rab for USAGE.CMP (output)
                blbs    r0,40$                          ; branch if ok
                brw     record_error                    ;

40$:


;--------------------------------------------------------------------------------
;       The IDENT record should be the first record in the file;  we read
;       it in, verify it, and print the contents of its fields
;--------------------------------------------------------------------------------
ident:
                $get    rab = data_rab                  ; read ident record
                blbs    r0,5$                           ; branch if ok
                moval   data_rab,r2                     ;
                brw     record_error                    ;

5$:             movab   version_msg,r0                  ; show program version
                bsbw    write_msg                       ;

                cmpw    #USG$C_IDENT_LEN,data_rab+rab$w_rsz     ; length ok?
                beql    10$
                movab   ident_len_msg,r0                ; print msg if length not ok
                bsbw    write_msg                       ;
                brw     done                            ;

10$:            movab   data_buff,r6                    ; address of ident record
                cmpb    #USG$K_IDENT,USG$B_TYPE(r6)     ; record type ok?
                beql    15$                             ; branch if ok
                movab   not_ident_msg,r0                ;
                bsbw    write_msg                       ;
                brw     done                            ;

;--------------------------------------------------------------------------------
;       The write_fmt macro is used to format and and write the contents
;       of the IDENT record
;--------------------------------------------------------------------------------

15$:            write_fmt -                             ; creation time
                        item    = time,-
                        fao_p1  = #data_buff+USG$Q_TIME

                write_fmt -                             ; volume serial number
                        item    = serial,-
                        fao_p1  = USG$L_SERIALNUM(r6)

                write_fmt -                             ; structure (volume set) name
                        item    = strucname,-
                        fao_p1  = #USG$S_STRUCNAME,-
                        fao_p2  = #data_buff+USG$T_STRUCNAME

                write_fmt -                             ; volume name
                        item    = volname,-
                        fao_p1  = #USG$S_VOLNAME,-
                        fao_p2  = #data_buff+USG$T_VOLNAME

                write_fmt -                             ; ownername (of volume)
                        item    = ownername,-
                        fao_p1  = #USG$S_OWNERNAME,-
                        fao_p2  = #data_buff+USG$T_OWNERNAME

                write_fmt -                             ; file format
                        item    = format,-
                        fao_p1  = #USG$S_FORMAT,-
                        fao_p2  = #data_buff+USG$T_FORMAT

                bsbw    blank_line                      ; write blank line


;--------------------------------------------------------------------------------
;       Comparison file initialization.
;
;       The code below does two things:
;
;               - it initializes the data structures for the first part
;                 of the output comparison file
;
;               - if an input comparison file exists, it reads in the
;                 file's IDENT record and checks whether the file can
;                 be used for comparison
;
;       The IDENT record from the USAGE.DAT file is used as the first part of
;       the output USAGE.CMP file.
;
;       To uniquely identify a comparison file, its USG$B_TYPE field has the
;       value -USG$K_IDENT (note the negation).  The program's version number
;       is also stored in the comparison file as a check on the integrity of the
;       file (a comparison file created by a different version of this program
;       may not have compatible pointer values in the tree structure).
;
;       When a comparison file is read in, we need to do these checks before
;       we can accept it as a comparison file:
;
;               - we need to make sure it is in fact a comparison file by
;                 checking USG$B_TYPE for the value -USG$K_IDENT
;
;               - we need to make sure it's for the same disk volume
;
;               - we need to check that it was created by the same version
;                 of the program
;
;--------------------------------------------------------------------------------
cmp_init:

;  intialize the data structures for the first part of the output .CMP file

                mnegb   #USG$K_IDENT,USG$B_TYPE(r6)     ; identifies .CMP file
                movc3   #USG$C_IDENT_LEN,(r6),tree_ident
                movw    #version,tree_version

;  if an input .CMP file exists, do some tests before accepting it

                movb    icmp_state,r7                   ; does it exist?
                bneq    10$                             ; branch if yes
                movab   no_icmp_msg,r0                  ; say no input .CMP
                bsbw    write_msg                       ;
                brw     70$                             ;

10$:            movab   cmp_tree_ident,r8               ; input address
                movl    r8,icmp_rab+rab$l_ubf           ;
                movl    #cmp_size,icmp_rab+rab$w_usz    ; set size
                $get    rab = icmp_rab                  ; read first part
                blbs    r0,20$                          ; branch if ok
                moval   icmp_rab,r2                     ;
                brw     record_error                    ;

20$:            cmpb    USG$B_TYPE(r8),#-USG$K_IDENT    ; really .CMP file?
                beql    25$                             ; branch if yes
                movab   wrong_type_msg,r0               ;
                brb     40$                             ;
25$:            cmpc3   #USG$S_VOLNAME,-                ; volume names match?
                        USG$T_VOLNAME(r6),-             ;
                        USG$T_VOLNAME(r8)               ;
                beql    30$                             ; branch if yes
                movab   wrong_volume_msg,r0             ;
                brb     40$                             ;

30$:            cmpw    #version,cmp_tree_version       ; versions match?
                bneq    35$                             ; branch if not
                                                        ; else all is ok
                write_fmt -                             ; show .CMP creation time
                        item    = icmp_time,-
                        fao_p1  = #cmp_tree_ident+USG$Q_TIME
                brb     70$

35$:            movab   wrong_version_msg,r0            ;
40$:            bsbw    write_msg                       ; report discrepancy
                movb    #-1,icmp_state                  ; set .CMP status

;--------  we finish with icmp_state set properly  --------;

70$:            bsbw    blank_line


;--------------------------------------------------------------------------------
;       If we have a proper input comparison file, we read it in.
;--------------------------------------------------------------------------------
read_cmp:       tstb    icmp_state                      ; check status
                bgtr    10$                             ; branch if ok
                brw     tree_init                       ; else ignore comparison file

10$:            addl2   #cmp_size,icmp_rab+rab$l_ubf    ; set address
                $get    rab = icmp_rab                  ; read first part
                blbs    r0,10$                          ; loop back if ok
                cmpl    r0,#RMS$_EOF                    ; exit loop if eof
                beql    relink                          ;
                moval   icmp_rab,r2                     ; else some other error
                brw     record_error                    ;


;--------------------------------------------------------------------------------
;       Relink comparison tree
;
;       The next_sibling and first_child links for each node in the comparison
;       tree contain the values that they had when the tree was built.  These
;       values are addresses within "tree" itself and are relative to the DATA
;       psect.  On being read back in, they need to be adjusted to point into
;       "cmp_tree".  Although it is possible to make the links relative to the
;       base of the tree itself, the extra code that would be needed in every
;       reference to a link would have made the code for tree building and tree
;       comparison very awkward.  Thus, by not choosing that method and doing
;       this relinking step instead, the process of tree building and comparison
;       is straightforward.
;
;       This relinking step does not guarantee comparison files to be compatible
;       across different versions of this program, however (even if the relative
;       distance between the two trees remains the same).
;
;       We traverse cmp_tree and adjust all the links by the value of
;       cmp_tree - tree.  Note also that a tree is guaranteed to have at least
;       one node.
;--------------------------------------------------------------------------------
relink:         pushl   #0                              ; indicates when to stop
                movab   cmp_tree,r1                     ; used to traverse tree
                subl3   #tree,r1,r2                     ; adjustment value
10$:            movl    first_child(r1),r0              ; child exists?
                beql    20$                             ; branch if not
                pushl   r1                              ; else save our place
                addl2   r2,r0                           ; adjust child pointer
                movl    r0,first_child(r1)              ;
                movl    r0,r1                           ; descend to child
                brb     10$                             ; continue

20$:            movl    next_sibling(r1),r0             ; sibling exists?
                beql    30$                             ; branch if not
                addl    r2,r0                           ; adjust sibling pointer
                movl    r0,next_sibling(r1)             ;
                movl    r0,r1                           ; go to sibling
                brb     10$                             ; test for child

30$:            movl    (sp)+,r1                        ; restore our place
                bneq    20$                             ; continue if not done

;       At this point, the comprison file has been read in and relinked


;--------------------------------------------------------------------------------
;
;       Read USAGE.DAT and accumulate usage information
;
;       Here we read the FILE records and build a tree representing the logical
;       structure of directories on the disk.  For every file on the disk there
;       is a FILE record in the USAGE.DAT file.  The FILE record contains, among
;       other things, the complete file specification and the number of blocks
;       used and allocated.  As part of the tree building process, the usage
;       information for a file is summed into that file's parent directory and
;       all subsequent parent directories, including totals for the entire disk.
;
;       Here is a simple overview of the algorithm; specific details and data
;       structures are given in the appropriate part of the code.
;
;       T1 [initialization]  Set totals to 0.  Create first node of tree.
;
;       T2 [new file]  Read a record from USAGE.DAT.  If end-of-file, then the
;               algorithm terminates.  Otherwise add usage data into totals,
;               set a pointer (p) to the first node, and set current branch name
;               (bn) to the first branch name in the directory specification.
;
;       T3 [scan for branch] Starting from p and following the next_sibling chain,
;               scan all nodes for a match with bn.  If a match is found, set p and
;               go to step T5.  If a match is not found (either because end of chain
;               or bn is lexically less than the name in the next node), continue
;               on with step T4.
;
;       T4 [new sibling]  Create a new node, add it to the next_sibling chain, and
;               set p to it.
;
;       T5 [accumulate]  The usage information in the record is added into node p.
;
;       T6 [follow branch name]  If there is another branch name in the directory
;               specification, then set bn to that and go on to the next step.
;               Otherwise we are done with this file and go back to step T2.
;
;       T7 [descend to child node]  If node p has a child node, then set p to
;               that and go to step T3.  Otherwise, create a child node and
;               set p to that.  Go on to step T5.
;       
;       Register usage:
;
;               r6  = pointer to data_buff (the current FILE record)
;               r7  = pointer to found node in the tree
;               r8  = pointer to first character in a branch name
;               r9  = length of branch name
;               r10 = count of available nodes in the tree
;               r11 = pointer to next available node
;--------------------------------------------------------------------------------


;--------------------------------------------------------------------------------
;       Initialization: set totals to zero, create first node of tree
;--------------------------------------------------------------------------------
tree_init:      moval   tree_total_files,r0             ; clear totals:
                clrl    (r0)+                           ; total files
                clrl    (r0)+                           ; total blocks used
                clrl    (r0)                            ; total blocks allocated

                movl    #max_nodes,r10                  ; ready to create first node
                bgtr    10$                             ; should have room for it
                brw     no_space                        ; else error

10$:            movab   tree,r11                        ; r11 points to node in tree
                clrb    branch_name(r11)                ; branch name is null: []
                clrb    name_length(r11)                ; length is 0
                clrl    file_count(r11)                 ; no files
                clrl    blocks_used(r11)                ; no blocks used
                clrl    blocks_allocated(r11)           ; no blocks allocated
                clrl    next_sibling(r11)               ; no sibling nodes
                clrl    first_child(r11)                ; no child nodes
                addl2   #node_size,r11                  ; update ptr to next avail node
                decl    r10                             ; decrement node count

;--------------------------------------------------------------------------------
;       New file: read a record from USAGE.DAT, add usage data into totals,
;       look at first branch name in the directory specification
;--------------------------------------------------------------------------------
new_file:       $get    rab = data_rab                  ; read FILE record
                blbs    r0,10$                          ; branch if ok
                cmpl    r0,#RMS$_EOF                    ; test and branch if eof
                beql    5$                              ; 
                moval   data_rab,r2                     ;
                brw     record_error                    ; else error

5$:             brw     print_tree                      ; on eof, print the tree

10$:            cmpb    #USG$K_FILE,USG$B_TYPE(r6)      ; record type ok?
                beql    20$                             ; branch if ok
                movab   not_file_msg,r0                 ;
                bsbw    write_msg                       ;
                brw     done                            ;

20$:            moval   tree_total_files,r0             ; ready to update totals
                incl    (r0)+                           ; total files
                addl2   USG$L_USED(r6),(r0)+            ; total used
                addl2   USG$L_ALLOCATED(r6),(r0)        ; total allocated

                movab   tree,r7                         ; ptr to first node in tree

;--------------------------------------------------------------------------------
;       Setup for first branch name: for a branch name such as "[abc.de.fghij]",
;       we set r8 to point to "a" and set r9 = 3.  We also decrement the count
;       in USG$W_DIR_LEN in order to simplify things later on.
;--------------------------------------------------------------------------------
                movab   USG$T_FILESPEC+1(r6),r8         ; ptr to branch name
                cvtwl   USG$W_DIR_LEN(r6),r9            ; full directory spec length
                subl2   #2,r9                           ; discount "[" and "]"
                subw2   #2,USG$W_DIR_LEN(r6)            ;
                beql    40$                             ; branch if null name
                locc    #^a\.\,r9,(r8)                  ; scan for "."
                beql    40$                             ; branch if not found
                subl2   r0,r9                           ; set name length
                decw    USG$W_DIR_LEN(r6)               ; remove "." from length
40$:                                                    ; r9 now set properly
                subw2   r9,USG$W_DIR_LEN(r6)            ; set remaining length

;--------------------------------------------------------------------------------
;       Scan: scan the nodes in the tree looking for a match with the current
;       branch name.  Here we have:
;
;                       r7 = node p (name) pointer
;                       r1 = node p name length
;                       r8 = current branch name pointer
;                       r9 = current branch name length
;
;       Note: cmpc5 uses r0..r3, the shorter name is zero-filled
;--------------------------------------------------------------------------------
scan:           cvtbw   name_length(r7),r1              ; cmpc needs word operand
                cmpc5   r9,(r8),#0,r1,(r7)              ; compare current to node p
                beql    accumulate                      ; branch if match
                blss    new_sibling                     ; branch if went too far

                                                        ; no match, advance to next node
                movl    r7,r5                           ; save node pointer
                movl    next_sibling(r7),r7             ; advance along node chain
                bneq    scan                            ; branch to test next node

;----------------------------------------------------------------------------------
;       New sibling: we arrive here either if the current branch name is lexically
;       less than the branch name at node p (r7) or if we're at the end of the
;       node chain.  In either case, r5 points to the node to which we will link
;       a new one.  Note that since the initial node has a null name, this also
;       guarantees at least one pass through the above loop and that r5 will get
;       a chance to be set to a valid value.
;
;       We fill in the fields of the new node according to the FILE record's
;       fields.  Since filling in the node information encompasses the "accumulate"
;       step, we can really skip that part.
;----------------------------------------------------------------------------------
new_sibling:    decl    r10                             ; decrement node count
                bgeq    10$                             ; branch if we have room
                brw     no_space                        ; else count had been zero

10$:            movl    #1,file_count(r11)              ; first file in new directory
                movl    USG$L_USED(r6),blocks_used(r11) ; set blocks used/allocated
                movl    USG$L_ALLOCATED(r6),blocks_allocated(r11)
                movl    next_sibling(r5),next_sibling(r11)      ; copy from prev node
                movl    r11,next_sibling(r5)            ; prev node now points here
                clrl    first_child(r11)                ; no child nodes yet
                movb    r9,name_length(r11)             ; name length fits in a byte
                movc3   r9,(r8),branch_name(r11)        ; movc3 uses r0..r5
                movl    r11,r7                          ; set node pointer
                addl2   #node_size,r11                  ; point to next available node
                brb     follow                          ; we've done "accumulate"

;----------------------------------------------------------------------------------
;       Accumulate:  usage information for the current FILE record is added into
;       node p
;----------------------------------------------------------------------------------
accumulate:     incl    file_count(r7)                  ; another file in this dir
                addl2   USG$L_USED(r6),blocks_used(r7)  ; set blocks used/allocated
                addl2   USG$L_ALLOCATED(r6),blocks_allocated(r7)

;----------------------------------------------------------------------------------
;       Follow:  if there's another branch name in the directory specification,
;       we set the current branch name to that in preparation for creating a
;       child node.  Otherwise, we are done with this file.
;----------------------------------------------------------------------------------
follow:         addl2   r9,r8                           ; possible next branch name
                incl    r8                              ;
                cvtwl   USG$W_DIR_LEN(r6),r9            ; get remaining length
                bneq    10$                             ; branch if more to do
                brw     new_file                        ; else done with this file

10$:            locc    #^a\.\,r9,(r8)                  ; scan for "."
                beql    40$                             ; branch if not found
                subl2   r0,r9                           ; set name length
                decw    USG$W_DIR_LEN(r6)               ; remove "." from length
40$:                                                    ; r9 now set properly
                subw2   r9,USG$W_DIR_LEN(r6)            ; set remaining length

;----------------------------------------------------------------------------------
;       Descend:  on following the new branch name, if node p has a child node
;       we set p to that and go back to scan the child nodes.  Otherwise we
;       create a child node.  The code here is very similar to that in creating
;       a new sibling.
;----------------------------------------------------------------------------------
descend:        movl    r7,r0                           ; point to the parent
                movl    first_child(r7),r7              ; child node?
                beql    10$                             ; branch if not
                brw     scan                            ; else scan child nodes

10$:            decl    r10                             ; decrement node count
                bgeq    20$                             ; branch if we have room
                brw     no_space                        ; else count had been zero

20$:            movl    r11,first_child(r0)             ; parent points to new child
                movc3   r9,(r8),branch_name(r11)        ; set name of child node
                movb    r9,name_length(r11)             ; name length fits in a byte
                movl    #1,file_count(r11)              ; first file in new directory
                movl    USG$L_USED(r6),blocks_used(r11) ; set blocks used/allocated
                movl    USG$L_ALLOCATED(r6),blocks_allocated(r11)
                clrl    next_sibling(r11)               ; no siblings yet
                clrl    first_child(r11)                ; no child nodes yet
                movl    r11,r7                          ; set node pointer
                addl2   #node_size,r11                  ; point to next available node
                brb     follow                          ; we've done "accumulate"


;--------------------------------------------------------------------------------
;       Compare and print the tree, with indentation to reflect the hierarchy
;
;       If we have an input comparison file, then we traverse both trees
;       simultaneoulsy and print information on which directories have
;       changed and by how much.
;
;       Here is a simplified overview of the algorithm used to compare the two
;       trees:
;
;       Algorithm C [compare trees]  We have two trees: the current tree and the
;       comparison tree.  Each node in a tree represents either a directory or
;       subdirectory.  At any given level, all the nodes in that level are linked
;       in lexical order.  We let p be a pointer to a node in the current tree and
;       let q be the corresponding pointer in the comparison tree.  The approach
;       used is to let p walk through the nodes of p while attempting to track
;       this movement with q in cmp_tree.  In addition, we have two variables,
;       p_level and q_level, and two stacks, p_stack and q_stack, which are used
;       in keeping track of our position.
;
;        C1 [initialization]  Push a 0 onto p_stack; when this value gets popped
;               off, the algorithm will terminate.  Set both p_level and q_level
;               to 1, set p to the first node in tree, and q to the first node
;               in cmp_tree.
;
;        C2 [clear comparison string]  Set cmp_string to to blanks.  This string
;               provides the information in the "change" column.  If a directory in
;               tree has no counterpart in cmp_tree, this string remains blank.
;
;        C3 [compare levels]  If q_level is greater than p_level, then pop q_stack
;               to get new values for q and q_level and repeat this step.
;               Otherwise, if q_level = p_level then go on to step C4, or if
;               q_level is less than p_level, go on to step C6.
;
;        C4 [compare nodes]  If node q does not exist (i.e. has the value 0),
;               then go on to step C6.  Otherwise compare name(q) with name(p).
;               If name(q) is less than name(p), set q to its next sibling and
;               repeat this step.  If name(q) = name(p) continue with step C5.
;               If name(q) is greater than name(p), go on to step C6.
;
;        C5 [set difference]  Set cmp_string to the difference in blocks_allocated(p)
;               and blocks_allocated(q).
;
;        C6 [output]  Output disk usage information for node p, incorporating the
;               information in cmp_string.
;
;        C7 [next directory]  If node p has any child nodes, go on to step C9.
;               Otherwise, set p to its next sibling and continue with step C8.
;
;        C8 [check continuation]  If node p exists, then continue with step C2.
;               Otherwise, pop p_stack to get new values for p_level and p.
;               If p is 0, then the algorithm terminates.  Otherwise, go on
;               with step C2.
;
;        C9 [set p continuation]  If node p has a next sibling, push a pointer
;               to it onto p_stack along with the current value of p_level.
;
;       C10 [descend in tree]  Increment p_level and set p to its child node.
;
;       C11 [check q correspondence]  If q_level is equal to what p_level was
;               just recently (e.g. p_level - 1) and node q has a child node,
;               then go on to the next step, otherwise go to step C2.
;
;       C12 [set q continuation]  If node q has a next sibling, push a pointer
;               to it onto q_stack along with the current value of q_level.
;
;       C13 [descend in cmp_tree]  Increment q_level, set q to its child node,
;               and go to step C2.
;
;       Register usage:
;
;           r0..r3   =  scratch, also used by cmpc instruction
;               r4   =  copy of icmp_state
;               r5   =  indentation amount
;               r6   =  p_level
;               r7   =  p
;               r8   =  q_level
;               r9   =  q
;               r10  =  q_stack pointer
;               r11  =  pointer to next available node (we need this for later)
;
;       NOTE: if no input comparison file is being used, we avoid doing anything
;       with the comparison tree.
;--------------------------------------------------------------------------------

print_tree:
                movab   heading_msg,r0                  ; write heading & blank line
                bsbw    write_msg                       ;
                bsbw    blank_line                      ;

;--------------------------------------------------------------------------------
;       Initialization:  initialize stacks, levels, and node pointers.  The
;       current indentation amount is also saved on the stack.
;--------------------------------------------------------------------------------
                pushl   #0                              ; mark p_stack top
                cvtbl   #1,r6                           ; set p_level to 1
                pushl   r6                              ; save on p_stack
                movab   tree,r7                         ; set p to walk through tree
                clrl    r5                              ; indent amount currently 0
                pushl   r5                              ; save indent amount
                cvtbl   icmp_state,r4                   ; use comparison tree?
                bleq    clear                           ; branch if not
                movab   q_stack_top,r10                 ; initialize q_stack pointer
                movl    r6,r8                           ; set q_level to 1
                movab   cmp_tree,r9                     ; set q to walk through cmp_tree

;--------------------------------------------------------------------------------
;       The "change" column is blank if there's no comparison file, but when
;       there is one, we want the "change" column to indicate those directories
;       that are new and have no equivalent in cmp_tree.
;--------------------------------------------------------------------------------

                movl    new_string,blank_string+4

;--------------------------------------------------------------------------------
;       Clear:  clear comparison string.  At this point we can also skip a lot
;       of comparison code if there is no comparison tree.
;--------------------------------------------------------------------------------
clear:          movq    blank_string,cmp_string         ; set comparison string to blanks
                tstl    r4                              ; use comparison tree?
                bgtr    cmp_levels                      ; branch if yes
                brw     output                          ; else skip comparisons

;--------------------------------------------------------------------------------
;       Compare levels:  if we're further down in cmp_tree than in tree, we try
;       to come back to the same level in cmp_tree.  If we're at the same
;       level, we go on to compare nodes.  If we're further up in cmp_tree
;       (meaning there's no branch in cmp_tree that corresponds to where we are
;       in tree), then we skip the comparisons.
;--------------------------------------------------------------------------------
cmp_levels:     cmpl    r8,r6                           ; compare q_level to p_level
                bgtr    10$                             ;
                beql    cmp_nodes                       ;
                brw     output                          ;

10$:            movl    (r10)+,r8                       ; pop q_level from q_stack
                movl    (r10)+,r9                       ; pop q from q_stack
                brb     cmp_levels                      ; compare again

;--------------------------------------------------------------------------------
;       Compare nodes:  if node q does not exist (e.g. we've reached the end
;       of its sibling chain at the current q_level), then we can skip this
;       comparison.  Otherwise we scan forward in q until we either find a
;       name that matches that in p, or until we've gone too far.
;--------------------------------------------------------------------------------
cmp_nodes:      tstl    r9                              ; does node q exist?
                bneq    10$                             ; branch if yes
                brw     output                          ;

10$:            cvtbw   name_length(r9),r0              ; get word operands
                cvtbw   name_length(r7),r2              ;
                cmpc5   r0,(r9),#0,r2,(r7)              ; compare names
                beql    set_diff                        ;
                blss    20$                             ;
                brw     output                          ;

20$:            movl    next_sibling(r9),r9             ; advance q to next sibling
                brb     cmp_nodes                       ; compare again

;--------------------------------------------------------------------------------
;       Set difference:  set cmp_string to reflect the difference in blocks
;       allocated between p and q.
;--------------------------------------------------------------------------------
set_diff:       subl3   blocks_allocated(r9),-          ; r0 = allocation difference
                        blocks_allocated(r7),-          ;
                        r0                              ;

                $fao_s  -                               ; set new cmp_string
                        ctrstr  =       cmp_fmt,-       ; format control string
                        outlen  =       fao_outlen,-    ; where to store length
                        outbuf  =       cmp_outbuf,-    ; descriptor for cmp_string
                        p1      =       r0              ; value to convert = difference
                blbs    r0,output                       ; branch if converted ok
                movab   cmp_cvt_msg,r0                  ; else write error message
                bsbw    write_msg                       ;
                brw     done                            ;

;--------------------------------------------------------------------------------
;       Output:  here we write a line of information for the file described
;       by node p.   At this time cmp_string has been set to show the allocation
;       difference.  If a comparison file is not being used, or if there is no
;       file corresponding to that at node p, cmp_string is blank.
;--------------------------------------------------------------------------------

output:         cvtbl   name_length(r7),r0              ; need longword arg for FA0
                $fao_s  -
                        ctrstr  =       tree_fmt,-      ; descriptor for format control string
                        outlen  =       fao_outlen,-    ; where to store length of formatted output
                        outbuf  =       fao_outbuf,-    ; descriptor for formatted output buffer
                        p1      =       r5,-
                        p2      =       #indent_string,-
                        p3      =       r0,-
                        p4      =       r7,-
                        p5      =       file_count(r7),-
                        p6      =       blocks_used(r7),-
                        p7      =       blocks_allocated(r7),-
                        p8      =       #cmp_field_size,-
                        p9      =       #cmp_string

                blbs    r0,10$                          ; branch if converted ok
                movab   tree_cvt_msg,r0                 ; else set up to write error message
                bsbw    write_msg                       ;
                brw     done                            ;

10$:            cvtwb   fao_outlen,lis_outlen           ; set length of formatted output
                movab   lis_outlen,r0                   ; .ASCIC format
                bsbw    write_msg                       ; write it

;--------------------------------------------------------------------------------
;       Next directory:  if node p has any child nodes, then follow that
;       path.  Otherwise, follow the next_sibling chain.
;--------------------------------------------------------------------------------
next_dir:       movl    first_child(r7),r0              ; any child nodes?
                bneq    set_cont                        ; branch if yes
                movl    next_sibling(r7),r7             ; else go to next sibling
                beql    10$                             ; branch if none
                brw     clear                           ; else continue on

10$:            movl    (sp)+,r5                        ; pop indent amount
                movl    (sp)+,r6                        ; pop p_level
                movl    (sp)+,r7                        ; pop p
                beql    20$                             ; branch if no more
                bsbw    blank_line                      ; blank line when coming up
                brw     clear                           ; continue on

20$:            brw     write_totals                    ; we're done here

;--------------------------------------------------------------------------------
;       Set continuation and descend:  if node p has a next sibling, push a
;       pointer to it on the stack and the current value of p_level.   We then
;       increment p_level and descend to the child node (a subdirectory).
;
;       We arrive here with r0 pointing to the child of node p.
;--------------------------------------------------------------------------------

set_cont:       movl    next_sibling(r7),r1             ; look for next sibling
                beql    10$                             ; branch if none
                pushl   r1                              ; save pointer to it
                pushl   r6                              ; save p_level
                pushl   r5                              ; save indent amount
10$:            addl2   #indent_value,r5                ; indent branch name on listing
                movl    r6,r1                           ; save for reference below
                incl    r6                              ; indicate one more level down
                movl    r0,r7                           ; descend to child node

;--------------------------------------------------------------------------------
;       If node q has a corresponding child node, we check and set its
;       continuation point and then descend in cmp_tree also.
;--------------------------------------------------------------------------------

                tstl    r4                              ; use comparison tree?
                bgtr    20$                             ; branch if yes
                brw     clear                           ; else ignore it

20$:            cmpl    r8,r1                           ; q_level = p_level?
                beql    30$                             ; branch if yes
                brw     clear                           ; else ignore this level

30$:            movl    first_child(r9),r0              ; corresponding child?
                bneq    40$                             ; branch if yes
                brw     clear                           ; else ignore

40$:            movl    next_sibling(r9),r1             ; look for next sibling
                beql    60$                             ; branch if none
                cmpl    r10,#q_stack                    ; room in q_stack?
                bgtru   50$                             ; branch if yes
                movab   q_stack_msg,r0                  ; say out of room
                bsbw    write_msg                       ;
                brw     done                            ;

50$:            movl    r1,-(r10)                       ; push next_sibling onto q_stack
                movl    r8,-(r10)                       ; push q_level onto q_stack
60$:            incl    r8                              ; down one level in cmp_tree
                movl    r0,r9                           ; point to child
                brw     clear                           ; continue on


;--------------------------------------------------------------------------------
;       Write out totals
;--------------------------------------------------------------------------------
write_totals:   bsbw    blank_line                      ; write blank line
                clrl    r1                              ; zero total difference so far
                tstl    r4                              ; branch if no comparison file
                bleq    10$                             ;
                subl3   cmp_tree_total_allocated,-      ; get overall difference
                        tree_total_allocated,-
                        r1
10$:            $fao_s  -
                        ctrstr  =       total_fmt,-     ; descriptor for format control string
                        outlen  =       fao_outlen,-    ; where to store length of formatted output
                        outbuf  =       fao_outbuf,-    ; descriptor for formatted output buffer
                        p1      =       tree_total_files,-
                        p2      =       tree_total_used,-
                        p3      =       tree_total_allocated,-
                        p4      =       r1
                blbs    r0,20$                          ; branch if converted ok
                movab   total_cvt_msg,r0                ; else set up to write error message
                bsbw    write_msg                       ;
                brw     done                            ;

20$:            cvtwb   fao_outlen,lis_outlen           ; set length of formatted output
                movab   lis_outlen,r0                   ; .ASCIC format
                bsbw    write_msg                       ; write it


;--------------------------------------------------------------------------------
;       Write out the USAGE.CMP file.  Since r11 points to the next available
;       node, it also serves to indicate when to stop writing
;--------------------------------------------------------------------------------
write_cmp:
                movw    #cmp_size,ocmp_rab+rab$w_rsz    ; set record size
                movab   tree_ident,r2                   ; where to begin output
10$:            cmpl    r2,r11                          ; branch out if done
                blssu   15$                             ;
                brw     done                            ;

15$:            movl    r2,ocmp_rab+rab$l_rbf           ; set output address
                $put    rab = ocmp_rab                  ; write to .CMP file
                blbs    r0,20$                          ; branch if ok
                moval   ocmp_rab,r2                     ;
                brw     record_error                    ; else abort out

20$:            addl2   #cmp_size,r2                    ; advance pointer
                brb     10$                             ;

;--------------------------------------------------------------------------------
;       Arrive here if we're unable to allocate nodes in the tree
;--------------------------------------------------------------------------------
no_space:       movab   no_space_msg,r0
                bsbw    write_msg
                brw     done


;--------------------------------------------------------------------------------
;       write_msg
;
;       A routine to write a message to the USAGE.LIS file.
;       Call with:      R0 = address of an .ASCIC string
;                       Exits on error.
;--------------------------------------------------------------------------------
write_msg:      cvtbw   (r0)+,lis_rab+rab$w_rsz         ; set message size
                movl    r0,lis_rab+rab$l_rbf            ; set message address
                $put    rab = lis_rab                   ; write to list file
                blbs    r0,10$                          ; branch if ok
                moval   lis_rab,r2                      ;
                brw     record_error                    ; else abort out

10$:            rsb                                     ; else return


;--------------------------------------------------------------------------------
;       blank_line
;
;       Prints a blank line
;
;       (we could imbed CR, LF's inside records, but viewing the USAGE.LIS file
;       in some editors won't handle this properly).
;--------------------------------------------------------------------------------
blank_line:     movab   blank_msg,r0
                bsbw    write_msg
                rsb


;--------------------------------------------------------------------------------
;       Exit and error handling
;
;       In this section we close any open files and then exit.
;       The status code associated with the error is in r0.
;
;--------------------------------------------------------------------------------

record_error:   pushl   r0                              ; save status
                bsbb    close_files                     ;
                movl    (sp)+,r0                        ; set status
file_error:     ret                                     ; return w/status in r0

done:           bsbb    close_files                     ;
                ret                                     ; return w/status in r0

close_files:    $close  fab = data_fab                  ; close USAGE.DAT
                $close  fab = lis_fab                   ; close USAGE.LIS
                $close  fab = ocmp_fab                  ; close USAGE.CMP (output)
                tstb    icmp_state                      ; look at USAGE.CMP (input)
                beql    10$                             ; branch if not open
                $close  fab = icmp_fab                  ; close USAGE.CMP (input)
10$:            rsb                                     ;


                .psect  DATA,wrt,noexe,LONG
;--------------------------------------------------------------------------------
;       Access blocks and buffer for USAGE.DAT
;
;       At the time of the $open, any filename components provided by the
;       user will be reflected in the fna and fns fields of the fab.  Any
;       missing components are provided by the dnm (default name) field of
;       the fab.  As a result of the $open, the resulting file specification
;       (based on any user input and defaults) is parsed and placed in the
;       nam block.  The file specification components in the nam block are
;       then used as defaults on subsequent $open or $create calls.
;--------------------------------------------------------------------------------

                .align  long
data_fab:       $fab    fna = fname_buff,-              ; possibly set by user
                        dnm = <sys$manager:usage.dat>,- ; provide default components
                        nam = data_nam,-                ; point to NAM block
                        fac = <get>,-                   ; GET access only
                        shr = <nil>,-                   ; exclusive access
                        fop = sqo                       ; sequential only

                .align  long
data_nam:       $nam    rsa = data_rsa,-                ; NAM used for related filespecs
                        rss = nam$c_maxrss              ; (maximum resultant string size)
data_rsa:       .blkb   nam$c_maxrss                    ; buffer for filespec components

                .align  long
data_rab:       $rab    fab = data_fab,-                ; point to FAB
                        rop = rah,-                     ; read-ahead
                        ubf = data_buff,-               ; addr of usage data buffer
                        usz = data_size                 ; data buffer size

data_buff:      .blkb   USG$C_FILE_LEN                  ; usage data buffer
data_size       =       . - data_buff


;--------------------------------------------------------------------------------
;               Access blocks for USAGE.CMP (optional input file)
;--------------------------------------------------------------------------------

                .align  long
icmp_fab:       $fab    fnm = <.cmp>,-                  ; primary name
                        nam = icmp_nam,-                ; point to NAM
                        fac = <get>,-                   ; GET access only
                        shr = <nil>,-                   ; exclusive access
                        fop = <nam,sqo>,-               ; NAM input, sequential only
                        rfm = fix,-                     ; fixed length records
                        mrs = cmp_size                  ; record size

                .align  long
icmp_nam:       $nam    rlf = data_nam                  ; provide default components

                .align  long
icmp_rab:       $rab    fab = icmp_fab,-                ; point to FAB
                        rop = rah                       ; read-ahead


;--------------------------------------------------------------------------------
;       Access blocks and buffer for USAGE.LIS (created output file)
;--------------------------------------------------------------------------------

                .align  long
lis_fab:        $fab    fnm = <.lis>,-                  ; primary name
                        nam = lis_nam,-                 ; point to NAM
                        fac = <put>,-                   ; PUT access only
                        shr = <nil>,-                   ; exclusive access
                        fop = <nam,sqo>,-               ; NAM input, sequential only
                        mrs = lis_size,-                ; max record size
                        rat = cr                        ; implied carriage control

                .align  long
lis_nam:        $nam    rlf = data_nam                  ; provide default components

                .align  long
lis_rab:        $rab    fab = lis_fab,-                 ; point to FAB
                        rop = wbh                       ; write-behind

lis_outlen:     .blkb                                   ; length of output
lis_buff:       .blkb   lis_size                        ; listing buffer


;--------------------------------------------------------------------------------
;       Access blocks and buffer for USAGE.CMP (created output file)
;--------------------------------------------------------------------------------

                .align  long
ocmp_fab:       $fab    fnm = <.cmp>,-                  ; primary name
                        nam = ocmp_nam,-                ; point to NAM
                        fac = <put>,-                   ; PUT access only
                        shr = <nil>,-                   ; exclusive access
                        fop = <nam,sqo>,-               ; NAM input, sequential only
                        rfm = fix,-                     ; fixed length records
                        mrs = cmp_size                  ; record size

                .align  long
ocmp_nam:       $nam    rlf = data_nam                  ; provide default components

                .align  long
ocmp_rab:       $rab    fab = ocmp_fab,-                ; point to FAB
                        rop = wbh                       ; write-behind


;--------------------------------------------------------------------------------
;               Messages
;--------------------------------------------------------------------------------

version_msg:            .ascic  \Disk Usage  v2.12\
ident_len_msg:          .ascic  \invalid record length for IDENT record\
not_ident_msg:          .ascic  \first record is not IDENT record\
time_cvt_msg:           .ascic  \unable to convert creation time\
serial_cvt_msg:         .ascic  \unable to convert serial number\
strucname_cvt_msg:      .ascic  \unable to convert volume set name\
volname_cvt_msg:        .ascic  \unable to convert volume name\
ownername_cvt_msg:      .ascic  \unable to convert owner name\
format_cvt_msg:         .ascic  \unable to convert format description\
not_file_msg:           .ascic  \record is not a FILE record\
no_icmp_msg:            .ascic  \No input comparison file\
wrong_type_msg:         .ascic  \input comparison file ignored: incorrect type field\
wrong_volume_msg:       .ascic  \input comparison file ignored: wrong volume\
wrong_version_msg:      .ascic  \input comparison file ignored: program version mismatch\
icmp_time_cvt_msg:      .ascic  \unable to convert comparison time\
heading_msg:            .ascic  \Directories                             File count  blk used/allocated   change\
no_space_msg:           .ascic  \out of room -- please increase max_nodes and reassemble\
blank_msg:              .ascic  \\
total_cvt_msg:          .ascic  \unable to convert totals\
cmp_cvt_msg:            .ascic  \unable to convert difference in block allocation\
q_stack_msg:            .ascic  \q_stack overflow -- please increase q_stack_size and reassemble\
tree_cvt_msg:           .ascic  \unable to convert name or size information\

;--------------------------------------------------------------------------------
;               Data structures for FA0 messages
;--------------------------------------------------------------------------------
fao_outlen:     .blkw
fao_outbuf:     string_dsc      lis_buff,lis_size

indent_string:  .ascic  \\

time_fmt:       .ascid  \Usage file created:     !%D\
serial_fmt:     .ascid  \Serial number (octal):  !OL\
strucname_fmt:  .ascid  \Volume set name:        !AD\
volname_fmt:    .ascid  \Volume name:            !AD\
ownername_fmt:  .ascid  \Owner name:             !AD\
format_fmt:     .ascid  \File format:            !AD\
icmp_time_fmt:  .ascid  \Comparison file dated:  !%D\
total_fmt:      .ascid  \Total files  !SL,   blocks used/allocated  !SL/!SL,   change  !SL\

tree_fmt:       .ascid  \!40<!#AC!AD!>  !8SL  !8SL/!8<!SL!>  !8AD\

;--------------------------------------------------------------------------------
;       Data structures used in setting the allocation difference
;--------------------------------------------------------------------------------

new_string:     .ascii  \ new\
blank_string:   .ascii  \        \
cmp_string:     .ascii  \        \
cmp_fmt:        .ascid  \!8SL\
cmp_outbuf:     string_dsc      cmp_string,cmp_field_size


;--------------------------------------------------------------------------------
;       Miscellaneous data structures
;
;       icmp_state reflects the state of the optional USAGE.CMP input file:
;
;               icmp_state < 0  ==>  file exists, but is not to be used
;               icmp_state = 0  ==>  file does not exist
;               icmp_state > 0  ==>  file exists and is being used
;
;--------------------------------------------------------------------------------

icmp_state:             .blkb                           ; state of USAGE.CMP (input)

;       This small stack is used while traversing the comparison tree

q_stack:                .blkl   q_stack_size
q_stack_top:


;       Data structures for optional name to override default (length is first word in dsc)

fname_buff:     .blkb           max_name_size
fname_dsc:
fname_length:   string_dsc      fname_buff,max_name_size

;--------------------------------------------------------------------------------
;               Data structures for the nodes
;--------------------------------------------------------------------------------

;       sizes of elements in the node data structure (in bytes)

branch_name_size        =       max_name_size   ; max size of branch name
name_length_size        =       1               ; since branch name length < 256 chars
file_count_size         =       4               ; longword count
blocks_used_size        =       4               ; longword count
blocks_allocated_size   =       4               ; longword count
next_sibling_size       =       4               ; longword pointer
first_child_size        =       4               ; longword pointer

;       offsets within the node data structure

branch_name             =       0
name_length             =       branch_name + branch_name_size
file_count              =       name_length + name_length_size
blocks_used             =       file_count + file_count_size
blocks_allocated        =       blocks_used + blocks_used_size
next_sibling            =       blocks_allocated + blocks_allocated_size
first_child             =       next_sibling + next_sibling_size

node_size               =       first_child + first_child_size


;--------------------------------------------------------------------------------
;       Data structures for the trees.  The tree built from the USAGE.DAT file
;       becomes the new comparison file that gets written out.  The comparison
;       file read in (if any) constitutes the comparison tree (cmp_tree).
;
;       To maintain and verify the integrity of the comparison files, the
;       IDENT record from the USAGE.DAT file which is used to build the tree
;       is also part of the first record in the comparison file.  The contents
;       of this first record is described in the cmp_init section.
;
;       The comparison file is written out in records of cmp_size bytes.  We
;       want to make sure that storage for the tree (starting with the IDENT
;       part) is allocated to the nearest multiple of cmp_size.  This guarantees
;       that for whatever amount gets written out (from tree_ident) there will
;       be room for it when being read back later (into cmp_tree_ident).
;
;       NOTE: various sections of code expect the totals to be grouped together
;
;--------------------------------------------------------------------------------
tree_ident:             .blkb   USG$C_IDENT_LEN         ; first part of usage.cmp
tree_version:           .blkw                           ; for integrity check
tree_total_files:       .blkl                           ; total files on disk
tree_total_used:        .blkl                           ; total blocks used
tree_total_allocated:   .blkl                           ; total blocks allocated
tree:                   .blkb   max_nodes * node_size   ; tree of nodes
size                    =       . - tree_ident
blocks                  =       <size + <cmp_size - 1>> / cmp_size
fill                    =       <blocks * cmp_size> - size
                        .blkb   fill

cmp_tree_ident:         .blkb   USG$C_IDENT_LEN         ; first part of usage.cmp
cmp_tree_version:       .blkw                           ; for integrity check
cmp_tree_total_files:   .blkl                           ; total files on disk
cmp_tree_total_used:    .blkl                           ; total blocks used
cmp_tree_total_allocated:.blkl                          ; total blocks allocated
cmp_tree:               .blkb   max_nodes * node_size   ; tree of nodes
                        .blkb   fill

                        .end    DISK_USAGE