[mod.computers.vax] Apology and peace offering

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