From:	CRDGW2::CRDGW2::MRGATE::"SMTP::CRVAX.SRI.COM::RELAY-INFO-VAX"  9-NOV-1989 09:12
To:	MRGATE::"ARISIA::EVERHART"
Subj:	Reporting on disk usage and maintaining quotas

Message-Id:  <8911091351.AA18366@crdgw1.ge.com>
Received: From NSFNET-RELAY.AC.UK by CRVAX.SRI.COM with TCP; Thu,  9 NOV 89 05:09:07 PDT
Received: from ncdlab.ulcc.ac.uk by vax.NSFnet-Relay.AC.UK 
           via Janet with NIFTP  id aa04790; 9 Nov 89 12:26 GMT
Date:           Thu, 9 Nov 89  12:26 BST
From: Nick de Smith <"PSI%JANET.000000000040::NICK%ncdlab.ulcc.ac.uk"@NSFnet-Relay.AC.UK>
To: INFO-VAX <@NSFnet-Relay.AC.UK:INFO-VAX@crvax.sri.com>
Subject:        Reporting on disk usage and maintaining quotas

Hi,

A couple of days ago I posted a rather crummy procedure for reloading and
sorting a disk's quota file. It worked, but was rather inflexible and slow.
Also, it relied on the output format of DISKQUOTA.EXE (nasty).

Included at the end of this message is a VMS_SHARE(1/1) file containing a proper
version of SORT_DISKQUOTA.COM which I will support.
It is easy to tailor this version to generate reports about disk usage for
specific volumes in your own, favourite, format (say, to have user SYSTEM
receive a MAIL message when people get close to or over their quotas, or to
write a file for inclusion in a billing package).

This command file produces an output command file (default name
RELOAD_DISKQUOTA.COM), that, when executed, will reload the specified disk's
quota file in alphabetical order. A report on disk usage is given, and warnings
are displayed about users/identifiers that are close to or over allocation. The
report format is very easy to tailor.

This can make maintenance much more clear on systems with large numbers of
users.

P1		Name of disk to use. Default is SYS$SYSDEVICE.
P2		Name of command file to generate. Default is
		SYS$DISK:[]RELOAD_DISKQUOTA.COM.

The procedure works by reading the specified QUOTA.SYS file, and loading a
formatted version of each active record into an indexed file. This, by default,
sorts the records on the primary key, which in this case is an ASCII
representation of the UIC (as used by DISKQUOTA). We read back the indexed file
sequentially to get the records in alphabetical order. I used to use SORT, but
this method is neater (you don't think so... well, I do!).

Note that if a file is owned by a "wildcard UIC", say "[USER,*]", and you do a
REBUILD, then an entry for this "wildcard" UIC appears in QUOTA.SYS and can be
seen by DISKQUOTA. However, you cannot REMOVE, MODIFY, SHOW (except with "SHOW
*") or ADD such an entry (you can delete it with REMOVE * but this is a bit
drastic). Is this a "feature" or a "bug" in DISKQUOTA? (I vote for the latter).
If such an entry exists, this procedure will attempt to ADD it, but the actual
ADD command (only that line) will fail.

regards,

nick	Comments, as usual, to: NICK@NCDLAB.ULCC.AC.UK

=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=
| Nick de Smith                  |  Voice:   +44 892 511000         |
| Applied Telematics Group Ltd   |  PSImail: +234213300154::NICK    |
| Telematics House               |  Fax:     +44 892 38556 (G3)     |
| Tunbridge Wells, Kent          |  Telex:   95398 TELEMA G (UK)    |
| TN1 1DJ, England               | Internet: NICK@NCDLAB.ULCC.AC.UK |
! Janet:  NICK@UK.AC.ULCC.NCDLAB | (NICK%NCDLAB.ULCC.AC.UK@UKACRL)  |
| "Though the wise man may make his home in a glass house, its the  |
|      happy man who drops his anchor in the bay of discontent"     |
=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=

$! ------------------ CUT HERE -----------------------
$ v='f$verify(f$trnlnm("SHARE_VERIFY"))'
$!
$! This archive created by VMS_SHARE Version 7.1-004  3-AUG-1989
$!   On  9-NOV-1989 11:43:39.04   By user NICK 
$!
$! This VMS_SHARE Written by:
$!    Andy Harper, Kings College London UK
$!
$! Acknowledgements to:
$!    James Gray       - Original VMS_SHARE
$!    Michael Bednarek - Original Concept and implementation
$!
$! TO UNPACK THIS SHARE FILE, CONCATENATE ALL PARTS IN ORDER
$! AND EXECUTE AS A COMMAND PROCEDURE  (  @name  )
$!
$! THE FOLLOWING FILE(S) WILL BE CREATED AFTER UNPACKING:
$!       1. SORT_DISKQUOTA.COM;1
$!
$set="set"
$set symbol/scope=(nolocal,noglobal)
$f=f$parse("SHARE_TEMP","SYS$SCRATCH:.TMP_"+f$getjpi("","PID"))
$e="write sys$error  ""%UNPACK"", "
$w="write sys$output ""%UNPACK"", "
$ if f$trnlnm("SHARE_LOG") then $ w = "!"
$ if f$getsyi("version") .ges. "V4.4" then $ goto START
$ e "-E-OLDVER, Must run at least VMS 4.4"
$ v=f$verify(v)
$ exit 44
$UNPACK: SUBROUTINE ! P1=filename, P2=checksum
$ if f$search(P1) .eqs. "" then $ goto file_absent
$ e "-W-EXISTS, File ''P1' exists. Skipped."
$ delete/nolog 'f'*
$ exit
$file_absent:
$ if f$parse(P1) .nes. "" then $ goto dirok
$ dn=f$parse(P1,,,"DIRECTORY")
$ w "-I-CREDIR, Creating directory ''dn'."
$ create/dir 'dn'
$ if $status then $ goto dirok
$ e "-E-CREDIRFAIL, Unable to create ''dn'. File skipped."
$ delete/nolog 'f'*
$ exit
$dirok:
$ w "-I-PROCESS, Processing file ''P1'."
$ define/user sys$output nl:
$ EDIT/TPU/NOSEC/NODIS/COM=SYS$INPUT 'f'/OUT='P1'
PROCEDURE Unpacker ON_ERROR ENDON_ERROR;SET(FACILITY_NAME,"UNPACK");SET(
SUCCESS,OFF);SET(INFORMATIONAL,OFF);f:=GET_INFO(COMMAND_LINE,"file_name");
buff:=CREATE_BUFFER(f,f);p:=SPAN(" ")@r&LINE_END;POSITION(BEGINNING_OF(buff))
;LOOP EXITIF SEARCH(p,FORWARD)=0;POSITION(r);ERASE(r);ENDLOOP;POSITION(
BEGINNING_OF(buff));g:=0;LOOP EXITIF MARK(NONE)=END_OF(buff);x:=
ERASE_CHARACTER(1);IF g = 0 THEN IF x="X" THEN MOVE_VERTICAL(1);ENDIF;IF x=
"V" THEN APPEND_LINE;MOVE_HORIZONTAL(-CURRENT_OFFSET);MOVE_VERTICAL(1);ENDIF;
IF x="+" THEN g:=1;ERASE_LINE;ENDIF;ELSE IF x="-" THEN g:=0;ENDIF;ERASE_LINE;
ENDIF;ENDLOOP;p:="`";POSITION(BEGINNING_OF(buff));LOOP r:=SEARCH(p,FORWARD);
EXITIF r=0;POSITION(r);ERASE(r);COPY_TEXT(ASCII(INT(ERASE_CHARACTER(3))));
ENDLOOP;o:=GET_INFO(COMMAND_LINE,"output_file");WRITE_FILE(buff,o);
ENDPROCEDURE;Unpacker;EXIT;
$ delete/nolog 'f'*
$ CHECKSUM 'P1'
$ IF CHECKSUM$CHECKSUM .eqs. P2 THEN $ EXIT
$ e "-E-CHKSMFAIL, Checksum of ''P1' failed."
$ ENDSUBROUTINE
$START:
$ create/nolog 'f'
X$`009Setv = 'F$Verify( "No" )
X$!
X$! File`009`009`009: SORT_DISKQUOTA.COM
X$! Author`009`009: Nick de Smith
X$! Creation date`009: 22-May-89
X$!
X$! Edit`009Edit date`009By`009Why
X$!  01`00908-Nov-89`009NMdS`009New (from original)
X$!`009`009`009`009Re-written to directly access the disk quota file
X$!`009`009`009`009Now gives better reports.
X$!`009`009`009`009Also, now use an indexed file to sort the quota file
X$! Description:
X$! This command file produces an output command file (default name RELOAD_DI
VSKQUOTA.COM), that, when
X$! executed, will reload the specified disk's quota file alphabetical order.
X$! A report on disk usage is given, and warnings are displayed about users/i
Vdentifiers that are close
X$! to or over allocation.
X$! This can make maintenance much more clear on systems with large numbers o
Vf users.
X$!
X$!`009P1`009`009Name of disk to use. Default is SYS$SYSDEVICE.
X$!`009P2`009`009Name of command file to generate. Default is RELOAD_DISKQUOT
VA.COM.
X$!
X$! The procedure works by reading the specified QUOTA.SYS file, and loading
V a formatted version of each
X$! active record into an indexed file. This, by default, sorts the records o
Vn the primary key, which in
X$! this case is an ASCII representation of the UIC (as used by DISKQUOTA).
X$! We read back the indexed file sequentially to get the records in alphabet
Vical order. I used to use SORT,
X$! but this method is neater?!!!???!!
X$!
X$! Note that if a file is owned by a "wildcard UIC", say "`091USER,*`093", a
Vnd you do a REBUILD, then an entry
X$! for this "wildcard" UIC appears in QUOTA.SYS and can be seen by DISKQUOTA
V. However, you cannot REMOVE,
X$! MODIFY, SHOW (except with "SHOW *") or ADD such an entry (you can delete
V it with REMOVE * but this is
X$! a bit drastic). Is this a "feature" or a bug?
X$! If such an entry exists, this procedure will attempt to ADD it, but the a
Vctual ADD command (only that
X$! line) will fail.
X$!
X$`009Set Symbol /Scope=(NoLocal,NoGlobal)`009`009`009`009`009! Insulate ours
Velves
X$!
X$`009Name`009= F$Parse( F$Environment( "PROCEDURE" ) ,,, "NAME" )`009`009! N
Vame of this procedure
X$`009Info`009= "%" + Name + "-I, "`009`009`009`009`009`009! Information mess
Vage
X$`009Warn`009= "%" + Name + "-W, "`009`009`009`009`009`009! Warning message
X$`009Fatal`009= "%" + Name + "-F, "`009`009`009`009`009`009! Fatal error mes
Vsage
X$`009Say`009= "Write SYS$OUTPUT"`009`009`009`009`009`009! Print to terminal
X$!
X$`009Say ""
X$`009Say Name, " - Sort diskquota file entries"
X$`009Say ""
X$!
X$`009On WARNING Then Goto HANDLER
X$`009On CONTROL_Y Then Goto END
X$!
X$`009Safety_Limit`009= 10`009`009`009`009`009`009`009! Warning threshold (%)
V for identifiers
X$`009Command`009`009= "RELOAD_DISKQUOTA.COM"`009`009`009`009! Default name o
Vf command file to generate
X$`009Sorted`009`009= "SYS$SCRATCH:DISKQUOTA_" + F$GetJPI( "", "PID" ) + ".SO
VRTED"`009! Name for temp sorted output file
X$!
X$`009Total_InUse`009= 0`009`009`009`009`009`009`009! Total number of blocks
V in use
X$`009Total_Allowed`009= 0`009`009`009`009`009`009`009! Total number of block
Vs that could be allowed
X$`009Total_With_Overdrafts = 0`009`009`009`009`009`009! Absolute maximum wit
Vh overdrafts
X$`009Total_Identifiers = 0`009`009`009`009`009`009`009! Total number of user
Vs
X$!
X$!`009Definitions of the disk quota file format taken from SYS$LIBRARY:LIB.M
VLB, $DQFDEF
X$!
X$`009DQF$L_FLAGS`009= 0`009`009`009`009`009`009`009! Flag word
X$`009`009DQF$M_ACTIVE`009= 1`009`009`009`009`009`009`009! Record is active
X$`009DQF$L_UIC`009= 4`009`009`009`009`009`009`009! UIC of record
X$`009DQF$L_USAGE`009= 8`009`009`009`009`009`009`009! Current usage
X$`009DQF$L_PERMQUOTA`009= 12`009`009`009`009`009`009`009! Allowed permanent
V quota
X$`009DQF$L_OVERDRAFT`009= 16`009`009`009`009`009`009`009! Allowed overdraft
X$!
X$`009Device`009:= 'P1`009`009`009`009`009`009`009`009! Pick up device name
X$`009Device = F$Parse( Device, "SYS$SYSDEVICE:",, "DEVICE" )`009`009`009! Ge
Vnerate name of device to use
X$`009If (Device .eqs. "") .or. .not. F$GetDVI( Device, "EXISTS" )
X$`009Then`009Say Fatal, "Invalid device name specfied"
X$`009`009Goto END
X$`009EndIf
X$`009Device = F$GetDVI( Device, "FULLDEVNAM" )`009`009`009`009! Get full dev
Vice name
X$`009Quota_File = Device + "`091000000`093QUOTA.SYS"`009`009`009`009! Name o
Vf disk quota file
X$`009If F$Search( Quota_File ) .eqs. ""`009`009`009`009`009! If quota file n
Vnot found...
X$`009Then`009Say Fatal, "Disk quotas not enabled on ", Device
X$`009`009Goto END
X$`009EndIf
X$!
X$`009Command = F$Parse( P2, Command )`009`009`009`009`009! Generate command
V file name
X$`009If Command .eqs. ""
X$`009Then`009Say Fatal, "Invalid command file name specfied"
X$`009`009Goto END
X$`009EndIf
X$!
X$`009Create /Fdl=SYS$INPUT &Sorted`009`009`009`009`009`009! Create the sorte
Vd output file
XFile
X`009Organization`009Indexed
X`009Best_Try_Contiguous Yes
X`009Extension`00916
XRecord
X`009Format`009`009Fixed
X`009Size`009`00944
XKey 0
X`009Name`009`009"Identifier"
X`009Type`009`009String
X`009Seg0_Position`0090
X`009Seg0_Length`00932
X$!
X$`009Open /Read /Share=Write /Error=QUOTA_OPEN_ERROR QUOTA_FILE &Quota_File`
V009! Open the disk quota file
X$`009Open /Read /Write SORTED &Sorted`009`009`009`009`009! Open sorted outpu
Vt file
X$!
X$`009Say Info, "Processing ", Device, " and generating ", Command
X$`009OutRec`091 0, 32 + 3*4 `093  :=`009`009`009`009`009`009! Pre-extend out
Vput record
X$ 10$:`009Read /End=20$ /Error=QUOTA_READ_ERROR QUOTA_FILE Record`009`009`00
V9! Read one record from the quota file
X$`009If (F$CvUI( DQF$L_FLAGS*8, 32, Record ) .and. DQF$M_ACTIVE)`009`009! If
V record is active...
X$`009Then`009OutRec`091    0, 32 `093 := 'F$Fao( "!%I", F$CvUI( DQF$L_UIC*8,
V 32, Record ) ) ! Save user identifier
X$`009`009OutRec`091 32*8, 32 `093 = F$CvUI( DQF$L_USAGE*8    , 32, Record )`
V009! Save current usage
X$`009`009OutRec`091 36*8, 32 `093 = F$CvUI( DQF$L_PERMQUOTA*8, 32, Record )`
V009!  ''  permanent quota
X$`009`009OutRec`091 40*8, 32 `093 = F$CvUI( DQF$L_OVERDRAFT*8, 32, Record )`
V009!  ''  allowed overdraft
X$`009`009Write SORTED OutRec`009`009`009`009`009`009! Save the output record
X$`009EndIf
X$`009Goto 10$
X$!
X$ 20$:`009Close QUOTA_FILE
X$`009Close SORTED
X$!
X$! Read the sorted file and convert it into a DISKQUOTA.EXE command file
X$!
X$`009Open /Read SORTED &Sorted`009`009`009`009`009`009! Open sorted listing
V file
X$`009Open /Write COMMAND &Command`009`009`009`009`009`009! Open output comma
Vnd file
X$`009Write COMMAND "$!"
X$`009Write COMMAND "$! Command file to reload ", F$TrnLNM( "SYS$NODE" ), Quo
Vta_File
X$`009Write COMMAND "$! Generated by ", F$Edit( F$GetJPI( "", "USERNAME" ), "
VCOLLAPSE" ), " using ", Name, " at ", F$Time()
X$`009Write COMMAND "$!"
X$`009Write COMMAND "$`009Run SYS$SYSTEM:DISKQUOTA"
X$`009Write COMMAND "Use ", Device
X$`009Write COMMAND "Remove *"
X$ LOOP:
X$`009Read /End=END_LOOP /Error=END_LOOP SORTED Record`009`009`009! Get an in
Vput record
X$`009Identifier`009= F$Extract( 0, 32, Record )`009`009`009`009! Extract the
V identifier
X$`009Current`009`009= F$CvUI( 32*8, 32, Record )`009`009`009`009! Current us
Vage
X$`009Permanent`009= F$CvUI( 36*8, 32, Record )`009`009`009`009! Extract perm
Vanent quota
X$`009Overdraft`009= F$CvUI( 40*8, 32, Record )`009`009`009`009! Extract perm
Vanent quota
X$`009Total_Identifiers = Total_Identifiers + 1`009`009`009`009! Count number
V of identifiers seen
X$`009Total_InUse`009= Total_InUse + Current`009`009`009`009`009! Check the r
Vunning total
X$`009Total_Allowed`009= Total_Allowed + Permanent`009`009`009`009! Check the
V total possible
X$`009Total_With_Overdrafts = Total_With_Overdrafts + Permanent + Overdraft`0
V09! Check the grand total possible
X$!
X$`009If Current .gt. Permanent`009`009`009`009`009`009! If someone is over q
Vuota
X$`009Then
X$`009`009Say Warn, F$Fao("!AS is using !UL block!%S over quota", Identifier,
V Current - Permanent )
X$`009Else
X$`009`009Temp = ((Permanent - Current)*100) / Permanent`009`009`009! Determi
Vne % of quota left
X$`009`009If Temp .le. Safety_Limit Then -`009`009`009`009! If less than x% l
Veft...
X`009`009`009Say Warn, F$Fao("!AS is within !UL% of their quota", Identifier,
V Temp)
X$`009EndIf
X$!
X$! I wanted to allow the current usage to be added at the end of the line, b
Vut DISKQUOTA is a crummy program
X$! and does not allow comments. In fact, its so crummy that if you do use th
Ve commented out next two lines, rather
X$! than the two lines following them, the addition will work as intended, bu
Vt you will get an error message for each
X$! line about the comment.
X$!
X$!`009Write COMMAND F$Fao( "Add !25AS /PermQuota=!10AS /Overdraft=!10AS!_!!
V !10UL block!%S", -
X$!`009`009`009 Identifier, F$String( Permanent ), F$String( Overdraft ), Cur
Vrent )
X$`009Write COMMAND F$Fao( "Add !25AS /PermQuota=!10AS /Overdraft=!UL", Ident
Vifier, F$String( Permanent ), Overdraft )
X$`009Goto LOOP
X$!
X$ END_LOOP:
X$`009Write COMMAND "Rebuild"
X$`009Write COMMAND "$!"
X$`009Close COMMAND
X$!
X$!`009Write out the usage report
X$!
X$`009Say Info, F$Fao( "Total of !UL block!%S in use by !UL identifier!%S", T
Votal_InUse, Total_Identifiers )
X$`009Say Info, F$Fao( "!UL further block!%S could be allocated making an tot
Val of !UL block!%S (!UL including overdrafts)", -
X`009`009Total_Allowed - Total_Inuse, Total_Allowed, Total_With_Overdrafts )
X$`009Say Info, F$Fao( "!AS volume size is !UL block!%S, with !UL block!%S cu
Vrrently free", -
X`009`009Device, F$GetDVI( Device, "MAXBLOCK" ), F$GetDVI( Device, "FREEBLOCK
VS" ) )
X$`009Say ""
X$`009Say Info, "Execute ", Command, " to rebuild your disk quota file for ",
V Device
X$`009Goto END
X$!
X$ HANDLER:
X$`009Say Fatal, F$Fao( "Unexpected error!/!AS", F$Message( $STATUS ) )
X$`009Goto END
X$!
X$ QUOTA_OPEN_ERROR:
X$`009Say Fatal, F$Fao( "Error opening !AS!/!AS", Quota_File, F$Message( $STA
VTUS ) )
X$`009Goto END
X$!
X$ QUOTA_READ_ERROR:
X$`009Say Fatal, F$Fao( "Error reading !AS!/!AS", Quota_File, F$Message( $STA
VTUS ) )
X$`009Goto END
X$!
X$! Clean up any old rubbish
X$!
X$ END:
X$`009Close /Error=E_1$ COMMAND
X$ E_1$:`009Close /Error=E_2$ SORTED
X$ E_2$:`009Close /Error=E_3$ QUOTA_FILE
X$ E_3$:`009If F$Search( Sorted   ) .nes. "" Then Delete /NoLog 'F$Parse( ";*
V", Sorted )
X$`009If F$Search( Command  ) .nes. "" Then Purge  /NoLog 'F$Element( 0, ";",
V Command )
X$`009Setv = F$Verify( Setv )
$ CALL UNPACK SORT_DISKQUOTA.COM;1 1983038348
$ v=f$verify(v)
$ EXIT

