home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / fs < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  47.9 KB  |  1,277 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) fs.gawk 1.9 96/10/25
  4. # Reports average file sizes on filesystems,
  5. # determines optimum number of inodes for filesystem given average filesize,
  6. # and determines maximum useful size of a filesystem with that average file
  7. # size.
  8. # 91/03/11 john h. dubois iii (john@armory.com)
  9. # 91/03/16 added more info to display.
  10. # 91/04/08 increased max fs size field width
  11. # 91/04/18 changed name from filesizes to fs
  12. # 91/06/30 replaced incorrect continues with nexts
  13. # 92/05/01 converted to #!awk script
  14. # 92/08/01 Remove any directory component from fs name
  15. # 92/08/22 Use space used by inode table & superblock in calculations
  16. # 93/10/17 Use total inodes, not inodes used, in calculating non-file space
  17. # 93/11/02 Try to deal with NFS garbage
  18. # 94/01/27 Added n option
  19. # 94/03/09 Use gawk so - options can be given
  20. # 94/06/24 Increased size of total-blocks and used-blocks fields for >=1GB fs
  21. # 94/06/26 Strip multiple /'s in device names; sometimes used to fool mount
  22. # 94/08/17 Let max fs size be up to 99 GB without pushing fields right
  23. # 95/02/10 Run output lines through FmtLinePatch()
  24. # 95/04/29 Added [tx] options.
  25. # 95/08/04 Make df input be /dev/null
  26. # 96/01/15 Use metric suffixes to reduce max field width.  Added s option.
  27. #          Do not print max fs size if fs has >64K inodes.
  28. # 96/01/27 Use i2emet instead of i2met
  29. # 96/01/30 Added b option and rcfile processing.
  30. # 96/02/10 Complain if no valid filesystems named.  Added e option.
  31. # 96/04/17 Only add values for lines that are printed to the total line.
  32. # 96/04/27 Require files to be block devs by default; added a option.
  33. # 96/06/12 Separated dodf() code into a function.  Added yYT options.
  34. # 96/06/26 Detect NFS mount even if the : is lost
  35. # 96/10/25 Print total line only if more than one fs is reported on.
  36.  
  37. # expects df to report in 512 byte blocks,
  38. # and expects df -i -v output to be in this format:
  39. #    1            2         3      4       5        6      7      8        9
  40. #Mount Dir   Filesystem  blocks      used      free    % used     iused    ifree    %iused
  41. #/           /dev/root    27438     18650      8788       68%     1140     2284       33%
  42. #/usr/spool/ /dev/news   103530     57276     46254       55%    11206     1722       87%
  43. #/usr        /dev/usr     80000     66974     13026       84%     3584     6416       36%
  44. #/u          /dev/u       50000     35224     14776       70%     1909     4331       31%
  45. BEGIN {
  46.     Name = "fs"
  47.     Usage = \
  48. "Usage: " Name " [-abhnstT] [-e<device,...>] [-[yY]<fstype,...>] [device ...]"
  49.     MaxInodes = 65488
  50.     rcFile = ".fs"
  51.     ARGC = Opts(Name,Usage,"abe:nstTy:Y:xh",0,
  52.     "~/" rcFile ":$UHOME/" rcFile ":/etc/default/fs","ALLOWFILES,NOMAXFS,"\
  53.     "EXCLUDEFS,SHOWNFS,NOMETRIC,TOTAL,SHOWFSTYPE,FSTYPES,NOTFSTYPES")
  54.     if ("h" in Options) {
  55.     printf \
  56. "%s: report average file sizes and other information for filesystems.\n"\
  57. "%s\n"\
  58. "For each mounted filesystem, %s prints:\n"\
  59. "its name, its size in blocks, blocks used, %% of blocks used, blocks free,\n"\
  60. "the maximum size of a filesystem containing files with the same average\n"\
  61. "size that this one has (given a maximum of %d files per filesystem),\n"\
  62. "number of inodes in its inode table, inodes used, %% of inodes used,\n"\
  63. "inodes free, the optimum number of inodes for a filesystem of this size\n"\
  64. "given the average size of files on it, and the average file size.\n"\
  65. "If a filesystem has more than %d inodes, the maximum filesystem size is\n"\
  66. "not printed, since this indicates that it has a much higher inode limit.\n"\
  67. "The metric suffixes used refer to powers of 2 (1K=1024, 1M=1048576, etc.)\n"\
  68. "when used for bytes, and to powers of 10 (1K=1000, 1M=1000000, etc.) when\n"\
  69. "used for inode counts.\n"\
  70. "If a list of devices is given, they are reported on instead of using the\n"\
  71. "list of mounted filesystems.  Devices given without a directory name are\n"\
  72. "assumed to be in the directory /dev unless the -a option is given.\n"\
  73. "Options:\n"\
  74. "Some of the following options can also be set by assigning values to\n"\
  75. "variables in a configuration file.  Three configuration files are read, in\n"\
  76. "order: a file named %s in the invoking user's home directory; a file\n"\
  77. "named %s in the directory specified by the environment variable UHOME\n"\
  78. "(if it is set); and the file /etc/default/fs.  Variables are assigned\n"\
  79. "to with the syntax:  varname=value  or in the case of flags, by simply\n"\
  80. "putting the indicated variable name in the file without a value.\n"\
  81. "A variable assigned to in one of these files will override values assigned\n"\
  82. "to the same variable in one of the files read after it.  To turn off an\n"\
  83. "option and prevent it from being set in a file read later, assign it a\n"\
  84. "value of 0.  e.g. if TOTAL is set in /etc/default/httplog, TOTAL=0 in\n"\
  85. "a %s file will override it.  Flag options can be turned off on the\n"\
  86. "command line by following them immediately with '-', e.g. -n- to turn off\n"\
  87. "the n option in such a way that it cannot be turned on in a config file.\n"\
  88. "Variable names appear in parentheses in the option descriptions.\n"\
  89. "-a: Allow any filename to be given, not just block devices.  For files\n"\
  90. "    other than block devices, the filesystem that the file resides on is\n"\
  91. "    reported on.  Files given with relative pathnames are not assumed to\n"\
  92. "    reside under /dev.  (ALLOWFILES)\n"\
  93. "-h: Print this help.\n"\
  94. "-b: Do not print the \"maximum filesystem size\" field.  (NOMAXFS)\n"\
  95. "-e<device,...>: Exclude the named filesystems from the output. (EXCLUDEFS)\n"\
  96. "-s: Do not use metric suffixes.  This may cause fields to be misaligned.\n"\
  97. "    All block reports will be in units of 1024 bytes.  (NOMETRIC)\n"\
  98. "-n: Show NFS mounts too.  (SHOWNFS)\n"\
  99. "-t: If more than one filesystem is reported on, print a total line. (TOTAL)\n"\
  100. "-y<fstype,..>: List only filesystems that are one of the types given in\n"\
  101. "    the comma-separated list.  The filesystem types are as produced by\n"\
  102. "    'dtype', except that \"UNIX 1K\" is converted to \"S51K\".  Types may\n"\
  103. "    be given in either case.  Example types: S51K, HTFS, RCKRDG. (FSTYPES)\n"\
  104. "-Y<fstype,..>: List only filesystems that are not one of the given types.\n"\
  105. "    (NOTFSTYPES)\n"\
  106. "-T: Show the filesystem type in the output.  This turns on the -b option.\n"\
  107. "    (SHOWTYPE)\n",
  108.     Name,Usage,Name,MaxInodes,MaxInodes,rcFile,rcFile,rcFile
  109.     exit(0)
  110.     }
  111.     if ((Err = ExclusiveOptions("n,t;y,Y",Options)) != "") {
  112.     printf "Error: %s\n",Err > "/dev/stderr"
  113.     Err = 1
  114.     exit(1)
  115.     }
  116.     doi2met = !("s" in Options)
  117.     Debug = "x" in Options
  118.     NoNFS = !("n" in Options)
  119.     AllowFiles = "a" in Options
  120.     if (!(numfs = dodf(ARGV,ARGC-1,AllowFiles,Debug,dfOut,fsDevs))) {
  121.     print "No valid filesystems named." > "/dev/stderr"
  122.     exit 1
  123.     }
  124.     showType = "T" in Options
  125.     if ("b" in Options || showType) {
  126.     maxWidth = ".0"
  127.     xtraD2 = " "
  128.     }
  129.     else {
  130.     maxWidth = "7"
  131.     xtraD1 = "----"
  132.     xtraD2 = "----"
  133.     }
  134.     TotLine = "t" in Options
  135.     if ("e" in Options) {
  136.     split(Options["e"],Elem,",")
  137.     for (i = 1; i in Elem; i++) {
  138.         fs = Elem[i]
  139.         if (fs !~ "/")
  140.         fs = "/dev/" fs
  141.         if (Debug)
  142.         print "Added to NotDevs[]: " fs > "/dev/stderr"
  143.         NotDevs[fs]
  144.     }
  145.     }
  146.     if (gotTypes = ("y" in Options)) {
  147.     i = MakeSet(Types,toupper(Options["y"]),",")
  148.     if (Debug)
  149.         printf "%d types given with y option.\n",i > "/dev/stderr"
  150.     }
  151.     else if (gotNotTypes = ("Y" in Options)) {
  152.     i = MakeSet(notTypes,toupper(Options["Y"]),",")
  153.     if (Debug)
  154.         printf "%d types given with Y option.\n",i > "/dev/stderr"
  155.     }
  156.  
  157.     # printf format string for both header and data lines
  158.     Format = "%-6s %7s %7s %3s %7s %" maxWidth "s %6s %5s %3s %5s %5s %7s"
  159.     if (showType)
  160.     Format = Format " %s"
  161.     print "Filesys " xtraD1 "-----Filesystem Space-----" xtraD2 \
  162.     "  ----------Inodes----------- AvgSize" (showType ? " FS" : "")
  163.     printf Format "\n","Name","Total","Used","%","Free","Max", \
  164.     "Total","Used","%","Free","Opt","(bytes)","Type"
  165.     split(Format,FieldLengths,/[^0-9]+/)
  166.  
  167.     if (gotTypes || gotNotTypes || showType) {
  168.     for (dev in fsDevs)
  169.         if (dev !~ ":")
  170.         fList = fList " " dev
  171.     getfsTypes(fList,fs2Type)
  172.     }
  173.     for (i = 1; i <= numfs; i++) {
  174.     fsname = dfOut[i,"fsname"]
  175.     if (Debug) {
  176.         printf "fsname: %s\n",fsname > "/dev/stderr"
  177.         if (gotTypes || gotNotTypes) {
  178.         printf "type: %s\n",fs2Type[fsname] > "/dev/stderr"
  179.         printf "%s in Types: %d;  in notTypes: %d\n",fs2Type[fsname],
  180.         fs2Type[fsname] in Types,fs2Type[fsname] in notTypes
  181.         }
  182.     }
  183.     if (!(fsname in NotDevs) && (!gotTypes || fs2Type[fsname] in Types) &&
  184.     (!gotNotTypes || !(fs2Type[fsname] in notTypes))) {
  185.         if (PrintLine(Format,NoNFS,MaxInodes,dfOut[i,"mountdir"],fsname,
  186.         dfOut[i,"blocks"]/2, dfOut[i,"bused"]/2,
  187.         dfOut[i,"inodes"], dfOut[i,"iused"],fs2Type) && TotLine) {
  188.         Blocks_tot += dfOut[i,"blocks"]
  189.         BlocksUsed_tot += dfOut[i,"bused"]
  190.         Inodes_tot += dfOut[i,"inodes"]
  191.         InodesUsed_tot += dfOut[i,"iused"]
  192.         numRep++
  193.         }
  194.     }
  195.     }
  196.     if (TotLine && numRep > 1)
  197.     PrintLine(Format,NoNFS,0,"","Total",Blocks_tot/2,
  198.     BlocksUsed_tot/2,Inodes_tot,InodesUsed_tot,fs2Type)
  199. }
  200.  
  201. function dodf(fslist,num,AllowFiles,Debug,Out,fsDevs,  i,dev,fList,Cmd,ret,n) {
  202.     if (num > 0) {
  203.     for (i = 1; i <= num; i++) {
  204.         dev = fslist[i]
  205.         if (AllowFiles || dev ~ "/")
  206.         fList = fList " " dev
  207.         else
  208.         fList = fList " /dev/" dev
  209.     }
  210.     if (AllowFiles)
  211.         Cmd = "set -- " fList "; "
  212.     else
  213.         Cmd = sprintf("for file in %s\n"\
  214.         "do\n"\
  215.         " test -b \"$file\" && set -- \"$@\" \"$file\" ||\n"\
  216.         " echo Does not exist or is not a block device: $file >&2\n"\
  217.         "done\n"\
  218.         "[ $# -eq 0 ] && exit 0\n",fList)
  219.     }
  220.     # Redirect input from /dev/null to work around bug in df/protlib
  221.     # Discard stderr so that failed NFS queries will be ignored.
  222.     Cmd = Cmd "exec </dev/null 2>/dev/null df -i -v \"$@\""
  223.     if (Debug)
  224.     print "df command is: " Cmd > "/dev/stderr"
  225.     Cmd | getline    # ignore header
  226.     if (Debug)
  227.     print "df header line:\n" $0
  228.     # Get 1st fs line from df.
  229.     # Don't print header if df doesn't produce anything
  230.     if ((ret = (Cmd | getline)) != 1) {
  231.     if (Debug)
  232.         print \
  233.         "No fs lines from df.  df exit status: " close(Cmd) > "/dev/stderr"
  234.     return 0
  235.     }
  236.  
  237.     # Get the rest of the fs lines from df.
  238.     while (ret == 1) {
  239.     # If a list of devices is given to df, it leaves mount dir field blank.
  240.     # Shift fields to compensate.
  241.     if (NF == 8) {
  242.         for (i = 8; i >= 1; i--)
  243.         $(i + 1) = $i
  244.         $1 = ""
  245.     }
  246.     if (Debug) {
  247.         print "df output: " $0 > "/dev/stderr"
  248.         printf "mountdir: %s   fsname: %s\n",$1,$2 > "/dev/stderr"
  249.     }
  250. #    1            2         3      4       5        6      7      8        9
  251. #Mount Dir   Filesystem  blocks      used      free    % used     iused    ifree    %iused
  252.     Out[++n,"mountdir"] = $1
  253.     Out[n,"fsname"] = $2
  254.     Out[n,"blocks"] = $3
  255.     Out[n,"bused"] = $4
  256.     Out[n,"inodes"] = $7+$8
  257.     Out[n,"iused"] = $7
  258.     fsDevs[$2]
  259.     ret = (Cmd | getline)
  260.     }
  261.     close(Cmd)
  262.     return n
  263. }
  264.  
  265. # Use dtype because a) fstyp runs a TYPE command for each filesystem type
  266. # until it gets one that returns true, so it is slow; c) can give more than 1
  267. # device name to dtype; c) dtype will work on filesystems stored in ordinary
  268. # files as well as devices.  Unfortunately, dtype reports generic "UNIX 1K"
  269. # instead of e.g. EAFS.  This is converted to S51K for compactness and to make
  270. # it a single word.
  271. function getfsTypes(fList,fs2Type,  Cmd,fs,type) {
  272.     Cmd = "exec dtype " fList
  273.     if (Debug)
  274.     print "dtype command: " Cmd > "/dev/stderr"
  275.     # dtype output:
  276.     # /dev/local     : HTFS filesystem
  277.     # /dev/boot      : UNIX 1K filesystem
  278.     # /dev/root      : HTFS filesystem -- needs cleaning
  279.     while ((Cmd | getline) == 1) {
  280.     if (sub(" filesystem[^:]*","")) {
  281.         fs = $1
  282.         sub(".*: ","")
  283.         type = $0 == "UNIX 1K" ? "S51K" : $0
  284.         if (Debug)
  285.         printf "fs type for %s: %s\n",fs,type > "/dev/stderr"
  286.         fs2Type[fs] = type
  287.     }
  288.     }
  289.     close(Cmd)
  290. }
  291.  
  292. # Input variables:
  293. # Format: printf format string.
  294. # NoNFS: true if NFS automounter lines should be ignored.
  295. # MaxInodes: the maximum number of inodes a filesystem can have.
  296. # Global vars used: FieldLengths[]
  297. # Return value: true if a line was printed (so that values for this line can be
  298. # added to total line).
  299. function PrintLine(Format,NoNFS,MaxInodes,MountDir,FsDevice,DivisionSize,
  300. BlocksUsed,TotalInodes,InodesUsed,Types,
  301. AvgFileSize,FileSpaceUsed,MaxFsSize,NonFileSpace,OptimumInodes,TotalFileSpace,
  302. FsName) {
  303.     # Allow more than one / after /dev; double / sometimes used to trick mount
  304.     # into not reading /etc/default/filesys
  305.     FsName = FsDevice
  306.     sub("^/+dev/+","",FsName)    
  307.     InodesFree = TotalInodes - InodesUsed
  308.     bigInodes = TotalInodes > MaxInodes
  309.     # Space used by inode table and superblock
  310.     # Actual size of superblock is different for different filesys types
  311.     NonFileSpace = int(TotalInodes / (bigInodes ? 8 : 16) + 2)
  312.     # Amount of space actually used by files
  313.     FileSpaceUsed = BlocksUsed - NonFileSpace
  314.     # Amount of space available to files
  315.     TotalFileSpace = DivisionSize - NonFileSpace
  316.     if (Debug)
  317.     printf \
  318.     "fsname: %s; Blocks used: %d; non-file space: %d; file space: %d\n",
  319.     FsName,BlocksUsed, NonFileSpace, FileSpaceUsed > "/dev/stderr"
  320.  
  321.     # NFS fs lines do not include inode info
  322.     if ((InodesUsed+0) > 0) {
  323.     AvgFileSize = FileSpaceUsed / InodesUsed
  324.     # max useful size of fs division
  325.     MaxFsSize = int(AvgFileSize * MaxInodes + MaxInodes / 16 + 2)
  326.     if (AvgFileSize > 0) {
  327.         OptimumInodes = int(TotalFileSpace / AvgFileSize)
  328.         AvgFileSize = int(AvgFileSize * 1024)
  329.     }
  330.     else
  331.         AvgFileSize = "-"
  332.     }
  333.     else
  334.     MaxFsSize = OptimumInodes = AvgFileSize = "-"
  335.     if (MaxInodes == 0)    # These are not meaningful for Total line
  336.     MaxFsSize = OptimumInodes = "-"
  337.     # NFS mount
  338.     # If the hostname is long, the : at the end is lost to truncation, so use
  339.     # an extra test
  340.     if (FsName ~ ":" || (index(FsName,".") && !InodesUsed && !InodesFree)) {
  341.     if (Debug)
  342.         printf "%s is an NFS mounted volume.\n",MountDir > "/dev/stderr"
  343.     if (NoNFS)
  344.         return 0
  345.     FsName = MountDir
  346.     sub("^.*/","",FsName)
  347.     FsName = "*" FsName
  348.     }
  349.     if (length(FsName) > 6)
  350.     FsName = substr(FsName,1,5) ">"
  351.     # The percentage calculations are done without adding 0.5 before
  352.     # doing int() because that's what df seems to do (so that when fs -t is
  353.     # done on a single filesystem, the filesys line will will match the total
  354.     # line).
  355.     print FmtLinePatch(FieldLengths,12,
  356.     #              Name   Total blocks
  357.     sprintf(Format,FsName,maybei2met(int(DivisionSize),7,1,1),
  358.     #          Blocks used            % blocks used
  359.     maybei2met(int(BlocksUsed),7,1,1),int(BlocksUsed/DivisionSize*100+0.5),
  360.     #          Blocks free
  361.     maybei2met(int(DivisionSize-BlocksUsed),7,1,1),
  362.     # Max fs size
  363.     bigInodes ? "-" : maybei2met(MaxFsSize,7,1,1),
  364.     # Total inodes                                  Inodes used
  365.     TotalInodes ? maybei2met(TotalInodes,5,0) : "-",maybei2met(InodesUsed,5,0),
  366.     # % inodes used
  367.     TotalInodes ? maybei2met(int(InodesUsed/TotalInodes*100+0.5),5,0) : "-",
  368.     # Free inodes
  369.     maybei2met(InodesFree,5,0),
  370.     # Optimal inodes              Average file size           Filesystem type
  371.     maybei2met(OptimumInodes,5,0),maybei2met(AvgFileSize,7,1),Types[FsDevice]))
  372.     return 1
  373. }
  374.  
  375. function maybei2met(Value,MaxLen,Pow2,Units) {
  376.     if (doi2met && Value != "-")
  377.     return i2emet(Value,MaxLen,Pow2,Units)
  378.     else
  379.     return Value
  380. }
  381.  
  382. # @(#) FmtLinePatch 1.0 95/02/10 jhdiii
  383. # FmtLinePatch: compensate for fields that overrun their bounds.
  384. # FieldLengths is an array giving the length of each field.
  385. # The array may start with index 1 or 2.  An empty value at index 1 is ignored.
  386. # NumFields is the number of fields on the line.
  387. # Each field is expected to be separated from the next by one space, so that
  388. # the total number of characters from the start of one field to the start of
  389. # the next is its fieldlength+1.
  390. # The field itself may be padded with spaces on the left or right.
  391. # If a field overruns its bounds (protrudes into the field on the right),
  392. # this function first tries to move it left; this can be done if there is more
  393. # than one space on its left.  It then removes excess spaces on the right.  If
  394. # there are not enough extra spaces available, the field will still cause the
  395. # next field over to be misaligned.
  396. # This function will be confused if a field is pushed far enough right that the
  397. # space(s) preceding it appear where the space after it is expected.
  398. function FmtLinePatch(FieldLengths,NumFields,Line,
  399. FieldInd,Width,Offset,c,SpacePos,Excess) {
  400.     FieldInd = 1
  401.     if (!(1 in FieldLengths && FieldLengths[1] != ""))
  402.     FieldInd++
  403.     Offset = 0
  404.     for (; NumFields; NumFields--) {    # For each field...
  405.     # Find what should be the space after the field.
  406.     Offset += FieldLengths[FieldInd++] + 1
  407.     # While it isn't a space...
  408.     while ((c = substr(Line,Offset,1)) != " " && c != "") {
  409.         # If the field is preceded by more than one space, remove it.
  410.         if ((SpacePos = FindC(Line,Offset," ",-1)) && SpacePos > 1 &&
  411.         substr(Line,SpacePos-1,1) == " ")
  412.         Line = DelStr(Line,SpacePos,1)
  413.         else
  414.         break    # No more excess preceding spaces to remove; give up.
  415.     }
  416.     # If there still isn't a space where there should be, but there are
  417.     # spaces after the field...
  418.     if (c != " " && c != "" && (SpacePos = FindC(Line,Offset," ",1))) {
  419.         for (Excess = SpacePos - Offset; Excess; Excess--)
  420.         # If the field is followed by more than one space, remove it.
  421.         if (substr(Line,SpacePos+1,1) == " ")
  422.             Line = DelStr(Line,SpacePos,1)
  423.         else
  424.             break # No more excess following spaces to remove; give up.
  425.     }
  426.     }
  427.     return Line
  428. }
  429.  
  430. # Delete the string starting at Start and having length Num from the middle
  431. # of string S, and return the remaining part.
  432. function DelStr(S,Start,Num) {
  433.     return substr(S,1,Start - 1) substr(S,Start+Num)
  434. }
  435.  
  436. # Search for char C in string S starting at position Pos, in the direction
  437. # specified by Dir (1 = forward, -1 = backward).  
  438. # Return position char found at for success, 0 if not found before start or end
  439. # of string.
  440. function FindC(S,Pos,C,Dir,  FoundC) {
  441.     while (Pos > 0 && (FoundC = substr(S,Pos,1)) != C && FoundC != "")
  442.     Pos += Dir
  443.     if (FoundC == C)
  444.     return Pos
  445.     else
  446.     return 0
  447. }
  448.  
  449. # @(#) i2emet.awk 1.0 96/01/27
  450. # jhdiii 96/01/27
  451. # Convert numeric value Value to one with the decimal point set according to
  452. # engineering convention.  In this convention, there is always between 1 and
  453. # 3 digits before the decimal point.  A metric suffix is attached to retain
  454. # the original value.
  455. # If Pow2 is true, then each factor of 1K is taken to be 1024; if it is false,
  456. # it is taken to be 1000.
  457. # If Pow2 is true, MaxLen must be >= 5; if Pow2 is false, MaxLen must be >= 4. 
  458. # If Units is given, it is the units that Value is passed in, where
  459. # Units=0 means Value is in base units; Units=-1 means Value is in milliunits,
  460. # Units=1 means Value is in kilounits, etc.
  461. # If NoZeroes is true, trailing zeroes in the fractional part are removed.
  462. function i2emet(Value,MaxLen,Pow2,Units,NoZeroes,  Len,Factor,i,suf2) {
  463.     if (Value == 0)
  464.     return "0"
  465.     if (!(1 in _Suf)) {
  466.     _MaxUnit = split("K,M,G,T,P,E,Z,Y",_Suf,",")
  467.     split("m,u,n,p,f,a,z,y",suf2,",")
  468.     for (i = 1; i in suf2; i++)
  469.         _Suf[-i] = suf2[i]
  470.     }
  471.     # Make sure awk treats all of these as numbers
  472.     Factor = (Pow2 ? 1024 : 1000)+0
  473.     Units += 0
  474.     Value += 0
  475.     if (Value < 1)
  476.     for (; Value < 1 && Units > -_MaxUnit; Value *= Factor)
  477.         Units--
  478.     else
  479.     for (; Value >= Factor && Units < _MaxUnit; Value /= Factor)
  480.         Units++
  481.     if (Units)
  482.     MaxLen -= 1        # Leave space for suffix
  483.     # Round reasonably carefully
  484.     fDig = MaxLen-length(int(Value))-1
  485.     if (fDig > 0)
  486.     Value = sprintf("%." fDig "f",Value)+0    # Turn it back into a number!
  487.     else
  488.     Value = int(Value+0.5)
  489.     # Rounding may have caused rollover of leading digit, making the result
  490.     # exceed the allowed range (e.g. 999.6 -> 1000)
  491.     if (Value >= Factor) {
  492.     Value /= Factor
  493.     Units++
  494.     }
  495.     if (substr(Value,MaxLen,1) == ".")
  496.     Value = substr(Value,1,MaxLen-1)    # Get rid of trailing '.'
  497.     else
  498.     Value = substr(Value,1,MaxLen)
  499.     if (NoZeroes && Value ~ /\..*0$/)
  500.     sub(/\.?0+$/,"",Value)
  501.     return Value _Suf[Units]
  502. }
  503.  
  504. ### Begin set library
  505. # 96/05/23 added return values  jhdiii
  506. # 96/05/25 added set2list()
  507.  
  508. # Return value: the number of new elements added to Inter
  509. function Intersection(A,B,Inter,  Elem,Count) {
  510.     for (Elem in A)
  511.     if (Elem in B && !(Elem in Inter)) {
  512.         Inter[Elem]
  513.         Count++
  514.     }
  515.     return Count
  516. }
  517.  
  518. # Return value: the number of new elements added to Both
  519. function Union(A,B,Both) {
  520.     return CopySet(A,Both) + CopySet(B,Both)
  521. }
  522.  
  523. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  524. # Return value: the number of elements deleted.
  525. function SubtractSet(Minuend,Subtrahend,  Elem,nDel) {
  526.     for (Elem in Subtrahend)
  527.     if (Elem in Minuend) {
  528.         delete Minuend[Elem]
  529.         nDel++
  530.     }
  531.     return nDel
  532. }
  533.  
  534. # Return value: the number of new elements added to To
  535. function CopySet(From,To,  Elem,n) {
  536.     for (Elem in From)
  537.     if (!(Elem in To)) {
  538.         To[Elem]
  539.         n++
  540.     }
  541.     return n
  542. }
  543.  
  544. # Returns 1 if Set is empty, 0 if not.
  545. function IsEmpty(Set,  i) {
  546.     for (i in Set)
  547.     return 0
  548.     return 1
  549. }
  550.  
  551. # MakeSet: make a set from a list.
  552. # An index with the name of each element of the list is created in the given
  553. # array.
  554. # Input variables: 
  555. # Elements is a string containing the list of elements.
  556. # Sep is the character that separates the elements of the list.
  557. # Output variables:
  558. # Set is the array.
  559. # Return value: the number of new elements added to the set.
  560. function MakeSet(Set,Elements,Sep,  i,Num,Names,nFound,ind) {
  561.     nFound = 0
  562.     Num = split(Elements,Names,Sep)
  563.     for (i = 1; i <= Num; i++) {
  564.     ind = Names[i]
  565.     if (!(ind in Set)) {
  566.         Set[ind]
  567.         nFound++
  568.     }
  569.     }
  570.     return nFound
  571. }
  572.  
  573. # Returns the number of elements in set Set
  574. function NumElem(Set,  elem,Num) {
  575.     for (elem in Set)
  576.     Num++
  577.     return Num
  578. }
  579.  
  580. # Remove all elements from Set
  581. function DeleteAll(Set,  i) {
  582.     split("",Set,",")
  583. }
  584.  
  585. # Returns a list of all of the elements in Set[], with each pair of elements
  586. # separated by Sep.
  587. function set2list(Set,Sep,  list,elem) {
  588.     for (elem in Set)
  589.     list = list Sep elem
  590.     return substr(list,2)    # skip 1st separator
  591. }
  592. ### End set library
  593. ### Start of ProcArgs library
  594. # @(#) ProcArgs 1.11 96/12/08
  595. # 92/02/29 john h. dubois iii (john@armory.com)
  596. # 93/07/18 Added "#" arg type
  597. # 93/09/26 Do not count -h against MinArgs
  598. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  599. #          Removed meaning of "+" or "-" by itself.
  600. # 94/03/08 Added & option and *()< option types.
  601. # 94/04/02 Added NoRCopt to Opts()
  602. # 94/06/11 Mark numeric variables as such.
  603. # 94/07/08 Opts(): Do not require any args if h option is given.
  604. # 95/01/22 Record options given more than once.  Record option num in argv.
  605. # 95/06/08 Added ExclusiveOptions().
  606. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  607. #          Expand $VARNAME at the start of its filenames.
  608. #          Let varname=0 and -option- turn off an option.
  609. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  610. #          of the vars should be searched for in the environment.
  611. #          Check for duplicate rcfiles.
  612. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  613. #          now return various negatives values on error, not just -1, and
  614. #          Opts() may set Err to various positive values, not just 1.
  615. #          Added AllowUnrecOpt.
  616. # 96/05/23 Check type given for & option
  617. # 96/06/15 Re-port to awk
  618. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  619. #          used by other functions.
  620. # 96/10/15 Added OptChars
  621. # 96/11/01 Added exOpts arg to Opts()
  622. # 96/11/16 Added ; type
  623. # 96/12/08 Added Opt2Set() & Opt2Sets()
  624. # 96/12/27 Added CmdLineOpt()
  625.  
  626. # optlist is a string which contains all of the possible command line options.
  627. # A character followed by certain characters indicates that the option takes
  628. # an argument, with type as follows:
  629. # :    String argument
  630. # ;    Non-empty string argument
  631. # *    Floating point argument
  632. # (    Non-negative floating point argument
  633. # )    Positive floating point argument
  634. # #    Integer argument
  635. # <    Non-negative integer argument
  636. # >    Positive integer argument
  637. # The only difference the type of argument makes is in the runtime argument
  638. # error checking that is done.
  639.  
  640. # The & option is a special case used to get numeric options without the
  641. # user having to give an option character.  It is shorthand for [-+.0-9].
  642. # If & is included in optlist and an option string that begins with one of
  643. # these characters is seen, the value given to "&" will include the first
  644. # char of the option.  & must be followed by a type character other than ":"
  645. # or ";".
  646. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  647.  
  648. # Strings in argv[] which begin with "-" or "+" are taken to be
  649. # strings of options, except that a string which consists solely of "-"
  650. # or "+" is taken to be a non-option string; like other non-option strings,
  651. # it stops the scanning of argv and is left in argv[].
  652. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  653. # If an option takes an argument, the argument may either immediately
  654. # follow it or be given separately.
  655. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  656. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  657. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  658. # this feature had a flaw that caused problems in some cases.  See the OptChars
  659. # parameter to explicitly set the option-specifier characters.
  660.  
  661. # If an option that does not take an argument is given,
  662. # an index with its name is created in Options and its value is set to the
  663. # number of times it occurs in argv[].
  664.  
  665. # If an option that does take an argument is given, an index with its name is
  666. # created in Options and its value is set to the value of the argument given
  667. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  668. # If an option that takes an argument is given more than once,
  669. # Options[option-name,"count"] is incremented, and the value is assigned to
  670. # the index (option-name,instance) where instance is 2 for the second occurance
  671. # of the option, etc.
  672. # In other words, the first time an option with a value is encountered, the
  673. # value is assigned to an index consisting only of its name; for any further
  674. # occurances of the option, the value index has an extra (count) dimension.
  675.  
  676. # The sequence number for each option found in argv[] is stored in
  677. # Options[option-name,"num",instance], where instance is 1 for the first
  678. # occurance of the option, etc.  The sequence number starts at 1 and is
  679. # incremented for each option, both those that have a value and those that
  680. # do not.  Options set from a config file have a value of 0 assigned to this.
  681.  
  682. # Options and their arguments are deleted from argv.
  683. # Note that this means that there may be gaps left in the indices of argv[].
  684. # If compress is nonzero, argv[] is packed by moving its elements so that
  685. # they have contiguous integer indices starting with 0.
  686. # Option processing will stop with the first unrecognized option, just as
  687. # though -- was given except that unlike -- the unrecognized option will not be
  688. # removed from ARGV[].  Normally, an error value is returned in this case.
  689. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  690. # be found, so the number of remaining arguments is returned instead.
  691. # If OptChars is not a null string, it is the set of characters that indicate
  692. # that an argument is an option string if the string begins with one of the
  693. # characters.  A string consisting solely of two of the same option-indicator
  694. # characters stops the scanning of argv[].  The default is "-+".
  695. # argv[0] is not examined.
  696. # The number of arguments left in argc is returned.
  697. # If an error occurs, the global string OptErr is set to an error message
  698. # and a negative value is returned.
  699. # Current error values:
  700. # -1: option that required an argument did not get it.
  701. # -2: argument of incorrect type supplied for an option.
  702. # -3: unrecognized (invalid) option.
  703. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  704. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  705. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  706. {
  707. # ArgNum is the index of the argument being processed.
  708. # ArgsLeft is the number of arguments left in argv.
  709. # Arg is the argument being processed.
  710. # ArgLen is the length of the argument being processed.
  711. # ArgInd is the position of the character in Arg being processed.
  712. # Option is the character in Arg being processed.
  713. # Pos is the position in OptList of the option being processed.
  714. # NumOpt is true if a numeric option may be given.
  715.     ArgsLeft = argc
  716.     NumOpt = index(OptList,"&")
  717.     OptionNum = 0
  718.     if (OptChars == "")
  719.     OptChars = "-+"
  720.     while (OptChars != "") {
  721.     c = substr(OptChars,1,1)
  722.     OptChars = substr(OptChars,2)
  723.     OptCharSet[c]
  724.     OptTerm[c c]
  725.     }
  726.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  727.     Arg = argv[ArgNum]
  728.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  729.         break    # Not an option; quit
  730.     if (Arg in OptTerm) {
  731.         delete argv[ArgNum]
  732.         ArgsLeft--
  733.         break
  734.     }
  735.     ArgLen = length(Arg)
  736.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  737.         Option = substr(Arg,ArgInd,1)
  738.         if (NumOpt && Option ~ /[-+.0-9]/) {
  739.         # If this option is a numeric option, make its flag be & and
  740.         # its option string flag position be the position of & in
  741.         # the option string.
  742.         Option = "&"
  743.         Pos = NumOpt
  744.         # Prefix Arg with a char so that ArgInd will point to the
  745.         # first char of the numeric option.
  746.         Arg = "&" Arg
  747.         ArgLen++
  748.         }
  749.         # Find position of flag in option string, to get its type (if any).
  750.         # Disallow & as literal flag.
  751.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  752.         if (AllowUnrecOpt) {
  753.             Escape = 1
  754.             break
  755.         }
  756.         else {
  757.             OptErr = "Invalid option: " specGiven Option
  758.             return -3
  759.         }
  760.         }
  761.  
  762.         # Find what the value of the option will be if it takes one.
  763.         # NeedNextOpt is true if the option specifier is the last char of
  764.         # this arg, which means that if the option requires a value it is
  765.         # the next arg.
  766.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  767.         if (GotValue = ArgNum + 1 < argc)
  768.             Value = argv[ArgNum+1]
  769.         }
  770.         else {    # Value is included with option
  771.         Value = substr(Arg,ArgInd + 1)
  772.         GotValue = 1
  773.         }
  774.  
  775.         if (HadValue = AssignVal(Option,Value,Options,
  776.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  777.         specGiven)) {
  778.         if (HadValue < 0)    # error occured
  779.             return HadValue
  780.         if (HadValue == 2)
  781.             ArgInd++    # Account for the single-char value we used.
  782.         else {
  783.             if (NeedNextOpt) {    # option took next arg as value
  784.             delete argv[++ArgNum]
  785.             ArgsLeft--
  786.             }
  787.             break    # This option has been used up
  788.         }
  789.         }
  790.     }
  791.     if (Escape)
  792.         break
  793.     # Do not delete arg until after processing of it, so that if it is not
  794.     # recognized it can be left in ARGV[].
  795.     delete argv[ArgNum]
  796.     ArgsLeft--
  797.     }
  798.     if (compress != 0) {
  799.     dest = 1
  800.     src = argc - ArgsLeft + 1
  801.     for (count = ArgsLeft - 1; count; count--) {
  802.         ARGV[dest] = ARGV[src]
  803.         dest++
  804.         src++
  805.     }
  806.     }
  807.     return ArgsLeft
  808. }
  809.  
  810. # Assignment to values in Options[] occurs only in this function.
  811. # Option: Option specifier character.
  812. # Value: Value to be assigned to option, if it takes a value.
  813. # Options[]: Options array to return values in.
  814. # ArgType: Argument type specifier character.
  815. # GotValue: Whether any value is available to be assigned to this option.
  816. # Name: Name of option being processed.
  817. # OptionNum: Number of this option (starting with 1) if set in argv[],
  818. #     or 0 if it was given in a config file or in the environment.
  819. # SingleOpt: true if the value (if any) that is available for this option was
  820. #     given as part of the same command line arg as the option.  Used only for
  821. #     options from the command line.
  822. # specGiven is the option specifier character use, if any (e.g. - or +),
  823. # for use in error messages.
  824. # Global variables: OptErr
  825. # Return value: negative value on error, 0 if option did not require an
  826. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  827. # the arg.
  828. # Current error values:
  829. # -1: Option that required an argument did not get it.
  830. # -2: Value of incorrect type supplied for option.
  831. # -3: Bad type given for option &
  832. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  833. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  834.     # If option takes a value...    [
  835.     NumTypes = "*()#<>]"
  836.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  837.     OptErr = "Bad type given for & option"
  838.     return -3
  839.     }
  840.  
  841.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  842.     if (!GotValue) {
  843.         if (Name != "")
  844.         OptErr = "Variable requires a value -- " Name
  845.         else
  846.         OptErr = "option requires an argument -- " Option
  847.         return -1
  848.     }
  849.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  850.         OptErr = Err
  851.         return -2
  852.     }
  853.     # Mark this as a numeric variable; will be propogated to Options[] val.
  854.     if (ArgType != ":" && ArgType != ";")
  855.         Value += 0
  856.     if ((Instance = ++Options[Option,"count"]) > 1)
  857.         Options[Option,Instance] = Value
  858.     else
  859.         Options[Option] = Value
  860.     }
  861.     # If this is an environ or rcfile assignment & it was given a value...
  862.     else if (!OptionNum && Value != "") {
  863.     UsedValue = 1
  864.     # If the value is "0" or "-" and this is the first instance of it,
  865.     # do not set Options[Option]; this allows an assignment in an rcfile to
  866.     # turn off an option (for the simple "Option in Options" test) in such
  867.     # a way that it cannot be turned on in a later file.
  868.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  869.         Instance = 1
  870.     else
  871.         Instance = ++Options[Option]
  872.     # Save the value even though this is a flag
  873.     Options[Option,Instance] = Value
  874.     }
  875.     # If this is a command line flag and has a - following it in the same arg,
  876.     # it is being turned off.
  877.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  878.     UsedValue = 2
  879.     if (Option in Options)
  880.         Instance = ++Options[Option]
  881.     else
  882.         Instance = 1
  883.     Options[Option,Instance]
  884.     }
  885.     # If this is a flag assignment without a value, increment the count for the
  886.     # flag unless it was turned off.  The indicator for a flag being turned off
  887.     # is that the flag index has not been set in Options[] but it has an
  888.     # instance count.
  889.     else if (Option in Options || !((Option,1) in Options))
  890.     # Increment number of times this flag seen; will inc null value to 1
  891.     Instance = ++Options[Option]
  892.     Options[Option,"num",Instance] = OptionNum
  893.     return UsedValue
  894. }
  895.  
  896. # Option is the option letter
  897. # Value is the value being assigned
  898. # Name is the var name of the option, if any
  899. # ArgType is one of:
  900. # :    String argument
  901. # ;    Non-null string argument
  902. # *    Floating point argument
  903. # (    Non-negative floating point argument
  904. # )    Positive floating point argument
  905. # #    Integer argument
  906. # <    Non-negative integer argument
  907. # >    Positive integer argument
  908. # specGiven is the option specifier character use, if any (e.g. - or +),
  909. # for use in error messages.
  910. # Returns null on success, err string on error
  911. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  912.     if (ArgType == ":")
  913.     return ""
  914.     if (ArgType == ";") {
  915.     if (Value == "")
  916.         Err = "must be a non-empty string"
  917.     }
  918.     # A number begins with optional + or -, and is followed by a string of
  919.     # digits or a decimal with digits before it, after it, or both
  920.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  921.     Err = "must be a number"
  922.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  923.     Err = "may not include a fraction"
  924.     else if (ArgType ~ "[()<>]" && Value < 0)
  925.     Err = "may not be negative"
  926.     # (
  927.     else if (ArgType ~ "[)>]" && Value == 0)
  928.     Err = "must be a positive number"
  929.     if (Err != "") {
  930.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  931.     if (Name != "")
  932.         return ErrStr "variable " substr(Name,1,1) " " Err
  933.     else {
  934.         if (Option == "&")
  935.         Option = Value
  936.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  937.     }
  938.     }
  939.     else
  940.     return ""
  941. }
  942.  
  943. # Note: only the above functions are needed by ProcArgs.
  944. # The rest of these functions call ProcArgs() and also do other
  945. # option-processing stuff.
  946.  
  947. # Opts: Process command line arguments.
  948. # Opts processes command line arguments using ProcArgs()
  949. # and checks for errors.  If an error occurs, a message is printed
  950. # and the program is exited.
  951. #
  952. # Input variables:
  953. # Name is the name of the program, for error messages.
  954. # Usage is a usage message, for error messages.
  955. # OptList the option description string, as used by ProcArgs().
  956. # MinArgs is the minimum number of non-option arguments that this
  957. # program should have, non including ARGV[0] and +h.
  958. # If the program does not require any non-option arguments,
  959. # MinArgs should be omitted or given as 0.
  960. # rcFiles, if given, is a colon-seprated list of filenames to read for
  961. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  962. # by the value of the environment variable HOME.  If a filename begins with
  963. # $, the part from the character after the $ up until (but not including)
  964. # the first character not in [a-zA-Z0-9_] will be searched for in the
  965. # environment; if found its value will be substituted, if not the filename will
  966. # be discarded.
  967. # rcfiles are read in the order given.
  968. # Values given in them will not override values given on the command line,
  969. # and values given in later files will not override those set in earlier
  970. # files, because AssignVal() will store each with a different instance index.
  971. # The first instance of each variable, either on the command line or in an
  972. # rcfile, will be stored with no instance index, and this is the value
  973. # normally used by programs that call this function.
  974. # VarNames is a comma-separated list of variable names to map to options,
  975. # in the same order as the options are given in OptList.
  976. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  977. # searched for in the environment.  If set to -1, all values will be searched
  978. # for in the environment.  Values given in the environment will override
  979. # those given in the rcfiles but not those given on the command line.
  980. # NoRCopt, if given, is an additional letter option that if given on the
  981. # command line prevents the rcfiles from being read.
  982. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  983. # ExclusiveOptions() for a description of exOpts.
  984. # Special options:
  985. # If x is made an option and is given, some debugging info is output.
  986. # h is assumed to be the help option.
  987.  
  988. # Global variables:
  989. # The command line arguments are taken from ARGV[].
  990. # The arguments that are option specifiers and values are removed from
  991. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  992. # The number of elements in ARGV[] should be in ARGC.
  993. # After processing, ARGC is set to the number of elements left in ARGV[].
  994. # The option values are put in Options[].
  995. # On error, Err is set to a positive integer value so it can be checked for in
  996. # an END block.
  997. # Return value: The number of elements left in ARGV is returned.
  998. # Must keep OptErr global since it may be set by InitOpts().
  999. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1000. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1001.     if (MinArgs == "")
  1002.     MinArgs = 0
  1003.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1004.     optChars)
  1005.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1006.     if (ArgsLeft >= 0) {
  1007.         OptErr = "Not enough arguments"
  1008.         Err = 4
  1009.     }
  1010.     else
  1011.         Err = -ArgsLeft
  1012.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1013.     Name,OptErr,Usage > "/dev/stderr"
  1014.     exit 1
  1015.     }
  1016.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1017.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1018.     {
  1019.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1020.     Err = -e
  1021.     exit 1
  1022.     }
  1023.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1024.     {
  1025.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1026.     Err = 1
  1027.     exit 1
  1028.     }
  1029.     return ArgsLeft
  1030. }
  1031.  
  1032. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1033. # <variable-name><assignment-char><value>.
  1034. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1035. # line and whitespace between the variable name and the assignment character) 
  1036. # is stripped.  Lines that do not contain an assignment operator or which
  1037. # contain a null variable name are ignored, other than possibly being noted in
  1038. # the return value.  If more than one assignment is made to a variable, the
  1039. # first assignment is used.
  1040. # Input variables:
  1041. # File is the file to read.
  1042. # Comment is the line-comment character.  If it is found as the first non-
  1043. #     whitespace character on a line, the line is ignored.
  1044. # Assign is the assignment string.  The first instance of Assign on a line
  1045. #     separates the variable name from its value.
  1046. # If StripWhite is true, whitespace around the value (whitespace between the
  1047. #     assignment char and trailing whitespace on the line) is stripped.
  1048. # VarPat is a pattern that variable names must match.  
  1049. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1050. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1051. #     a line; no assignment operator is needed.  These variables are set in
  1052. #     the output array with a null value.  Lines containing nothing but
  1053. #     whitespace are still ignored.
  1054. # Output variables:
  1055. # Values[] contains the assignments, with the indexes being the variable names
  1056. #     and the values being the assigned values.
  1057. # Lines[] contains the line number that each variable occured on.  A flag set
  1058. #     is record by giving it an index in Lines[] but not in Values[].
  1059. # Return value:
  1060. # If any errors occur, a string consisting of descriptions of the errors
  1061. # separated by newlines is returned.  In no case will the string start with a
  1062. # numeric value.  If no errors occur,  the number of lines read is returned.
  1063. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1064. FlagsOK,
  1065. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1066.     if (Comment != "")
  1067.     Comment = "^" Comment
  1068.     AssignLen = length(Assign)
  1069.     if (VarPat == "")
  1070.     VarPat = "."    # null varname not allowed
  1071.     while ((Status = (getline Line < File)) == 1) {
  1072.     LineNum++
  1073.     sub("^[ \t]+","",Line)
  1074.     if (Line == "")        # blank line
  1075.         continue
  1076.     if (Comment != "" && Line ~ Comment)
  1077.         continue
  1078.     if (Pos = index(Line,Assign)) {
  1079.         Var = substr(Line,1,Pos-1)
  1080.         Val = substr(Line,Pos+AssignLen)
  1081.         if (StripWhite) {
  1082.         sub("^[ \t]+","",Val)
  1083.         sub("[ \t]+$","",Val)
  1084.         }
  1085.     }
  1086.     else {
  1087.         Var = Line    # If no value, var is entire line
  1088.         Val = ""
  1089.     }
  1090.     if (!FlagsOK && Val == "") {
  1091.         Errs = Errs \
  1092.         sprintf("\nBad assignment on line %d of file %s: %s",
  1093.         LineNum,File,Line)
  1094.         continue
  1095.     }
  1096.     sub("[ \t]+$","",Var)
  1097.     if (Var !~ VarPat) {
  1098.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1099.         LineNum,File,Var)
  1100.         continue
  1101.     }
  1102.     if (!(Var in Lines)) {
  1103.         Lines[Var] = LineNum
  1104.         if (Pos)
  1105.         Values[Var] = Val
  1106.     }
  1107.     }
  1108.     if (Status)
  1109.     Errs = Errs "\nCould not read file " File
  1110.     close(File)
  1111.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1112. }
  1113.  
  1114. # Variables:
  1115. # Data is stored in Options[].
  1116. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1117. # Global vars:
  1118. # Sets OptErr.  Uses ENVIRON[].
  1119. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1120. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1121. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1122. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1123.     split("",filesRead,"")    # make awk know this is an array
  1124.     NumVars = split(VarNames,Vars,",")
  1125.     TypesInd = Ret = 0
  1126.     if (EnvSearch == -1)
  1127.     EnvSearch = NumVars
  1128.     for (i = 1; i <= NumVars; i++) {
  1129.     Var = Vars[i]
  1130.     CharOpt = substr(OptList,++TypesInd,1)
  1131.     if (CharOpt ~ "^[:;*()#<>&]$")
  1132.         CharOpt = substr(OptList,++TypesInd,1)
  1133.     Map[Var] = CharOpt
  1134.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1135.     # Do not overwrite entries from environment
  1136.     if (i <= EnvSearch && Var in ENVIRON &&
  1137.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1138.         return Err
  1139.     }
  1140.  
  1141.     numrcFiles = split(rcFiles,fNames,":")
  1142.     for (i = 1; i <= numrcFiles; i++) {
  1143.     rcFile = fNames[i]
  1144.     if (rcFile ~ "^~/")
  1145.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1146.     else if (rcFile ~ /^\$/) {
  1147.         rcFile = substr(rcFile,2)
  1148.         match(rcFile,"^[a-zA-Z0-9_]*")
  1149.         envvar = substr(rcFile,1,RLENGTH)
  1150.         if (envvar in ENVIRON)
  1151.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1152.         else
  1153.         continue
  1154.     }
  1155.     if (rcFile in filesRead)
  1156.         continue
  1157.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1158.     # may be the same
  1159.     filesRead[rcFile]
  1160.     if ("x" in Options)
  1161.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1162.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1163.     if (retStr > 0)
  1164.         READ_RCFILE = 1
  1165.     else if (ret != "") {
  1166.         OptErr = retStr
  1167.         Ret = -1
  1168.     }
  1169.     for (Var in Lines)
  1170.         if (Var in Map) {
  1171.         if ((Err = AssignVal(Map[Var],
  1172.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1173.         Var in Values,Var,0)) < 0)
  1174.             return Err
  1175.         }
  1176.         else {
  1177.         OptErr = sprintf(\
  1178.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1179.         Lines[Var],rcFile)
  1180.         Ret = -1
  1181.         }
  1182.     }
  1183.  
  1184.     if ("x" in Options)
  1185.     for (Var in Map)
  1186.         if (Map[Var] in Options)
  1187.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1188.         "/dev/stderr"
  1189.         else
  1190.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1191.     return Ret
  1192. }
  1193.  
  1194. # OptSets is a semicolon-separated list of sets of option sets.
  1195. # Within a list of option sets, the option sets are separated by commas.  For
  1196. # each set of sets, if any option in one of the sets is in Options[] AND any
  1197. # option in one of the other sets is in Options[], an error string is returned.
  1198. # If no conflicts are found, nothing is returned.
  1199. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1200. # the exclusions presented by the first set of sets (ab,def,g) if:
  1201. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1202. # (a or b is in Options[]) AND (g is in Options) OR
  1203. # (d, e, or f is in Options[]) AND (g is in Options)
  1204. # An error will be returned due to the exclusions presented by the second set
  1205. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1206. # todo: make options given on command line unset options given in config file
  1207. # todo: that they conflict with.
  1208. function ExclusiveOptions(OptSets,Options,
  1209. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1210. SetNum,OSetNum) {
  1211.     NumSetSets = split(OptSets,SetSets,";")
  1212.     # For each set of sets...
  1213.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1214.     # NumSets is the number of sets in this set of sets.
  1215.     NumSets = split(SetSets[SetSet],Sets,",")
  1216.     # For each set in a set of sets except the last...
  1217.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1218.         s1 = Sets[SetNum]
  1219.         L1 = length(s1)
  1220.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1221.         # If any of the options in this set was given, check whether
  1222.         # any of the options in the other sets was given.  Only check
  1223.         # later sets since earlier sets will have already been checked
  1224.         # against this set.
  1225.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1226.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1227.             s2 = Sets[OSetNum]
  1228.             L2 = length(s2)
  1229.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1230.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1231.                 ErrStr = ErrStr "\n"\
  1232.                 sprintf("Cannot give both %s and %s options.",
  1233.                 c1,c2)
  1234.             }
  1235.     }
  1236.     }
  1237.     if (ErrStr != "")
  1238.     return substr(ErrStr,2)
  1239.     return ""
  1240. }
  1241.  
  1242. # The value of each instance of option Opt that occurs in Options[] is made an
  1243. # index of Set[].
  1244. # The return value is the number of instances of Opt in Options.
  1245. function Opt2Set(Options,Opt,Set,  count) {
  1246.     if (!(Opt in Options))
  1247.     return 0
  1248.     Set[Options[Opt]]
  1249.     count = Options[Opt,"count"]
  1250.     for (; count > 1; count--)
  1251.     Set[Options[Opt,count]]
  1252.     return count
  1253. }
  1254.  
  1255. # The value of each instance of option Opt that occurs in Options[] that
  1256. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1257. # Other values are made indexes of Set[].
  1258. # The return value is the number of instances of Opt in Options.
  1259. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1260.     ret = Opt2Set(Options,Opt,aSet)
  1261.     for (value in aSet)
  1262.     if (substr(value,1,1) == "!")
  1263.         nSet[substr(value,2)]
  1264.     else
  1265.         Set[value]
  1266.     return ret
  1267. }
  1268.  
  1269. # Returns true if option Opt was given on the command line.
  1270. function CmdLineOpt(Options,Opt,  i) {
  1271.     for (i = 1; (Opt,"num",i) in Options; i++)
  1272.     if (Options[Opt,"num",i] != 0)
  1273.         return 1
  1274.     return 0
  1275. }
  1276. ### End of ProcArgs library
  1277.