HOLLINGE@SASK.BITNET.UUCP (06/05/87)
This is my first contribution to world-wide public domain utilities. (That means don't flame me too hard if I screw it up.) The program I have written is designed to be used in system administration. It takes a disk and communicates with the disk ACP to determine who is using how much space and reports it in a (hopefully) useful fashion. I have tested it on VMS 4.3 and 4.5 with DEC RA81s and RM03s on VAX 78Xs and 86XXs. I see no reason why it shouldn't work on any VAX running VMS 4.X and any disk using the standard disk ACP. The source code is written in VAX-C (since I was originally a UNIX-hacker). Please give me some feedback on how useful/bad/idiotic/entertaining this program is to you. Glenn Hollinger, hollinger@sask.BITNET Systems Programmer, glenn@sask.UUCP University of Saskatchewan. ihnp4!sask!glenn $!-----------------------Cut Here-------------------------------- $! This is a shar file for VMS. Cut on this line and feed it to $! the DCL command interpreter. Have a nice day. $! $! Start of HOG.C $ Create HOG.C /* * HOG -- Who's hogging all the disk space? * * By Glenn L. Hollinger, University of Saskatchewan * * This program is provided free for the use of anyone who happens to lay hands * upon a copy. It may be freely distributed except for profit. A copy of * this notice must be included in all copies. * * Briefly, This program uses QIOs to talk to the disk ACP about quotas. * To get any info, quotas must be enabled on the disk in question. To * retrieve infomation about quotas, the user must also have read access * to the quota file on that disk. Queries are specified by disk and by * how many biggest users to report. Reporting may be limited to users * over or under a certain usage. Output may be redirected on the command * line. * * Output includes the identifier (or UIC), disk usage, permanent quota, * Overdraft allowed, and usage as a percentage of total disk space, used * disk space, and permanent quota. */ /* * Instructions: Compile this module, use set command on the cld and link * A few simple steps, add water and stir: * $ cc hog.c * $ set command /object hog_table.cld * $ link hog.obj,hog_table.obj,sys$input:/opt * sys$library:vaxcrtl/l * Optionally, add the help file to the system help library * $! Change this command for your particular help file setup * $! library /replace sys$help:vms.hlb hog.hlp * Set up a symbol and try it out: * $ hog :== $ disk:[directory]hog.exe * $ hog disk */ #include <stdio.h> #include <iodef.h> #include <dvidef.h> #include <fibdef.h> #include <ssdef.h> #include <descrip.h> #include <climsgdef.h> #define PERCENT(x,y) (y ? ((float) x * 100) / y : 100.0) #define NULL 0 #define EOS '\0' #define TRUE 1 #define FALSE 0 /* Slightly modified fibdef1 from <fibdef.h> */ struct fibdef2 { unsigned char fibdef$$_fill_14 [16]; int fib$l_wcc; char fibdef$$_file_15 [2]; unsigned short int fib$w_cntrlfunc; union { unsigned long int fib$l_cntrlval; struct { unsigned fib$v_all_mem : 1; unsigned fib$v_all_grp : 1; unsigned fib$v_mod_use : 1; unsigned fib$v_mod_perm : 1; unsigned fib$v_mod_over : 1; unsigned fib$v_fill_4 : 3; } fib$r_cntrlval_bits; } fib$r_cntrlval_overlay; }; int sys$assign(), sys$qiow(), sys$dassgn(), sys$idtoasc(), cli$parse(), cli$present(), cli$get_value(), lib$getdvi(); lib$get_foriegn(), lib$get_input(); int hog(), note_usage(), display_usage(); extern char hog_table(); /* How many of the top users to report */ static int top, /* Flag /OVER and /UNDER qualifiers */ over_lim, under_lim; /* Report users over this limit */ static unsigned int over_val, /* Report users under this limit */ under_val; /* Number of blocks on the disk examined */ static unsigned int max_blocks; main(/* argc,argv unused */) { /* System call return status */ unsigned int status; /* Max command line length */ unsigned short int command_l = 1024, /* Length of value returned by cli$get_value */ value_l; /* Temporary signed-unsigned conversion variable */ int i; /* The disk to work on */ char disk[1025], /* The output file (or device) */ output[1025], /* The foriegn command line */ command_s[1025], /* The item to look up in cli$present, etc */ item_s[1030], /* Value returned by cli$get_value */ value_s[1025]; /* Fixed length string desc. */ $DESCRIPTOR(command,command_s); $DESCRIPTOR(item,item_s); $DESCRIPTOR(value,value_s); /* Get the foriegn command line (if any) */ status = lib$get_foreign(&command,NULL,&command_l,NULL); if (status != SS$_NORMAL) { printf("Error: cannot parse command line\n"); exit(1); } command_s[command_l] = EOS; /* Build the real command, "HOG 'foreign command'" */ strcpy(item_s,"HOG "); strcat(item_s,command_s); item.dsc$w_length = strlen(item_s); /* Run the command through the parser */ status = cli$dcl_parse(&item,hog_table, lib$get_input,lib$get_input,NULL); if (status != CLI$_NORMAL) { printf("Error: cannot parse command line\n"); exit(status); } /* Now the important stuff, which disk? */ strcpy(item_s,"DISK"); item.dsc$w_length = strlen(item_s); status = cli$present(&item); if (status == CLI$_PRESENT) { status = cli$get_value(&item,&value,&value_l); if (status != SS$_NORMAL) { printf("Error: cannot determine disk\n"); exit(status); } value_s[value_l] = EOS; strcpy(disk,value_s); } else strcpy(disk,"SYS$DISK:"); /* Which output file? */ strcpy(item_s,"OUTPUT"); item.dsc$w_length = strlen(item_s); status = cli$present(&item); if (status == CLI$_PRESENT) { status = cli$get_value(&item,&value,&value_l); if (status != SS$_NORMAL) { printf("Error: cannot determine /OUTPUT value\n"); exit(status); } value_s[value_l] = EOS; strcpy(output,value_s); } else strcpy(output,"SYS$OUTPUT:"); /* How many users to report? */ strcpy(item_s,"TOP"); item.dsc$w_length = strlen(item_s); status = cli$present(&item); if (status == CLI$_PRESENT) { status = cli$get_value(&item,&value,&value_l); if (status != SS$_NORMAL) { printf("Error: cannot determine /TOP value\n"); exit(status); } value_s[value_l] = EOS; top = atoi(value_s); } else top = 0; /* Over what usage limit */ strcpy(item_s,"OVER"); item.dsc$w_length = strlen(item_s); status = cli$present(&item); if (status == CLI$_PRESENT) { status = cli$get_value(&item,&value,&value_l); if (status != SS$_NORMAL) { printf("Error: cannot determine /OVER value\n"); exit(status); } value_s[value_l] = EOS; i = atoi(value_s); if (i >= 0) { over_val = i; over_lim = TRUE; } else { over_val = 0; over_lim = FALSE; } } else { over_val = 0; over_lim = FALSE; } /* Under what usage limit */ strcpy(item_s,"UNDER"); item.dsc$w_length = strlen(item_s); status = cli$present(&item); if (status == CLI$_PRESENT) { status = cli$get_value(&item,&value,&value_l); if (status != SS$_NORMAL) { printf("Error: cannot determine /UNDER value\n"); exit(status); } value_s[value_l] = EOS; i = atoi(value_s); if (i >= 0) under_val = i; else under_val = 0; under_lim = TRUE; } else { under_val = 0; under_lim = FALSE; } /* Now, the whole task is done in one step (;-) */ hog(disk,output); /* Done. */ return; } /* Here is where all the work gets done. */ hog(disk,output) /* Disk to report */ char *disk, /* Output device */ *output; { /* Output file descriptor */ FILE *lis; /* SYS$ function return codes */ unsigned int status, /* Item to check in SYS$GETDVI */ dvi_item; /* Device I/O channel */ unsigned short int chan, /* Length of returned QFTB block */ len, /* SYS$QIOW fuction request */ func; /* ACPCONTROL success status */ struct { short int status; short int iosb$$unused1; int iosb$$unused2; } iosb; /* Quota File Transfer Block (QFTB) */ struct qftb { unsigned int dqf$l_flags, /* User */ dqf$l_uic, /* Current usage of user */ dqf$l_usage, /* Permanent Quota of user */ dqf$l_permquota, /* Overdraft Quota of user */ dqf$l_overdraft, dqf$$l_spare[3]; } /* Input QFTB (unused) */ in_qftb_v = {0}, /* Output QFTB */ out_qftb_v = {0}; /* ACPCONTROL FIB block */ struct fibdef2 fib_s; /* Fixed length string descriptors */ $DESCRIPTOR(device,disk); $DESCRIPTOR(in_qftb,&in_qftb_v); $DESCRIPTOR(out_qftb,&out_qftb_v); $DESCRIPTOR(fib,&fib_s); /* Ensure correct lengths */ in_qftb.dsc$w_length = sizeof(in_qftb_v); out_qftb.dsc$w_length = sizeof(out_qftb_v); fib.dsc$w_length = sizeof(fib_s); device.dsc$w_length = strlen(disk); /* Access the disk to report on */ status = sys$assign(&device,&chan,NULL,NULL); if (status != SS$_NORMAL) { printf("Error: cannot access device %s\n",disk); exit(status); } /* How many disk blocks are available? */ dvi_item = DVI$_MAXBLOCK; status = lib$getdvi(&dvi_item,&chan,NULL,&max_blocks,NULL,NULL); if (status != SS$_NORMAL) { printf("Error: cannot access device %s\n",disk); exit(status); } /* Establish the output file */ if ((lis = fopen(output,"w")) == NULL) { printf("Error: Cannot open %s for output\n",output); exit(1); } /* We want to control the acp, */ func = IO$_ACPCONTROL; /* and examine all quotas... */ fib_s.fib$w_cntrlfunc = FIB$C_EXA_QUOTA; /* and iterate on all UICs, */ fib_s.fib$l_wcc = 0; fib_s.fib$r_cntrlval_overlay.fib$r_cntrlval_bits.fib$v_all_mem = 1; fib_s.fib$r_cntrlval_overlay.fib$r_cntrlval_bits.fib$v_all_grp = 1; for (;;) { /* OK, ask for a piece of quota info */ status = sys$qiow(NULL,chan,func,&iosb,NULL,NULL, &fib,&in_qftb,&len,&out_qftb,NULL,NULL); /* Check for transmission errors */ if (status != SS$_NORMAL) { printf("Error: cannot access diskquota information\n"); exit(status); } /* Check ACPCONTROL return status */ if (iosb.status == SS$_NODISKQUOTA) break; if (iosb.status != SS$_NORMAL) { printf("Error: cannot access Quota File\n"); exit((int) iosb.status); } /* Write it down for later reporting */ note_usage(out_qftb_v.dqf$l_uic,out_qftb_v.dqf$l_usage, out_qftb_v.dqf$l_permquota,out_qftb_v.dqf$l_overdraft); } /* Write the output */ display_usage(lis); /* That's the listing file */ fclose(lis); /* And let go of the device */ status = sys$dassgn(chan); if (status != SS$_NORMAL) { printf("Error: cannot deassign channel\n"); exit(status); } }; /* Linked list block definition */ struct q_list { union { /* remap the long UIC to 2 shorts */ unsigned int full; struct { unsigned short int member; unsigned short int group; } sub; } uic; unsigned int usage, perm, over; struct q_list *next; }; /* The list head pointer, NULL for no list. * Note the list is in increasing * order, not decreasing as most would expect. */ static struct q_list *list_head = (struct qlist *) NULL; /* How many are currently in the list? */ static int count = 0; /* Track total disk block usage */ static int total_usage = 0; /* Look at this piece of the usage info. If we may want to display it, * keep it for later. If not, discard. Also discard any info we kept * before and are now certain we don't want to display */ int note_usage(uic,usage,perm,over) unsigned int uic, usage, perm, over; { /* Tango down the linked list */ register struct q_list *p, *q; /* Note the additional disk block usage */ total_usage += usage; /* If we have too much or too little usage, ignore them */ if ((over_lim && usage <= over_val)||(under_lim && usage >= under_val)) return; /* Find the place in the list to insert the new info * Itterate down the list until the node after the current node * is greater than the new usage and the current node is less */ p = list_head; if (p != NULL) { while (p -> next != NULL) { if (p -> next -> usage > usage) break; p = p -> next; } } /* p is either NULL (no list), or points to the first node, */ /* or points to the one to insert after */ if (top != NULL && count == top) { /* There must be at least one node in the list at this point */ /* Skip the insert if we have enough nodes and this is */ /* less than the minimum usage in the list. */ if (usage <= list_head -> usage) return; /* strip the first node off and reuse it */ q = list_head; if (p == list_head) p = list_head -> next; list_head = list_head -> next; } else { /* make a new node and insert it */ q = (struct q_list *) malloc(sizeof(struct q_list)); count ++; } /* lace into list */ if (p == NULL) { /* There is no list yet, make one */ q -> next = NULL; list_head = q; } else if (p -> usage > usage) { /* usage is less than listhead (p == listhead) */ q -> next = p; list_head = q; } else { /* usage is more than p, and less than p -> next */ q -> next = p -> next; p -> next = q; } /* fill in the new data */ q -> uic.full = uic; q -> usage = usage; q -> perm = perm; q -> over = over; return; }; /* Take the list of usage to display and display it */ int display_usage(lis) FILE *lis; { /* Move down the list printing items */ struct q_list *p; /* Length of translated identifier name */ unsigned short int namlen; /* System call return status */ unsigned int status; /* The identifier name returned */ char nambuf_s[50]; /* Percentage calculation variables */ float percent_used, percent_total, percent_perm; /* Fixed length string descriptor */ $DESCRIPTOR(nambuf,nambuf_s); /* This should really by paginated and page-headed (frown) */ fprintf(lis, "Identifier\t\t Usage Perm Over %% Used %% Total %% Perm\n"); /* Look down the list (smallest usage to largest) */ p = list_head; while (p != NULL) { /* Calculate usage percentages, watch for divide by 0 */ percent_used = PERCENT(p -> usage, total_usage); percent_total = PERCENT(p -> usage, max_blocks); percent_perm = PERCENT(p -> usage, p ->perm); /* Try to find an identifier to name this uic with */ status = sys$idtoasc(p -> uic.full,&namlen,&nambuf, NULL,NULL,NULL); /* Use identifier if it exists, else use the numeric format */ if (status == SS$_NORMAL) { nambuf_s[namlen] = EOS; fprintf(lis,"%-24s %7d %7d %7d %7.2f %7.2f %7.2f\n", nambuf_s, p -> usage, p -> perm, p -> over, percent_used,percent_total,percent_perm); } else { fprintf(lis,"[%06o,%06o] \t %7d %7d %7d %7.2f %7.2f %7.2f\n", p -> uic.sub.group, p -> uic.sub.member, p -> usage, p -> perm, p -> over, percent_used, percent_total, percent_perm); } /* And the next... */ p = p -> next; } }; $! Start of HOG_TABLE.CLD $ Create HOG_TABLE.CLD module hog_table define verb hog routine hog parameter p1 label=disk value(default=sys$disk) qualifier top value(default=20) qualifier output value(default=hog.lis) qualifier over value(default=4000) qualifier under value(default=4001) $! Start of HOG.HLP $ Create HOG.HLP 1 HOG HOG is a utility to display who is using how much disk space on the disk drives. It will take one optional parameter and two optional qualifiers. The general action is to examine the quotas on a disk and report them ordered by usage. The parameter names the disk to be examined. The qualifiers can be used to specify an output file and limit the display to a number of top users. Usage is as follows; $ HOG [disk] [/OUTPUT[=file]] [/TOP[=count]] [/OVER[=blk_cnt]] [/UNDER=[=blk_cnt]] 2 Usage_Notes HOG will only report on disk volumes which have quotas enabled on them. This is a restriction because of how HOG works. To change this, a different approach must be used, either reading QUOTA.SYS or INDEXF.SYS directly. For a user to get information from HOG, they must either 1. own the disk volume queried or 2. have read access to the quota file on the disk volume queried. Do not install this program with privileges, since it does not bother to check whether or not it is OK to write the output file as the user specifies. It could trash any file on the system. Potentially, a very large pagefile quota could be required to do a complete HOG listing of a disk with very many quotas on it. However, we have a disk with over 1200 quota entries which requires a pagefile quota of 3000 blocks, so we expect only the most extreme cases will have problems with this. 2 Sample_Output HOG output is in 7 columns. The columns are as follows, in order; UIC -- The UIC this line of output pertains to Usage -- The disk space used in blocks Perm -- The disk space permanently allocated to the UIC Over -- The temporary overdraft allowed % Used -- The percentage Usage is of the total used space % Perm -- The percentage Usage is of the uic's permanent allocation % Total -- The percentage Usage is of total disk space See Examples for sample output 2 Defaults The defaults for HOG are to report on all user quotas on the current (default) disk. This is the same as the command: $ HOG SYS$DISK /TOP=0 /OVER=-1 /UNDER=9999999 2 Examples As an example, consider a disk with the following quotas: Identifier Usage Permanent Overdraft [000000,000000] 0 1000 100 [SYSTEM] 40000 100000 1000 [USER1] 5000 30000 1000 [USER2] 3000 30000 1000 [USER3] 100000 400000 1000 Listed below are a number of commands and the users those commands would include in the report generated. $ HOG All UICs $ HOG /TOP=2 [USER3],[SYSTEM] $ HOG /TOP=3 /UNDER=90000 [SYSTEM],[USER1],[USER3] $ HOG /OVER=10000 [USER3],[SYSTEM] $ HOG /TOP=3 /UNDER=90000 /OVER=10000 [SYSTEM] To redirect the report to a file, use the /OUTPUT qualifier; $ HOG /OUTPUT=HOG.LIS The following is a sample of output from the HOG command Identifier Usage Perm Over % Used % Total % Perm [000000,000000] 0 1000 100 0.00 0.00 0.00 HOLLINGER 0 999999 99999 0.00 0.00 0.00 [000001,000001] 9 1000 100 0.00 0.00 0.90 SYSTEM 40003 999999 99999 5.65 4.49 4.00 VLSI 668571 999999 99999 94.35 75.03 66.86 2 DISK The disk you specify as the parameter to HOG determines the disk reported on. If no disk is specified, the default disk, SYS$DISK:, is assumed. 2 /OUTPUT[=HOG.LIS] The default output is to the terminal via SYS$OUTPUT. If /OUTPUT is specified without a value, the output is directed to the file HOG.LIS in the current directory. A value may be given with the /OUTPUT qualifier to change the name and directory of the file created. 2 /TOP[=count] The default action of HOG is to report all user quotas on the disk being examined. The /TOP qualifier can be used to restrict the analysis to the biggest users on the disk being examined. If /TOP is used without a value, the value 20 is assumed. Specifying the value of /TOP as 0 will cause all user quotas to be reported. The /OVER and /UNDER qualifiers are used to limit the users reported on before the /TOP qualifier is considered. 2 /OVER[=blk_cnt] The default action of HOG is to report user quotas regardless of the usage of each UIC. The /OVER qualifier can be used to restrict the report to UICs with usage over a certain limit. The unit for blk_cnt is disk blocks. If /OVER is used without a value, 4000 is assumed. The qualifiers /OVER and /UNDER are used to limit the users reported on before the /TOP qualifier is considered. 2 /UNDER[=blk_cnt] The default action of HOG is to report user quotas regardless of the usage of each UIC. The /UNDER qualifier can be used to restrict the report to UICs with usage under a certain limit. The unit for blk_cnt is disk blocks. If /UNDER is used without a value, 4001 is assumed. The /OVER and /UNDER qualifiers are used to limit the users reported on before the /TOP qualifier is considered. $! Start of MAKE.COM $ Create MAKE.COM $! Compile the image. Requires VAX-C, Set Command, Linker $ cc pak:[local.src.hog]hog.c $ set command/obj hog_table.cld $ link pak:[local.src.hog]hog.obj,hog_table.obj,sys$input:/opt sys$library:vaxcrtl.exe/share $! Liraryize the help file $ library/create/help hog.hlb hog.hlp $ exit $! End of Shar file