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

  1. #!/usr/local/bin/gawk -f
  2. # @(#) treset.gawk 2.1 97/02/26
  3. # 93/04/02 john h. dubois iii (john@armory.com)
  4. # 93/05/06 Only print "Killing" once
  5. # 93/05/09 Added c option
  6. # 93/12/01 Get rid of leading /dev/ in TTY names given.
  7. # 93/12/12 kill -9 anything left after kill -1
  8. # 93/12/29 corrected typos
  9. # 94/01/21 Added o option
  10. # 94/03/09 Use gawk so - options can be given
  11. # 94/05/01 Match [mv]getty too.  Find TTY name as any arg to getty.
  12. # 94/12/21 Added u and U options.  Give full path to fuser.
  13. # 94/12/26 Only print 'killing:' if procs will be killed.  Added d, q options.
  14. #          Do not print a warning about non-getty procs if -c is given.
  15. # 96/01/05 5.0 port: make ps input come from /dev/null
  16. # 96/01/08 Act on both modem & non modem control versions of any TTY named.
  17. # 96/05/21 2.0 Read rcfile; rewrote to rationalize options; added many options.
  18. # 96/06/04 Renamed some options to rationalize names, added BPl options.
  19. #          Reformat ps lines before printing.
  20. # 96/06/10 Use dolsof()
  21. # 96/06/14 Added uA options.
  22. # 96/11/26 Include name in more error messages.
  23.  
  24. BEGIN {
  25.     Name = "treset"
  26.     Usage = \
  27.     "Usage: " Name " [-abcCghHlMnopqtu] [-[pbPB]<seconds>] [-d<date-spec>]\n"\
  28.     "              [-A<path>] [-T<pattern>] [TTY ...]"
  29.     rcFile = "/etc/default/treset"
  30.     ARGC = Opts(Name,Usage,"aA:bB>cCd:gHMopP>qT:uhlntx>",0,rcFile,
  31.     "ALLTTYS,PATH,BADINIT,BADINITMIN,CTLKILL,CONSOLE,DATEFORMAT,GETTYKILL,"\
  32.     "HARD,MODEM,OPENKILL,PROCHUNG,PROCHUNGMIN,QUIET,TTYPAT,FUSER",0,"n")
  33.     if ("h" in Options) {
  34.     printf \
  35. "%s: reset enabled TTYs by killing processes using the TTYs.\n"\
  36. "%s\n"\
  37. "TTY is the name of an enabled TTY to be put in the pool of TTYs that are\n"\
  38. "examined for whether they should be reset.  It may be the full (e.g.\n"\
  39. "tty3A) or short (e.g. 3A) name of a TTY, with or without the leading /dev/\n"\
  40. "component.  Regardless of whether the modem-control or non-modem-control\n"\
  41. "name for a port is specified, both versions of the name are put in the TTY\n"\
  42. "pool.  If no TTYs are named, all enabled modem-control TTYs (as determined\n"\
  43. "by examining /etc/inittab) are put in the TTY pool.\n"\
  44. "By default, any of the TTYs in the TTY pool that have a getty processing\n"\
  45. "running on them are selected for reset.  This is the default because it is\n"\
  46. "relatively safe: if a TTY has a getty process running on it, it generally\n"\
  47. "means that a user is not logged in on the TTY, though this may not be the\n"\
  48. "case if the TTY has been incorrectly configured so that both the\n"\
  49. "modem-control and non-modem-control TTY names are enabled.\n"\
  50. "A TTY is reset by killing any process using it.  A process is considered\n"\
  51. "to be using a TTY if it is a getty process running on the TTY, if it has\n"\
  52. "the TTY as its controlling TTY, or if it has open or is waiting to open\n"\
  53. "the TTY.\n"\
  54. "Options:\n"\
  55. "Some of the following options can also be set by assigning values to\n"\
  56. "variables in the configuration file %s.  Variables are\n"\
  57. "assigned to with the syntax:  varname=value  or in the case of flags, by\n"\
  58. "simply putting the indicated variable name in the file without a value. \n"\
  59. "Flag options can be turned off on the command line by following them\n"\
  60. "immediately with '-', e.g. -C- to turn off the C option in such a way that\n"\
  61. "it cannot be turned on in the config file.  Variable names appear in\n"\
  62. "parentheses in the option descriptions.\n"\
  63. "The following options control which TTYs are put in the pool of TTYs that\n"\
  64. "are examined for whether they should be reset.  If any of them are given,\n"\
  65. "only those which match one of the given options are put in the pool.\n"\
  66. "If any TTY names are given on the command line, those TTYs are compared\n"\
  67. "against these options; otherwise, all enabled TTYs are compared against\n"\
  68. "these ottions.  A tty whose name includes at least one letter after the\n"\
  69. "\"tty\" part of the name is considered a serial TTY.  If any of the the\n"\
  70. "letters is upper case, the TTY is a modem-control TTY; if not it is a\n"\
  71. "non-modem-control TTY.\n"\
  72. "A tty whose name consists entirely of digits is considered a console TTY.\n"\
  73. "-C: Examine console TTYs.  (CONSOLE)\n"\
  74. "-H: Examine serial TTYs for which the non-modem-control name is the one\n"\
  75. "    that is enabled.  (HARD)\n"\
  76. "-M: Examine serial TTYs for which the modem-control name is the one that\n"\
  77. "    is enabled.  This is the default if no TTY names are given.  (MODEM)\n"\
  78. "-T<ttypat>: Examine TTYs whose short name (without the leading\n"\
  79. "    \"/dev/tty\") matches the given pattern, which is implicitely\n"\
  80. "    anchored at the start and end.  Case is significant.  <pattern> is a\n"\
  81. "    regular expression in the style of egrep(C), not a shell filename\n"\
  82. "    pattern.  Example: -T'[1-4][A-H]' will match modem-control sio\n"\
  83. "    (standard serial) ports.  (TTYPAT)\n"\
  84. "The following options control which TTYs are selected for reset.  If\n"\
  85. "none are given, the default selection criteria are used.  These criteria\n"\
  86. "are equivalent to -pb, which will select for reset any TTY that has a\n"\
  87. "getty running on it, whether or not it also has other processes using it.\n"\
  88. "-a: Select all TTYs in the TTY pool for reset.  (ALLTTYS)\n"\
  89. "-p: Select for reset any TTY in the TTY pool that has both getty and\n"\
  90. "    non-getty processes using it.  This is intended to specifically reset\n"\
  91. "    lines that are hung due to processes hanging around on them after the\n"\
  92. "    user who invoked the processes has logged out.  (PROCHUNG)\n"\
  93. "-b: Select for reset any TTY in the TTY pool that is only being used by a\n"\
  94. "    getty process.  This is intended to select for reset lines that are\n"\
  95. "    hung due to modems not successfully initialized for dialin.  (BADINIT)\n"\
  96. "-P<seconds>, -B<seconds>: Like -p and -b, but select the TTYs for reset\n"\
  97. "    if the getty process is at least <seconds> old.\n"\
  98. "The following three options control which processes among those considered\n"\
  99. "to be using a TTY selected for reset are killed.  They do not affect what\n"\
  100. "processes are considered to be using a TTY; only which among them are\n"\
  101. "killed.  If any of them is given, only those processes using a TTY in the\n"\
  102. "given manner are killed.  The default is equivalent to giving all three\n"\
  103. "options.\n"\
  104. "-c: Kill any process (including a getty process) that has one of the TTYs\n"\
  105. "    selected for reset as its controlling TTY.  (CTLKILL)\n"\
  106. "-o: Kill any process (including a getty process) that has open or is\n"\
  107. "    waiting to open one of the TTYs selected for reset.  (OPENKILL)\n"\
  108. "-g: Kill any getty process running on one of the TTYs selected for reset.\n"\
  109. "    (GETTYKILL)\n"\
  110. "Other options:\n"\
  111. "-d<date-spec>: Prefix normal messages with the date formatted according to\n"\
  112. "    <date-spec> (see date(C)), for use when output is stored in a logfile.\n"\
  113. "    Use  -d%%c to get a normal date and time representation.  (DATEFORMAT)\n"\
  114. "-q: Quiet: Print messages only for errors or when TTYs are being reset.\n"\
  115. "    (QUIET)\n"\
  116. "-l: Print actions in a format suitable for logging.  This gives the -d\n"\
  117. "    option an initial value of %%c; it can be overridden by giving an\n"\
  118. "    explicit -d option.\n"\
  119. "-n: Do not read the configuration file.\n"\
  120. "-h: Print this help.\n"\
  121. "-t: Tell what processes would be killed but don't kill them.\n"\
  122. "-A<path>: Set the search path used to search for auxilliary utilities.\n"\
  123. "    <path> is given as for the PATH environment variale.  This is\n"\
  124. "    currently used only to find 'lsof'.  (PATH)\n"\
  125. "-u: Force the use of 'fuser' to find which processes have TTY devices\n"\
  126. "    open.  The default is to use 'lsof' if it is available.  (FUSER)\n",
  127.     Name,Usage,rcFile
  128.     exit(0)
  129.     }
  130.     if ((Err = ExclusiveOptions("a,bG;p,P;b,B",Options)) != "") {
  131.     printf "%s: Error: %s\n",Name,Err > "/dev/stderr"
  132.     Err = 1
  133.     exit(1)
  134.     }
  135.     if ("x" in Options)
  136.     Debug = Options["x"]
  137.     if (Log = "l" in Options)
  138.     dateFormat = "%c"
  139.     if ("d" in Options)
  140.     dateFormat = Options["d"]
  141.     Quiet = Log || "q" in Options
  142.     if ("u" in Options)
  143.     Use_lsof = 0
  144.     if ("A" in Options)
  145.     PATH = Options["A"]
  146.  
  147.     split("UID,PID,PPID,STIME,TTY,TIME,ARGS",OutFields,",")
  148.  
  149.     # CHMT options are dealt with by using them to create the pattern that
  150.     # is used to create the TTY pool
  151.     if ("C" in Options)        # match console TTYs
  152.     ttyPat = "|[0-9]+"
  153.     if ("H" in Options)        # match non-modem-control serial TTYs
  154.     ttyPat = ttyPat "|[^A-Z]*[a-z][^A-Z]*"
  155.     if ("T" in Options)        # match ttys that match pattern
  156.     ttyPat = ttyPat "|" Options["T"]
  157.     # Match modem-control serial TTYs
  158.     if ("M" in Options || (ttyPat == "" && ARGC < 2))
  159.     ttyPat = ttyPat "|.*[A-Z].*"
  160.     if (ttyPat != "")
  161.     ttyPat = "^" substr(ttyPat,2) "$"    # get rid of leading |
  162.     # Generate the pool of TTYs to check
  163.     if (!(nPool = GetTTYpool(ARGC,ARGV,ttyPat,TTYs,Debug,Quiet))) {
  164.     if (!Quiet)
  165.         hPrint("No matching enabled TTYs.")
  166.     exit 0
  167.     }
  168.     if (!Quiet)
  169.     printf "Checking %d tty(s) for whether they should be reset...\n",
  170.     nPool > "/dev/stderr"
  171.  
  172.     # Generate the list of TTYs to be reset
  173.     if (!(nReset = FindTTYsToReset(resetTTYs,gettyProcs,ctlProcs,openProcs,
  174.     PIDs,psData,TTYs,"a" in Options,"p" in Options || "P" in Options,
  175.     "b" in Options || "B" in Options,"P" in Options ? Options["P"] : 0,
  176.     "B" in Options ? Options["B"] : 0,OutFields))) {
  177.     if (!Quiet)
  178.         hPrint("No TTYs to be reset.")
  179.     exit 0
  180.     }
  181.     hPrint("")
  182.     printf "%d TTY(s) to be reset:",nReset 
  183.     for (tty in resetTTYs)
  184.     printf " %s",tty
  185.     print ""
  186.  
  187.     nKill = findPIDsToKill(killPIDs,resetTTYs,gettyProcs,ctlProcs,openProcs,
  188.     "g" in Options,"c" in Options,"o" in Options)
  189.     if (!nKill) {
  190.     printf "%s: No processes to kill?!\n",Name > "/dev/stderr"
  191.     exit 0
  192.     }
  193.  
  194.     Tell = "t" in Options
  195.     if (Tell)
  196.     print "Processes that would be killed:"
  197.     else
  198.     printf "%d process(es) to be killed:\n",nKill
  199.     print makePSline(-1,psData,OutFields)
  200.     for (pid in killPIDs) {
  201.     print makePSline(pid,psData,OutFields)
  202.     KillList = KillList " " pid
  203.     }
  204.     if (Tell)
  205.     exit 0
  206.  
  207.     Cmd = sprintf("echo 'Doing kill -1%s'; kill -1 %s; sleep 3;"\
  208.     "echo 'Doing kill -9%s'; kill -9 %s",KillList,KillList,KillList,KillList)
  209.     if (Log)
  210.     Cmd = "exec >/dev/null 2>&1; " Cmd
  211.     system(Cmd)
  212. }
  213.  
  214. function findPIDsToKill(killPIDs,resetTTYs,gettyProcs,ctlProcs,openProcs,
  215. KillGettys,KillControl,KillOpen,n) {
  216.     if (!(KillControl || KillGettys || KillOpen))
  217.     KillControl = KillGettys = KillOpen = 1
  218.     if (Debug > 1)
  219.     printf "Kill: %scontrol %sgetty %sopen\nProcesses to be killed:\n",
  220.     KillControl ? "+" : "-",KillGettys ? "+" : "-",KillOpen ? "+" : "-"
  221.     for (tty in resetTTYs) {
  222.     if (KillControl && tty in ctlProcs) {
  223.         if (Debug > 1)
  224.         printf "    Process(es) with %s as controlling tty: %s\n",
  225.         tty,ctlProcs[tty]
  226.         n += MakeSet(killPIDs,ctlProcs[tty],",")
  227.     }
  228.     if (KillGettys && tty in gettyProcs) {
  229.         if (Debug > 1)
  230.         printf "    getty(s) on %s: %s\n",tty,gettyProcs[tty]
  231.         n += MakeSet(killPIDs,gettyProcs[tty],",")
  232.     }
  233.     if (KillOpen && tty in openProcs) {
  234.         if (Debug > 1)
  235.         printf "    Process(es) with %s open/wtop: %s\n",tty,
  236.         openProcs[tty]
  237.         n += MakeSet(killPIDs,openProcs[tty],",")
  238.     }
  239.     }
  240.     return n
  241. }
  242.  
  243. # Input variables:
  244. # TTYs[] is the set of TTYs to check for whether they should be reset.
  245. # ResetAllTTYs: Select all TTYs in the TTY pool for reset.
  246. # ResetTTYsWithBoth: Select for reset any TTY in the TTY pool that has both
  247. #    getty and non-getty processes using it.
  248. # ResetTTYsWithGetty: Select for reset any TTY in the TTY pool that is only
  249. #    being used by a getty process.
  250. # If none of the above three is true, any TTY with a getty running on it is
  251. #    selected.
  252. # BothTime: Minimum number of seconds that the getty must have been running
  253. # for a TTY to be selected by ResetTTYsWithBoth.
  254. # GettyOnlyTime: Minimum number of seconds that the getty must have been
  255. # running for a TTY to be selected by ResetTTYsWithGetty.
  256. # Output variables: 
  257. # resetTTYs[] is the set of TTYs to reset (lowercased full names).
  258. # gettyProcs[] gives a comma-separated list of getty process IDs, indexed by
  259. #    lowercased full TTY name.
  260. # ctlProcs[] gives a comma-separated list of processes indexed by lowercased
  261. #    full controlling TTY name.
  262. # openProcs[] gives a comma-separated list of processes that have TTYs open
  263. #    or are waiting for TTYs to open, indexed by lowercase full TTY name.
  264. # PIDs[] and psOut[] give ps data, as described for getPS().
  265. # Return value: the number of TTYs in resetTTYs[].
  266. function FindTTYsToReset(resetTTYs,gettyProcs,ctlProcs,openProcs,PIDs,psOut,
  267. TTYs,ResetAllTTYs,ResetTTYsWithBoth,ResetTTYsWithGetty,BothTime,GettyOnlyTime,
  268. OutFields,  ttyPIDs,byPID,nOpen,tty,TTYgettys,p,n,openTTYs,numNonGettys,
  269. nReset,byFile,Seconds,gettyAge,gettyPID,curTime) {
  270.  
  271.     # gather TTY use info with ps
  272.     getPSttyInfo(gettyProcs,ctlProcs,PIDs,psOut,TTYs,Debug)
  273.     # Find which TTYs are open or waiting to open.
  274.     if ((File2PID(byFile,byPID,TTYs,"/dev",Debug)) < 0) {
  275.     ErrPrint("Open-file search failed.")
  276.     exit 1
  277.     }
  278.     nOpen = mergeTTYvalues(byFile,openProcs)
  279.     if (Debug) {
  280.     printf "%d TTY(s) open/waiting to open:\n",nOpen
  281.     for (tty in openProcs)
  282.         printf "%s ",tty
  283.     print ""
  284.     }
  285.     if (ResetAllTTYs)
  286.     return mergeTTYvalues(TTYs,resetTTYs)
  287.     # If none of the tty selection options given, default to any TTY
  288.     # with a getty on it.
  289.     if (!(ResetTTYsWithBoth || ResetTTYsWithGetty))
  290.     ResetTTYsWithBoth = ResetTTYsWithGetty = 1
  291.     if (BothTime || GettyOnlyTime)
  292.     # Get time the same way ps does, so the times can be subtracted.
  293.     curTime = hms2sec(strftime("%T"))
  294.     for (tty in gettyProcs) {
  295.     # Subtract getty PIDs from list of processes with tty open;
  296.     # if anything is left, there are non-getty procs on tty
  297.     DeleteAll(TTYpids)
  298.     DeleteAll(TTYgettys)
  299.     openTTYs = MakeSet(TTYpids,openProcs[tty],",")
  300.     n = MakeSet(TTYgettys,gettyProcs[tty],",")
  301.     numNonGettys = openTTYs - SubtractSet(TTYpids,TTYgettys)
  302.     if (Debug) {
  303.         printf "%6s: %d getty process(es): <%s>\n",tty,n,gettyProcs[tty] \
  304.                         > "/dev/stderr"
  305.         printf "%6s  %d procs with tty open/wtop: <%s>\n","",openTTYs,
  306.                         openProcs[tty] > "/dev/stderr"
  307.         printf "%6s  %d non-getty\n","",numNonGettys > "/dev/stderr"
  308.     }
  309.     
  310.     if (numNonGettys > 0 ? ResetTTYsWithBoth : ResetTTYsWithGetty) {
  311.         Seconds = numNonGettys > 0 ? BothTime : GettyOnlyTime
  312.         if (Seconds) {
  313.         # If more than one getty found, it's already been complained
  314.         # about.  Only worry about the first one found here.
  315.         gettyPID = gettyProcs[tty]
  316.         sub(",.*","",gettyPID)
  317.         gettyAge = curTime - hms2sec(psOut[gettyPID,"STIME"])
  318.         if (Debug)
  319.             printf \
  320.             "Age requirement: %d; getty (pid %d) age: %d (STIME=%s)\n",
  321.             Seconds,gettyPID,gettyAge,
  322.             psOut[gettyPID,"STIME"] > "/dev/stderr"
  323.         }
  324.         else
  325.         gettyAge = 0
  326.         # If age requirement not given, or getty is old enough...
  327.         if (gettyAge >= Seconds) {
  328.         resetTTYs[tty]
  329.         nReset++
  330.         if (Debug)
  331.             printf "Adding %s to reset list.\n",tty > "/dev/stderr"
  332.         }
  333.     }
  334.     if (numNonGettys > 0 && Debug > 3) {
  335.         printf \
  336.         "\n%s: Found non-getty process(es) with %s open/waiting to open:\n",
  337.         Name,tty > "/dev/stderr"
  338.         for (p in TTYpids)
  339.         print makePSline(p,psOut,OutFields) > "/dev/stderr"
  340.     }
  341.     }
  342.     return nReset
  343. }
  344.  
  345. function mergeTTYvalues(Mixed,Lowercased,  tty,lTTY,count,oValue) {
  346.     for (tty in Mixed) {
  347.     lTTY = tolower(tty)
  348.     if (lTTY in Lowercased) {
  349.         oValue = Lowercased[lTTY]
  350.         Lowercased[lTTY] = ((oValue == "") ? "" : oValue ",") Mixed[tty]
  351.     }
  352.     else {
  353.         Lowercased[lTTY] = Mixed[tty]
  354.         count++
  355.     }
  356.     }
  357.     return count
  358. }
  359.  
  360. # Find processes using TTYs according to ps.
  361. # Input vars:
  362. # TTYs[]: tty names we are interested in.
  363. # Debug: debugging level
  364. # Output vars:
  365. # PIDs[] and psOut[] give ps data, as described for getPS().
  366. # gettyTTYs[]: TTYs with gettys running on them, indexed by lowercased full TTY
  367. #              name. The value is a comma-separted string of PIDs of the gettys
  368. #              running on the TTY (should be only one).
  369. # ngettyCTTYs[]: TTYs that are the controlling TTYs of non-getty processes,
  370. #           indexed by lowercased full TTY name.  Value is a comma-separated
  371. #           string of PIDs that the TTY is the controlling TTY for.
  372. function getPSttyInfo(gettyTTYs,ngettyCTTYs,PIDs,psOut,TTYs,Debug,
  373. Children,pid,tty,lTTY) {
  374.     ## Gather information from ps and fuser/lsof
  375.     if (getPS(PIDs,psOut,"PPID,TTY,CMD,ARGS,STIME,UID,TIME",Children,
  376.     Debug > 5) < 0) {
  377.     ErrPrint("ps failed.")
  378.     exit 1
  379.     }
  380.     # Find which TTYs gettys are running on.
  381.     # A getty is a process whose parent has PID 1, whose name
  382.     # ends in "getty", and which has a TTY name as an argument.
  383.     # This block sets
  384.     if (Debug)
  385.     printf "Examining ps output" > "/dev/stderr"
  386.     delete PIDs["ps"]
  387.     for (pid in PIDs) {
  388.     if (psOut[pid,"PPID"] == 1 && psOut[pid,"CMD"] ~ "getty$" &&
  389.     (tty = gettyTTY(psOut[pid,"ARGS"])) != "") {    # If getty process
  390.         if (tty in TTYs) {    # If a tty that we care about
  391.         lTTY = tolower(tty)    # map to canonical (lowercase) name
  392.         if (lTTY in gettyTTYs) {
  393.             printf "%s: More than one getty running on %s!\n",
  394.             Name,tty > "/dev/stderr"
  395.             gettyTTYs[lTTY] = gettyTTYs[lTTY] "," pid
  396.         }
  397.         else
  398.             gettyTTYs[lTTY] = pid
  399.         if (Debug > 3)
  400.             printf "\nFound getty running on %s\n",tty > "/dev/stderr"
  401.         }
  402.     }
  403.     # If non-getty proc has a TTY that we care about as controlling tty...
  404.     else if ((tty = canonTTY(psOut[pid,"TTY"])) in TTYs) {
  405.         # tty in ps data may be short or long name.
  406.         lTTY = tolower(tty)    # map to canonical name
  407.         if (lTTY in ngettyCTTYs)
  408.         ngettyCTTYs[lTTY] = ngettyCTTYs[lTTY] "," pid
  409.         else
  410.         ngettyCTTYs[lTTY] = pid
  411.         if (Debug > 3)
  412.         printf "\nFound non-getty attached to %s\n",tty > "/dev/stderr"
  413.     }
  414.     else if (Debug)
  415.         printf "." > "/dev/stderr"
  416.     }
  417.     if (Debug)
  418.     print "" > "/dev/stderr"
  419. }
  420.  
  421. # GetTTYpool: generate list of TTYs to check for whether they should be reset.
  422. # If any tty names are given, the initial tty pool consists of them.
  423. # If not, the initial tty pool consists of all enabled TTYs.
  424. # If ttyPat is given, the initial tty pool is compared to it and only TTYs
  425. # whose short name matches it are included in the final TTY pool.
  426. # TTYs with a letter in them are included in both modem-control and
  427. # non-modem-control forms in the final TTY pool.
  428. # Output variables:
  429. # TTYs[] will contain the names of TTYs without leading /dev/ but including
  430. # leading "tty"
  431. # Return value: the number of tty names put in TTYs[] (with modem-control+
  432. # non-modem-control pairs counted only once each).
  433. function GetTTYpool(ARGC,ARGV,ttyPat,TTYs,Debug,Quiet,
  434. count,i,tty,bNames,initialTTYs,mTTY,nmTTY,enabledTTYs,cEnabledTTYs,stty) {
  435.     if (Debug)
  436.     if (ttyPat == "")
  437.         printf "TTY pattern: empty (no TTYs excluded by pattern)\n" \
  438.         > "/dev/stderr"
  439.     else
  440.         printf "TTY pattern: <%s>\n",ttyPat > "/dev/stderr"
  441.     # Get set of enabled TTYs regardless of whether any TTYs were named,
  442.     # so that user can be notified if any un-enabled TTYs were named.
  443.     FindGettys(enabledTTYs)
  444.     for (tty in enabledTTYs)
  445.     cEnabledTTYs[nodevTTY(tty)]
  446.     # Generate list of TTYs to compare to TTY pattern
  447.     if (ARGC < 2)    # No TTY names given on command line
  448.     for (tty in cEnabledTTYs)
  449.         initialTTYs[tty]
  450.     else
  451.     for (i = 1; i < ARGC; i++)
  452.         initialTTYs[canonTTY(ARGV[i])]
  453.     if (Debug)
  454.     printf "Initial TTY pool:\n" > "/dev/stderr"
  455.     for (tty in initialTTYs) {
  456.     # Find last letter in the TTY name, if any.
  457.     stty = shortTTY(tty)
  458.     if (match(stty,"[a-zA-Z][^a-zA-Z]*$")) {
  459.         # convert it to upper and lower case forms.
  460.         bothTTYnames(tty,bNames)
  461.         mTTY = bNames["upper"]
  462.         nmTTY = bNames["lower"]
  463.         if (!Quiet && !(mTTY in cEnabledTTYs || nmTTY in cEnabledTTYs))
  464.         ErrPrint(\
  465. "Note: Neither " mTTY " nor " nmTTY " found in inittab.  Resetting anyway...")
  466.         if (Debug)
  467.         printf "%s,%s ",mTTY,nmTTY > "/dev/stderr"
  468.     }
  469.     else {
  470.         mTTY = nmTTY = tty
  471.         if (!(tty in cEnabledTTYs))
  472.         ErrPrint("\n" tty " not found in inittab.")
  473.         if (Debug)
  474.         printf "%s ",tty > "/dev/stderr"
  475.     }
  476.     # Test for ttypat after doing name conversion so that names can be
  477.     # checked for in inittab regardless of whether tty is selected
  478.     if (ttyPat != "" && stty !~ ttyPat)
  479.         continue
  480.     TTYs[mTTY]
  481.     TTYs[nmTTY]
  482.     count++
  483.     }
  484.     if (Debug) {
  485.     printf "\nTTY pool to be checked: " > "/dev/stderr"
  486.     for (tty in TTYs)
  487.         printf "%s ",tty > "/dev/stderr"
  488.     print "" > "/dev/stderr"
  489.     printf "%d real ttys.\n",count > "/dev/stderr"
  490.     }
  491.     return count
  492. }
  493.  
  494. function hPrint(S) {
  495.     if (dateFormat != "" && !SaidDate) {
  496.     printf "** %s\n",strftime(Options["d"])
  497.     SaidDate = 1
  498.     }
  499.     if (S != "")
  500.     print S
  501. }
  502.  
  503. ### End main program.  Begin library routines.
  504.  
  505. ### Begin FindGettys
  506. # 93/04    jhdiii
  507. # 96/05/14 Split gettyTTY() into its of function because it is also useful
  508. #          for examing ps output.
  509.  
  510. # Getty lines:
  511. #01:2345:respawn:/etc/getty tty01 sc_m
  512. #3A:23:respawn:/usr/lib/uucp/uugetty -t60 tty3A 3
  513.  
  514. # Return the set of enabled ttys in GettyLines[].
  515. # Return value: the number of enabled TTYs found in /etc/inittab.
  516. function FindGettys(GettyLines,  Line,F,NumGettys,File,tty) {
  517.     File = "/etc/inittab"
  518.     while ((getline Line < File) == 1) {
  519.     if (Line ~ "^#")
  520.         continue
  521.     split(Line,F,":")
  522.     if (F[3] == "respawn" && F[4] ~ "^[^ \t]*getty " && \
  523.     (tty = gettyTTY(F[4])) != "") {
  524.         GettyLines[tty]
  525.         NumGettys++
  526.     }
  527.     }
  528.     close(File)
  529.     return NumGettys
  530. }
  531.  
  532. # Given getty command line gettyCmd, return the TTY argument, if any
  533. function gettyTTY(gettyCmd,  Elem,Cmd) {
  534.     split(gettyCmd,Cmd,"[ \t]+")
  535.     for (Elem = 2; Elem in Cmd; Elem++)
  536.     if (Cmd[Elem] ~ "(^|/)tty")
  537.         return Cmd[Elem]
  538. }
  539.  
  540. # Return the set of enabled modem-control ttys in GettyLines[].
  541. # Return value: the number of enabled modem-control TTYs found in /etc/inittab.
  542. function FindMGettys(GettyLines,  Elem,NumGettys) {
  543.     NumGettys = FindGettys(GettyLines)
  544.     for (Elem in GettyLines)
  545.     if (Elem !~ "[A-Z]$") {
  546.         delete GettyLines[Elem]
  547.         NumGettys--
  548.     }
  549.     return NumGettys
  550. }
  551.  
  552. ### End FindGettys
  553. ### Begin Strings routines
  554.  
  555. # Delete the string starting at Start and having length Num from the middle
  556. # of string S, and return the remaining part.
  557. function DelStr(S,Start,Num) {
  558.     return substr(S,1,Start - 1) substr(S,Start+Num)
  559. }
  560.  
  561. # Insert NewStr into S at position Pos (between the Pos-1'th and the Pos'th
  562. # characters).  S is padded with spaces if neccessary.
  563. function InsertStr(S,Pos,NewStr,  e) {
  564.     e = length(S)+1    # The position after the end of S
  565.     if (e >= Pos)
  566.     return substr(S,1,Pos-1) NewStr substr(S,Pos)
  567.     for (; e < Pos; e++)
  568.     S = S " "
  569.     return S NewStr
  570. }
  571.  
  572. # Search for char C in string S starting at position Pos, in the direction
  573. # specified by Dir (1 = forward, -1 = backward).  
  574. # Return position char found at for success, 0 if not found before start or end
  575. # of string.
  576. function FindC(S,Pos,C,Dir,  FoundC) {
  577.     while (Pos > 0 && (FoundC = substr(S,Pos,1)) != C && FoundC != "")
  578.     Pos += Dir
  579.     if (FoundC == C)
  580.     return Pos
  581.     else
  582.     return 0
  583. }
  584.  
  585. # Split string S into array Arr, one character per index, starting with 1.
  586. # The number of characters in the string is returned.
  587. function SplitS(S,Arr,  len,i) {
  588.     len = length(S)
  589.     for (i = 1; i <= len; i++)
  590.     Arr[i] = substr(S,i,1)
  591.     return len
  592. }
  593.  
  594. # Paste NewStr onto S at position Pos, overwriting what was there
  595. # S is padded with spaces if neccessary.
  596. function PasteStr(S,Pos,NewStr,  e) {
  597.     e = length(S)+1    # The position after the end of S
  598.     if (e >= Pos)
  599.     return substr(S,1,Pos-1) NewStr substr(S,Pos+length(NewStr))
  600.     for (; e < Pos; e++)
  601.     S = S " "
  602.     return S NewStr
  603. }
  604.  
  605. ### End Strings routines
  606.  
  607. function ErrPrint(S) {
  608.     print Name ": " S > "/dev/stderr"
  609.     close("/dev/stderr")    # flush
  610. }
  611.  
  612. ### Start File2PID routines
  613. # 96/05/23 jhdiii
  614. # File2PID: find what processes have the given files open or are waiting to
  615. #           open them.  If lsof is available, it is used because it is much
  616. #           faster than fuser; if not, /etc/fuser is used.
  617. # fileList[] is the set of files to check.
  618. # An index is created in byFile[] for each file that is open.  The value of
  619. # each element is set to be a comma-separated list of the PIDs of the processes
  620. # that have that file open or are waiting to open it.
  621. # byPID[] is set to the reverse index: for each PID, a semicolon-separated list
  622. # of the files (out of those given in byFile[]) that that process has open or
  623. # is waiting to open.
  624. # If Dir is non-null, the files are checked for relative to the given
  625. # directory.
  626. # Globals: sets/uses Use_lsof, uses global PATH if set
  627. # Return value:
  628. # On error, -1.
  629. # Otherwise, the number of files that were open/waiting in open.
  630. function File2PID(byFile,byPID,fileList,Dir,Debug,
  631. Cmd,ret,Files,NumFound,i,Path) {
  632.     for (file in fileList)
  633.     Files = Files " " file
  634.     if (Files == "")
  635.     return 0
  636.     if (PATH != "")
  637.     Path = "PATH=\"" PATH "\";export PATH;"
  638.     Cmd = Path "type lsof"
  639.     Cmd | getline
  640.     close(Cmd)
  641.     if (Debug)
  642.     print Cmd " returned: " $0 > "/dev/stderr"
  643.     Cmd = ""
  644.     if (Use_lsof == "")
  645.     Use_lsof = ($2 == "is")
  646.     if (Use_lsof) {
  647.     ret = dolsof("n","-nOP" Files,Out,PIDs,Dir,Debug)
  648.     for (pid in PIDs) {
  649.         if (Debug)
  650.         printf "From lsof: %d has %s open\n",pid,Out[pid,"n",1] \
  651.         > "/dev/stderr"
  652.         NumFound += mapFile(Out[pid,"n",1],pid,fileList,byFile,byPID)
  653.     }
  654.     }
  655.     else {
  656.     # Format returned by fuser:
  657.     #    ptyp0:    29592 29593 ...
  658.     if (Dir != "")
  659.         Cmd = "cd '" Dir "';"
  660.     Cmd = Path Cmd "exec /etc/fuser 2>/dev/null" Files
  661.     if (Debug)
  662.         print "Open-file command:" Cmd > "/dev/stderr"
  663.     while ((ret = (Cmd | getline)) == 1) {
  664.         if (Debug >= 5)
  665.         print "\n" $0 > "/dev/stderr"
  666.         else if (Debug == 4)
  667.         printf "." > "/dev/stderr"
  668.         file = $1
  669.         sub(":$","",file)
  670.         for (i = 2; i <= NF; i++)
  671.         NumFound += mapFile(file,$i,fileList,byFile,byPID)
  672.         }
  673.     if (Debug == 4)
  674.         print "" > "/dev/stderr"
  675.     close(Cmd)
  676.     }
  677.     if (ret == -1)
  678.     return -1
  679.     return NumFound
  680. }
  681.  
  682. # mapFile: map file to pid that has it open & vice versa.
  683. # file is an open file.
  684. # pid is a process that has it open.
  685. # fileList[] is the set of file names to pay attention to.
  686. # file is added to the value of byPID[pid],
  687. # and pid is added to the value of byFile[file].
  688. # Return value:
  689. # If file was not already an index of byFile[], 1; if it was, 0.
  690. function mapFile(file,pid,fileList,byFile,byPID) {
  691.     if (!(file in fileList))    # Generally, a header line
  692.     return 0
  693.     if (pid in byPID)
  694.     byPID[pid] = byPID[pid] "," file
  695.     else
  696.     byPID[pid] = file
  697.     if (byFile[file] != "") {
  698.     byFile[file] = byFile[file] "," pid
  699.     return 0
  700.     }
  701.     else {
  702.     byFile[file] = pid
  703.     return 1
  704.     }
  705. }
  706. ### end File2PID routines
  707. ### Begin set library
  708. # 96/05/23 added return values  jhdiii
  709. # 96/05/25 added set2list()
  710.  
  711. # Return value: the number of new elements added to Inter
  712. function Intersection(A,B,Inter,  Elem,Count) {
  713.     for (Elem in A)
  714.     if (Elem in B && !(Elem in Inter)) {
  715.         Inter[Elem]
  716.         Count++
  717.     }
  718.     return Count
  719. }
  720.  
  721. # Return value: the number of new elements added to Both
  722. function Union(A,B,Both) {
  723.     return CopySet(A,Both) + CopySet(B,Both)
  724. }
  725.  
  726. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  727. # Return value: the number of elements deleted.
  728. function SubtractSet(Minuend,Subtrahend,  Elem,nDel) {
  729.     for (Elem in Subtrahend)
  730.     if (Elem in Minuend) {
  731.         delete Minuend[Elem]
  732.         nDel++
  733.     }
  734.     return nDel
  735. }
  736.  
  737. # Return value: the number of new elements added to To
  738. function CopySet(From,To,  Elem,n) {
  739.     for (Elem in From)
  740.     if (!(Elem in To)) {
  741.         To[Elem]
  742.         n++
  743.     }
  744.     return n
  745. }
  746.  
  747. # Returns 1 if Set is empty, 0 if not.
  748. function IsEmpty(Set,  i) {
  749.     for (i in Set)
  750.     return 0
  751.     return 1
  752. }
  753.  
  754. # MakeSet: make a set from a list.
  755. # An index with the name of each element of the list is created in the given
  756. # array.
  757. # Input variables: 
  758. # Elements is a string containing the list of elements.
  759. # Sep is the character that separates the elements of the list.
  760. # Output variables:
  761. # Set is the array.
  762. # Return value: the number of new elements added to the set.
  763. function MakeSet(Set,Elements,Sep,  i,Num,Names,nFound,ind) {
  764.     nFound = 0
  765.     Num = split(Elements,Names,Sep)
  766.     for (i = 1; i <= Num; i++) {
  767.     ind = Names[i]
  768.     if (!(ind in Set)) {
  769.         Set[ind]
  770.         nFound++
  771.     }
  772.     }
  773.     return nFound
  774. }
  775.  
  776. # Returns the number of elements in set Set
  777. function NumElem(Set,  elem,Num) {
  778.     for (elem in Set)
  779.     Num++
  780.     return Num
  781. }
  782.  
  783. # Remove all elements from Set
  784. function DeleteAll(Set,  i) {
  785.     split("",Set,",")
  786. }
  787.  
  788. # Returns a list of all of the elements in Set[], with each pair of elements
  789. # separated by Sep.
  790. function set2list(Set,Sep,  list,elem) {
  791.     for (elem in Set)
  792.     list = list Sep elem
  793.     return substr(list,2)    # skip 1st separator
  794. }
  795. ### End set library
  796.  
  797. ### start canonTTY library
  798. function nodevTTY(tty) {
  799.     sub("^/dev/","",tty)
  800.     return tty
  801. }
  802.  
  803. function canonTTY(tty) {
  804.     if (tty ~ "^/dev/")
  805.     sub("^/dev/","",tty)
  806.     else if (tty !~ /^tty/)
  807.     tty = "tty" tty
  808.     return tty
  809. }
  810.  
  811. function shortTTY(tty) {
  812.     # Strip leading "tty" only if name did not begin with /dev
  813.     if (!sub("^/dev/","",tty))
  814.     sub("^tty","",tty)
  815.     return tty
  816. }
  817.  
  818. # names["lower"] and names["upper"] are made the canonical non-modem-control
  819. # and modem-control versions of the TTY name respectively.
  820. # These are the name as passed but with the first alpha char after "tty"
  821. # (if any) converted to lowercase and uppercase respectively.
  822. # If the TTY name is an absolute path and is not of the form /dev/tty*, the
  823. # case conversion is not done.
  824. # Any leading /dev/ is stripped.  If the name does not contain any directory
  825. # component and does not begin with "tty", it is prefixed with "tty".
  826. function bothTTYnames(tty,names,  sTTY,letter) {
  827.     sTTY = shortTTY(tty)
  828.     if (tty ~ "^/" && tty !~ "^/dev/tty") {
  829.     names["lower"] = names["upper"] = sTTY
  830.     return 1
  831.     }
  832.     match(sTTY,"[a-zA-Z][^a-zA-Z]*$")
  833.     letter = substr(sTTY,RSTART,1)
  834.     tty = (tty ~ "/") ? "" : "tty"
  835.     names["upper"] = tty PasteStr(sTTY,RSTART,toupper(letter))
  836.     names["lower"] = tty PasteStr(sTTY,RSTART,tolower(letter))
  837.     return 0
  838. }
  839. ### end canonTTY library
  840.  
  841. ### Begin ps lib
  842. # getPS 1.1    jhdiii 96/05/25
  843. # 96/02/11    Added Debug flag.
  844. # 96/05/09    Added COMM field.
  845. # 96/05/23    Added selection args, and saving of "ps" PID.
  846. # 96/05/25    Added makePSline()
  847.  
  848. # Note: makePSline() needs assign() from array lib.
  849.  
  850. # Do a ps -f and save the output into an array, indexed by pid and field name.
  851. # Input vars:
  852. # Fields: Comma-separated list of fields to put in Procs.
  853. # If Debug is true, debugging info is output.
  854. # selectionArgs may be set to ps options that will report on selected processes
  855. # (e.g. -usomeone -ttty01)
  856. # The default for selectionArgs is -e, which causes information on all
  857. # processes to be recorded.
  858. #
  859. # Output vars:
  860. # PIDs[]: the set of all PIDs seen.
  861. # Also, the element with index "ps" is set to the PID for the ps process.
  862. # Procs[pid,fieldname]: output by field.
  863. #
  864. # Possible fields are:
  865. # UID: User ID; name if available, else number.
  866. # PPID: Parent process ID.
  867. # C: CPU scheduling.
  868. # STIME: Start time.  If the start time in the ps output contains a space,
  869. # it is replaced with a "-".  "-" is returned for a defunct process.
  870. # TTY: tty name; may or may not have leading "tty" part.  "-" for defunct proc;
  871. # "?" for proc with no controlling tty.
  872. # TIME: CPU time used.
  873. # CMD: First element of arg vector.
  874. # ARGS: Entire (truncated) arg vector (command + args).
  875. # LINE: Entire ps output line.
  876. # COMM: Process accounting name of process: the name of the executable file,
  877. #       without path.  This is only available under 5.0, and cannot be
  878. #       request along with CMD or ARGS.
  879. #
  880. # The header line read is also put in Procs with the index "Header".
  881. # The PIDs of the children of each process are put in a comma-separated list
  882. # in Children[pid].
  883. # Return value: the number of processes found, or -2 if an invalid field name
  884. # is passed, or -1 if an error occurs reading from ps.
  885. # Globals: FS is set to " "
  886. #
  887. # ps -f produces output in these forms, under various conditions & releases:
  888. #     UID   PID  PPID  C    STIME     TTY        TIME CMD
  889. #    root 10118 10107  2   Jan-03   ttyp0    00:00:05 -ksh
  890. #    root 10118 10107  2   Jan 03   ttyp0    00:00:05 -ksh
  891. #    root 18197     1  0 08:02:56   ttyp0    00:00:03 /usr/bin/X11/scoterm -geo
  892. function getPS(PIDs,Procs,Fields,Children,Debug,selectionArgs,
  893. stimeI,pidI,ttyI,ppidI,WantLine,psArgs,
  894. FieldNames,Wanted,Cmd,getI,Field2Ind,i,Name,Lines,WantArgs,Header,CmdIndex) {
  895.     FS = " "    # magic pattern to reset FS to its default special behaviour
  896.     split("UID,PID,PPID,C,STIME,TTY,TIME,CMD",FieldNames,",")
  897.     FieldNames[0] = "LINE"
  898.     for (i in FieldNames)
  899.     Field2Ind[FieldNames[i]] = i
  900.     split(Fields,Wanted,",")
  901.     pidI = Field2Ind["PID"]
  902.     ppidI = Field2Ind["PPID"]
  903.     stimeI = Field2Ind["STIME"]
  904.     ttyI = Field2Ind["TTY"]
  905.     timeI = Field2Ind["TIME"]
  906.     cmdI = Field2Ind["CMD"]
  907.     psArgs = "-f"
  908.     for (i in Wanted) {
  909.     Name = Wanted[i]
  910.     if (Debug)
  911.         printf "Asked for %s\n",Name > "/dev/stderr"
  912.     if (Name == "ARGS")
  913.         WantArgs = 1
  914.     else if (Name == "LINE")
  915.         WantLine = 1
  916.     else if (Name == "COMM") {
  917.         psArgs = "-ouser -opid -oppid -oc -ostime -otty -otime -ocomm"
  918.         FieldNames[getI[Field2Ind[Name] = 8]] = Name
  919.     }
  920.     else if (Name in Field2Ind)
  921.         getI[Field2Ind[Name]]
  922.     else
  923.         return -2
  924.     }
  925.     Lines = 0
  926.     if (selectionArgs == "")
  927.     selectionArgs = "-e"
  928.     Cmd = "echo $$; exec /bin/ps " selectionArgs " " psArgs " < /dev/null"
  929.     if ((Cmd | getline PIDs["ps"]) != 1)
  930.     return -1
  931.     if ((Cmd | getline Header) != 1)
  932.     return -1
  933.     Procs["Header"] = Header
  934.     if (!(CmdIndex = index(Header,"CMD")) && 
  935.     !(CmdIndex = index(Header,"COMMAND")))
  936.     return -1
  937.     while ((Cmd | getline) == 1) {
  938.     PIDs[pid = $pidI]
  939.     if (Debug)
  940.         printf "Process %d (%d fields): %s\n",pid,NF,$0 > "/dev/stderr"
  941.     ppid = $ppidI
  942.     if (ppid in Children)
  943.         Children[ppid] = Children[ppid] "," pid
  944.     else
  945.         Children[ppid] = pid
  946.     if (WantArgs)
  947.         Procs[pid,"ARGS"] = substr($0,CmdIndex)
  948.     # Handle this as a special case so that it can be set before the
  949.     # line (possibly) modified
  950.     if (WantLine)
  951.         Procs[pid,"LINE"] = $0
  952.     # Time field with either contain a : (time), a - (new date format),
  953.     # or neither, in which case it occupies 2 fields (old date format).
  954.     if (NF == 6) {    # old ps defunct proc
  955.         # Assign new values to fields, from right to left to avoid
  956.         # overwriting fields before value is moved
  957.         $cmdI = $ttyI
  958.         $timeI = $stimeI
  959.         $ttyI = "-"
  960.         $stimeI = "-"
  961.     }
  962.     if ($stimeI !~ "[-:]") {
  963.         if (!timePos)
  964.         timePos = index($0,$stimeI)
  965.         # Replace space in stime field with "-"
  966.         $0 = substr($0,1,timePos+2) "-" substr($0,timePos+5)
  967.     }
  968.     for (i in getI) {
  969.         Procs[pid,FieldNames[i]] = $i
  970.         if (Debug)
  971.         printf "%s=%s ",FieldNames[i],$i > "/dev/stderr"
  972.     }
  973.     if (Debug)
  974.         print "" > "/dev/stderr"
  975.     Lines++
  976.     }
  977.     close(Cmd)
  978.     return Lines
  979. }
  980.  
  981. # makePSline: generate a line containing desired fields from ps data.
  982. # pid is the ID of the process to generate a line for.
  983. # If a pid of -1 is passed, a header line is returned.
  984. # Procs[] is the ps data, as generated by getPS().
  985. # Fields[] is the set of fields desired in the output, with indexes starting
  986. #    at 1.  The values are field names as e.g. passed to getPS().
  987. # Sep is the separator to put between fields.  If null, a single space is used.
  988. # Return value: a line consisting of the fields requested, in the order of
  989. # their indices in Fields[].
  990. # Example:
  991. # split("UID,PID,PPID,C,STIME,TTY,TIME,CMD",FieldNames,",")
  992. # makePSline(pid,psOut,FieldNames)
  993. function makePSline(pid,Procs,Fields,Sep,  i,fieldName,line,width,value) {
  994.     if (Sep == "")
  995.     Sep = " "
  996.     if (!("PID" in _makePSlineWidths))
  997.     # Make TIME before right-adjusted; some versions of ps drop leading
  998.     # 0 fields from it.
  999.     Assign(_makePSlineWidths,
  1000.     "UID=-8 PID=5 PPID=5 C=1 STIME=-8 TTY=-4 TIME=8 COMM=-8"," ","=")
  1001.     for (i = 1; i in Fields; i++) {
  1002.     fieldName = Fields[i]
  1003.     if (fieldName in _makePSlineWidths)
  1004.         width = _makePSlineWidths[fieldName]
  1005.     else
  1006.         width = ""
  1007.     if (pid == -1)
  1008.         value = fieldName
  1009.     else if (fieldName == "PID")
  1010.         value = pid
  1011.     else
  1012.         value = Procs[pid,fieldName]
  1013.     if (fieldName == "TTY")
  1014.         sub("^tty","",value)
  1015.     line = line Sep sprintf("%" width "s",value)
  1016.     }
  1017.     return substr(line,length(Sep)+1)
  1018. }
  1019.  
  1020. ### End ps lib
  1021. ### Begin array routines
  1022.  
  1023. # InitArr: Initialize an array with values.
  1024. # Ind and Vals are separated into lists on Sep.
  1025. # For each item in Ind, an index with that name is created in Arr[],
  1026. # and the value with the same position in Vals is stored in it.
  1027. # Global variables: none.
  1028. function InitArr(Arr,Ind,Vals,sep,  numind,indnames,values) {
  1029.     split(Ind,indnames,sep)
  1030.     split(Vals,values,sep)
  1031.     for (numind in indnames)
  1032.     Arr[indnames[numind]] = values[numind]
  1033. }
  1034.  
  1035. function ClearArr(Arr,  Elem) {
  1036.     for (Elem in Arr)
  1037.     delete Arr[Elem]
  1038. }
  1039. # Subtract the values in Subtrahend from those in Minuend
  1040. function SubtractArr(Minuend,Subtrahend,  Elem) {
  1041.     for (Elem in Subtrahend)
  1042.     Minuend[Elem] -= Subtrahend[Elem]
  1043. }
  1044. # For each element of the array In, an element is created in Out having
  1045. # an index equal to the value of the element in In and a value equal to 
  1046. # the index of the element in In.
  1047. function Invert(In,Out,  Index) {
  1048.     for (Index in In)
  1049.     Out[In[Index]] = Index
  1050. }
  1051.  
  1052. # Assign: make an array from a list of assignments.
  1053. # An index with the name of each variable in the list is created in the array.
  1054. # Its value is set to the value given for it.
  1055. # Input variables: 
  1056. # Elements is a string containing the list of variable-value pairs.
  1057. # Sep is the string that separates the pairs in the list.
  1058. # AssignOp is the string that separates variables from values.
  1059. # Output variables:
  1060. # Arr is the array.
  1061. # Return value: the number of elements added to the set.
  1062. # Example:
  1063. # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
  1064. function Assign(Arr,Elements,Sep,AssignOp,
  1065. Num,Names,Elem,Assignments,Assignment,i) {
  1066.     Num = split(Elements,Assignments,Sep)
  1067.     for (i = 1; i <= Num; i++) {
  1068.     Assignment = Assignments[i]
  1069.     Ind = index(Assignment,AssignOp)
  1070.     Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1)
  1071.     }
  1072.     return Num
  1073. }
  1074.  
  1075. # Packs Arr[], which should have integer indices starting at or above n, to
  1076. # contiguous integer indices starting with n.
  1077. # If n is not given it defaults to 0.
  1078. # Num should be the number of elements in Arr.
  1079. function PackArr(Arr,Num,n,  NewInd,OldInd) {
  1080.     NewInd = OldInd = n+0
  1081.     for (; Num; Num--) {
  1082.     while (!(OldInd in Arr))
  1083.         OldInd++
  1084.     if (NewInd != OldInd) {
  1085.         Arr[NewInd] = Arr[OldInd]
  1086.         delete Arr[OldInd]
  1087.     }
  1088.     OldInd++
  1089.     NewInd++
  1090.     }
  1091. }
  1092. ### End array routines
  1093. ### Start of ProcArgs library
  1094. # @(#) ProcArgs 1.11 96/12/08
  1095. # 92/02/29 john h. dubois iii (john@armory.com)
  1096. # 93/07/18 Added "#" arg type
  1097. # 93/09/26 Do not count -h against MinArgs
  1098. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  1099. #          Removed meaning of "+" or "-" by itself.
  1100. # 94/03/08 Added & option and *()< option types.
  1101. # 94/04/02 Added NoRCopt to Opts()
  1102. # 94/06/11 Mark numeric variables as such.
  1103. # 94/07/08 Opts(): Do not require any args if h option is given.
  1104. # 95/01/22 Record options given more than once.  Record option num in argv.
  1105. # 95/06/08 Added ExclusiveOptions().
  1106. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  1107. #          Expand $VARNAME at the start of its filenames.
  1108. #          Let varname=0 and -option- turn off an option.
  1109. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  1110. #          of the vars should be searched for in the environment.
  1111. #          Check for duplicate rcfiles.
  1112. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  1113. #          now return various negatives values on error, not just -1, and
  1114. #          Opts() may set Err to various positive values, not just 1.
  1115. #          Added AllowUnrecOpt.
  1116. # 96/05/23 Check type given for & option
  1117. # 96/06/15 Re-port to awk
  1118. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  1119. #          used by other functions.
  1120. # 96/10/15 Added OptChars
  1121. # 96/11/01 Added exOpts arg to Opts()
  1122. # 96/11/16 Added ; type
  1123. # 96/12/08 Added Opt2Set() & Opt2Sets()
  1124. # 96/12/27 Added CmdLineOpt()
  1125.  
  1126. # optlist is a string which contains all of the possible command line options.
  1127. # A character followed by certain characters indicates that the option takes
  1128. # an argument, with type as follows:
  1129. # :    String argument
  1130. # ;    Non-empty string argument
  1131. # *    Floating point argument
  1132. # (    Non-negative floating point argument
  1133. # )    Positive floating point argument
  1134. # #    Integer argument
  1135. # <    Non-negative integer argument
  1136. # >    Positive integer argument
  1137. # The only difference the type of argument makes is in the runtime argument
  1138. # error checking that is done.
  1139.  
  1140. # The & option is a special case used to get numeric options without the
  1141. # user having to give an option character.  It is shorthand for [-+.0-9].
  1142. # If & is included in optlist and an option string that begins with one of
  1143. # these characters is seen, the value given to "&" will include the first
  1144. # char of the option.  & must be followed by a type character other than ":"
  1145. # or ";".
  1146. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  1147.  
  1148. # Strings in argv[] which begin with "-" or "+" are taken to be
  1149. # strings of options, except that a string which consists solely of "-"
  1150. # or "+" is taken to be a non-option string; like other non-option strings,
  1151. # it stops the scanning of argv and is left in argv[].
  1152. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  1153. # If an option takes an argument, the argument may either immediately
  1154. # follow it or be given separately.
  1155. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  1156. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  1157. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  1158. # this feature had a flaw that caused problems in some cases.  See the OptChars
  1159. # parameter to explicitly set the option-specifier characters.
  1160.  
  1161. # If an option that does not take an argument is given,
  1162. # an index with its name is created in Options and its value is set to the
  1163. # number of times it occurs in argv[].
  1164.  
  1165. # If an option that does take an argument is given, an index with its name is
  1166. # created in Options and its value is set to the value of the argument given
  1167. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  1168. # If an option that takes an argument is given more than once,
  1169. # Options[option-name,"count"] is incremented, and the value is assigned to
  1170. # the index (option-name,instance) where instance is 2 for the second occurance
  1171. # of the option, etc.
  1172. # In other words, the first time an option with a value is encountered, the
  1173. # value is assigned to an index consisting only of its name; for any further
  1174. # occurances of the option, the value index has an extra (count) dimension.
  1175.  
  1176. # The sequence number for each option found in argv[] is stored in
  1177. # Options[option-name,"num",instance], where instance is 1 for the first
  1178. # occurance of the option, etc.  The sequence number starts at 1 and is
  1179. # incremented for each option, both those that have a value and those that
  1180. # do not.  Options set from a config file have a value of 0 assigned to this.
  1181.  
  1182. # Options and their arguments are deleted from argv.
  1183. # Note that this means that there may be gaps left in the indices of argv[].
  1184. # If compress is nonzero, argv[] is packed by moving its elements so that
  1185. # they have contiguous integer indices starting with 0.
  1186. # Option processing will stop with the first unrecognized option, just as
  1187. # though -- was given except that unlike -- the unrecognized option will not be
  1188. # removed from ARGV[].  Normally, an error value is returned in this case.
  1189. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  1190. # be found, so the number of remaining arguments is returned instead.
  1191. # If OptChars is not a null string, it is the set of characters that indicate
  1192. # that an argument is an option string if the string begins with one of the
  1193. # characters.  A string consisting solely of two of the same option-indicator
  1194. # characters stops the scanning of argv[].  The default is "-+".
  1195. # argv[0] is not examined.
  1196. # The number of arguments left in argc is returned.
  1197. # If an error occurs, the global string OptErr is set to an error message
  1198. # and a negative value is returned.
  1199. # Current error values:
  1200. # -1: option that required an argument did not get it.
  1201. # -2: argument of incorrect type supplied for an option.
  1202. # -3: unrecognized (invalid) option.
  1203. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  1204. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  1205. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  1206. {
  1207. # ArgNum is the index of the argument being processed.
  1208. # ArgsLeft is the number of arguments left in argv.
  1209. # Arg is the argument being processed.
  1210. # ArgLen is the length of the argument being processed.
  1211. # ArgInd is the position of the character in Arg being processed.
  1212. # Option is the character in Arg being processed.
  1213. # Pos is the position in OptList of the option being processed.
  1214. # NumOpt is true if a numeric option may be given.
  1215.     ArgsLeft = argc
  1216.     NumOpt = index(OptList,"&")
  1217.     OptionNum = 0
  1218.     if (OptChars == "")
  1219.     OptChars = "-+"
  1220.     while (OptChars != "") {
  1221.     c = substr(OptChars,1,1)
  1222.     OptChars = substr(OptChars,2)
  1223.     OptCharSet[c]
  1224.     OptTerm[c c]
  1225.     }
  1226.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  1227.     Arg = argv[ArgNum]
  1228.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  1229.         break    # Not an option; quit
  1230.     if (Arg in OptTerm) {
  1231.         delete argv[ArgNum]
  1232.         ArgsLeft--
  1233.         break
  1234.     }
  1235.     ArgLen = length(Arg)
  1236.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  1237.         Option = substr(Arg,ArgInd,1)
  1238.         if (NumOpt && Option ~ /[-+.0-9]/) {
  1239.         # If this option is a numeric option, make its flag be & and
  1240.         # its option string flag position be the position of & in
  1241.         # the option string.
  1242.         Option = "&"
  1243.         Pos = NumOpt
  1244.         # Prefix Arg with a char so that ArgInd will point to the
  1245.         # first char of the numeric option.
  1246.         Arg = "&" Arg
  1247.         ArgLen++
  1248.         }
  1249.         # Find position of flag in option string, to get its type (if any).
  1250.         # Disallow & as literal flag.
  1251.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  1252.         if (AllowUnrecOpt) {
  1253.             Escape = 1
  1254.             break
  1255.         }
  1256.         else {
  1257.             OptErr = "Invalid option: " specGiven Option
  1258.             return -3
  1259.         }
  1260.         }
  1261.  
  1262.         # Find what the value of the option will be if it takes one.
  1263.         # NeedNextOpt is true if the option specifier is the last char of
  1264.         # this arg, which means that if the option requires a value it is
  1265.         # the next arg.
  1266.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  1267.         if (GotValue = ArgNum + 1 < argc)
  1268.             Value = argv[ArgNum+1]
  1269.         }
  1270.         else {    # Value is included with option
  1271.         Value = substr(Arg,ArgInd + 1)
  1272.         GotValue = 1
  1273.         }
  1274.  
  1275.         if (HadValue = AssignVal(Option,Value,Options,
  1276.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  1277.         specGiven)) {
  1278.         if (HadValue < 0)    # error occured
  1279.             return HadValue
  1280.         if (HadValue == 2)
  1281.             ArgInd++    # Account for the single-char value we used.
  1282.         else {
  1283.             if (NeedNextOpt) {    # option took next arg as value
  1284.             delete argv[++ArgNum]
  1285.             ArgsLeft--
  1286.             }
  1287.             break    # This option has been used up
  1288.         }
  1289.         }
  1290.     }
  1291.     if (Escape)
  1292.         break
  1293.     # Do not delete arg until after processing of it, so that if it is not
  1294.     # recognized it can be left in ARGV[].
  1295.     delete argv[ArgNum]
  1296.     ArgsLeft--
  1297.     }
  1298.     if (compress != 0) {
  1299.     dest = 1
  1300.     src = argc - ArgsLeft + 1
  1301.     for (count = ArgsLeft - 1; count; count--) {
  1302.         ARGV[dest] = ARGV[src]
  1303.         dest++
  1304.         src++
  1305.     }
  1306.     }
  1307.     return ArgsLeft
  1308. }
  1309.  
  1310. # Assignment to values in Options[] occurs only in this function.
  1311. # Option: Option specifier character.
  1312. # Value: Value to be assigned to option, if it takes a value.
  1313. # Options[]: Options array to return values in.
  1314. # ArgType: Argument type specifier character.
  1315. # GotValue: Whether any value is available to be assigned to this option.
  1316. # Name: Name of option being processed.
  1317. # OptionNum: Number of this option (starting with 1) if set in argv[],
  1318. #     or 0 if it was given in a config file or in the environment.
  1319. # SingleOpt: true if the value (if any) that is available for this option was
  1320. #     given as part of the same command line arg as the option.  Used only for
  1321. #     options from the command line.
  1322. # specGiven is the option specifier character use, if any (e.g. - or +),
  1323. # for use in error messages.
  1324. # Global variables: OptErr
  1325. # Return value: negative value on error, 0 if option did not require an
  1326. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  1327. # the arg.
  1328. # Current error values:
  1329. # -1: Option that required an argument did not get it.
  1330. # -2: Value of incorrect type supplied for option.
  1331. # -3: Bad type given for option &
  1332. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  1333. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  1334.     # If option takes a value...    [
  1335.     NumTypes = "*()#<>]"
  1336.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  1337.     OptErr = "Bad type given for & option"
  1338.     return -3
  1339.     }
  1340.  
  1341.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  1342.     if (!GotValue) {
  1343.         if (Name != "")
  1344.         OptErr = "Variable requires a value -- " Name
  1345.         else
  1346.         OptErr = "option requires an argument -- " Option
  1347.         return -1
  1348.     }
  1349.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  1350.         OptErr = Err
  1351.         return -2
  1352.     }
  1353.     # Mark this as a numeric variable; will be propogated to Options[] val.
  1354.     if (ArgType != ":" && ArgType != ";")
  1355.         Value += 0
  1356.     if ((Instance = ++Options[Option,"count"]) > 1)
  1357.         Options[Option,Instance] = Value
  1358.     else
  1359.         Options[Option] = Value
  1360.     }
  1361.     # If this is an environ or rcfile assignment & it was given a value...
  1362.     else if (!OptionNum && Value != "") {
  1363.     UsedValue = 1
  1364.     # If the value is "0" or "-" and this is the first instance of it,
  1365.     # do not set Options[Option]; this allows an assignment in an rcfile to
  1366.     # turn off an option (for the simple "Option in Options" test) in such
  1367.     # a way that it cannot be turned on in a later file.
  1368.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  1369.         Instance = 1
  1370.     else
  1371.         Instance = ++Options[Option]
  1372.     # Save the value even though this is a flag
  1373.     Options[Option,Instance] = Value
  1374.     }
  1375.     # If this is a command line flag and has a - following it in the same arg,
  1376.     # it is being turned off.
  1377.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  1378.     UsedValue = 2
  1379.     if (Option in Options)
  1380.         Instance = ++Options[Option]
  1381.     else
  1382.         Instance = 1
  1383.     Options[Option,Instance]
  1384.     }
  1385.     # If this is a flag assignment without a value, increment the count for the
  1386.     # flag unless it was turned off.  The indicator for a flag being turned off
  1387.     # is that the flag index has not been set in Options[] but it has an
  1388.     # instance count.
  1389.     else if (Option in Options || !((Option,1) in Options))
  1390.     # Increment number of times this flag seen; will inc null value to 1
  1391.     Instance = ++Options[Option]
  1392.     Options[Option,"num",Instance] = OptionNum
  1393.     return UsedValue
  1394. }
  1395.  
  1396. # Option is the option letter
  1397. # Value is the value being assigned
  1398. # Name is the var name of the option, if any
  1399. # ArgType is one of:
  1400. # :    String argument
  1401. # ;    Non-null string argument
  1402. # *    Floating point argument
  1403. # (    Non-negative floating point argument
  1404. # )    Positive floating point argument
  1405. # #    Integer argument
  1406. # <    Non-negative integer argument
  1407. # >    Positive integer argument
  1408. # specGiven is the option specifier character use, if any (e.g. - or +),
  1409. # for use in error messages.
  1410. # Returns null on success, err string on error
  1411. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1412.     if (ArgType == ":")
  1413.     return ""
  1414.     if (ArgType == ";") {
  1415.     if (Value == "")
  1416.         Err = "must be a non-empty string"
  1417.     }
  1418.     # A number begins with optional + or -, and is followed by a string of
  1419.     # digits or a decimal with digits before it, after it, or both
  1420.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1421.     Err = "must be a number"
  1422.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1423.     Err = "may not include a fraction"
  1424.     else if (ArgType ~ "[()<>]" && Value < 0)
  1425.     Err = "may not be negative"
  1426.     # (
  1427.     else if (ArgType ~ "[)>]" && Value == 0)
  1428.     Err = "must be a positive number"
  1429.     if (Err != "") {
  1430.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1431.     if (Name != "")
  1432.         return ErrStr "variable " substr(Name,1,1) " " Err
  1433.     else {
  1434.         if (Option == "&")
  1435.         Option = Value
  1436.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1437.     }
  1438.     }
  1439.     else
  1440.     return ""
  1441. }
  1442.  
  1443. # Note: only the above functions are needed by ProcArgs.
  1444. # The rest of these functions call ProcArgs() and also do other
  1445. # option-processing stuff.
  1446.  
  1447. # Opts: Process command line arguments.
  1448. # Opts processes command line arguments using ProcArgs()
  1449. # and checks for errors.  If an error occurs, a message is printed
  1450. # and the program is exited.
  1451. #
  1452. # Input variables:
  1453. # Name is the name of the program, for error messages.
  1454. # Usage is a usage message, for error messages.
  1455. # OptList the option description string, as used by ProcArgs().
  1456. # MinArgs is the minimum number of non-option arguments that this
  1457. # program should have, non including ARGV[0] and +h.
  1458. # If the program does not require any non-option arguments,
  1459. # MinArgs should be omitted or given as 0.
  1460. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1461. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1462. # by the value of the environment variable HOME.  If a filename begins with
  1463. # $, the part from the character after the $ up until (but not including)
  1464. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1465. # environment; if found its value will be substituted, if not the filename will
  1466. # be discarded.
  1467. # rcfiles are read in the order given.
  1468. # Values given in them will not override values given on the command line,
  1469. # and values given in later files will not override those set in earlier
  1470. # files, because AssignVal() will store each with a different instance index.
  1471. # The first instance of each variable, either on the command line or in an
  1472. # rcfile, will be stored with no instance index, and this is the value
  1473. # normally used by programs that call this function.
  1474. # VarNames is a comma-separated list of variable names to map to options,
  1475. # in the same order as the options are given in OptList.
  1476. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1477. # searched for in the environment.  If set to -1, all values will be searched
  1478. # for in the environment.  Values given in the environment will override
  1479. # those given in the rcfiles but not those given on the command line.
  1480. # NoRCopt, if given, is an additional letter option that if given on the
  1481. # command line prevents the rcfiles from being read.
  1482. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1483. # ExclusiveOptions() for a description of exOpts.
  1484. # Special options:
  1485. # If x is made an option and is given, some debugging info is output.
  1486. # h is assumed to be the help option.
  1487.  
  1488. # Global variables:
  1489. # The command line arguments are taken from ARGV[].
  1490. # The arguments that are option specifiers and values are removed from
  1491. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1492. # The number of elements in ARGV[] should be in ARGC.
  1493. # After processing, ARGC is set to the number of elements left in ARGV[].
  1494. # The option values are put in Options[].
  1495. # On error, Err is set to a positive integer value so it can be checked for in
  1496. # an END block.
  1497. # Return value: The number of elements left in ARGV is returned.
  1498. # Must keep OptErr global since it may be set by InitOpts().
  1499. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1500. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1501.     if (MinArgs == "")
  1502.     MinArgs = 0
  1503.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1504.     optChars)
  1505.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1506.     if (ArgsLeft >= 0) {
  1507.         OptErr = "Not enough arguments"
  1508.         Err = 4
  1509.     }
  1510.     else
  1511.         Err = -ArgsLeft
  1512.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1513.     Name,OptErr,Usage > "/dev/stderr"
  1514.     exit 1
  1515.     }
  1516.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1517.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1518.     {
  1519.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1520.     Err = -e
  1521.     exit 1
  1522.     }
  1523.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1524.     {
  1525.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1526.     Err = 1
  1527.     exit 1
  1528.     }
  1529.     return ArgsLeft
  1530. }
  1531.  
  1532. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1533. # <variable-name><assignment-char><value>.
  1534. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1535. # line and whitespace between the variable name and the assignment character) 
  1536. # is stripped.  Lines that do not contain an assignment operator or which
  1537. # contain a null variable name are ignored, other than possibly being noted in
  1538. # the return value.  If more than one assignment is made to a variable, the
  1539. # first assignment is used.
  1540. # Input variables:
  1541. # File is the file to read.
  1542. # Comment is the line-comment character.  If it is found as the first non-
  1543. #     whitespace character on a line, the line is ignored.
  1544. # Assign is the assignment string.  The first instance of Assign on a line
  1545. #     separates the variable name from its value.
  1546. # If StripWhite is true, whitespace around the value (whitespace between the
  1547. #     assignment char and trailing whitespace on the line) is stripped.
  1548. # VarPat is a pattern that variable names must match.  
  1549. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1550. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1551. #     a line; no assignment operator is needed.  These variables are set in
  1552. #     the output array with a null value.  Lines containing nothing but
  1553. #     whitespace are still ignored.
  1554. # Output variables:
  1555. # Values[] contains the assignments, with the indexes being the variable names
  1556. #     and the values being the assigned values.
  1557. # Lines[] contains the line number that each variable occured on.  A flag set
  1558. #     is record by giving it an index in Lines[] but not in Values[].
  1559. # Return value:
  1560. # If any errors occur, a string consisting of descriptions of the errors
  1561. # separated by newlines is returned.  In no case will the string start with a
  1562. # numeric value.  If no errors occur,  the number of lines read is returned.
  1563. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1564. FlagsOK,
  1565. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1566.     if (Comment != "")
  1567.     Comment = "^" Comment
  1568.     AssignLen = length(Assign)
  1569.     if (VarPat == "")
  1570.     VarPat = "."    # null varname not allowed
  1571.     while ((Status = (getline Line < File)) == 1) {
  1572.     LineNum++
  1573.     sub("^[ \t]+","",Line)
  1574.     if (Line == "")        # blank line
  1575.         continue
  1576.     if (Comment != "" && Line ~ Comment)
  1577.         continue
  1578.     if (Pos = index(Line,Assign)) {
  1579.         Var = substr(Line,1,Pos-1)
  1580.         Val = substr(Line,Pos+AssignLen)
  1581.         if (StripWhite) {
  1582.         sub("^[ \t]+","",Val)
  1583.         sub("[ \t]+$","",Val)
  1584.         }
  1585.     }
  1586.     else {
  1587.         Var = Line    # If no value, var is entire line
  1588.         Val = ""
  1589.     }
  1590.     if (!FlagsOK && Val == "") {
  1591.         Errs = Errs \
  1592.         sprintf("\nBad assignment on line %d of file %s: %s",
  1593.         LineNum,File,Line)
  1594.         continue
  1595.     }
  1596.     sub("[ \t]+$","",Var)
  1597.     if (Var !~ VarPat) {
  1598.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1599.         LineNum,File,Var)
  1600.         continue
  1601.     }
  1602.     if (!(Var in Lines)) {
  1603.         Lines[Var] = LineNum
  1604.         if (Pos)
  1605.         Values[Var] = Val
  1606.     }
  1607.     }
  1608.     if (Status)
  1609.     Errs = Errs "\nCould not read file " File
  1610.     close(File)
  1611.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1612. }
  1613.  
  1614. # Variables:
  1615. # Data is stored in Options[].
  1616. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1617. # Global vars:
  1618. # Sets OptErr.  Uses ENVIRON[].
  1619. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1620. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1621. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1622. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1623.     split("",filesRead,"")    # make awk know this is an array
  1624.     NumVars = split(VarNames,Vars,",")
  1625.     TypesInd = Ret = 0
  1626.     if (EnvSearch == -1)
  1627.     EnvSearch = NumVars
  1628.     for (i = 1; i <= NumVars; i++) {
  1629.     Var = Vars[i]
  1630.     CharOpt = substr(OptList,++TypesInd,1)
  1631.     if (CharOpt ~ "^[:;*()#<>&]$")
  1632.         CharOpt = substr(OptList,++TypesInd,1)
  1633.     Map[Var] = CharOpt
  1634.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1635.     # Do not overwrite entries from environment
  1636.     if (i <= EnvSearch && Var in ENVIRON &&
  1637.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1638.         return Err
  1639.     }
  1640.  
  1641.     numrcFiles = split(rcFiles,fNames,":")
  1642.     for (i = 1; i <= numrcFiles; i++) {
  1643.     rcFile = fNames[i]
  1644.     if (rcFile ~ "^~/")
  1645.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1646.     else if (rcFile ~ /^\$/) {
  1647.         rcFile = substr(rcFile,2)
  1648.         match(rcFile,"^[a-zA-Z0-9_]*")
  1649.         envvar = substr(rcFile,1,RLENGTH)
  1650.         if (envvar in ENVIRON)
  1651.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1652.         else
  1653.         continue
  1654.     }
  1655.     if (rcFile in filesRead)
  1656.         continue
  1657.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1658.     # may be the same
  1659.     filesRead[rcFile]
  1660.     if ("x" in Options)
  1661.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1662.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1663.     if (retStr > 0)
  1664.         READ_RCFILE = 1
  1665.     else if (ret != "") {
  1666.         OptErr = retStr
  1667.         Ret = -1
  1668.     }
  1669.     for (Var in Lines)
  1670.         if (Var in Map) {
  1671.         if ((Err = AssignVal(Map[Var],
  1672.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1673.         Var in Values,Var,0)) < 0)
  1674.             return Err
  1675.         }
  1676.         else {
  1677.         OptErr = sprintf(\
  1678.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1679.         Lines[Var],rcFile)
  1680.         Ret = -1
  1681.         }
  1682.     }
  1683.  
  1684.     if ("x" in Options)
  1685.     for (Var in Map)
  1686.         if (Map[Var] in Options)
  1687.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1688.         "/dev/stderr"
  1689.         else
  1690.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1691.     return Ret
  1692. }
  1693.  
  1694. # OptSets is a semicolon-separated list of sets of option sets.
  1695. # Within a list of option sets, the option sets are separated by commas.  For
  1696. # each set of sets, if any option in one of the sets is in Options[] AND any
  1697. # option in one of the other sets is in Options[], an error string is returned.
  1698. # If no conflicts are found, nothing is returned.
  1699. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1700. # the exclusions presented by the first set of sets (ab,def,g) if:
  1701. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1702. # (a or b is in Options[]) AND (g is in Options) OR
  1703. # (d, e, or f is in Options[]) AND (g is in Options)
  1704. # An error will be returned due to the exclusions presented by the second set
  1705. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1706. # todo: make options given on command line unset options given in config file
  1707. # todo: that they conflict with.
  1708. function ExclusiveOptions(OptSets,Options,
  1709. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1710. SetNum,OSetNum) {
  1711.     NumSetSets = split(OptSets,SetSets,";")
  1712.     # For each set of sets...
  1713.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1714.     # NumSets is the number of sets in this set of sets.
  1715.     NumSets = split(SetSets[SetSet],Sets,",")
  1716.     # For each set in a set of sets except the last...
  1717.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1718.         s1 = Sets[SetNum]
  1719.         L1 = length(s1)
  1720.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1721.         # If any of the options in this set was given, check whether
  1722.         # any of the options in the other sets was given.  Only check
  1723.         # later sets since earlier sets will have already been checked
  1724.         # against this set.
  1725.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1726.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1727.             s2 = Sets[OSetNum]
  1728.             L2 = length(s2)
  1729.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1730.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1731.                 ErrStr = ErrStr "\n"\
  1732.                 sprintf("Cannot give both %s and %s options.",
  1733.                 c1,c2)
  1734.             }
  1735.     }
  1736.     }
  1737.     if (ErrStr != "")
  1738.     return substr(ErrStr,2)
  1739.     return ""
  1740. }
  1741.  
  1742. # The value of each instance of option Opt that occurs in Options[] is made an
  1743. # index of Set[].
  1744. # The return value is the number of instances of Opt in Options.
  1745. function Opt2Set(Options,Opt,Set,  count) {
  1746.     if (!(Opt in Options))
  1747.     return 0
  1748.     Set[Options[Opt]]
  1749.     count = Options[Opt,"count"]
  1750.     for (; count > 1; count--)
  1751.     Set[Options[Opt,count]]
  1752.     return count
  1753. }
  1754.  
  1755. # The value of each instance of option Opt that occurs in Options[] that
  1756. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1757. # Other values are made indexes of Set[].
  1758. # The return value is the number of instances of Opt in Options.
  1759. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1760.     ret = Opt2Set(Options,Opt,aSet)
  1761.     for (value in aSet)
  1762.     if (substr(value,1,1) == "!")
  1763.         nSet[substr(value,2)]
  1764.     else
  1765.         Set[value]
  1766.     return ret
  1767. }
  1768.  
  1769. # Returns true if option Opt was given on the command line.
  1770. function CmdLineOpt(Options,Opt,  i) {
  1771.     for (i = 1; (Opt,"num",i) in Options; i++)
  1772.     if (Options[Opt,"num",i] != 0)
  1773.         return 1
  1774.     return 0
  1775. }
  1776. ### End of ProcArgs library
  1777.  
  1778. function hms2sec(time,  n,Elem) {
  1779.     n = split(time,Elem,":")
  1780.     if (n == 1)
  1781.     return Elem[1]
  1782.     else if (n == 2)
  1783.     return Elem[1]*60 + Elem[2]
  1784.     else if (n == 3)
  1785.     return Elem[1]*3600 + Elem[2]*60 + Elem[3]
  1786. }
  1787. ### start of dolsof
  1788. # @(#) dolsof 1.0 97/03/05
  1789. # 96/06/03 john dubois (john@armory.com) 
  1790. # 96/10/01 Use NAMELIST
  1791. # 97/03/05 Added w to lsof standard args.
  1792. # dolsof: run lsof and return output.
  1793. # Fields is a string giving the single-character codes of the fields to record.
  1794. # From the lsof man page:
  1795. # * a access mode
  1796. #   c process command name
  1797. # * d device character code
  1798. # * D major/minor device number (0x<hexadecimal>)
  1799. # * f file descriptor
  1800. # * i inode number
  1801. # * l lock status
  1802. #   L process login name
  1803. # * n file name, comment, Internet address
  1804. # * o offset (0t<decimal> or 0x<hexadecimal>)
  1805. #   p process ID (always selected)
  1806. #   g process group ID
  1807. # * P protocol name
  1808. # * s size
  1809. # * S stream identification
  1810. # * t type
  1811. #   u process user ID
  1812. # Args is the string of arguments to pass to lsof, generally a list of files.
  1813. # If Dir is passed as non-null, it is a directory to cd to before running lsof.
  1814. # If Debug is true, the constructed lsof command line is written to /dev/stderr
  1815. # The output is stored in Out[], indexed by pid,field-letter or for those
  1816. # fields marked with *, pid,field-letter,i where i is an index number starting
  1817. # with 1.  The number of entries (max value for i) for each process is stored
  1818. # in the special index consisting solely of the pid.
  1819. # PIDs[] is made the set of all PIDs seen in the output.
  1820. # Globals: 
  1821. # If PATH is set, it is used to find lsof.
  1822. # If NAMELIST is set, it is passed as the symbol table file to lsof.
  1823. function dolsof(Fields,Args,Out,PIDs,Dir,Debug,
  1824. Cmd,ret,field,value,oIC,Path,i) {
  1825.     oIC = IGNORECASE
  1826.     IGNORECASE = 0    # this must be global
  1827.     if (Dir != "")
  1828.     Cmd = "cd '" Dir "' && "
  1829.     if (PATH != "")
  1830.     Path = "PATH=\"" PATH "\";export PATH;"
  1831.     if (NAMELIST != "")
  1832.     Args = "-k " NAMELIST " " Args
  1833.     Cmd = Path Cmd "exec lsof -wF " Fields " " Args
  1834.     if (Debug)
  1835.     print "lsof command line: " Cmd > "/dev/stderr"
  1836.     while ((ret = (Cmd | getline)) == 1) {
  1837.     field = substr($0,1,1)
  1838.     value = substr($0,2)
  1839.     if (field == "p") {
  1840.         PIDs[pid = value]
  1841.         i = ++Out[pid]
  1842.     }
  1843.     else if (index("adDfilnoPsSt",field))
  1844.         Out[pid,field,i] = value
  1845.     else
  1846.         Out[pid,field] = value
  1847.     }
  1848.     close(Cmd)
  1849.     IGNORECASE = oIC
  1850.     return ret
  1851. }
  1852. ### end of dolsof
  1853.