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