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

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) exp_notify.gawk 2.1 96/06/04
  4. # 92/09/06 john h. dubois iii (john@armory.com)
  5. # 92/12/12 Modified to act according to mail preferences file.
  6. # 92/12/20 Report negative balances as credits.
  7. # 92/12/27 Don't report 0 balance even if min notify is negative
  8. # 93/06/25 Added positive balance total display.
  9. # 93/07/03 Added all command line options.
  10. # 94/07/10 v 2.0: Generalized from rent/house-exp processing program.
  11. # 95/01/02 Added j option/SUBJECT parameter.
  12. # 95/08/26 Use only the basename of the program name for the short name.
  13. # 96/03/01 Prefix name to error messages
  14. # 96/06/04 exec commands.
  15.  
  16. # todo: record amounts owed, and let users specify that they are only mailed
  17. # when the amount owed changes, and also at some regular interval (e.g.
  18. # monthly).
  19.  
  20. BEGIN {
  21.     Lib = "/usr/local/lib/balances"
  22.     MapFile = "namemap"
  23.     PrefFile = ".balances"
  24.     Signature = "the balances administrator"
  25.  
  26.     rcFile = ".balconfig"
  27.     OptList = "eotn:p:l:s:r:j:hx"
  28.     Name = "exp_notify"
  29.     Subject = "Balances"
  30.     Usage = \
  31. "Usage: " Name " [-heot] [-n<mapfile>] [-p<preffile>] [-l<libdir>]\n"\
  32. "       [-s<signature>] [-r<reports>] [-j<subject>] [config file]"
  33.  
  34.     ARGC = Opts(Name,Usage,OptList,0)
  35.     if (ARGC == -1) {
  36.     print OptErr
  37.     exit(1)
  38.     }
  39.     if (ARGC > 2) {
  40.     print Usage
  41.     print "Use -h for help."
  42.     exit(1)
  43.     }
  44.     if ("h" in Options) {
  45.     printf \
  46. "%s: notify users by mail of the balances they owe.\n"\
  47. "%s\n"\
  48. "%s processes the output of programs that track balances\n"\
  49. "and mails users a report describing what they owe.  Those who do not owe\n"\
  50. "anything are not mailed.\n"\
  51. "The information used to configure %s can be supplied on the command\n"\
  52. "line or in a configuration file.  If no config file is specified on the\n"\
  53. "command line, the default file %s is searched for in the user's home\n"\
  54. "directory.  Values assigned in a configuration file are given in the form\n"\
  55. "varname=value\n"\
  56. "\n"\
  57. "Configuration parameters:\n"\
  58. "\n"\
  59. "The report string is given with -r on the command line or assigned to\n"\
  60. "REPORTS in a config file.  There is no default for it, so one of the two\n"\
  61. "must be given.  It has the form:\n"\
  62. "Program arg ...,Mail Preferences Name,Full Name,Default Threshold:...\n"\
  63. "A report description is given for each report to be run.  Report\n"\
  64. "descriptions are separated from each other by colons.  Each report\n"\
  65. "description has one to four fields, separated from each other by commas.\n"\
  66. "The Program field gives the name of the balance program to execute, and\n"\
  67. "any arguments it needs.  The balance programs must either be in the\n"\
  68. "search path or in the library directory.\n"\
  69. "The Mail Preferences Name field gives a one-word name for the report, for\n"\
  70. "use in a preferences file (described later).  If not given or null, the\n"\
  71. "basename of the first word of the Program field is used.\n"\
  72. "The Full Name field gives a verbose name for the report, to be used in\n"\
  73. "mail sent to users.  If not given or null, the Mail Preferences Name is\n"\
  74. "used.\n"\
  75. "The Default Threshold field gives a default threshold for informing users\n"\
  76. "that they have a balance of this type, in fractional dollars.  If not\n"\
  77. "given or null, 0.01 is used, which will inform users of a positive balance\n"\
  78. "of one cent or more.\n"\
  79. "Example: exp -b,expenses,house expenses,10.00:rent -bn\n"\
  80. "Balances programs should produce output in this form:\n"\
  81. "real-name amount [comment]\n"\
  82. "real-name is used to look up the email address to send the report to.\n"\
  83. "The amount should be in dollars (with optional fractional part) without a\n"\
  84. "leading $.  If there are three or more fields, the extra fields are taken\n"\
  85. "to be a comment and are mailed along with the reports.\n"\
  86. "\n"\
  87. "The mapfile is given with -n on the command line or assigned to MAPFILE in\n"\
  88. "a config file.  It maps names given in the output of the balance programs\n"\
  89. "to email addresses to which balance reports should be sent.  If the email\n"\
  90. "address is a local account name, it is also used to find a preferences\n"\
  91. "file in the user's home directory.  The format of the mapfile is:\n"\
  92. "email-address   name\n"\
  93. "...\n"\
  94. "There must be a name given for every name that will appear in the output\n"\
  95. "of any of the balances programs.  Empty lines and lines that begin with\n"\
  96. "'#' are ignored.  Multiple names can be mapped to a single email address.\n"\
  97. "The default is \"%s\" in the library directory.\n"\
  98. "\n"\
  99. "The preferences file is given with -p on the command line or assigned to\n"\
  100. "PREFFILE in a config file.  It is the name of the file that local users\n"\
  101. "may put in their home directories to control the threshold at which they\n"\
  102. "are informed of balances of various types.  The format is:\n"\
  103. "balance-type threshold\n"\
  104. "...\n"\
  105. "where the thresholds are decimal dollar amounts without a leading '$'.\n"\
  106. "The balance-type is the one-word name for the report as given in a report\n"\
  107. "description.  Unrecognized names are ignored.  The default is \"%s\".\n"\
  108. "\n"\
  109. "The libdir is given with -l on the command line or assigned to LIBDIR in a\n"\
  110. "config file.  It is where the mapfile is searched for if an explicit\n"\
  111. "mapfile name is not given, and where the balances programs are searched\n"\
  112. "for in addition to the execution path.  The default is %s.\n"\
  113. "\n"\
  114. "The signature is given with -s on the command line or assigned to\n"\
  115. "SIGNATURE in a preferences file.  It is the tagline appended to mail sent\n"\
  116. "to users.  The default is \"%s\".\n"\
  117. "\n"\
  118. "The subject line used when the mail is sent given with -j on the command\n"\
  119. "line or assigned to SUBJECT in a preferences file.  The default is \"%s\".\n"\
  120. "\n"\
  121. "Other options (put these var name alone on a line in the config file):\n"\
  122. "-h: Print this help information.\n"\
  123. "-e: Email balance reports to users.  Config file var: MAIL\n"\
  124. "-o: Print a report on all users to the standard output (default).\n"\
  125. "    Both -e and -o may be given.  Config file var: OUTPUT\n"\
  126. "-t: Show exactly what would be mailed to users.  Config file var: TELL\n"\
  127. "\n"\
  128. "Example config file:\n"\
  129. "REPORTS=exp -b,expenses,house expenses,10.00:rent -bn\n"\
  130. "PREFFILE=.house\n"\
  131. "MAPFILE=/usr/local/lib/house/inmates\n"\
  132. "LIBDIR=/usr/local/lib/house\n"\
  133. "SUBJECT=House balances\n"\
  134. "SIGNATURE=the daemon of expensives\n",
  135.     Name,Usage,Name,Name,rcFile,MapFile,PrefFile,Lib,Signature,Subject
  136.     exit(0)
  137.     }
  138.     Debug = "x" in Options
  139.     if (ARGC == 2)
  140.     rcFile = ARGV[1]
  141.     else
  142.     rcFile = "~/" rcFile
  143.     if (InitOpts(rcFile,Options2,OptList,
  144. "MAIL,OUTPUT,TELL,MAPFILE,PREFFILE,LIBDIR,SIGNATURE,REPORTS,SUBJECT",0) == -1)
  145.     {
  146.     print Name ": " OptErr ".  Use -h for help."
  147.     exit 1
  148.     }
  149.     if (ARGC == 2 && !READ_RCFILE) {
  150.     printf Name ": Could not read config file '%s'.\n",rcFile
  151.     exit 1
  152.     }
  153.     # Avoid overwriting options givenon cmd line
  154.     for (e in Options2)
  155.     if (!(e in Options))
  156.         Options[e] = Options2[e]
  157.  
  158.     if ("l" in Options)
  159.     Lib = Options["l"]
  160.     if ("n" in Options)
  161.     MapFile = Options["n"]
  162.     else
  163.     MapFile = Lib "/" MapFile
  164.     if ("p" in Options)
  165.     PrefFile = Options["p"]
  166.     if ("s" in Options)
  167.     Signature = Options["s"]
  168.     if ("j" in Options)
  169.     Subject = Options["j"]
  170.     if (!("r" in Options)) {
  171.     print Name ": No report string given."
  172.     exit 1
  173.     }
  174.  
  175.     if (!(NumBalances = SetupReports(Options["r"])))
  176.     exit 1
  177.  
  178.     # This file maps real names<->email addresses
  179.     # Each line has two fields, email address and real name
  180.     # Multiple lines may be used to map multiple real names to a single
  181.     # email address, since the balance programs may use different names
  182.     # Name2Addr[real-name] = email address
  183.     while ((ret = (getline < MapFile)) == 1)
  184.     if (NF && $1 !~ "^#") {
  185.         Name2Addr[$2] = $1
  186.         if (!($1 in Addr2Name))
  187.         Addr2Name[$1] = $2
  188.     }
  189.     close(MapFile)
  190.     if (ret)
  191.     ErrExit("Error reading user mapping file " MapFile,1)
  192.  
  193.     if (Debug)
  194.     print "Reading preference files (and processing /etc/passwd!)" \
  195.     > "/dev/stderr"
  196.     # Read users' balance mail preference files
  197.     ReadPrefs(Addr2Name,PrefFile,Thresholds)
  198.  
  199.     # Run all balance programs
  200.     for (BalNum = 1; BalNum <= NumBalances; BalNum++)
  201.     if (ReadBalances(BalanceProgs[BalNum],Name2Addr,Balances,Comments))
  202.         ErrExit(\
  203.         "Error running balance program \"" BalanceProgs[BalNum] "\"")
  204.  
  205.     Mail = "e" in Options
  206.     Output = "o" in Options
  207.     Tell = "t" in Options
  208.     if (!Mail)
  209.     Output = 1
  210.     for (User in Addr2Name) {
  211.     Balance = ProcUser(User,Output,Mail,Tell,Balances,Comments)
  212.     if (Debug)
  213.         printf "Debug: balance for %s: %.02f\n",User,Balance \
  214.         > "/dev/stderr"
  215.     Total += Balance    # Accumulate total balances for all users
  216.     if (Balance > 0)
  217.         PosTotal += Balance    # Accumulate positive balance for all users
  218.     }
  219.  
  220.     if (Output) {
  221.     printf "\nTotal of positive balances: %.02f\n",PosTotal
  222.     printf "Total of all balances: %.02f\n",Total
  223.     }
  224. }
  225.  
  226. # Sets the following global arrays:
  227. #    BalanceTypes[1..n]: Long names of balance types, used in mailed messages.
  228. #    BalanceProgs[1..n]: Balance program names & args to run.
  229. #    Thresholds[1..n]:  Default notification thresholds.
  230. #    PrefTypes[1..n]: Short names of balance types as used in preferences file.
  231. #    PrefType2Num[]: Mapping of balance types given in a preferences
  232. #        file to expense type number.
  233. # Types contains the report descriptions string.
  234. # Example:
  235. # "exp -b,expenses,house expenses,10.00:rent -bn"
  236. function SetupReports(Types,  
  237. NumReports,RepNum,Command,ShortName,Thresh,NumF,CmdF) {
  238.     if (!(NumReports = split(Types,Reports,":"))) {
  239.     printf "%s: Empty report setup string: %s\n",Name,Types > "/dev/stderr"
  240.     return 0
  241.     }
  242.     for (RepNum = 1; RepNum <= NumReports; RepNum++) {
  243.     NumF = split(Reports[RepNum],Fields,",")
  244.     if (!((1 <= NumF) && (NumF <= 4))) {
  245.         printf "%s: Wrong number of fields in report setup string: %s\n",
  246.         Name,Reports[RepNum] > "/dev/stderr"
  247.         return 0
  248.     }
  249.     BalanceProgs[RepNum] = Fields[1]
  250.  
  251.     # Get short name (mail preferences name).
  252.     if (NumF >= 2 && Fields[2] != "") {  # If a 2nd field, it's short name
  253.         if ((ShortName = Fields[2]) ~ "[ \t]") {
  254.         printf \
  255.         "%s: Space or tab in preferences file balance name: %s\n",
  256.         Name,ShortName > "/dev/stderr"
  257.         return 0
  258.         }
  259.     }
  260.     else {            # If not, inherit from command name
  261.         split(Fields[1],CmdF)
  262.         ShortName = CmdF[1]
  263.         sub(".*/","",ShortName)    # get rid of path
  264.     }
  265.     PrefTypes[RepNum] = ShortName
  266.     PrefType2Num[ShortName] = RepNum
  267.  
  268.     if (NumF >= 3 && Fields[3] != "")    # If a 3rd field, it's long name
  269.         BalanceTypes[RepNum] = Fields[3]
  270.     else            # If not, inherit from short name
  271.         BalanceTypes[RepNum] = ShortName
  272.  
  273.     if (NumF == 4 && Fields[4] != "") {    # If a 4th field, threshold
  274.         if ((Thresh = Fields[4]) !~ /^[-+.0-9]+$/) {
  275.         printf "%s: Bad threshold in setup string: %s\n",
  276.         Name,Thresh > "/dev/stderr"
  277.         return 0
  278.         }
  279.     }
  280.     else    # If not, default is 1 cent
  281.         Thresh = 0.01
  282.     Thresholds[RepNum] = Thresh + 0
  283.     }
  284.     return NumReports
  285. }
  286.  
  287. # ProcUser: Process user balances & possibly mail them to user.
  288. # User: User (login name) to process balances for
  289. # Output: Whether to print info for balance administrator to output
  290. # Mail: Whether to send mail.
  291. # Globals used: NumBalances, Signature, Subject, BalanceTypes[], Thresholds[],
  292. #               Addr2Name[], PrefTypes[],
  293. function ProcUser(User,Output,Mail,ShowMessages,Balances,Comments,
  294. BigMsg,NamePrinted,BalNum,Balance,Msg,Cmd,MessageFormat,TotBalance) {
  295.  
  296.     # Keep track of whether we have yet emitted a name for the user to
  297.     # the stream that will be mailed to the balances administrator
  298.     NamePrinted = 0
  299.     for (BalNum = 1; BalNum <= NumBalances; BalNum++) {
  300.     # If user has a balance of this type...
  301.     if ((BalNum,User) in Balances) {    
  302.         TotBalance += Balance = Balances[BalNum,User]
  303.         # Generate a balance line to be mailed to the user,
  304.         # describing either a debit or credit
  305.         if (Balance >= 0)
  306.         MessageFormat = "You owe $%.02f for %s"
  307.         else
  308.         MessageFormat = "You have a credit of $%.02f for %s"
  309.         Msg = sprintf(MessageFormat,abs(Balance),BalanceTypes[BalNum])
  310.         if (Comments[BalNum,User] != "")
  311.         Msg = Msg ": " Comments[BalNum,User]
  312.         # If the balance is positive, print the notification message
  313.         # for the balance admin too, if wanted
  314.         if (Balance > 0 && Output) {
  315.         if (!NamePrinted) {
  316.             printf "** %s (%s)\n",Addr2Name[User],User
  317.             NamePrinted = 1
  318.         }
  319.         printf "%s\n",Msg
  320.         }
  321.         # If user has a nonzero balance and it's larger than the
  322.         # user's threshold for this balance type, add the notification
  323.         # message to the message that will be mailed to the user.
  324.         if (Balance && Balance >= Thresholds[User,BalNum])
  325.         BigMsg = BigMsg Msg "\n"
  326.         else if (Balance > 0 && Output)
  327.         # If the user has a positive balance & is not going to
  328.         # get a message about it, let the balances admin know
  329.         printf "(min %s balance notification: %0.2f)\n",
  330.         PrefTypes[BalNum],Thresholds[User,BalNum]
  331.     }
  332.     }
  333.     # If the user has a message built up, mail it.
  334.     if (BigMsg != "" && (ShowMessages || Mail)) {
  335.     Cmd = "exec mail -s '" Subject "' " User
  336.     if (TotBalance > 0)
  337.         BigMsg = BigMsg "\nThis is now due and payable.  Thank you :-)"
  338.     BigMsg = BigMsg "\n\n     -- " Signature
  339.     if (Mail) {
  340.         print BigMsg | Cmd
  341.         close(Cmd)
  342.     }
  343.     if (ShowMessages)
  344.         printf "#######\nMessage to be piped into \"%s\":\n%s\n#######\n",
  345.         Cmd,BigMsg
  346.     }
  347.     return TotBalance
  348. }
  349.  
  350. # Globals: PW stuff.
  351. #    Thresholds[User,1..n]:  User notification thresholds.
  352. function ReadPrefs(Addr2Name,PrefFile,Thresholds,
  353. AcctName,PWEnt,MailPrefFile,BalNum) {
  354.     # The mail preferences file includes lines of the form
  355.     # balance-type min-balance-notify
  356.     # Other lines are ignored
  357.     # Thresholds[account-name,balance-num] = min-balance-notify
  358.     for (AcctName in Addr2Name) {
  359.     # Look for a pref file only for local users
  360.     if (AcctName ~ "^[a-zA-Z0-9_]+$")
  361.         if (!getpwnam(AcctName,PWEnt)) {
  362.         printf "%s: Could not get passwd entry for %s.\n",
  363.         Name,AcctName > "/dev/stderr"
  364.         }
  365.         else {
  366.         MailPrefFile = PWEnt[PW_HOME] "/" PrefFile
  367.         ReadPrefFile(MailPrefFile,Thresholds,AcctName,PrefType2Num)
  368.         }
  369.     for (BalNum in Thresholds)
  370.         if (!((AcctName,BalNum) in Thresholds))
  371.         Thresholds[AcctName,BalType] = Thresholds[BalType]
  372.     }
  373. }
  374.  
  375. # The indexes of PrefType2Num are the balance types that may appear in a
  376. # preferences file.  The value of each index is the preference type number
  377. # to use as an index in Arr[].
  378. # If Filename is readable, for each balance type name, 
  379. # Arr[Dim,PrefNum] is set to the value it gives.
  380. # If Dim is null, the value is assigned to just Arr[PrefNum].
  381. # Globals: None.
  382. function ReadPrefFile(Filename,Arr,Dim,PrefType2Num,  ret) {
  383.     if (Dim != "")
  384.     Dim = Dim SUBSEP
  385.     while ((ret = (getline < Filename)) == 1) {
  386.     if ($1 in PrefType2Num) {
  387.         if ($2 !~ "^-?[0-9]+\\.?[0-9]*$")
  388.         printf "%s: Bad value for balance type \"%s\" in %s.\n",
  389.         Name,$1,Filename > "/dev/stderr"
  390.         else {
  391.         Arr[Dim PrefType2Num[$1]] = $2
  392.         if (Debug)
  393.             printf "Reading %s: Setting preference for %s to %s.\n",
  394.             Filename,$1,$2 > "/dev/stderr"
  395.         }
  396.     }
  397.     }
  398.     if (ret != 0 && Debug)
  399.     printf \
  400.     "%s: Could not read pref file %s: no such file or not readable.\n",
  401.     Name,Filename > "/dev/stderr"
  402.     close(Filename)
  403. }
  404.  
  405. # ReadBalances: execute a balances program
  406. # The output should have the form
  407. # RealName Amount Comment
  408. # Name2Addr[] is used to map real names to email addresses
  409. # For each output line, Balances[BalNum,account-name] is set to the output
  410. # and Comments[BalNum,account-name] is set to any comment text
  411. # Globals changed: $*
  412. function ReadBalances(Cmd,Name2Addr,Balances,Comments,
  413. Addr,ret,FullCmd,Err) {
  414.     FullCmd = "PATH=$PATH:" Lib "; " Cmd
  415.     while ((ret = (FullCmd | getline)) == 1) {
  416.     if (!NF)
  417.         continue
  418.     if (!($1 in Name2Addr)) {
  419.         printf "%s: Unknown name in output of \"%s\": %s\n",Name,Cmd,$1 \
  420.         > "/dev/stderr"
  421.         Err = 1
  422.     }
  423.     else {
  424.         Addr = Name2Addr[$1]
  425.         Balances[BalNum,Addr] = $2
  426.         $1 = $2 = ""
  427.         if ($0 !~ "^[ \t]*$") {
  428.         Comments[BalNum,Addr] = $0
  429.         # Get rid of leading whitespace left from nulling $1 and $2
  430.         sub("^[ \t]+","",Comments[BalNum,Addr])
  431.         }
  432.     }
  433.     }
  434.     ret = ret || close(FullCmd) || Err
  435.     return (ret)
  436. }
  437.  
  438.  
  439. function ErrExit(Message,ExitValue) {
  440.     printf "%s: %s.\n",Name,Message > "/dev/stderr"
  441.     exit(ExitValue)
  442. }
  443.  
  444. function sign(value) {
  445.     if (value > 0)
  446.     return 1
  447.     else if (value < 0)
  448.     return -1
  449.     else
  450.     return 0
  451. }
  452.  
  453. function abs(value) {
  454.     if (value >= 0)
  455.     return value
  456.     else
  457.     return -value
  458. }
  459.  
  460. ### Start of ProcArgs library
  461. # @(#) ProcArgs 1.11 96/12/08
  462. # 92/02/29 john h. dubois iii (john@armory.com)
  463. # 93/07/18 Added "#" arg type
  464. # 93/09/26 Do not count -h against MinArgs
  465. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  466. #          Removed meaning of "+" or "-" by itself.
  467. # 94/03/08 Added & option and *()< option types.
  468. # 94/04/02 Added NoRCopt to Opts()
  469. # 94/06/11 Mark numeric variables as such.
  470. # 94/07/08 Opts(): Do not require any args if h option is given.
  471. # 95/01/22 Record options given more than once.  Record option num in argv.
  472. # 95/06/08 Added ExclusiveOptions().
  473. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  474. #          Expand $VARNAME at the start of its filenames.
  475. #          Let varname=0 and -option- turn off an option.
  476. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  477. #          of the vars should be searched for in the environment.
  478. #          Check for duplicate rcfiles.
  479. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  480. #          now return various negatives values on error, not just -1, and
  481. #          Opts() may set Err to various positive values, not just 1.
  482. #          Added AllowUnrecOpt.
  483. # 96/05/23 Check type given for & option
  484. # 96/06/15 Re-port to awk
  485. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  486. #          used by other functions.
  487. # 96/10/15 Added OptChars
  488. # 96/11/01 Added exOpts arg to Opts()
  489. # 96/11/16 Added ; type
  490. # 96/12/08 Added Opt2Set() & Opt2Sets()
  491. # 96/12/27 Added CmdLineOpt()
  492.  
  493. # optlist is a string which contains all of the possible command line options.
  494. # A character followed by certain characters indicates that the option takes
  495. # an argument, with type as follows:
  496. # :    String argument
  497. # ;    Non-empty string argument
  498. # *    Floating point argument
  499. # (    Non-negative floating point argument
  500. # )    Positive floating point argument
  501. # #    Integer argument
  502. # <    Non-negative integer argument
  503. # >    Positive integer argument
  504. # The only difference the type of argument makes is in the runtime argument
  505. # error checking that is done.
  506.  
  507. # The & option is a special case used to get numeric options without the
  508. # user having to give an option character.  It is shorthand for [-+.0-9].
  509. # If & is included in optlist and an option string that begins with one of
  510. # these characters is seen, the value given to "&" will include the first
  511. # char of the option.  & must be followed by a type character other than ":"
  512. # or ";".
  513. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  514.  
  515. # Strings in argv[] which begin with "-" or "+" are taken to be
  516. # strings of options, except that a string which consists solely of "-"
  517. # or "+" is taken to be a non-option string; like other non-option strings,
  518. # it stops the scanning of argv and is left in argv[].
  519. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  520. # If an option takes an argument, the argument may either immediately
  521. # follow it or be given separately.
  522. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  523. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  524. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  525. # this feature had a flaw that caused problems in some cases.  See the OptChars
  526. # parameter to explicitly set the option-specifier characters.
  527.  
  528. # If an option that does not take an argument is given,
  529. # an index with its name is created in Options and its value is set to the
  530. # number of times it occurs in argv[].
  531.  
  532. # If an option that does take an argument is given, an index with its name is
  533. # created in Options and its value is set to the value of the argument given
  534. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  535. # If an option that takes an argument is given more than once,
  536. # Options[option-name,"count"] is incremented, and the value is assigned to
  537. # the index (option-name,instance) where instance is 2 for the second occurance
  538. # of the option, etc.
  539. # In other words, the first time an option with a value is encountered, the
  540. # value is assigned to an index consisting only of its name; for any further
  541. # occurances of the option, the value index has an extra (count) dimension.
  542.  
  543. # The sequence number for each option found in argv[] is stored in
  544. # Options[option-name,"num",instance], where instance is 1 for the first
  545. # occurance of the option, etc.  The sequence number starts at 1 and is
  546. # incremented for each option, both those that have a value and those that
  547. # do not.  Options set from a config file have a value of 0 assigned to this.
  548.  
  549. # Options and their arguments are deleted from argv.
  550. # Note that this means that there may be gaps left in the indices of argv[].
  551. # If compress is nonzero, argv[] is packed by moving its elements so that
  552. # they have contiguous integer indices starting with 0.
  553. # Option processing will stop with the first unrecognized option, just as
  554. # though -- was given except that unlike -- the unrecognized option will not be
  555. # removed from ARGV[].  Normally, an error value is returned in this case.
  556. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  557. # be found, so the number of remaining arguments is returned instead.
  558. # If OptChars is not a null string, it is the set of characters that indicate
  559. # that an argument is an option string if the string begins with one of the
  560. # characters.  A string consisting solely of two of the same option-indicator
  561. # characters stops the scanning of argv[].  The default is "-+".
  562. # argv[0] is not examined.
  563. # The number of arguments left in argc is returned.
  564. # If an error occurs, the global string OptErr is set to an error message
  565. # and a negative value is returned.
  566. # Current error values:
  567. # -1: option that required an argument did not get it.
  568. # -2: argument of incorrect type supplied for an option.
  569. # -3: unrecognized (invalid) option.
  570. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  571. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  572. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  573. {
  574. # ArgNum is the index of the argument being processed.
  575. # ArgsLeft is the number of arguments left in argv.
  576. # Arg is the argument being processed.
  577. # ArgLen is the length of the argument being processed.
  578. # ArgInd is the position of the character in Arg being processed.
  579. # Option is the character in Arg being processed.
  580. # Pos is the position in OptList of the option being processed.
  581. # NumOpt is true if a numeric option may be given.
  582.     ArgsLeft = argc
  583.     NumOpt = index(OptList,"&")
  584.     OptionNum = 0
  585.     if (OptChars == "")
  586.     OptChars = "-+"
  587.     while (OptChars != "") {
  588.     c = substr(OptChars,1,1)
  589.     OptChars = substr(OptChars,2)
  590.     OptCharSet[c]
  591.     OptTerm[c c]
  592.     }
  593.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  594.     Arg = argv[ArgNum]
  595.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  596.         break    # Not an option; quit
  597.     if (Arg in OptTerm) {
  598.         delete argv[ArgNum]
  599.         ArgsLeft--
  600.         break
  601.     }
  602.     ArgLen = length(Arg)
  603.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  604.         Option = substr(Arg,ArgInd,1)
  605.         if (NumOpt && Option ~ /[-+.0-9]/) {
  606.         # If this option is a numeric option, make its flag be & and
  607.         # its option string flag position be the position of & in
  608.         # the option string.
  609.         Option = "&"
  610.         Pos = NumOpt
  611.         # Prefix Arg with a char so that ArgInd will point to the
  612.         # first char of the numeric option.
  613.         Arg = "&" Arg
  614.         ArgLen++
  615.         }
  616.         # Find position of flag in option string, to get its type (if any).
  617.         # Disallow & as literal flag.
  618.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  619.         if (AllowUnrecOpt) {
  620.             Escape = 1
  621.             break
  622.         }
  623.         else {
  624.             OptErr = "Invalid option: " specGiven Option
  625.             return -3
  626.         }
  627.         }
  628.  
  629.         # Find what the value of the option will be if it takes one.
  630.         # NeedNextOpt is true if the option specifier is the last char of
  631.         # this arg, which means that if the option requires a value it is
  632.         # the next arg.
  633.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  634.         if (GotValue = ArgNum + 1 < argc)
  635.             Value = argv[ArgNum+1]
  636.         }
  637.         else {    # Value is included with option
  638.         Value = substr(Arg,ArgInd + 1)
  639.         GotValue = 1
  640.         }
  641.  
  642.         if (HadValue = AssignVal(Option,Value,Options,
  643.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  644.         specGiven)) {
  645.         if (HadValue < 0)    # error occured
  646.             return HadValue
  647.         if (HadValue == 2)
  648.             ArgInd++    # Account for the single-char value we used.
  649.         else {
  650.             if (NeedNextOpt) {    # option took next arg as value
  651.             delete argv[++ArgNum]
  652.             ArgsLeft--
  653.             }
  654.             break    # This option has been used up
  655.         }
  656.         }
  657.     }
  658.     if (Escape)
  659.         break
  660.     # Do not delete arg until after processing of it, so that if it is not
  661.     # recognized it can be left in ARGV[].
  662.     delete argv[ArgNum]
  663.     ArgsLeft--
  664.     }
  665.     if (compress != 0) {
  666.     dest = 1
  667.     src = argc - ArgsLeft + 1
  668.     for (count = ArgsLeft - 1; count; count--) {
  669.         ARGV[dest] = ARGV[src]
  670.         dest++
  671.         src++
  672.     }
  673.     }
  674.     return ArgsLeft
  675. }
  676.  
  677. # Assignment to values in Options[] occurs only in this function.
  678. # Option: Option specifier character.
  679. # Value: Value to be assigned to option, if it takes a value.
  680. # Options[]: Options array to return values in.
  681. # ArgType: Argument type specifier character.
  682. # GotValue: Whether any value is available to be assigned to this option.
  683. # Name: Name of option being processed.
  684. # OptionNum: Number of this option (starting with 1) if set in argv[],
  685. #     or 0 if it was given in a config file or in the environment.
  686. # SingleOpt: true if the value (if any) that is available for this option was
  687. #     given as part of the same command line arg as the option.  Used only for
  688. #     options from the command line.
  689. # specGiven is the option specifier character use, if any (e.g. - or +),
  690. # for use in error messages.
  691. # Global variables: OptErr
  692. # Return value: negative value on error, 0 if option did not require an
  693. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  694. # the arg.
  695. # Current error values:
  696. # -1: Option that required an argument did not get it.
  697. # -2: Value of incorrect type supplied for option.
  698. # -3: Bad type given for option &
  699. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  700. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  701.     # If option takes a value...    [
  702.     NumTypes = "*()#<>]"
  703.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  704.     OptErr = "Bad type given for & option"
  705.     return -3
  706.     }
  707.  
  708.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  709.     if (!GotValue) {
  710.         if (Name != "")
  711.         OptErr = "Variable requires a value -- " Name
  712.         else
  713.         OptErr = "option requires an argument -- " Option
  714.         return -1
  715.     }
  716.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  717.         OptErr = Err
  718.         return -2
  719.     }
  720.     # Mark this as a numeric variable; will be propogated to Options[] val.
  721.     if (ArgType != ":" && ArgType != ";")
  722.         Value += 0
  723.     if ((Instance = ++Options[Option,"count"]) > 1)
  724.         Options[Option,Instance] = Value
  725.     else
  726.         Options[Option] = Value
  727.     }
  728.     # If this is an environ or rcfile assignment & it was given a value...
  729.     else if (!OptionNum && Value != "") {
  730.     UsedValue = 1
  731.     # If the value is "0" or "-" and this is the first instance of it,
  732.     # do not set Options[Option]; this allows an assignment in an rcfile to
  733.     # turn off an option (for the simple "Option in Options" test) in such
  734.     # a way that it cannot be turned on in a later file.
  735.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  736.         Instance = 1
  737.     else
  738.         Instance = ++Options[Option]
  739.     # Save the value even though this is a flag
  740.     Options[Option,Instance] = Value
  741.     }
  742.     # If this is a command line flag and has a - following it in the same arg,
  743.     # it is being turned off.
  744.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  745.     UsedValue = 2
  746.     if (Option in Options)
  747.         Instance = ++Options[Option]
  748.     else
  749.         Instance = 1
  750.     Options[Option,Instance]
  751.     }
  752.     # If this is a flag assignment without a value, increment the count for the
  753.     # flag unless it was turned off.  The indicator for a flag being turned off
  754.     # is that the flag index has not been set in Options[] but it has an
  755.     # instance count.
  756.     else if (Option in Options || !((Option,1) in Options))
  757.     # Increment number of times this flag seen; will inc null value to 1
  758.     Instance = ++Options[Option]
  759.     Options[Option,"num",Instance] = OptionNum
  760.     return UsedValue
  761. }
  762.  
  763. # Option is the option letter
  764. # Value is the value being assigned
  765. # Name is the var name of the option, if any
  766. # ArgType is one of:
  767. # :    String argument
  768. # ;    Non-null string argument
  769. # *    Floating point argument
  770. # (    Non-negative floating point argument
  771. # )    Positive floating point argument
  772. # #    Integer argument
  773. # <    Non-negative integer argument
  774. # >    Positive integer argument
  775. # specGiven is the option specifier character use, if any (e.g. - or +),
  776. # for use in error messages.
  777. # Returns null on success, err string on error
  778. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  779.     if (ArgType == ":")
  780.     return ""
  781.     if (ArgType == ";") {
  782.     if (Value == "")
  783.         Err = "must be a non-empty string"
  784.     }
  785.     # A number begins with optional + or -, and is followed by a string of
  786.     # digits or a decimal with digits before it, after it, or both
  787.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  788.     Err = "must be a number"
  789.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  790.     Err = "may not include a fraction"
  791.     else if (ArgType ~ "[()<>]" && Value < 0)
  792.     Err = "may not be negative"
  793.     # (
  794.     else if (ArgType ~ "[)>]" && Value == 0)
  795.     Err = "must be a positive number"
  796.     if (Err != "") {
  797.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  798.     if (Name != "")
  799.         return ErrStr "variable " substr(Name,1,1) " " Err
  800.     else {
  801.         if (Option == "&")
  802.         Option = Value
  803.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  804.     }
  805.     }
  806.     else
  807.     return ""
  808. }
  809.  
  810. # Note: only the above functions are needed by ProcArgs.
  811. # The rest of these functions call ProcArgs() and also do other
  812. # option-processing stuff.
  813.  
  814. # Opts: Process command line arguments.
  815. # Opts processes command line arguments using ProcArgs()
  816. # and checks for errors.  If an error occurs, a message is printed
  817. # and the program is exited.
  818. #
  819. # Input variables:
  820. # Name is the name of the program, for error messages.
  821. # Usage is a usage message, for error messages.
  822. # OptList the option description string, as used by ProcArgs().
  823. # MinArgs is the minimum number of non-option arguments that this
  824. # program should have, non including ARGV[0] and +h.
  825. # If the program does not require any non-option arguments,
  826. # MinArgs should be omitted or given as 0.
  827. # rcFiles, if given, is a colon-seprated list of filenames to read for
  828. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  829. # by the value of the environment variable HOME.  If a filename begins with
  830. # $, the part from the character after the $ up until (but not including)
  831. # the first character not in [a-zA-Z0-9_] will be searched for in the
  832. # environment; if found its value will be substituted, if not the filename will
  833. # be discarded.
  834. # rcfiles are read in the order given.
  835. # Values given in them will not override values given on the command line,
  836. # and values given in later files will not override those set in earlier
  837. # files, because AssignVal() will store each with a different instance index.
  838. # The first instance of each variable, either on the command line or in an
  839. # rcfile, will be stored with no instance index, and this is the value
  840. # normally used by programs that call this function.
  841. # VarNames is a comma-separated list of variable names to map to options,
  842. # in the same order as the options are given in OptList.
  843. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  844. # searched for in the environment.  If set to -1, all values will be searched
  845. # for in the environment.  Values given in the environment will override
  846. # those given in the rcfiles but not those given on the command line.
  847. # NoRCopt, if given, is an additional letter option that if given on the
  848. # command line prevents the rcfiles from being read.
  849. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  850. # ExclusiveOptions() for a description of exOpts.
  851. # Special options:
  852. # If x is made an option and is given, some debugging info is output.
  853. # h is assumed to be the help option.
  854.  
  855. # Global variables:
  856. # The command line arguments are taken from ARGV[].
  857. # The arguments that are option specifiers and values are removed from
  858. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  859. # The number of elements in ARGV[] should be in ARGC.
  860. # After processing, ARGC is set to the number of elements left in ARGV[].
  861. # The option values are put in Options[].
  862. # On error, Err is set to a positive integer value so it can be checked for in
  863. # an END block.
  864. # Return value: The number of elements left in ARGV is returned.
  865. # Must keep OptErr global since it may be set by InitOpts().
  866. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  867. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  868.     if (MinArgs == "")
  869.     MinArgs = 0
  870.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  871.     optChars)
  872.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  873.     if (ArgsLeft >= 0) {
  874.         OptErr = "Not enough arguments"
  875.         Err = 4
  876.     }
  877.     else
  878.         Err = -ArgsLeft
  879.     printf "%s: %s.\nUse -h for help.\n%s\n",
  880.     Name,OptErr,Usage > "/dev/stderr"
  881.     exit 1
  882.     }
  883.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  884.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  885.     {
  886.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  887.     Err = -e
  888.     exit 1
  889.     }
  890.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  891.     {
  892.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  893.     Err = 1
  894.     exit 1
  895.     }
  896.     return ArgsLeft
  897. }
  898.  
  899. # ReadConfFile(): Read a file containing var/value assignments, in the form
  900. # <variable-name><assignment-char><value>.
  901. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  902. # line and whitespace between the variable name and the assignment character) 
  903. # is stripped.  Lines that do not contain an assignment operator or which
  904. # contain a null variable name are ignored, other than possibly being noted in
  905. # the return value.  If more than one assignment is made to a variable, the
  906. # first assignment is used.
  907. # Input variables:
  908. # File is the file to read.
  909. # Comment is the line-comment character.  If it is found as the first non-
  910. #     whitespace character on a line, the line is ignored.
  911. # Assign is the assignment string.  The first instance of Assign on a line
  912. #     separates the variable name from its value.
  913. # If StripWhite is true, whitespace around the value (whitespace between the
  914. #     assignment char and trailing whitespace on the line) is stripped.
  915. # VarPat is a pattern that variable names must match.  
  916. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  917. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  918. #     a line; no assignment operator is needed.  These variables are set in
  919. #     the output array with a null value.  Lines containing nothing but
  920. #     whitespace are still ignored.
  921. # Output variables:
  922. # Values[] contains the assignments, with the indexes being the variable names
  923. #     and the values being the assigned values.
  924. # Lines[] contains the line number that each variable occured on.  A flag set
  925. #     is record by giving it an index in Lines[] but not in Values[].
  926. # Return value:
  927. # If any errors occur, a string consisting of descriptions of the errors
  928. # separated by newlines is returned.  In no case will the string start with a
  929. # numeric value.  If no errors occur,  the number of lines read is returned.
  930. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  931. FlagsOK,
  932. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  933.     if (Comment != "")
  934.     Comment = "^" Comment
  935.     AssignLen = length(Assign)
  936.     if (VarPat == "")
  937.     VarPat = "."    # null varname not allowed
  938.     while ((Status = (getline Line < File)) == 1) {
  939.     LineNum++
  940.     sub("^[ \t]+","",Line)
  941.     if (Line == "")        # blank line
  942.         continue
  943.     if (Comment != "" && Line ~ Comment)
  944.         continue
  945.     if (Pos = index(Line,Assign)) {
  946.         Var = substr(Line,1,Pos-1)
  947.         Val = substr(Line,Pos+AssignLen)
  948.         if (StripWhite) {
  949.         sub("^[ \t]+","",Val)
  950.         sub("[ \t]+$","",Val)
  951.         }
  952.     }
  953.     else {
  954.         Var = Line    # If no value, var is entire line
  955.         Val = ""
  956.     }
  957.     if (!FlagsOK && Val == "") {
  958.         Errs = Errs \
  959.         sprintf("\nBad assignment on line %d of file %s: %s",
  960.         LineNum,File,Line)
  961.         continue
  962.     }
  963.     sub("[ \t]+$","",Var)
  964.     if (Var !~ VarPat) {
  965.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  966.         LineNum,File,Var)
  967.         continue
  968.     }
  969.     if (!(Var in Lines)) {
  970.         Lines[Var] = LineNum
  971.         if (Pos)
  972.         Values[Var] = Val
  973.     }
  974.     }
  975.     if (Status)
  976.     Errs = Errs "\nCould not read file " File
  977.     close(File)
  978.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  979. }
  980.  
  981. # Variables:
  982. # Data is stored in Options[].
  983. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  984. # Global vars:
  985. # Sets OptErr.  Uses ENVIRON[].
  986. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  987. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  988. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  989. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  990.     split("",filesRead,"")    # make awk know this is an array
  991.     NumVars = split(VarNames,Vars,",")
  992.     TypesInd = Ret = 0
  993.     if (EnvSearch == -1)
  994.     EnvSearch = NumVars
  995.     for (i = 1; i <= NumVars; i++) {
  996.     Var = Vars[i]
  997.     CharOpt = substr(OptList,++TypesInd,1)
  998.     if (CharOpt ~ "^[:;*()#<>&]$")
  999.         CharOpt = substr(OptList,++TypesInd,1)
  1000.     Map[Var] = CharOpt
  1001.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1002.     # Do not overwrite entries from environment
  1003.     if (i <= EnvSearch && Var in ENVIRON &&
  1004.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1005.         return Err
  1006.     }
  1007.  
  1008.     numrcFiles = split(rcFiles,fNames,":")
  1009.     for (i = 1; i <= numrcFiles; i++) {
  1010.     rcFile = fNames[i]
  1011.     if (rcFile ~ "^~/")
  1012.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1013.     else if (rcFile ~ /^\$/) {
  1014.         rcFile = substr(rcFile,2)
  1015.         match(rcFile,"^[a-zA-Z0-9_]*")
  1016.         envvar = substr(rcFile,1,RLENGTH)
  1017.         if (envvar in ENVIRON)
  1018.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1019.         else
  1020.         continue
  1021.     }
  1022.     if (rcFile in filesRead)
  1023.         continue
  1024.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1025.     # may be the same
  1026.     filesRead[rcFile]
  1027.     if ("x" in Options)
  1028.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1029.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1030.     if (retStr > 0)
  1031.         READ_RCFILE = 1
  1032.     else if (ret != "") {
  1033.         OptErr = retStr
  1034.         Ret = -1
  1035.     }
  1036.     for (Var in Lines)
  1037.         if (Var in Map) {
  1038.         if ((Err = AssignVal(Map[Var],
  1039.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1040.         Var in Values,Var,0)) < 0)
  1041.             return Err
  1042.         }
  1043.         else {
  1044.         OptErr = sprintf(\
  1045.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1046.         Lines[Var],rcFile)
  1047.         Ret = -1
  1048.         }
  1049.     }
  1050.  
  1051.     if ("x" in Options)
  1052.     for (Var in Map)
  1053.         if (Map[Var] in Options)
  1054.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1055.         "/dev/stderr"
  1056.         else
  1057.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1058.     return Ret
  1059. }
  1060.  
  1061. # OptSets is a semicolon-separated list of sets of option sets.
  1062. # Within a list of option sets, the option sets are separated by commas.  For
  1063. # each set of sets, if any option in one of the sets is in Options[] AND any
  1064. # option in one of the other sets is in Options[], an error string is returned.
  1065. # If no conflicts are found, nothing is returned.
  1066. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1067. # the exclusions presented by the first set of sets (ab,def,g) if:
  1068. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1069. # (a or b is in Options[]) AND (g is in Options) OR
  1070. # (d, e, or f is in Options[]) AND (g is in Options)
  1071. # An error will be returned due to the exclusions presented by the second set
  1072. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1073. # todo: make options given on command line unset options given in config file
  1074. # todo: that they conflict with.
  1075. function ExclusiveOptions(OptSets,Options,
  1076. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1077. SetNum,OSetNum) {
  1078.     NumSetSets = split(OptSets,SetSets,";")
  1079.     # For each set of sets...
  1080.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1081.     # NumSets is the number of sets in this set of sets.
  1082.     NumSets = split(SetSets[SetSet],Sets,",")
  1083.     # For each set in a set of sets except the last...
  1084.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1085.         s1 = Sets[SetNum]
  1086.         L1 = length(s1)
  1087.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1088.         # If any of the options in this set was given, check whether
  1089.         # any of the options in the other sets was given.  Only check
  1090.         # later sets since earlier sets will have already been checked
  1091.         # against this set.
  1092.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1093.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1094.             s2 = Sets[OSetNum]
  1095.             L2 = length(s2)
  1096.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1097.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1098.                 ErrStr = ErrStr "\n"\
  1099.                 sprintf("Cannot give both %s and %s options.",
  1100.                 c1,c2)
  1101.             }
  1102.     }
  1103.     }
  1104.     if (ErrStr != "")
  1105.     return substr(ErrStr,2)
  1106.     return ""
  1107. }
  1108.  
  1109. # The value of each instance of option Opt that occurs in Options[] is made an
  1110. # index of Set[].
  1111. # The return value is the number of instances of Opt in Options.
  1112. function Opt2Set(Options,Opt,Set,  count) {
  1113.     if (!(Opt in Options))
  1114.     return 0
  1115.     Set[Options[Opt]]
  1116.     count = Options[Opt,"count"]
  1117.     for (; count > 1; count--)
  1118.     Set[Options[Opt,count]]
  1119.     return count
  1120. }
  1121.  
  1122. # The value of each instance of option Opt that occurs in Options[] that
  1123. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1124. # Other values are made indexes of Set[].
  1125. # The return value is the number of instances of Opt in Options.
  1126. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1127.     ret = Opt2Set(Options,Opt,aSet)
  1128.     for (value in aSet)
  1129.     if (substr(value,1,1) == "!")
  1130.         nSet[substr(value,2)]
  1131.     else
  1132.         Set[value]
  1133.     return ret
  1134. }
  1135.  
  1136. # Returns true if option Opt was given on the command line.
  1137. function CmdLineOpt(Options,Opt,  i) {
  1138.     for (i = 1; (Opt,"num",i) in Options; i++)
  1139.     if (Options[Opt,"num",i] != 0)
  1140.         return 1
  1141.     return 0
  1142. }
  1143. ### End of ProcArgs library
  1144. ### Begin pwent library
  1145.  
  1146. # @(#) pwent.awk 1.2 96/06/27
  1147. # 92/08/10 john h. dubois III (john@armory.com)
  1148. # 93/12/13 fixed to not clobber $*
  1149. # 96/01/05 Send error messages to /dev/stderr
  1150. # 96/05/24 Let getpwnam() return a specific field if requested.
  1151. #          Added PW_REAL and PW_OFFICE.
  1152. # 96/06/03 Added Type field to getpwent()
  1153. # 96/06/24 Allow a Field to be requested for getpwent() also.
  1154. # 96/06/29 Added PW_RECORD, and getpwreal().
  1155. #          Changed PWLines to be index by record number instead of name.
  1156. # 96/11/17 Added getpwuid()
  1157.  
  1158. # Require: ReadShells()
  1159.  
  1160. # getpwent, getpwnam: get an entry from the passwd file.
  1161. # Each of the following passwd functions returns an array which contains
  1162. # a passwd file entry.  The array contains the fields of the entry.
  1163. # Global variables:
  1164. # The following variables are defined with the values of the indexes of the
  1165. # entries: PW_NAME, PW_PASSWORD, PW_UID, PW_GID, PW_GCOS, PW_HOME, PW_SHELL
  1166. # PWLines[] contains the lines of the password file, indexed by record number,
  1167. # starting with 1.
  1168. # _pwNames[] is a mapping of name to passwd record number.
  1169. # getpwentNum is the number of the next entry to be returned by getpwent().
  1170.  
  1171. # Left FS global because making it local does not work in gawk.
  1172. function ReadPasswd(  User,Line,i,Ind,ret,OFS) {
  1173.     if (PW_Name)
  1174.     return 1
  1175.     PW_NAME = 1
  1176.     PW_PASSWORD = 2
  1177.     PW_UID = 3
  1178.     PW_GID = 4
  1179.     PW_GCOS = 5
  1180.     PW_HOME = 6
  1181.     PW_SHELL = 7
  1182.     PW_REAL = -1    # for PWGetFields()
  1183.     PW_OFFICE = -2
  1184.     PW_RECORD = -3
  1185.  
  1186.     Ind = getpwentNum = 1
  1187.     OFS = FS
  1188.     FS = ":"
  1189.     while ((ret = (getline Line < "/etc/passwd")) == 1) {
  1190.     User = Line
  1191.     sub(":.*","",User)
  1192.     _pwNames[User] = Ind
  1193.     PWLines[Ind++] = Line
  1194.     }
  1195.     FS = OFS
  1196.     close("/etc/passwd")
  1197.     if (ret) {
  1198.     printf "ReadPasswd(): Could not open /etc/passwd.\n" > "/dev/stderr"
  1199.     return 0
  1200.     }
  1201.     return 1
  1202. }
  1203.  
  1204. # setpwent resets the passwd file entry pointer used by getpwent
  1205. # to the first entry.
  1206. function setpwent() {
  1207.     getpwentNum = 1
  1208. }
  1209.  
  1210. # getpwent sets PWEnt to the next entry in the passwd file.
  1211. # If Type is set to -1, the entry for the next "real" user is returned (others
  1212. # are skipped over), where a real user is a user whose login shell is listed in
  1213. # /etc/shells.  This requires the ReadShells() function.  Other values for
  1214. # Type are not yet defined and are ignored.
  1215. # If the last entry has already been returned, 0 is return if Field is null,
  1216. # ":" if not.
  1217. # If the entry for the next real user has been requested and /etc/shells
  1218. # cannot be read, -1 is returned if Field is null, "\n" if not.
  1219. # See PWGetFields() for other return values and the meaning of the Field
  1220. # parameter.
  1221. function getpwent(PWEnt,Type,Field,  entNum) {
  1222.     if (!PW_NAME)
  1223.     ReadPasswd()
  1224.     if (!(getpwentNum in PWLines))
  1225.     return Field ? ":" : 0
  1226.     if (Type == -1) {
  1227.     if (!_DidReadShells && ReadShells(LoginShells) == -1)
  1228.         return Field ? "\n" : -1
  1229.     split(PWLines[getpwentNum++],PWEnt,":")
  1230.     while (!(PWEnt[PW_SHELL] in LoginShells)) {
  1231.         if (!(getpwentNum in PWLines))
  1232.         return Field ? ":" : 0
  1233.         split(PWLines[getpwentNum++],PWEnt,":")
  1234.     }
  1235.     return PWGetFields("",PWEnt,Field,getpwentNum - 1)
  1236.     }
  1237.     else {
  1238.     entNum = getpwentNum
  1239.     return PWGetFields(PWLines[getpwentNum++],PWEnt,Field,entNum)
  1240.     }
  1241. }
  1242.  
  1243. function MakeInd(  Elem,Ind,Line,uid,home) {
  1244.     for (Ind = 1; Ind in PWLines; Ind++) {
  1245.     Line = PWLines[Ind]
  1246.     split(Line,Elem,":")
  1247.     uid = Elem[PW_UID]
  1248.     if (!(uid in uidInd))
  1249.         uidInd[uid] = Ind
  1250.     home = Elem[PW_HOME]
  1251.     if (!(home in HomeInd))
  1252.         HomeInd[home] = Ind
  1253.     }
  1254.     IndDone = 1
  1255. }
  1256.  
  1257. # PWGetFields() splits PWLine into PWEnt[], and optionally returns a field
  1258. # from it.  If PWLine is null, PWEnt[] is assumed to have already been filled
  1259. # in with a password entry.
  1260. # If Field is not passed or is null, the return value is 1.
  1261. # If Field is non-null, it should a PW_ value.  In this case, the value of the
  1262. # requested field is returned.
  1263. # entNum is the value that PWEnt[PW_RECORD] should be set to.  It should be
  1264. # the index in PWLines[] of the record being processed.
  1265. # In addition to the PW_ values used by the rest of the functions in this
  1266. # library, this function can be passed PW_REAL and PW_OFFICE.
  1267. # PW_REAL will get the part of the GCOS field before the first comma.
  1268. # PW_OFFICE will get the part of the GCOS field after the first comma.
  1269. # If either of these is requested, both values will also be assigned to their
  1270. # indices in PWEnt[], unless there is no comma in the GCOS field, in which case
  1271. # PW_OFFICE will not be set.
  1272. # NOTE: since the global field names are set in ReadShells(), it must be
  1273. # executed before any of the field name can be passed.
  1274. function PWGetFields(PWLine,PWEnt,Field,entNum,  gcos,ind) {
  1275.     if (PWLine != "")
  1276.     split(PWLine,PWEnt,":")
  1277.     PWEnt[PW_RECORD] = entNum
  1278.     if (!Field)
  1279.     return 1
  1280.     if (Field < 0) {
  1281.     if (ind = index(gcos = PWEnt[PW_GCOS],",")) {
  1282.         PWEnt[PW_OFFICE] = substr(gcos,ind+1)
  1283.         PWEnt[PW_REAL] = substr(gcos,1,ind-1)
  1284.     }
  1285.     else
  1286.         PWEnt[PW_REAL] = gcos
  1287.     }
  1288.     return PWEnt[Field]
  1289. }
  1290.  
  1291. # getpwnam sets PWEnt to the passwd entry for login name Name.
  1292. # If Name does not exist in the password file, the return value is ":"
  1293. # if Field was passed, 0 if not.
  1294. # For other return values and parameter explanation, see PWGetFields()
  1295. function getpwnam(Name,PWEnt,Field) {
  1296.     if (!PW_NAME)
  1297.     ReadPasswd()
  1298.     if (Name in _pwNames)
  1299.     return PWGetFields(PWLines[_pwNames[Name]],PWEnt,Field,_pwNames[Name])
  1300.     else
  1301.     return Field ? ":" : 0
  1302. }
  1303.  
  1304. # getpwhome sets PWEnt to the passwd entry whose home dir is Home.
  1305. # See getpwnam() for return values and the meaning of the Field param.
  1306. function getpwhome(Home,PWEnt,Field) {
  1307.     if (!PW_NAME)
  1308.     ReadPasswd()
  1309.     if (!IndDone)
  1310.     MakeInd()
  1311.     if (Home in HomeInd)
  1312.     return PWGetFields(PWLines[HomeInd[Home]],PWEnt,Field,HomeInd[Home])
  1313.     else
  1314.     return Field ? ":" : 0
  1315. }
  1316.  
  1317. # getpwuid sets PWEnt to the passwd entry whose uid is UID.
  1318. # See getpwnam() for return values and the meaning of the Field param.
  1319. function getpwuid(UID,PWEnt,Field) {
  1320.     if (!PW_NAME)
  1321.     ReadPasswd()
  1322.     if (!IndDone)
  1323.     MakeInd()
  1324.     if ((UID + 0) in uidInd)
  1325.     return PWGetFields(PWLines[uidInd[UID]],PWEnt,Field,uidInd[UID])
  1326.     else
  1327.     return Field ? ":" : 0
  1328. }
  1329.  
  1330. # Make an index by real name.  For each passwd file entry, the real-name
  1331. # is lowercased and split into components on non-alphanums.   The passwd entry
  1332. # index that the name came from is added to the value of each such component
  1333. # in the global _RealInd[].  The indexes stored this way are separated by
  1334. # commas.  If the real-name contains no alphanums, its index is stored under
  1335. # the null index.
  1336. function _makeRealInd(  PWEnt,ret,Elem,nelem,i,Component) {
  1337.     setpwent()
  1338.     while ((ret = getpwent(PWEnt,"",PW_REAL)) != ":") {
  1339.     nelem = split(tolower(ret),Elem,/[^a-z0-9]+/)
  1340.     for (i = 1; i <= nelem; i++) {
  1341.         Component = Elem[i]
  1342.         if (Component == "" && nelem > 1)
  1343.         continue
  1344.         if (Component in _RealInd)
  1345.         _RealInd[Component] = _RealInd[Component] "," PWEnt[PW_RECORD]
  1346.         else
  1347.         _RealInd[Component] = PWEnt[PW_RECORD]
  1348.     }
  1349.     }
  1350.     _realIndDone = 1
  1351. }
  1352.  
  1353. # Make Name into a pattern that will match a name that contains all of the
  1354. # same name components (sequences of alphanums) in the same order.  If Name
  1355. # contains no name components, a null string is returned.
  1356. function MakeNamePat(Name,  Elem,nelem,i,Pat,e) {
  1357.     nelem = split(Name,Elem,/[^a-zA-Z0-9]+/)
  1358.     for (i = 1; i <= nelem; i++) {
  1359.     if ((e = Elem[i]) == "")
  1360.         continue
  1361.     if (Pat == "")
  1362.         Pat = "(^|[^a-zA-Z0-9])" e
  1363.     else
  1364.         Pat = Pat "[^a-zA-Z0-9](.*[^a-zA-Z0-9])?" e
  1365.     }
  1366.     if (Pat == "")    # If Name contained no alphanums...
  1367.     return ""
  1368.     Pat = Pat "([^a-zA-Z0-9]|$)"
  1369.     return Pat
  1370. }
  1371.  
  1372. # getpwgreal sets PWEnt to the first passwd entry whose PW_REAL (see
  1373. # PWGetFields()) field matches Real.  Matching occurs if the alphanumeric
  1374. # components of Real occur in the same order in the entry.  Non-alphanums are
  1375. # ignored.  All of the components in Real must occur in the entry, but not all
  1376. # of the components in the entry must occur in Real.
  1377. # If the given name does not exist in the password file,
  1378. # the return value is ":" if Field was passed, 0 if not.
  1379. # If Next is true, getpwreal() sets PWEnt to the next passwd entry whose
  1380. # PW_REAL field matches the last previous Real parameter passed.
  1381. # In this case,  if the last entry has already been returned,
  1382. # the return value is ":" if Field was passed, 0 if not.
  1383. # Different IgnoreCase and Full parameters may be given when doing a Next
  1384. # search.  Both must always be passed; they do not default to the original
  1385. # values when doing a Next search.  The only parameter ignored when doing a
  1386. # Next search is Real.
  1387. # If IgnoreCase is true, case is ignored when searching.
  1388. # If Full is true, a match of the full name is required (including any
  1389. # punctuation).
  1390. # For successful return values and Field parameter explanation,
  1391. # see PWGetFields()
  1392. # Globals: For the Next search, between invokations these varies store values:
  1393. # _getpwrealInd[]: The set of pw indices that matched the query.
  1394. # _getpwrealIndInd: The next index in _getpwrealInd[] to look at.
  1395. # _getpwrealReal: The Real value passed with the original query.
  1396. # _getpwrealPat: Real converted to a component order search pattern.
  1397. function getpwreal(Real,PWEnt,Field,IgnoreCase,Full,Next,  ind,name,Pat) {
  1398.     if (!Next) {
  1399.     if (!PW_NAME)
  1400.         ReadPasswd()
  1401.     if (!_realIndDone)
  1402.         _makeRealInd()
  1403.     _getpwrealReal = Real
  1404.     _getpwrealPat = MakeNamePat(Real)
  1405.     # Get first component from Real
  1406.     Real = tolower(Real)
  1407.     gsub("^[^a-z0-9]+","",Real)
  1408.     gsub("[^a-z0-9].*","",Real)
  1409.     if (!(Real in _RealInd))
  1410.         return Field ? ":" : 0
  1411.     split(_RealInd[Real],_getpwrealInd,",")
  1412.     _getpwrealIndInd = 1
  1413.     }
  1414.     if (Full)
  1415.     Pat = _getpwrealReal
  1416.     else
  1417.     Pat = _getpwrealPat
  1418.     if (IgnoreCase)
  1419.     Pat = tolower(Pat)
  1420.     while (_getpwrealIndInd in _getpwrealInd) {
  1421.     ind = _getpwrealInd[_getpwrealIndInd++]
  1422.     name = PWGetFields(PWLines[ind],PWEnt,PW_REAL,ind)
  1423.     if (IgnoreCase)
  1424.         name = tolower(name)
  1425.     if (Full ? (name == Pat) : (name ~ Pat))
  1426.         return PWGetFields("",PWEnt,Field,ind)
  1427.     }
  1428.     return Field ? ":" : 0
  1429. }
  1430.  
  1431. ### End pwent library
  1432.