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));
}