carl@CITHEX.CALTECH.EDU.UUCP (02/06/87)
First, my apologies for forwarding the chain letter to infovax. I did it simply because I thought the forwarding history was somewhat amusing (it gave some idea of the length of time mail forwarding/delivery took over various networks, and showed the various syntaxes used by various, as somebody put it "brain-damaged VMS mailers"). Needless to say, I won't do it again. Now, as a peace offering: In light of the response to my earlier posting regarding ODS-2 documentation, the number of postings in the last year regarding the discrepency between dir/size=all/grand and diskquota reports of disk usage, the number of postings asking for ways to generate C source modules equivalent to various modules in the system macro libraries, and the difficulty I had figuring out how to use LIB$INSERT_TREE in a C program, there follow three files that address each of these questions. First: C source modules from the system macro libraries: $! MAKEHFILE.COM -- Convert a module in SYS$LIBRARY:STARLET.MLB or $! SYS$SHARE:LIB./MLB into something C can deal with. $! Note: This is not sophisticated enough to define $! structures; it simply picks out $EQU type definitions $! from the library module and converts them to #defines. $! Usage: @MAKEHFILE module-name $ set noon $ IF "''F$SEARCH("TMPFILE.TMP")'" .NES. "" THEN delete tmpfile.tmp;* $ on error then goto oops $ library/extract='p1'/output=tmpfile.tmp/macro sys$share:starlet $ goto form $ oops: on error then exit $ library/extract='p1'/output=tmpfile.tmp/macro sys$share:lib $ form: ON ERROR THEN GOTO DONE $ OPEN/READ INFILE tmpfile.tmp $ DELETE TMPFILE.TMP; $ OPEN/WRIT OUFILE TMPFILE.TMP $ MAXLEN = 0 $ LOOP: READ/ERRO=SORT INFILE SYM $ sym = f$edit(sym,"COMPRESS,TRIM") $ if f$element(0," ", sym) .nes. "$EQU" then goto LOOP $ token = f$element(1," ",sym) $ IF F$LEN(TOKEN) .GT. MAXLEN THEN MAXLEN = F$LEN(TOKEN) $ value = F$FAO("!XL",f$integer(f$element(2," ",sym))) $ WRITE OUFILE VALUE,TOKEN $ IF "''P2'" .NES. "" THEN WRITE 'P2' VALUE,TOKEN $ GOTO LOOP $ SORT: CLOSE INFILE $ CLOSE OUFILE $ SORT/KEY=(POS=9,SIZ='MAXLEN') TMPFILE.TMP TMPFILE.TMP $ OPEN/READ INFILE TMPFILE.TMP $ DELETE TMPFILE.TMP;* $ OPEN/WRITE OUFILE 'P1'.H $ SPACES = F$FAO("!#* ", MAXLEN + 1) $ MAXLEN = MAXLEN + 9 $ NICE: READ/ERR=DONE INFILE SYM $ VALUE = F$EXTRACT(0,8,SYM) $ SYM = SYM - VALUE + F$EXTR(0, MAXLEN-F$LEN(SYM), SPACES) + "0x" + VALUE $ WRITE OUFILE "#define ", SYM $ IF "''P2'" .NES. "" THEN WRITE 'P2' "#define ", SYM $ GOTO NICE $ DONE: CLOSE INFILE $ CLOSE OUFILE $ IF "''F$SEARCH("TMPFILE.TMP")'" .NES. "" THEN delete tmpfile.tmp;* Next: A file to define C stuctures for ODS-2 Home blocks and file headers /******************************************************************************\ * ODS2.H -- ODS-2.1 structure definitions * \******************************************************************************/ /* Home Block -- VBN 2 of INDEXF.SYS */ struct HOME_BLOCK { unsigned long HBLB; /* Home Block LBN */ unsigned long AHLB; /* Alternate Home Block LBN */ unsigned long IHLB; /* Backup Index File Header LBN */ unsigned short VLEV; /* Structure Level and Version */ unsigned short SBCL; /* Storage Bitmap Cluster Factor */ unsigned short HBVB; /* Home Block VBN */ unsigned short AHVB; /* Backup Home Block VBN */ unsigned short IHVB; /* Backup Index File Header VBN */ unsigned short IBVB; /* Index File Bitmap VBN */ unsigned long IBLB; /* Index File Bitmap LBN */ unsigned long FMAX; /* Maximum Number of Files */ unsigned short IBSZ; /* Index File Bitmap Size */ unsigned short RSVF; /* Number of Reserved Files */ unsigned short DVTY; /* Disk Device Type */ unsigned short RVN; /* Relative Volume Number */ unsigned short NVOL; /* Number of Volumes */ unsigned short VCHA; /* Volume Characteristics */ /**************************************\ * need to define * * CH.NDC, CH.NAT, CH.RCK, and CH.WCK * \**************************************/ unsigned long VOWN; /* Volume Owner UIC */ unsigned long VSMX; /* Volume Security Mask */ unsigned short VPRO; /* Volume Protection */ unsigned short DFPR; /* Default File Protection */ unsigned short DRPC; /* Default Record Protection */ unsigned short CHK1; /* First Checksum */ unsigned long VDAT[2]; /* Volume Creation Date */ unsigned char WISZ; /* Default Window Size */ unsigned char LRUC; /* Directory Pre-Access Limit */ unsigned short FIEX; /* Default File Extend */ unsigned char H_1[388]; /* (not used) */ unsigned char SNAM[12]; /* Structure Name */ unsigned char INDN[12]; /* Volume Name */ unsigned char INDO[12]; /* Volume Owner */ unsigned char INDF[12]; /* Format Type */ unsigned char H_2[2]; /* (not used) */ unsigned short CHK2; /* Second Checksum */ }; /* File Header Header */ struct FILE_HEADER_HEADER { unsigned char IDOF; /* Ident Area Offset */ unsigned char MPOF; /* Map Area Offset */ unsigned char ACOF; /* Access Control List Offset */ unsigned char RSOF; /* Reserved Area Offset */ unsigned short FSEG; /* Extension Segment Number */ unsigned short FLEV; /* Structure Level and Version */ unsigned short FNUM; /* File Number */ unsigned short FSEQ; /* File Sequence Number */ unsigned short FRVN; /* Relative Volume Number */ unsigned short EFNU; /* Extension File Number */ unsigned short EFSQ; /* Extension File Sequence Number */ unsigned short ERVN; /* Extension Relative Volume Number */ unsigned char UFAT[32]; /* User File Attributes */ variant_union { unsigned long FCHA; /* File Characteristics */ variant_struct { unsigned char UCHA; /* User Controlled Char. */ unsigned char SCHA; /* System Controlled Char. */ unsigned char F_1[2]; /* not named */ } F_2; } F_3; unsigned char F_4[2]; /* not used */ unsigned char USE; /* Map Words in Use */ unsigned char PRIV; /* Accessor Privilege Level */ variant_union { unsigned long FOWN; /* File Owner UIC */ variant_struct { unsigned short PROG; /* Programmer (Member) No. */ unsigned short PROJ; /* Project (Group) Number */ } F_5; } F_6; unsigned short FPRO; /* File Protection Code */ /**************************************\ * Need to define * * FP.RDV, FP.WRV, FP.EXE, and FP.DEL * \**************************************/ unsigned short RPRO; /* Record Protection Code */ /**************************************\ * Need to define * * RP.RDV, RP.WRV, RP.EXE, and RP.DEL * \**************************************/ unsigned char F_7[4]; /* Not Used */ unsigned long SEMK; /* Security Mask */ }; #define HDHD sizeof(struct FILE_HEADER_HEADER) /* File Header Ident Area */ struct FILE_HEADER_IDENT { unsigned char FNAM[20]; /* File Name */ unsigned short RVNO; /* Revision Number */ unsigned long CRDT[2]; /* Creation Date and Time */ unsigned long RVDT[2]; /* Revision Date and Time */ unsigned long EXDT[2]; /* Expiration Date and Time */ unsigned long BKDT[2]; /* Backup Date and Time */ unsigned char ULAB[80]; /* User Label */ }; #define IDHD sizeof(struct FILE_HEADER_IDENT) /* File Header */ struct FILE_HEADER { struct FILE_HEADER_HEADER H; /* Header Area */ char FH_1[510-HDHD]; /* Ident, Map, ACL and Reserved Areas */ unsigned short CKSM; /* Block Checksum */ }; /* Retrieval pointers */ struct RTR_PTR_00 { unsigned PLACEMENT : 14; /* Placement Control */ unsigned FORMAT : 2; /* Retrieval Pointer Format */ }; struct RTR_PTR_01 { unsigned char COUNT_01; /* Count */ unsigned HI_LBN_01 : 6; /* High-Order Part of LBN */ unsigned FORMAT : 2; /* Retrieval Pointer Format */ unsigned short LO_LBN_01; /* Low-Order Part of LBN */ }; struct RTR_PTR_10 { unsigned COUNT_10 : 14; /* Count */ unsigned FORMAT : 2; /* Retrieval Pointer Format */ unsigned long LBN_10; /* Logical Block Number */ }; struct RTR_PTR_11 { unsigned HI_CNT_11 : 14; /* High-Order Part of Count */ unsigned FORMAT : 2; /* Retrieval Pointer Format */ unsigned short LO_CNT_11; /* Low-Order Part of Count */ unsigned long LBN_11; /* Logical Block Number */ }; Finally: A program to produce a disk usage summary by UIC. The motivation for this file is as follows: If you're not using disk quotas, the only ways to get disk usage summaries out of VMS are via DIRECTORY/SIZE=ALL and ANALYZE/DISK/USAGE. Both are inefficient ways of doing this, the former because you have to either repeatedly use $ DIRECTORY/SIZE=ALL/BY_OWNER=uic/GRAND DISK:[000000...] or use $ DIRECTORY/SIZE=ALL/OWNER/OUTPUT=filespec DISK:[000000...] then process the output file, aggregating the usage by owner; and the latter because it insists on checking the validity of the file structure as well as generating a usage summary, and the usage summary itself requires postprocessing. The following program reads the index file for the volume, aggregating usage statistics on the fly, then prints out a formatted summary. The aggregation is done via the LIB$INSERT_TREE library routine, which is documented for, as I recall, PASCAL users (yes, there are descriptions of all the parameters in the standard system routine description format, but the translation of these into C concepts is non-trivial), and the printing of the summary via LIB$TRAVERSE_TREE. Since DEC didn't see fit to include definitions for return codes from LIB$*_TREE routines in the LIBDEF module of VAXCDEF.TLB, you need to use MAKEHFILE.COM (or equivalent) to create a less brain-damaged version of this module. The program below assumes you called the result $LIBDEF.H. To use the utility, define DISKACCT as a foreign command, then invoke it as $ DISKACCT DISK:[000000]INDEXF.SYS. Note that you need read access to the volume's index file to use it. /******************************************************************************\ * DISKACCT.C -- Summarize disk usage by UIC * \******************************************************************************/ #include <descrip.h> #include <$libdef.h> #include <file.h> #include "ods2.h" #line 9 "diskacct.c" struct NODE { struct NODE *node$a_left; struct NODE *node$a_right; short node$w_mbz; long node$l_uic; long node$l_cnt; } *root = 0; long compare(uic, node, cnt) struct NODE *node; long *uic, *cnt; { if (*uic == node->node$l_uic) node->node$l_cnt += *cnt; return (*uic > node->node$l_uic ? 1 : *uic < node->node$l_uic ? -1 : 0); } long allocate (uic, newnode, cnt) struct NODE **newnode; long *uic, *cnt; { struct NODE *node; long i; i = sizeof ( struct NODE ); if (((i = LIB$GET_VM(&i, newnode)) && 7) != 1) exit(i); node = *newnode; node->node$a_left = node->node$a_right = node->node$w_mbz = 0; node->node$l_uic = *uic; node->node$l_cnt = *cnt; return(1); } long action(node, n) struct NODE *node; long n; { printf("[%06o,%06o] %d\n",(node->node$l_uic & 0xFFFF0000) >> 16, node->node$l_uic & 0x0000FFFF, node->node$l_cnt); return(1); } struct FILE_HEADER header; struct HOME_BLOCK home; #define FIRST_FID_INDEX (4 * home.SBCL + home.IBSZ) main(nargs, args) int nargs; char **args; { unsigned long fid, map_bytes, i, count, uic, header_num, level, status; unsigned char format; unsigned short cksm, *ptr; unsigned long FFFFFFFF = 0xFFFFFFFF; struct NODE *newnode; long ctlflag = 0; struct RTR_PTR_00 *rp_0; struct RTR_PTR_01 *rp_1; struct RTR_PTR_10 *rp_2; struct RTR_PTR_11 *rp_3; if ((fid = open(*++args, O_RDONLY, 0, "shr=put")) < 0) exit(0); if (read(fid, (char *) &home, 512) != 512) exit(0); if (read(fid, (char *) &home, 512) != 512) exit(0); for (i = FIRST_FID_INDEX - 2; i > 0; --i) if (read(fid, (char *) &header, 512) != 512) exit(0); header_num = 0; while (read(fid, (char *) &header, 512) == 512) { for (ptr = (char *) &header, cksm = 0, i = 0; i < 255; ++i, ++ptr) cksm += *ptr; header_num++; level = header.H.FLEV & 0xFF00; if (cksm != header.CKSM && (header.CKSM != 0125252 && header.H.SCHA & 16 != 0)) ; else if (header.H.IDOF < (short *) &header.H.FOWN - (short *) &header) ; else if (header.H.IDOF > header.H.MPOF) ; else if (header.H.MPOF > header.H.ACOF) ; else if (header.H.ACOF > header.H.RSOF) ; else if (level != 0x200) ; else if (header.H.FLEV & 0xFF < 1) ; else if (header.H.FNUM != header_num) ; else if (header.H.USE > header.H.ACOF - header.H.MPOF) ; else { rp_0 = (struct RTR_PTR_00 *) (((unsigned short *) &header) + header.H.MPOF); uic = header.H.FOWN; for (map_bytes = 2 * header.H.USE, count = 0; map_bytes > 0;) { switch (format = rp_0->FORMAT) { case 0: rp_0++; map_bytes -= sizeof(struct RTR_PTR_00); break; case 1: rp_1 = (struct RTR_PTR_01 *) rp_0; if (rp_1->HI_LBN_01 != 0x03F || rp_1->LO_LBN_01 != FFFF) count += rp_1->COUNT_01 + 1; map_bytes -= sizeof(struct RTR_PTR_01); rp_1++; rp_0 = (struct RTR_PTR_00 *) rp_1; break; case 2: rp_2 = (struct RTR_PTR_10 *) rp_0; if (rp_2->LBN_10 != FFFFFFFF) count += rp_2->COUNT_10 + 1; map_bytes -= sizeof(struct RTR_PTR_10); rp_2++; rp_0 = (struct RTR_PTR_00 *) rp_2; break; case 3: rp_3 = (struct RTR_PTR_11 *) rp_0; if (rp_3->LBN_11 != FFFFFFFF) count += rp_3->HI_CNT_11 << 16 + rp_3->LO_CNT_11 + 1; map_bytes -= sizeof(struct RTR_PTR_11); rp_3++; rp_0 = (struct RTR_PTR_00 *) rp_3; break; default: puts("Invalid retrieval pointer encountered\n"); exit(0); } } if (((status = LIB$INSERT_TREE(&root, &uic, &ctlflag, compare, allocate, &newnode, &count)) & 7) != 1 && status != LIB$_KEYALRINS) exit(status); } } exit(LIB$TRAVERSE_TREE(&root, action, 0)); }