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