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

  1. #!/usr/local/bin/gawk -f
  2. # @(#) mlast.gawk 1.5 97/07/11
  3. # 93/03/14 john h. dubois iii (john@armory.com)
  4. # 93/12/03 Process last -w /etc/utmp first so that last does not have to
  5. #          search through huge wtmp for users who have been logged in a
  6. #          long time.  Sort output to print most recent first.
  7. #          Added command line options.
  8. # 93/12/19 Use gawk for strftime() and /dev/stderr
  9. #          Removed uninteresting fields from last output & added age field
  10. # 93/12/31 Added x option
  11. # 94/01/22 Fixed sorting
  12. # 94/03/13 Exit 0 from command for gawk.  Close all files.
  13. # 94/04/25 Added l and o options, header
  14. # 94/05/10 Added Old field
  15. # 94/06/06 Added L option
  16. # 95/05/21 Allow option defaults to be given in .mlast
  17. # 96/01/08 Make all debugging outpout go to /dev/stderr.
  18. #          Changed meaning of t option.  Added -number option.
  19. #          Fixed endless loop if read from last fails.
  20. # 96/01/12 Subtract a minute from 'old'.
  21. # 96/01/13 Added i option.
  22. # 96/01/21 Also read $UHOME/.mlast and /etc/default/mlast
  23. # 96/07/17 Added es options.
  24. # 96/12/28 Added r option.
  25. # 97/02/26 Let TTY names be given on command line w/o option letter.
  26. # 97/07/11 1.5 Added gds options.
  27.  
  28. BEGIN {
  29.     Name = "mlast"
  30.     Usage = "Usage: " Name " [-dehknorstTx] [-<number>] [-i<login,...>]\n"\
  31.     "            [-L<tty,...>] [-g<max-time-gap>] [tty ...]"
  32.     rcFile = ".mlast"
  33.     reportInterval = 100
  34.     ARGC = Opts(Name,Usage,"g>sdnl:L:oti:r&>eThxk",0,
  35.     "~/" rcFile ":$UHOME/" rcFile ":/etc/default/mlast","MAXTIMEGAP,SORTED,"\
  36.     "SHOWDAY,NOSORT,TTYS,IGNORETTYS,NOHEADER,TTYSORT,IGNORENAMES,RUNNING",0,"k")
  37.     if ("h" in Options) {
  38.     printf \
  39. "%s: show last logins on enabled modem lines.\n"\
  40. "For each enabled modem line, a line is printed listing, for that TTY: the\n"\
  41. "last user to log in, the date of the last login, the duration of the last\n"\
  42. "login, comments (e.g. whether a user is currently logged in on it), the\n"\
  43. "amount of time that has passed since the last login (Age), and the amount\n"\
  44. "of time that has passed since the end of the last login, if there is\n"\
  45. "currently noone logged in on it (Old).  Lines are sorted by Age, with the\n"\
  46. "oldest lines printed first.  If any TTY names are given on the command\n"\
  47. "line, they are reported on instead of the set of enabled TTYs.  TTY names\n"\
  48. "may be given with or without a leading \"/dev/\", with or without a\n"\
  49. "leading \"tty\", and as either the modem- or non-modem-control name.\n"\
  50. "Options:\n"\
  51. "Some of the following options can also be set by assigning values to\n"\
  52. "variables in a configuration file.  Three configuration files are read,\n"\
  53. "in order: a file named %s in the invoking user's home directory; a file\n"\
  54. "named %s in the directory specified by the environment variable UHOME\n"\
  55. "(if it is set); and the file /etc/default/mlast.  Variables are assigned\n"\
  56. "to with the syntax:  varname=value  or in the case of flags, by simply\n"\
  57. "putting the indicated variable name in the file without a value.\n"\
  58. "A variable assigned to in one of these files will override values assigned\n"\
  59. "to the same variable in one of the files read after it.  To turn off an\n"\
  60. "option and prevent it from being set in a file read later, assign it a\n"\
  61. "value of 0.  e.g. if TTYSORT is set in /etc/default/mlast, TTYSORT=0 in\n"\
  62. "a %s file will override it.  Flag options can be turned off on the\n"\
  63. "command line by following them immediately with '-', e.g. -n- to turn off\n"\
  64. "the n option in such a way that it cannot be turned on in a config file.\n"\
  65. "-e: List enabled modem TTYs, then exit.\n"\
  66. "-T: Print the names of the TTYs that would be searched for, then exit.\n"\
  67. "-h: Print this help.\n"\
  68. "-k: Do not read the configuration files.\n"\
  69. "-<number>: Process only the last <number> login records in the wtmp file.\n"\
  70. "-n: Show logins as they are found, with no sorting.  This lets output be\n"\
  71. "    seen without waiting a long time for %s to find the wtmp entry\n"\
  72. "    for a modem line that has not been logged into for a long time.\n"\
  73. "    In the configuration file, put NOSORT.\n"\
  74. "-r: Like -n, but at intervals also reports what lines are still being\n"\
  75. "    searched for to the standard error output.  The report is printed when\n"\
  76. "    %d records have been read since the last line was reported on.\n"\
  77. "-s: Print sorted login records after they have all been found.  This is the\n"\
  78. "    default, so this option is only used with -n or -r, which causes the\n"\
  79. "    login records to be printed as they are found, then then printed again\n"\
  80. "    in sorted order after they have all been found.  (SORTED)\n"\
  81. "-d: Each time a new day is encountered in the last-login records, print it\n"\
  82. "    to show how far back login searching has reached.  (SHOWDAY)\n"\
  83. "-t: Sort by TTY name.   In the configuration file, put TTYSORT.\n"\
  84. "-l<TTY,..>: Search for last logins on the TTYs given in the comma- or\n"\
  85. "    space-separated list.  This is another way of specifying which TTYs\n"\
  86. "    to report on; the alternative is to simply give them as separate\n"\
  87. "    arguments on the command line, without an option letter.  In the\n"\
  88. "    configuration file, assign a value to TTYS, e.g. TTYS=1a,2a\n"\
  89. "-L<TTY,..>: Do not report on the named TTYs even if they are enabled.\n"\
  90. "    In the configuration file, assign a value to IGNORETTYS, e.g.\n"\
  91. "    IGNORETTYS=tty1f,tty1g\n"\
  92. "-i<login,...>: When searching for the last login on a TTY, ignore the\n"\
  93. "    login names given in the comma-separate list.  Variable: IGNORENAMES.\n"\
  94. "-o: Do not print header.  In the configuration file, put NOHEADER.\n"\
  95. "-g<max-time-gap>: Do rudimentary verification of wtmp records by skipping\n"\
  96. "    records that have timestamps more than <max-time-gap> days different\n"\
  97. "    than the last (non-skipped) record processed from the same file. \n"\
  98. "    (MAXTIMEGAP)\n"\
  99. "-x: Print debugging info.\n",
  100.     Name,rcFile,rcFile,rcFile,Name,reportInterval
  101.     exit 0
  102.     }
  103.     Year = strftime("%Y")
  104.     MkMonth2Num()
  105.     Running = "r" in Options
  106.     Unsorted = "n" in Options || Running
  107.     Sorted = !Unsorted || "s" in Options
  108.     ttySort = "t" in Options
  109.     Debug = "x" in Options
  110.     ShowDay = "d" in Options
  111.     doHeader = !("o" in Options)
  112.     if ("g" in Options)
  113.     maxTimeGap = Options["g"] * 86400
  114.     if ("e" in Options) {
  115.     NumGettys = FindMGettys(GettyLines)
  116.     qsortByArbIndex(GettyLines,k)
  117.     for (i = 1; i <= NumGettys; i++)
  118.         print k[i]
  119.     exit 0
  120.     }
  121.     if ("&" in Options)
  122.     NumLines = " -n " Options["&"]
  123.     CurTime = systime()
  124.     if (Debug)
  125.     printf "Current time: %d\n",CurTime > "/dev/stderr"
  126.     if ("i" in Options)
  127.     MakeSet(IgnoreLogins,Options["i"],"[, ]+")
  128.     if (ARGC > 1) {
  129.     for (i = 1; i < ARGC; i++)
  130.         if (!(ARGV[i] in GettyLines)) {
  131.         GettyLines[ARGV[i]]
  132.         NumGettys++
  133.         }
  134.     makeTTYmap(GettyLines,ttyMap)
  135.     }
  136.     else if ("l" in Options) {
  137.     NumGettys = MakeSet(GettyLines,Options["l"],"[, ]+")
  138.     makeTTYmap(GettyLines,ttyMap)
  139.     }
  140.     else {
  141.     NumGettys = FindMGettys(GettyLines)
  142.     if ("L" in Options) {
  143.         MakeSet(NonGettyLines,Options["L"],"[, ]+")
  144.         for (tty in NonGettyLines)
  145.         if (tty in GettyLines) {
  146.             delete GettyLines[tty]
  147.             NumGettys--
  148.         }
  149.     }
  150.     for (tty in GettyLines)
  151.         ttyMap[tty] = tty
  152.     }
  153.     if ("T" in Options) {
  154.     NumGettys = qsortByArbIndex(GettyLines,k)
  155.     for (i = 1; i <= NumGettys; i++)
  156.         print k[i]
  157.     exit 0
  158.     }
  159.     if (Debug) {
  160.     printf "Searching for:" > "/dev/stderr"
  161.     for (Line in GettyLines)
  162.         printf " %s",Line > "/dev/stderr"
  163.     print "" > "/dev/stderr"
  164.     }
  165.     Format = "%-9s %-8s %16s %-21s %11s %9s"
  166.     if (doHeader) {
  167.     Header = sprintf(Format,"User","TTY","Login date","Dur","Age","Old")
  168.     # If sorting, hold off on printing header so it doesn't come before
  169.     # debugging and such that we might be printing
  170.     if (Unsorted)
  171.         print Header
  172.     }
  173.     split("/usr/bin/last -w /etc/utmp:/usr/bin/last" NumLines,Cmds,":")
  174.     for (i = 1; i in Cmds; i++)
  175.     if (!(NumGettys = \
  176.     ProcLast(Cmds[i],GettyLines,NumGettys,Last,Ages,Format,IgnoreLogins,
  177.     ShowDay,(i > 1) ? maxTimeGap : 0)))
  178.         break
  179.     if (Sorted) {
  180.     if (ttySort) {
  181.         if (Debug) {
  182.         printf "Sorting tty names:" > "/dev/stderr"
  183.         for (i in Ages)
  184.             printf " %s",i > "/dev/stderr"
  185.         print "" > "/dev/stderr"
  186.         }
  187.         qsortByArbIndex(Ages,k)
  188.     }
  189.     else {
  190.         if (Debug) {
  191.         printf "Sorting ages:" > "/dev/stderr"
  192.         for (i in Ages)
  193.             printf " %s",Ages[i] > "/dev/stderr"
  194.         print "" > "/dev/stderr"
  195.         }
  196.         qsortArbIndByValue(Ages,k)
  197.     }
  198.     if (Debug)
  199.         print "Done sorting." > "/dev/stderr"
  200.     print Header
  201.     for (i = 1; i in k; i++)
  202.         print Last[k[i]] 
  203.     }
  204. }
  205.  
  206. function makeTTYmap(GettyLines,ttyMap,  names) {
  207.     for (tty in GettyLines) {
  208.     ttyMap[tty] = tty
  209.     bothTTYnames(tty,names)
  210.     ttyMap[names["upper"]] = tty
  211.     ttyMap[names["lower"]] = tty
  212.     }
  213. }
  214.  
  215. # Last produces output like this:
  216. #User     Line  Device       PID    Login time       Elapsed Time Comments
  217. #rstevew  3C    tty3C        26585  Sun Mar 14 04:05      00:21   logged in
  218. #   1      2      3            4     5   6   7   8          9       10 ...
  219. # Globals: Count, LastDay, PrintedDay
  220.  
  221. function ProcLast(Cmd,GettyLines,NumGettys,Last,Ages,Format,IgnoreLogins,
  222. ShowDay,maxTimeGap,
  223. Device,Month,DayOfMonth,TimeElem,Fields,Line,Age,LoginTime,Old,User,
  224. TimeSinceLogin,Day,lastRec) {
  225.     # Exit 0 for gawk
  226.     # Timezone is wiped out so last will print in GMT, to avoid having to
  227.     # figure daylight savings when comparing last time to current time.
  228.     Cmd = "TZ= " Cmd "; exit 0"
  229.     if (Debug)
  230.     print "Command is: " Cmd > "/dev/stderr"
  231.     while ((Cmd | getline) == 1) {
  232.     if (Running && ++reportCount == reportInterval)
  233.         lineReport(NumGettys,GettyLines)
  234.     if (Debug && ++Count == 100) {
  235.         Total += Count
  236.         Count = 0
  237.         if (PrintedDay) {
  238.         PrintedDay = 0
  239.         print "" > "/dev/stderr"
  240.         }
  241.         printf "%d records read (now at %s %s %s %s).\n",
  242.         Total,$5,$6,$7,$8 > "/dev/stderr"
  243.     }
  244.     if ($1 == "User")
  245.         continue
  246.     if (ShowDay) {
  247.         Day = $6 "-" $7
  248.         if (Day != LastDay) {
  249.         if (!PrintedDay) {
  250.             printf "Now at" > "/dev/stderr"
  251.             PrintedDay = 1
  252.         }
  253.         printf " %s",Day > "/dev/stderr"
  254.         LastDay = Day
  255.         }
  256.     }
  257.     User = $1
  258.     Device = $3
  259.     if (!(Device in ttyMap) || User in IgnoreLogins) {
  260.         if (maxTimeGap)
  261.         lastRec = $0
  262.         continue
  263.     }
  264.  
  265.     Month = $6
  266.     DayOfMonth = $7
  267.     Ages[Device] = LoginTime = lastTime2unixtime(Year,Month,DayOfMonth,$8)
  268.  
  269.     if (maxTimeGap) {
  270.         if (lastRec != "" && !checkGap(maxTimeGap,lastRec,LoginTime,Year)) {
  271.         lastRec = $0
  272.         continue
  273.         }
  274.         lastRec = $0
  275.     }
  276.  
  277.     TimeSinceLogin = CurTime - LoginTime
  278.  
  279.     # Split login duration into hours & minutes
  280.     split($9,TimeElem,":")
  281.     # Calculate time since end of last login
  282.     # Due to the way last calculates login duration, old may end up a
  283.     # minute longer than it actually is, so subtract 60 seconds.
  284.     Old = TimeSinceLogin - (TimeElem[1]*3600 + TimeElem[2]*60) - 60
  285.  
  286.     # Get Comments field by discarding User, Line, Device, PID, and date
  287.     match($0,"^[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +[^ ]+ +")
  288.     Fields = substr($0,RLENGTH + 1)
  289.  
  290.     Age = sec2dhm(TimeSinceLogin)
  291.     if (Old+0 > 60)
  292.         Old = sec2dhm(Old)
  293.     else
  294.         Old = ""
  295.     # Because last login time is given for GMT, must recreate it
  296.     # with timezone taken into consideration
  297.     #                     user tty    login date
  298.     Line = sprintf(Format,User,Device,strftime("%a %b %d %H:%M",LoginTime),
  299.     Fields,Age,Old)
  300.     if (Debug) {
  301.         if (PrintedDay) {
  302.         PrintedDay = 0
  303.         print "" > "/dev/stderr"
  304.         }
  305.         printf "Found record for %s in output of \"%s\":\n%s\n",
  306.         Device,Cmd,$0 > "/dev/stderr"
  307.         printf "Date: %d (%s), age: %d (%s)\n",LoginTime,
  308.         strftime("%c",LoginTime),TimeSinceLogin,Age > "/dev/stderr"
  309.     }
  310.  
  311.     if (Unsorted) {
  312.         if (PrintedDay) {
  313.         PrintedDay = 0
  314.         print "" > "/dev/stderr"
  315.         }
  316.         print Line
  317.     }
  318.     Last[Device] = Line
  319.     delete GettyLines[ttyMap[Device]]
  320.     delete ttyMap[Device]
  321.     reportCount = 0
  322.     if (!--NumGettys)
  323.         break
  324.     if (Debug)
  325.         lineReport(NumGettys,GettyLines)
  326.     }
  327.     if (PrintedDay) {
  328.     PrintedDay = 0
  329.     print "" > "/dev/stderr"
  330.     }
  331.     close(Cmd)
  332.     return NumGettys
  333. }
  334.  
  335. function checkGap(maxTimeGap,lastRec,LoginTime,Year,  Elem,lastTime) {
  336.     split(lastRec,Elem," +")
  337.     lastTime = lastTime2unixtime(Year,Elem[6],Elem[7],Elem[8])
  338. #    print abs(LoginTime-lastTime)
  339.     if ((abs(LoginTime - lastTime) > maxTimeGap))
  340.     {
  341.     if (PrintedDay) {
  342.         PrintedDay = 0
  343.         print "" > "/dev/stderr"
  344.     }
  345.     printf "Skipping record for %s - time gap too large (%d days)\n",
  346.     strftime("%B %d %Y",LoginTime),abs(LoginTime - lastTime)/86400 \
  347.     > "/dev/stderr"
  348.     return 0
  349.     }
  350.     return 1
  351. }
  352.  
  353. function lastTime2unixtime(Year,Month,Day,Time,  TimeElem) {
  354.     split(Time,TimeElem,":")
  355.     return unixtime(Year,Month2Num[Month],Day,TimeElem[1],TimeElem[2],0)
  356. }
  357.  
  358. function lineReport(NumGettys,GettyLines,  Device) {
  359.     if (PrintedDay) {
  360.     PrintedDay = 0
  361.     print "" > "/dev/stderr"
  362.     }
  363.     printf "Still searching for %d line(s):",NumGettys > "/dev/stderr"
  364.     for (Device in GettyLines)
  365.     printf " %s",Device > "/dev/stderr"
  366.     print "" > "/dev/stderr"
  367. }
  368.  
  369. # Getty lines:
  370. #01:2345:respawn:/etc/getty tty01 sc_m
  371. #3A:23:respawn:/usr/lib/uucp/uugetty -t60 tty3A 3
  372.  
  373. function FindGettys(GettyLines,  Line,F,Elem,NumGettys,Cmd,File) {
  374.     File = "/etc/inittab"
  375.     while ((getline Line < File) == 1) {
  376.     if (Line ~ "^#")
  377.         continue
  378.     split(Line,F,":")
  379.     if (F[3] == "respawn" && F[4] ~ "^[^ \t]*getty ") {
  380.         split(F[4],Cmd,"[ \t]+")
  381.         for (Elem = 2; Elem in Cmd; Elem++)
  382.         if (Cmd[Elem] ~ "(^|/)tty") {
  383.             GettyLines[Cmd[Elem]]
  384.             NumGettys++
  385.             break
  386.         }
  387.     }
  388.     }
  389.     close(File)
  390.     return NumGettys
  391. }
  392.  
  393. function FindMGettys(GettyLines,  Elem,NumGettys) {
  394.     NumGettys = FindGettys(GettyLines)
  395.     for (Elem in GettyLines)
  396.     if (Elem !~ "[A-Z]") {
  397.         delete GettyLines[Elem]
  398.         NumGettys--
  399.     }
  400.     return NumGettys
  401. }
  402.  
  403. # MakeSet: make a set from a list.
  404. # An index with the name of each element of the list
  405. # is created in the given array.
  406. # Input variables: 
  407. # Elements is a string containing the list of elements.
  408. # Sep is the character that separates the elements of the list.
  409. # Output variables:
  410. # Set is the array.
  411. # Return value: the number of elements added to the set.
  412. function MakeSet(Set,Elements,Sep,  i,Num,Names) {
  413.     Num = split(Elements,Names,Sep)
  414.     for (i = 1; i <= Num; i++)
  415.     Set[Names[i]]
  416.     return Num
  417. }
  418.  
  419. ### Begin qsort routines
  420.  
  421. # Arr[] is an array of values with arbitrary indices.
  422. # k[] is returned with numeric indices 1..n.
  423. # The values in k[] are the indices of Arr[],
  424. # ordered so that if Arr[] is stepped through
  425. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  426. # through in order of the values of its elements.
  427. # The return value is the number of elements in the arrays (n).
  428. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  429.     ElNum = 0
  430.     for (ArrInd in Arr)
  431.     k[++ElNum] = ArrInd
  432.     qsortSegment(Arr,k,1,ElNum)
  433.     return ElNum
  434. }
  435.  
  436. # Sort a segment of an array.
  437. # Arr[] contains data with arbitrary indices.
  438. # k[] has indices 1..nelem, with the indices of arr[] as values.
  439. # This function sorts the elements of arr that are pointed to by
  440. # k[start..end], swapping the values of elements of k[] so that
  441. # when this function returns arr[k[start..end]] will be in order.
  442. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  443.     # handle two-element case explicitly for a tiny speedup
  444.     if ((end - start) == 1) {
  445.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  446.         k[start] = tmpe
  447.         k[end] = tmps
  448.     }
  449.     return
  450.     }
  451.     # Make sure comparisons act on these as numbers
  452.     left = start+0
  453.     right = end+0
  454.     sepval = Arr[k[int((left + right) / 2)]]
  455.     # Make every element <= sepval be to the left of every element > sepval
  456.     while (left < right) {
  457.     while (Arr[k[left]] < sepval)
  458.         left++
  459.     while (Arr[k[right]] > sepval)
  460.         right--
  461.     if (left < right) {
  462.         tmp = k[left]
  463.         k[left++] = k[right]
  464.         k[right--] = tmp
  465.     }
  466.     }
  467.     if (left == right)
  468.     if (Arr[k[left]] < sepval)
  469.         left++
  470.     else
  471.         right--
  472.     if (start < right)
  473.     qsortSegment(Arr,k,start,right)
  474.     if (left < end)
  475.     qsortSegment(Arr,k,left,end)
  476. }
  477.  
  478. # Arr[] is an array of values with arbitrary indices.
  479. # k[] is returned with numeric indices 1..n.
  480. # The values in k are the indices of Arr[],
  481. # ordered so that if Arr[] is stepped through
  482. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  483. # through in order of the values of its indices.
  484. # The return value is the number of elements in the arrays (n).
  485. # If the indexes are numeric, Numeric should be true, so that they can be
  486. # compared as such rather than as strings.  Numeric indexes do not have to be
  487. # contiguous.
  488. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  489.     ElNum = 0
  490.     if (Numeric)
  491.     # Indexes do not preserve numeric type, so must be forced
  492.     for (ArrInd in Arr)
  493.         k[++ElNum] = ArrInd+0
  494.     else
  495.     for (ArrInd in Arr)
  496.         k[++ElNum] = ArrInd
  497.     qsortNumIndByValue(k,1,ElNum)
  498.     return ElNum
  499. }
  500.  
  501. # Arr is an array of elements with contiguous numeric indexes to be sorted
  502. # by value.
  503. # start and end are the starting and ending indexes of the range to be sorted.
  504. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  505.     # handle two-element case explicitly for a tiny speedup
  506.     if ((start - end) == 1) {
  507.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  508.         Arr[start] = tmpe
  509.         Arr[end] = tmps
  510.     }
  511.     return
  512.     }
  513.     left = start+0
  514.     right = end+0
  515.     sepval = Arr[int((left + right) / 2)]
  516.     while (left < right) {
  517.     while (Arr[left] < sepval)
  518.         left++
  519.     while (Arr[right] > sepval)
  520.         right--
  521.     if (left <= right) {
  522.         tmp = Arr[left]
  523.         Arr[left++] = Arr[right]
  524.         Arr[right--] = tmp
  525.     }
  526.     }
  527.     if (start < right)
  528.     qsortNumIndByValue(Arr,start,right)
  529.     if (left < end)
  530.     qsortNumIndByValue(Arr,left,end)
  531. }
  532.  
  533. ### End qsort routines
  534.  
  535. ### Start of ProcArgs library
  536. # @(#) ProcArgs 1.11 96/12/08
  537. # 92/02/29 john h. dubois iii (john@armory.com)
  538. # 93/07/18 Added "#" arg type
  539. # 93/09/26 Do not count -h against MinArgs
  540. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  541. #          Removed meaning of "+" or "-" by itself.
  542. # 94/03/08 Added & option and *()< option types.
  543. # 94/04/02 Added NoRCopt to Opts()
  544. # 94/06/11 Mark numeric variables as such.
  545. # 94/07/08 Opts(): Do not require any args if h option is given.
  546. # 95/01/22 Record options given more than once.  Record option num in argv.
  547. # 95/06/08 Added ExclusiveOptions().
  548. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  549. #          Expand $VARNAME at the start of its filenames.
  550. #          Let varname=0 and -option- turn off an option.
  551. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  552. #          of the vars should be searched for in the environment.
  553. #          Check for duplicate rcfiles.
  554. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  555. #          now return various negatives values on error, not just -1, and
  556. #          Opts() may set Err to various positive values, not just 1.
  557. #          Added AllowUnrecOpt.
  558. # 96/05/23 Check type given for & option
  559. # 96/06/15 Re-port to awk
  560. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  561. #          used by other functions.
  562. # 96/10/15 Added OptChars
  563. # 96/11/01 Added exOpts arg to Opts()
  564. # 96/11/16 Added ; type
  565. # 96/12/08 Added Opt2Set() & Opt2Sets()
  566. # 96/12/27 Added CmdLineOpt()
  567.  
  568. # optlist is a string which contains all of the possible command line options.
  569. # A character followed by certain characters indicates that the option takes
  570. # an argument, with type as follows:
  571. # :    String argument
  572. # ;    Non-empty string argument
  573. # *    Floating point argument
  574. # (    Non-negative floating point argument
  575. # )    Positive floating point argument
  576. # #    Integer argument
  577. # <    Non-negative integer argument
  578. # >    Positive integer argument
  579. # The only difference the type of argument makes is in the runtime argument
  580. # error checking that is done.
  581.  
  582. # The & option is a special case used to get numeric options without the
  583. # user having to give an option character.  It is shorthand for [-+.0-9].
  584. # If & is included in optlist and an option string that begins with one of
  585. # these characters is seen, the value given to "&" will include the first
  586. # char of the option.  & must be followed by a type character other than ":"
  587. # or ";".
  588. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  589.  
  590. # Strings in argv[] which begin with "-" or "+" are taken to be
  591. # strings of options, except that a string which consists solely of "-"
  592. # or "+" is taken to be a non-option string; like other non-option strings,
  593. # it stops the scanning of argv and is left in argv[].
  594. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  595. # If an option takes an argument, the argument may either immediately
  596. # follow it or be given separately.
  597. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  598. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  599. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  600. # this feature had a flaw that caused problems in some cases.  See the OptChars
  601. # parameter to explicitly set the option-specifier characters.
  602.  
  603. # If an option that does not take an argument is given,
  604. # an index with its name is created in Options and its value is set to the
  605. # number of times it occurs in argv[].
  606.  
  607. # If an option that does take an argument is given, an index with its name is
  608. # created in Options and its value is set to the value of the argument given
  609. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  610. # If an option that takes an argument is given more than once,
  611. # Options[option-name,"count"] is incremented, and the value is assigned to
  612. # the index (option-name,instance) where instance is 2 for the second occurance
  613. # of the option, etc.
  614. # In other words, the first time an option with a value is encountered, the
  615. # value is assigned to an index consisting only of its name; for any further
  616. # occurances of the option, the value index has an extra (count) dimension.
  617.  
  618. # The sequence number for each option found in argv[] is stored in
  619. # Options[option-name,"num",instance], where instance is 1 for the first
  620. # occurance of the option, etc.  The sequence number starts at 1 and is
  621. # incremented for each option, both those that have a value and those that
  622. # do not.  Options set from a config file have a value of 0 assigned to this.
  623.  
  624. # Options and their arguments are deleted from argv.
  625. # Note that this means that there may be gaps left in the indices of argv[].
  626. # If compress is nonzero, argv[] is packed by moving its elements so that
  627. # they have contiguous integer indices starting with 0.
  628. # Option processing will stop with the first unrecognized option, just as
  629. # though -- was given except that unlike -- the unrecognized option will not be
  630. # removed from ARGV[].  Normally, an error value is returned in this case.
  631. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  632. # be found, so the number of remaining arguments is returned instead.
  633. # If OptChars is not a null string, it is the set of characters that indicate
  634. # that an argument is an option string if the string begins with one of the
  635. # characters.  A string consisting solely of two of the same option-indicator
  636. # characters stops the scanning of argv[].  The default is "-+".
  637. # argv[0] is not examined.
  638. # The number of arguments left in argc is returned.
  639. # If an error occurs, the global string OptErr is set to an error message
  640. # and a negative value is returned.
  641. # Current error values:
  642. # -1: option that required an argument did not get it.
  643. # -2: argument of incorrect type supplied for an option.
  644. # -3: unrecognized (invalid) option.
  645. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  646. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  647. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  648. {
  649. # ArgNum is the index of the argument being processed.
  650. # ArgsLeft is the number of arguments left in argv.
  651. # Arg is the argument being processed.
  652. # ArgLen is the length of the argument being processed.
  653. # ArgInd is the position of the character in Arg being processed.
  654. # Option is the character in Arg being processed.
  655. # Pos is the position in OptList of the option being processed.
  656. # NumOpt is true if a numeric option may be given.
  657.     ArgsLeft = argc
  658.     NumOpt = index(OptList,"&")
  659.     OptionNum = 0
  660.     if (OptChars == "")
  661.     OptChars = "-+"
  662.     while (OptChars != "") {
  663.     c = substr(OptChars,1,1)
  664.     OptChars = substr(OptChars,2)
  665.     OptCharSet[c]
  666.     OptTerm[c c]
  667.     }
  668.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  669.     Arg = argv[ArgNum]
  670.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  671.         break    # Not an option; quit
  672.     if (Arg in OptTerm) {
  673.         delete argv[ArgNum]
  674.         ArgsLeft--
  675.         break
  676.     }
  677.     ArgLen = length(Arg)
  678.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  679.         Option = substr(Arg,ArgInd,1)
  680.         if (NumOpt && Option ~ /[-+.0-9]/) {
  681.         # If this option is a numeric option, make its flag be & and
  682.         # its option string flag position be the position of & in
  683.         # the option string.
  684.         Option = "&"
  685.         Pos = NumOpt
  686.         # Prefix Arg with a char so that ArgInd will point to the
  687.         # first char of the numeric option.
  688.         Arg = "&" Arg
  689.         ArgLen++
  690.         }
  691.         # Find position of flag in option string, to get its type (if any).
  692.         # Disallow & as literal flag.
  693.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  694.         if (AllowUnrecOpt) {
  695.             Escape = 1
  696.             break
  697.         }
  698.         else {
  699.             OptErr = "Invalid option: " specGiven Option
  700.             return -3
  701.         }
  702.         }
  703.  
  704.         # Find what the value of the option will be if it takes one.
  705.         # NeedNextOpt is true if the option specifier is the last char of
  706.         # this arg, which means that if the option requires a value it is
  707.         # the next arg.
  708.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  709.         if (GotValue = ArgNum + 1 < argc)
  710.             Value = argv[ArgNum+1]
  711.         }
  712.         else {    # Value is included with option
  713.         Value = substr(Arg,ArgInd + 1)
  714.         GotValue = 1
  715.         }
  716.  
  717.         if (HadValue = AssignVal(Option,Value,Options,
  718.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  719.         specGiven)) {
  720.         if (HadValue < 0)    # error occured
  721.             return HadValue
  722.         if (HadValue == 2)
  723.             ArgInd++    # Account for the single-char value we used.
  724.         else {
  725.             if (NeedNextOpt) {    # option took next arg as value
  726.             delete argv[++ArgNum]
  727.             ArgsLeft--
  728.             }
  729.             break    # This option has been used up
  730.         }
  731.         }
  732.     }
  733.     if (Escape)
  734.         break
  735.     # Do not delete arg until after processing of it, so that if it is not
  736.     # recognized it can be left in ARGV[].
  737.     delete argv[ArgNum]
  738.     ArgsLeft--
  739.     }
  740.     if (compress != 0) {
  741.     dest = 1
  742.     src = argc - ArgsLeft + 1
  743.     for (count = ArgsLeft - 1; count; count--) {
  744.         ARGV[dest] = ARGV[src]
  745.         dest++
  746.         src++
  747.     }
  748.     }
  749.     return ArgsLeft
  750. }
  751.  
  752. # Assignment to values in Options[] occurs only in this function.
  753. # Option: Option specifier character.
  754. # Value: Value to be assigned to option, if it takes a value.
  755. # Options[]: Options array to return values in.
  756. # ArgType: Argument type specifier character.
  757. # GotValue: Whether any value is available to be assigned to this option.
  758. # Name: Name of option being processed.
  759. # OptionNum: Number of this option (starting with 1) if set in argv[],
  760. #     or 0 if it was given in a config file or in the environment.
  761. # SingleOpt: true if the value (if any) that is available for this option was
  762. #     given as part of the same command line arg as the option.  Used only for
  763. #     options from the command line.
  764. # specGiven is the option specifier character use, if any (e.g. - or +),
  765. # for use in error messages.
  766. # Global variables: OptErr
  767. # Return value: negative value on error, 0 if option did not require an
  768. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  769. # the arg.
  770. # Current error values:
  771. # -1: Option that required an argument did not get it.
  772. # -2: Value of incorrect type supplied for option.
  773. # -3: Bad type given for option &
  774. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  775. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  776.     # If option takes a value...    [
  777.     NumTypes = "*()#<>]"
  778.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  779.     OptErr = "Bad type given for & option"
  780.     return -3
  781.     }
  782.  
  783.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  784.     if (!GotValue) {
  785.         if (Name != "")
  786.         OptErr = "Variable requires a value -- " Name
  787.         else
  788.         OptErr = "option requires an argument -- " Option
  789.         return -1
  790.     }
  791.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  792.         OptErr = Err
  793.         return -2
  794.     }
  795.     # Mark this as a numeric variable; will be propogated to Options[] val.
  796.     if (ArgType != ":" && ArgType != ";")
  797.         Value += 0
  798.     if ((Instance = ++Options[Option,"count"]) > 1)
  799.         Options[Option,Instance] = Value
  800.     else
  801.         Options[Option] = Value
  802.     }
  803.     # If this is an environ or rcfile assignment & it was given a value...
  804.     else if (!OptionNum && Value != "") {
  805.     UsedValue = 1
  806.     # If the value is "0" or "-" and this is the first instance of it,
  807.     # do not set Options[Option]; this allows an assignment in an rcfile to
  808.     # turn off an option (for the simple "Option in Options" test) in such
  809.     # a way that it cannot be turned on in a later file.
  810.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  811.         Instance = 1
  812.     else
  813.         Instance = ++Options[Option]
  814.     # Save the value even though this is a flag
  815.     Options[Option,Instance] = Value
  816.     }
  817.     # If this is a command line flag and has a - following it in the same arg,
  818.     # it is being turned off.
  819.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  820.     UsedValue = 2
  821.     if (Option in Options)
  822.         Instance = ++Options[Option]
  823.     else
  824.         Instance = 1
  825.     Options[Option,Instance]
  826.     }
  827.     # If this is a flag assignment without a value, increment the count for the
  828.     # flag unless it was turned off.  The indicator for a flag being turned off
  829.     # is that the flag index has not been set in Options[] but it has an
  830.     # instance count.
  831.     else if (Option in Options || !((Option,1) in Options))
  832.     # Increment number of times this flag seen; will inc null value to 1
  833.     Instance = ++Options[Option]
  834.     Options[Option,"num",Instance] = OptionNum
  835.     return UsedValue
  836. }
  837.  
  838. # Option is the option letter
  839. # Value is the value being assigned
  840. # Name is the var name of the option, if any
  841. # ArgType is one of:
  842. # :    String argument
  843. # ;    Non-null string argument
  844. # *    Floating point argument
  845. # (    Non-negative floating point argument
  846. # )    Positive floating point argument
  847. # #    Integer argument
  848. # <    Non-negative integer argument
  849. # >    Positive integer argument
  850. # specGiven is the option specifier character use, if any (e.g. - or +),
  851. # for use in error messages.
  852. # Returns null on success, err string on error
  853. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  854.     if (ArgType == ":")
  855.     return ""
  856.     if (ArgType == ";") {
  857.     if (Value == "")
  858.         Err = "must be a non-empty string"
  859.     }
  860.     # A number begins with optional + or -, and is followed by a string of
  861.     # digits or a decimal with digits before it, after it, or both
  862.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  863.     Err = "must be a number"
  864.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  865.     Err = "may not include a fraction"
  866.     else if (ArgType ~ "[()<>]" && Value < 0)
  867.     Err = "may not be negative"
  868.     # (
  869.     else if (ArgType ~ "[)>]" && Value == 0)
  870.     Err = "must be a positive number"
  871.     if (Err != "") {
  872.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  873.     if (Name != "")
  874.         return ErrStr "variable " substr(Name,1,1) " " Err
  875.     else {
  876.         if (Option == "&")
  877.         Option = Value
  878.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  879.     }
  880.     }
  881.     else
  882.     return ""
  883. }
  884.  
  885. # Note: only the above functions are needed by ProcArgs.
  886. # The rest of these functions call ProcArgs() and also do other
  887. # option-processing stuff.
  888.  
  889. # Opts: Process command line arguments.
  890. # Opts processes command line arguments using ProcArgs()
  891. # and checks for errors.  If an error occurs, a message is printed
  892. # and the program is exited.
  893. #
  894. # Input variables:
  895. # Name is the name of the program, for error messages.
  896. # Usage is a usage message, for error messages.
  897. # OptList the option description string, as used by ProcArgs().
  898. # MinArgs is the minimum number of non-option arguments that this
  899. # program should have, non including ARGV[0] and +h.
  900. # If the program does not require any non-option arguments,
  901. # MinArgs should be omitted or given as 0.
  902. # rcFiles, if given, is a colon-seprated list of filenames to read for
  903. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  904. # by the value of the environment variable HOME.  If a filename begins with
  905. # $, the part from the character after the $ up until (but not including)
  906. # the first character not in [a-zA-Z0-9_] will be searched for in the
  907. # environment; if found its value will be substituted, if not the filename will
  908. # be discarded.
  909. # rcfiles are read in the order given.
  910. # Values given in them will not override values given on the command line,
  911. # and values given in later files will not override those set in earlier
  912. # files, because AssignVal() will store each with a different instance index.
  913. # The first instance of each variable, either on the command line or in an
  914. # rcfile, will be stored with no instance index, and this is the value
  915. # normally used by programs that call this function.
  916. # VarNames is a comma-separated list of variable names to map to options,
  917. # in the same order as the options are given in OptList.
  918. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  919. # searched for in the environment.  If set to -1, all values will be searched
  920. # for in the environment.  Values given in the environment will override
  921. # those given in the rcfiles but not those given on the command line.
  922. # NoRCopt, if given, is an additional letter option that if given on the
  923. # command line prevents the rcfiles from being read.
  924. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  925. # ExclusiveOptions() for a description of exOpts.
  926. # Special options:
  927. # If x is made an option and is given, some debugging info is output.
  928. # h is assumed to be the help option.
  929.  
  930. # Global variables:
  931. # The command line arguments are taken from ARGV[].
  932. # The arguments that are option specifiers and values are removed from
  933. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  934. # The number of elements in ARGV[] should be in ARGC.
  935. # After processing, ARGC is set to the number of elements left in ARGV[].
  936. # The option values are put in Options[].
  937. # On error, Err is set to a positive integer value so it can be checked for in
  938. # an END block.
  939. # Return value: The number of elements left in ARGV is returned.
  940. # Must keep OptErr global since it may be set by InitOpts().
  941. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  942. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  943.     if (MinArgs == "")
  944.     MinArgs = 0
  945.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  946.     optChars)
  947.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  948.     if (ArgsLeft >= 0) {
  949.         OptErr = "Not enough arguments"
  950.         Err = 4
  951.     }
  952.     else
  953.         Err = -ArgsLeft
  954.     printf "%s: %s.\nUse -h for help.\n%s\n",
  955.     Name,OptErr,Usage > "/dev/stderr"
  956.     exit 1
  957.     }
  958.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  959.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  960.     {
  961.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  962.     Err = -e
  963.     exit 1
  964.     }
  965.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  966.     {
  967.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  968.     Err = 1
  969.     exit 1
  970.     }
  971.     return ArgsLeft
  972. }
  973.  
  974. # ReadConfFile(): Read a file containing var/value assignments, in the form
  975. # <variable-name><assignment-char><value>.
  976. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  977. # line and whitespace between the variable name and the assignment character) 
  978. # is stripped.  Lines that do not contain an assignment operator or which
  979. # contain a null variable name are ignored, other than possibly being noted in
  980. # the return value.  If more than one assignment is made to a variable, the
  981. # first assignment is used.
  982. # Input variables:
  983. # File is the file to read.
  984. # Comment is the line-comment character.  If it is found as the first non-
  985. #     whitespace character on a line, the line is ignored.
  986. # Assign is the assignment string.  The first instance of Assign on a line
  987. #     separates the variable name from its value.
  988. # If StripWhite is true, whitespace around the value (whitespace between the
  989. #     assignment char and trailing whitespace on the line) is stripped.
  990. # VarPat is a pattern that variable names must match.  
  991. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  992. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  993. #     a line; no assignment operator is needed.  These variables are set in
  994. #     the output array with a null value.  Lines containing nothing but
  995. #     whitespace are still ignored.
  996. # Output variables:
  997. # Values[] contains the assignments, with the indexes being the variable names
  998. #     and the values being the assigned values.
  999. # Lines[] contains the line number that each variable occured on.  A flag set
  1000. #     is record by giving it an index in Lines[] but not in Values[].
  1001. # Return value:
  1002. # If any errors occur, a string consisting of descriptions of the errors
  1003. # separated by newlines is returned.  In no case will the string start with a
  1004. # numeric value.  If no errors occur,  the number of lines read is returned.
  1005. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1006. FlagsOK,
  1007. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1008.     if (Comment != "")
  1009.     Comment = "^" Comment
  1010.     AssignLen = length(Assign)
  1011.     if (VarPat == "")
  1012.     VarPat = "."    # null varname not allowed
  1013.     while ((Status = (getline Line < File)) == 1) {
  1014.     LineNum++
  1015.     sub("^[ \t]+","",Line)
  1016.     if (Line == "")        # blank line
  1017.         continue
  1018.     if (Comment != "" && Line ~ Comment)
  1019.         continue
  1020.     if (Pos = index(Line,Assign)) {
  1021.         Var = substr(Line,1,Pos-1)
  1022.         Val = substr(Line,Pos+AssignLen)
  1023.         if (StripWhite) {
  1024.         sub("^[ \t]+","",Val)
  1025.         sub("[ \t]+$","",Val)
  1026.         }
  1027.     }
  1028.     else {
  1029.         Var = Line    # If no value, var is entire line
  1030.         Val = ""
  1031.     }
  1032.     if (!FlagsOK && Val == "") {
  1033.         Errs = Errs \
  1034.         sprintf("\nBad assignment on line %d of file %s: %s",
  1035.         LineNum,File,Line)
  1036.         continue
  1037.     }
  1038.     sub("[ \t]+$","",Var)
  1039.     if (Var !~ VarPat) {
  1040.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1041.         LineNum,File,Var)
  1042.         continue
  1043.     }
  1044.     if (!(Var in Lines)) {
  1045.         Lines[Var] = LineNum
  1046.         if (Pos)
  1047.         Values[Var] = Val
  1048.     }
  1049.     }
  1050.     if (Status)
  1051.     Errs = Errs "\nCould not read file " File
  1052.     close(File)
  1053.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1054. }
  1055.  
  1056. # Variables:
  1057. # Data is stored in Options[].
  1058. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1059. # Global vars:
  1060. # Sets OptErr.  Uses ENVIRON[].
  1061. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1062. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1063. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1064. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1065.     split("",filesRead,"")    # make awk know this is an array
  1066.     NumVars = split(VarNames,Vars,",")
  1067.     TypesInd = Ret = 0
  1068.     if (EnvSearch == -1)
  1069.     EnvSearch = NumVars
  1070.     for (i = 1; i <= NumVars; i++) {
  1071.     Var = Vars[i]
  1072.     CharOpt = substr(OptList,++TypesInd,1)
  1073.     if (CharOpt ~ "^[:;*()#<>&]$")
  1074.         CharOpt = substr(OptList,++TypesInd,1)
  1075.     Map[Var] = CharOpt
  1076.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1077.     # Do not overwrite entries from environment
  1078.     if (i <= EnvSearch && Var in ENVIRON &&
  1079.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1080.         return Err
  1081.     }
  1082.  
  1083.     numrcFiles = split(rcFiles,fNames,":")
  1084.     for (i = 1; i <= numrcFiles; i++) {
  1085.     rcFile = fNames[i]
  1086.     if (rcFile ~ "^~/")
  1087.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1088.     else if (rcFile ~ /^\$/) {
  1089.         rcFile = substr(rcFile,2)
  1090.         match(rcFile,"^[a-zA-Z0-9_]*")
  1091.         envvar = substr(rcFile,1,RLENGTH)
  1092.         if (envvar in ENVIRON)
  1093.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1094.         else
  1095.         continue
  1096.     }
  1097.     if (rcFile in filesRead)
  1098.         continue
  1099.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1100.     # may be the same
  1101.     filesRead[rcFile]
  1102.     if ("x" in Options)
  1103.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1104.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1105.     if (retStr > 0)
  1106.         READ_RCFILE = 1
  1107.     else if (ret != "") {
  1108.         OptErr = retStr
  1109.         Ret = -1
  1110.     }
  1111.     for (Var in Lines)
  1112.         if (Var in Map) {
  1113.         if ((Err = AssignVal(Map[Var],
  1114.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1115.         Var in Values,Var,0)) < 0)
  1116.             return Err
  1117.         }
  1118.         else {
  1119.         OptErr = sprintf(\
  1120.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1121.         Lines[Var],rcFile)
  1122.         Ret = -1
  1123.         }
  1124.     }
  1125.  
  1126.     if ("x" in Options)
  1127.     for (Var in Map)
  1128.         if (Map[Var] in Options)
  1129.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1130.         "/dev/stderr"
  1131.         else
  1132.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1133.     return Ret
  1134. }
  1135.  
  1136. # OptSets is a semicolon-separated list of sets of option sets.
  1137. # Within a list of option sets, the option sets are separated by commas.  For
  1138. # each set of sets, if any option in one of the sets is in Options[] AND any
  1139. # option in one of the other sets is in Options[], an error string is returned.
  1140. # If no conflicts are found, nothing is returned.
  1141. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1142. # the exclusions presented by the first set of sets (ab,def,g) if:
  1143. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1144. # (a or b is in Options[]) AND (g is in Options) OR
  1145. # (d, e, or f is in Options[]) AND (g is in Options)
  1146. # An error will be returned due to the exclusions presented by the second set
  1147. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1148. # todo: make options given on command line unset options given in config file
  1149. # todo: that they conflict with.
  1150. function ExclusiveOptions(OptSets,Options,
  1151. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1152. SetNum,OSetNum) {
  1153.     NumSetSets = split(OptSets,SetSets,";")
  1154.     # For each set of sets...
  1155.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1156.     # NumSets is the number of sets in this set of sets.
  1157.     NumSets = split(SetSets[SetSet],Sets,",")
  1158.     # For each set in a set of sets except the last...
  1159.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1160.         s1 = Sets[SetNum]
  1161.         L1 = length(s1)
  1162.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1163.         # If any of the options in this set was given, check whether
  1164.         # any of the options in the other sets was given.  Only check
  1165.         # later sets since earlier sets will have already been checked
  1166.         # against this set.
  1167.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1168.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1169.             s2 = Sets[OSetNum]
  1170.             L2 = length(s2)
  1171.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1172.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1173.                 ErrStr = ErrStr "\n"\
  1174.                 sprintf("Cannot give both %s and %s options.",
  1175.                 c1,c2)
  1176.             }
  1177.     }
  1178.     }
  1179.     if (ErrStr != "")
  1180.     return substr(ErrStr,2)
  1181.     return ""
  1182. }
  1183.  
  1184. # The value of each instance of option Opt that occurs in Options[] is made an
  1185. # index of Set[].
  1186. # The return value is the number of instances of Opt in Options.
  1187. function Opt2Set(Options,Opt,Set,  count) {
  1188.     if (!(Opt in Options))
  1189.     return 0
  1190.     Set[Options[Opt]]
  1191.     count = Options[Opt,"count"]
  1192.     for (; count > 1; count--)
  1193.     Set[Options[Opt,count]]
  1194.     return count
  1195. }
  1196.  
  1197. # The value of each instance of option Opt that occurs in Options[] that
  1198. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1199. # Other values are made indexes of Set[].
  1200. # The return value is the number of instances of Opt in Options.
  1201. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1202.     ret = Opt2Set(Options,Opt,aSet)
  1203.     for (value in aSet)
  1204.     if (substr(value,1,1) == "!")
  1205.         nSet[substr(value,2)]
  1206.     else
  1207.         Set[value]
  1208.     return ret
  1209. }
  1210.  
  1211. # Returns true if option Opt was given on the command line.
  1212. function CmdLineOpt(Options,Opt,  i) {
  1213.     for (i = 1; (Opt,"num",i) in Options; i++)
  1214.     if (Options[Opt,"num",i] != 0)
  1215.         return 1
  1216.     return 0
  1217. }
  1218. ### End of ProcArgs library
  1219. ### Begin timedate routines.
  1220. # These functions operate on absolute dates & times.
  1221.  
  1222. # convert month/day or year/month/day date to yymmdd date
  1223. # uses global "year" var if year not given
  1224. function makedate(InDate,Elements,d,date) {
  1225.     Elements = split(InDate,d,"/")
  1226.     date = d[1] * 100 + d[2]
  1227.     if (Elements == 2)
  1228.     date += year
  1229.     else if (Elements == 3)
  1230.     date = date * 100 + d[3]
  1231.     else
  1232.     return -1
  1233.     return date
  1234. }
  1235.  
  1236. # convert yymmdd date to yy/mm/dd date
  1237. function unmakedate(Date) {
  1238.     return substr(Date,1,2) "/" substr(Date,3,2) "/" substr(Date,5,2)
  1239. }
  1240.  
  1241. function MkMonth2Num(  Month) {
  1242.     split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",Months,",")
  1243.     for (Month in Months)
  1244.     Month2Num[Months[Month]] = sprintf("%02d",Month)
  1245. }
  1246.  
  1247. # Takes a date and stores its components in the following elements of Date[]:
  1248. # year    yy
  1249. # month    mm
  1250. # day    dd
  1251. # hour    hh
  1252. # min    mm
  1253. # The following are set in Date[] if given:
  1254. # tz    TTT|offset
  1255. # lyear    yyyy
  1256. # weekday Www
  1257. # sec    ss
  1258.  
  1259. # On success, the date in touch/date/etc. format (MMddhhmmyy) is returned.
  1260. # On failure, a negative value is returned.
  1261.  
  1262. # InDate form:
  1263. # [Weekday[,] (Month [d]d)|([d]d Month) TZ|time|year TZ|time|year [TZ|time|year]
  1264. # where Www is a weekday name that starts with a recognized 3-char prefix,
  1265. # Month is a month name that starts with a recognized 3-char prefix,
  1266. # [d]d is a day of the month,
  1267. # TZ is a timezone name (three upper case alpha chars) or offset from GMT
  1268. # as [-+]NNNN, time is of the form [h]h:mm[:ss],
  1269. # and year is of the form [cc]nn where cc is >= 19.
  1270. # Common patterns for a particular date:
  1271. # [Tue[,]]    Jun    12    11:02:46    [BST|-0800]    [19]90
  1272. # [Tue[,]]    12    Jun    [19]90        11:02:46    [BST|-0800]
  1273. function ParseDate(InDate,Date,  El,MonthName,TimeEl,i,Months,Ind,Num) {
  1274.     if (!("Jan" in Month2Num)) {
  1275.     MkMonth2Num()
  1276.     split("month,day,hour,min,year",TouchParts,",")
  1277.     }
  1278.     # Clear Date[]
  1279.     split("",Date," ")
  1280.     Num = split(InDate,El," +")
  1281.     if (!(4 <= Num && Num <= 6))
  1282.     return -1
  1283.     Ind = 1
  1284.     if (El[Ind] ~ "^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*,?") {
  1285.     Date["weekday"] = substr(El[Ind],1,3)
  1286.     Ind++
  1287.     }
  1288.     if ((El[Ind] + 0) > 0) {
  1289.     Date["day"] = sprintf("%02d",El[Ind++])
  1290.     MonthName = substr(El[Ind++],1,3)
  1291.     }
  1292.     else {
  1293.     MonthName = substr(El[Ind++],1,3)
  1294.     Date["day"] = sprintf("%02d",El[Ind++])
  1295.     }
  1296.     if ((Date["day"] + 0 > 31) || (Date["day"] + 0 < 1))
  1297.     return -5
  1298.     if (!(MonthName in Month2Num))
  1299.     return -3
  1300.     Date["month"] = Month2Num[MonthName]
  1301.     for (; Ind <= Num; Ind++) {
  1302.     if ((El[Ind] ~ "^[0-2]?[0-9]:[0-5][0-9](:[0-5][0-9])?$") && \
  1303.     !("hour" in Date)) {
  1304.         split(El[Ind],TimeEl,":")
  1305.         Date["hour"] = sprintf("%02d",TimeEl[1])
  1306.         Date["min"] =  TimeEl[2]
  1307.         if (3 in TimeEl)
  1308.         Date["sec"] =  TimeEl[3]
  1309.     }
  1310.     else if ((El[Ind] ~ "^[1-9][0-9][0-9][0-9]$") && !("year" in Date)) {
  1311.         Date["year"] = substr(El[Ind],3)
  1312.         Date["lyear"] = El[Ind]
  1313.     }
  1314.     else if ((El[Ind] ~ "^[0-9][0-9]$") && !("year" in Date))
  1315.         Date["year"] = El[Ind]
  1316.     else if ((El[Ind] ~ "^([A-Z][A-Z][A-Z])|([-+][0-2][0-9][0-5][0-9])$") \
  1317.     && !("tz" in Date))
  1318.         Date["tz"] = El[Ind]
  1319.     else
  1320.         return -2
  1321.     }
  1322.     for (i in TouchParts)
  1323.     if (!(TouchParts[i] in Date))
  1324.         return -i - 5
  1325.     touchdate = Date["month"] Date["day"] Date["hour"] Date["min"] Date["year"]
  1326.     return touchdate
  1327. }
  1328.  
  1329. # Convert a file timestamp as printed by 'l' to an epoch time.
  1330. # noTZ should be 1 if the date output was produced with a 0 TZ; in this case
  1331. # no attempt is made to undo the TZ adjustment.
  1332. function lDate2unixtime(Mon,Day,Year,noTZ,  Month) {
  1333.     if (!(1 in Month2Num)) {
  1334.     MkMonth2Num()
  1335.     CurYear = strftime("%y")
  1336.     CurMonth = strftime("%m")
  1337.     }
  1338.     Month = Month2Num[Mon]
  1339.     # Deal with varying dates printed by l
  1340.     # Use year if given
  1341.     # Subtract 1 from year if month given is from last year
  1342.     if (Year ~ ":")    # If year is actually time...
  1343.     Year = (CurYear - (Month > CurMonth)) % 100
  1344.     return date2unixtime(Year,Month,Day,noTZ)
  1345. }
  1346.  
  1347. # Returns the number of seconds that passed from 1970 Jan 1 00:00:00
  1348. # to the given date.
  1349. # Timezone should be given as a numeric offset from GMT in seconds.
  1350. # Use 0 for Timezone if the date being converted was not generated with a
  1351. # timezone adjustment.
  1352. function unixtime(Year,Month,Day,Hour,Minute,Second,Timezone) {
  1353.     return ((YMD2day(Year,Month,Day) * 24 + Hour) * 60 + Minute) * 60 + \
  1354.     Second + Timezone
  1355. }
  1356.  
  1357. # date2unixtime returns the number of seconds that passed from 
  1358. # 1970 Jan 1 00:00:00 GMT to the given date, which is assumed to be in the
  1359. # local timezone.  Note that if the given date occured in daylight savings
  1360. # time and the current time (which is used to calculate TZOffset) is not,
  1361. # or vice versa, this will be off by the DST shift.
  1362. # If noTZ is set, the date is taken to be in GMT and TZ modifications are not
  1363. # done.
  1364. # Globals: Sets/uses TZOffset and MDays[].
  1365. function date2unixtime(Year,Month,Day,noTZ,   LeapDays) {
  1366.     if (!noTZ && TZOffset == "")
  1367.     MakeTZOffset()
  1368.     if (Year > 100)
  1369.     Year -= 1900
  1370.     LeapDays = int((Year - 68) / 4)
  1371.     if (Month <= 2 && Year % 4 == 0)
  1372.     LeapDays -= 1
  1373.     if (!MDays[2])
  1374.     split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
  1375.     return ((Year - 70) * 365 + MDays[Month + 0] + Day - 1 + LeapDays) \
  1376.     * 24 * 3600 - (noTZ ? 0 : TZOffset)
  1377. }
  1378.  
  1379. # Sets global TZOffset to the number of seconds that need to be substracted
  1380. # from the local date (without time of day) to give an epoch time.
  1381. # TZOffset can also be added to systime() before doing %86400 to get the
  1382. # current day number in the local timezone.
  1383. # Note that TZOffset is only correct if the given date is in the same DST
  1384. # phase as the current date.
  1385. # 95/03/26 Calculate TZOffset more accurately.
  1386. function MakeTZOffset(  t) {
  1387.     t = systime()
  1388.     TZOffset = strftime("%H",t)*3600+strftime("%M",t)*60+strftime("%S",t) - \
  1389.     t%86400
  1390.     if (strftime("%j",0) != "001")    # If TZ offset > 0
  1391.     TZOffset -= 24*3600
  1392. }
  1393.  
  1394. # Convert a numeric timezone to a number of seconds.
  1395. # Example: converts -0830 to -30600
  1396. function TZ2sec(NTimezone) {
  1397.     if (NTimezone < 0) {
  1398.     NTimezone = substr(NTimezone,2)
  1399.     Mult = -1
  1400.     }
  1401.     else
  1402.     Mult = 1
  1403.     return (substr(NTimezone,1,2)*3600+substr(NTimezone,3,2)*60)*Mult
  1404. }
  1405.  
  1406. # Only works for current time... does *not* take a systime argument!
  1407. function my_strftime(Format,  Time) {
  1408.     "date \"+" Format "\"" | getline Time
  1409.     return Time
  1410. }
  1411.  
  1412. ### End timedate routines
  1413. ### Begin epochdays routines.
  1414. # These functions operate on epoch days and epoch months, which have the same 0
  1415. # time as UNIX epoch seconds.  These functions are mainly used to avoid having
  1416. # to deal with timezone issues.
  1417. # @(#) epochdays 1.1 95/08/26
  1418.  
  1419. # YMD2day(year,month,day-of-month) returns the number of days that passed from 
  1420. # 1970 Jan 1 to the given date.
  1421. # All parameters should be given in numeric form.
  1422. # If year < 70, it is assumed to be part of the 2000 century
  1423. # If year in (70..99), it is assumed to be part of the 1900 century.
  1424. # Globals: sets and uses MDays[]
  1425. function YMD2day(Year,Month,Day,   LeapDays) {
  1426.     Year+=0
  1427.     Month+=0
  1428.     if (Year < 70)
  1429.     Year += 100
  1430.     else if (Year >= 100)
  1431.     Year -= 1900
  1432.     # Year is now the number of years since 1900.
  1433.     LeapDays = int((Year - 68) / 4)
  1434.     if (Month <= 2 && Year % 4 == 0)
  1435.     LeapDays -= 1
  1436.     if (!(0 in MDays))
  1437.     split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
  1438.     return (Year - 70) * 365 + MDays[Month] + Day - 1 + LeapDays
  1439. }
  1440.  
  1441. # date2day("yy/mm/dd") returns the number of days that passed from 
  1442. # 1970 Jan 1 to the given date.  -1 is returned on error.
  1443. # The fields are returned in Fields: year in Fields[1], month in Fields[2],
  1444. # and day (if given) in Fields[3].
  1445. function date2day(Date,Fields,  Num,Year,Month) {
  1446.     Num = split(Date,Fields,"/")
  1447.     if (Num != 2 && Num != 3)
  1448.     return -1
  1449.     if (!(Year = Fields[1] + 0) || !(Month = Fields[2] + 0))
  1450.     return -1
  1451.     if (Num == 3)
  1452.     Day = Fields[3]
  1453.     return YMD2day(Year,Month,Day)
  1454. }
  1455.  
  1456. # diffdays(year1,month1,day-of-month1,year2,month2,day-of-month2)
  1457. # returns the number of complete days that passed from date 1 to date 2
  1458. function diffdays(year1,month1,day1,year2,month2,day2) {
  1459.     return YMD2day(year2,month2,day2) - YMD2day(year1,month1,day1)
  1460. }
  1461.  
  1462. # Given an epoch month, return the first day of that month
  1463. function month2day(Month) {
  1464.     return YMD2day(int(Month/12) + 1970,Month % 12 + 1,1)
  1465. }
  1466.  
  1467. # Given an epoch day, returns epoch month
  1468. function day2month(Day,  Date) {
  1469.     day2YMD(Day,Date)
  1470.     return (Date["y"]-1970)*12 + Date["m"]-1
  1471. }
  1472.  
  1473. # Given an epoch month, returns the number of days in that month.
  1474. function monthdays(month,  year) {
  1475.     if (!(0 in MDur))
  1476.     split("31 28 31 30 31 30 31 31 30 31 30 31",MDur)
  1477.     year = int(month/12)
  1478.     month = month%12+1
  1479.     return (!((year+2)%4) && month == 2) ? 29 : MDur[month]
  1480. }
  1481.  
  1482. # Given an epoch day (day since 1970 Jan 1; day 0 = 1970 Jan 1, etc.), 
  1483. # returns the date elements in Date:
  1484. # Date["y"] = year (4 digits), Date["m"] = month (jan = 1, etc.),
  1485. # Date["d"] = day of month.
  1486. # Globals: Sets/uses MDays[].
  1487. function day2YMD(Day,Date,  QYears,Year,NonLeapYears,Month) {
  1488.     if (!(0 in LDays)) {
  1489.     split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
  1490.     split("0 31 60 91 121 152 182 213 244 274 305 335 366",LDays," ")
  1491.     }
  1492.     Day += 365
  1493.     # Day is now # of days since Jan 1 1969.  1968 was a leap year.
  1494.     QYears = int(Day / (365*4+1))
  1495.     Year = 1969 + QYears * 4
  1496.     Day -= QYears * (365*4+1)
  1497.     # Day now contains no complete leap years.
  1498.     Year += NonLeapYears = int(Day/365)
  1499.     Leap = !(Year % 4)
  1500.     Day -= NonLeapYears * 365
  1501.     # Day now contains the day of year.
  1502.     # Find the month.  Divide day by 32 to get either the correct month or
  1503.     # the month prior to it.
  1504.     Month = int(Day++ / 32) + 1
  1505.     if (Day > (Leap ? LDays[Month+1] : MDays[Month+1]))
  1506.     Month++
  1507.     Day -= Leap ? LDays[Month] : MDays[Month]
  1508.     Date["d"] = Day
  1509.     Date["m"] = Month
  1510.     Date["y"] = Year
  1511. }
  1512.  
  1513. # Given a month number, return a date in the form yy/mm
  1514. function month2date(MonthNum) {
  1515.     return sprintf("%02d/%02d",(MonthNum / 12 + 70) % 100, MonthNum % 12 + 1)
  1516. }
  1517.  
  1518. # Given a day number, return a date in the form yy/mm/dd or yyyy/mm/dd
  1519. # If century is true, the century is included as part of the year;
  1520. # otherwise it is stripped.
  1521. function day2date(day,century,  year) {
  1522.     day2YMD(day,Date)
  1523.     year = Date["y"]
  1524.     if (!century)
  1525.     year %= 100
  1526.     return sprintf("%02d/%02d/%02d",year,Date["m"],Date["d"])
  1527. }
  1528.  
  1529. ### End epochdays routines
  1530. ### Begin timeperiod routines.
  1531. # These functions operate on periods of time.
  1532.  
  1533. # Converts Seconds to the form [[[[<days>d]<hours>h]<min>m]<sec>s]
  1534. function sec2dhms(Seconds,  Days,Hours,Minutes,Time) {
  1535.     Days = int(Seconds / 86400)
  1536.     Seconds %= 86400
  1537.     Hours = int(Seconds / 3600)
  1538.     Seconds %= 3600
  1539.     Minutes = int(Seconds / 60)
  1540.     Seconds %= 60
  1541.     if (Days)
  1542.     Time = Days "d"
  1543.     if (Time || Hours)
  1544.     Time = Time Hours "h"
  1545.     if (Time || Minutes)
  1546.     Time = Time Minutes "m"
  1547.     if (!Time || Seconds)
  1548.     Time = Time Seconds "s"
  1549.     return Time
  1550. }
  1551.  
  1552. # Converts Seconds to the form [[[<days>d]hh:]mm:]ss
  1553. function sec2dhms2(Seconds,  Days,Hours,Minutes,Time) {
  1554.     Days = int(Seconds / 86400)
  1555.     Seconds %= 86400
  1556.     Hours = int(Seconds / 3600)
  1557.     Seconds %= 3600
  1558.     Minutes = int(Seconds / 60)
  1559.     Seconds %= 60
  1560.     if (Days)
  1561.     Time = Days "d "
  1562.     if (Time || Hours)
  1563.     Time = Time sprintf("%02d",Hours) ":"
  1564.     if (Time || Minutes)
  1565.     Time = Time sprintf("%02d",Minutes) ":"
  1566.     Time = Time sprintf("%02d",Seconds)
  1567.     return Time
  1568. }
  1569.  
  1570. # Converts Seconds to the form [[<days>d]hh:]mm|m
  1571. function sec2dhm(Seconds,  Days,Hours,Minutes,Time) {
  1572.     Days = int(Seconds / 86400)
  1573.     Seconds %= 86400
  1574.     Hours = int(Seconds / 3600)
  1575.     Seconds %= 3600
  1576.     Minutes = int(Seconds / 60)
  1577.     if (Days)
  1578.     Time = Days "d "
  1579.     if (Time || Hours)
  1580.     Time = Time sprintf("%02d",Hours) ":"
  1581.     if (Time)
  1582.     Time = Time sprintf("%02d",Minutes)
  1583.     else
  1584.     Time = Minutes
  1585.     return Time
  1586. }
  1587.  
  1588. # Converts Seconds to the form hours:mm:ss
  1589. function sec2hms(Seconds,  Hours,Minutes) {
  1590.     Hours = int(Seconds / 3600)
  1591.     Seconds %= 3600
  1592.     Minutes = int(Seconds / 60)
  1593.     Seconds %= 60
  1594.     return sprintf("%d:%02d:%02d",Hours,Minutes,Seconds)
  1595. }
  1596.  
  1597. ### End timeperiod routines
  1598. ### start canonTTY library
  1599. function nodevTTY(tty) {
  1600.     sub("^/dev/","",tty)
  1601.     return tty
  1602. }
  1603.  
  1604. function canonTTY(tty) {
  1605.     if (tty ~ "^/dev/")
  1606.     sub("^/dev/","",tty)
  1607.     else if (tty !~ /^tty/)
  1608.     tty = "tty" tty
  1609.     return tty
  1610. }
  1611.  
  1612. function shortTTY(tty) {
  1613.     # Strip leading "tty" only if name did not begin with /dev
  1614.     if (!sub("^/dev/","",tty))
  1615.     sub("^tty","",tty)
  1616.     return tty
  1617. }
  1618.  
  1619. # names["lower"] and names["upper"] are made the canonical non-modem-control
  1620. # and modem-control versions of the TTY name respectively.
  1621. # These are the name as passed but with the first alpha char after "tty"
  1622. # (if any) converted to lowercase and uppercase respectively.
  1623. # If the TTY name is an absolute path and is not of the form /dev/tty*, the
  1624. # case conversion is not done.
  1625. # Any leading /dev/ is stripped.  If the name does not contain any directory
  1626. # component and does not begin with "tty", it is prefixed with "tty".
  1627. function bothTTYnames(tty,names,  sTTY,letter) {
  1628.     sTTY = shortTTY(tty)
  1629.     if (tty ~ "^/" && tty !~ "^/dev/tty") {
  1630.     names["lower"] = names["upper"] = sTTY
  1631.     return 1
  1632.     }
  1633.     match(sTTY,"[a-zA-Z][^a-zA-Z]*$")
  1634.     letter = substr(sTTY,RSTART,1)
  1635.     tty = (tty ~ "/") ? "" : "tty"
  1636.     names["upper"] = tty PasteStr(sTTY,RSTART,toupper(letter))
  1637.     names["lower"] = tty PasteStr(sTTY,RSTART,tolower(letter))
  1638.     return 0
  1639. }
  1640. ### end canonTTY library
  1641. ### Begin Strings routines
  1642.  
  1643. # Delete the string starting at Start and having length Num from the middle
  1644. # of string S, and return the remaining part.
  1645. function DelStr(S,Start,Num) {
  1646.     return substr(S,1,Start - 1) substr(S,Start+Num)
  1647. }
  1648.  
  1649. # Insert NewStr into S at position Pos (between the Pos-1'th and the Pos'th
  1650. # characters).  S is padded with spaces if neccessary.
  1651. function InsertStr(S,Pos,NewStr,  e) {
  1652.     e = length(S)+1    # The position after the end of S
  1653.     if (e >= Pos)
  1654.     return substr(S,1,Pos-1) NewStr substr(S,Pos)
  1655.     for (; e < Pos; e++)
  1656.     S = S " "
  1657.     return S NewStr
  1658. }
  1659.  
  1660. # Search for char C in string S starting at position Pos, in the direction
  1661. # specified by Dir (1 = forward, -1 = backward).
  1662. # Return position char found at for success, 0 if not found before start or end
  1663. # of string.
  1664. function FindC(S,Pos,C,Dir,  FoundC) {
  1665.     while (Pos > 0 && (FoundC = substr(S,Pos,1)) != C && FoundC != "")
  1666.     Pos += Dir
  1667.     if (FoundC == C)
  1668.     return Pos
  1669.     else
  1670.     return 0
  1671. }
  1672.  
  1673. # Split string S into array Arr, one character per index, starting with 1.
  1674. # The number of characters in the string is returned.
  1675. function SplitS(S,Arr,  len,i) {
  1676.     len = length(S)
  1677.     for (i = 1; i <= len; i++)
  1678.     Arr[i] = substr(S,i,1)
  1679.     return len
  1680. }
  1681.  
  1682. # Paste NewStr onto S at position Pos, overwriting what was there
  1683. # S is padded with spaces if neccessary.
  1684. function PasteStr(S,Pos,NewStr,  e) {
  1685.     e = length(S)+1    # The position after the end of S
  1686.     if (e >= Pos)
  1687.     return substr(S,1,Pos-1) NewStr substr(S,Pos+length(NewStr))
  1688.     for (; e < Pos; e++)
  1689.     S = S " "
  1690.     return S NewStr
  1691. }
  1692.  
  1693. ### End Strings routines
  1694. ### Begin sign routines
  1695.  
  1696. function sign(value) {
  1697.     if (value > 0)
  1698.     return 1
  1699.     else if (value < 0)
  1700.     return -1
  1701.     else
  1702.     return 0
  1703. }
  1704.  
  1705. function abs(value) {
  1706.     if (value >= 0)
  1707.     return value
  1708.     else
  1709.     return -value
  1710. }
  1711.  
  1712. ### End sign routines
  1713.