#!/usr/local/bin/gawk -f # @(#) filegroup.gawk 3.2 95/01/14 # 94/03/04 john h. dubois iii (john@armory.com) # 94/08/04 Added q & a options. Bugfixes. # 94/08/07 Converted to #!awk program. # 94/08/10 Added x option. # 94/09/22 Warn about groups with status 'n'. Added pgt options. # Use status translation table. # 94/11/09 Added -XYNS # 94/11/16 Added multiple debug levels. Fixed Report logic. # 94/11/21 Added rR opts, and "r" status. # 94/12/12 Made a option work again # 95/01/14 Only accept legal statuses wtih -a. # filegroup: change status of group reception # Meaning of status: # y: spool group # n: spool group but do not allow posting # x: do not spool group # m: spool group & send postings to moderator # =: postings to group should be spooled in a different group. # This is extended with 'xm' to mean that group should not be spooled, # while retaining the information that if it is changed to spooled status it # should be moderated. BEGIN { Setup(Groups,VerboseState) while ((GlobAct || NumGroups || Change) && (ret = (getline < active)) == 1) if (GlobAct || $1 in Groups) { if (Report) printf "%s %s\n",$1,VerboseState[$4] else if (ModifyGroup($1,$2,$3,$4,NewStatus,NewActive)) { Change = 1 if (Debug > 1) printf "Modified specified group %s\n",$1 > "/dev/stderr" } NumGroups-- delete Groups[$1] } else if (UseNSTable) { if (ModifyGroup($1,$2,$3,$4,NSTable,NewActive)) { Change = 1 if (Debug > 1) printf "Modified non-specified group %s\n",$1 \ > "/dev/stderr" } } else if (!Report) # If not just reporting print $0 > NewActive if (ret == -1) { printf "Error reading active file '%s'. Exiting.\n", active > "/dev/stderr" exit 1 } if (NumGroups > 0) { if (Debug) printf "%d groups not found in active file.\n",NumElem(Groups) \ > "/dev/stderr" if (CreationStatus != "") { for (Group in Groups) printf "%s 0000000000 00001 %s\n",Group,CreationStatus \ > NewActive print \ "These groups do not exist in the active file (adding them):" \ > "/dev/stderr" Change = 1 } else { print "These groups do not exist in the active file:" \ > "/dev/stderr" } for (Group in Groups) print Group > "/dev/stderr" } if (Debug) printf "Change=%d\n",Change > "/dev/stderr" if (Change && !Report && !Test) system("a=" active "; chmod 664 $a+ && chgrp news $a+ &&"\ " chown news $a+ && ln $a $a- && mv $a+ $a") exit 0 } # Sets globals: # active Active file name # NewActive Name of new active file. # Quiet Do not warn about groups that were already on. # CreationStatus Status of groups to be added to active file. # Debug Debugging level. # Report Whether group status should be reported only. # NumGroups Number of groups to process. # GlobAct Act on all newsgroups. # Test Do not move new active file into place. # UseNSTable Use the NSTable for groups not specified. # NewStatus[] Translation table from old status -> new status # NSTable[] Translation table for groups not specified. # Groups[] Names of groups to process. # VerboseState[] Name for each possible state. function Setup(Groups,VerboseState, Name,Usage) { Name = "filegroup" Usage = \ "Usage: " Name " [-hnxydqgt] [-d] [-a] [-A]\n"\ " [-s [newsgroup ...]" active = "/usr/lib/news/active" Report = 1 # Opts normally uses -x to turn on debugging; removed that code. ARGC = Opts(Name,Usage,"a:d>A:ghnrqs:txyXYNRS:",0) if ("h" in Options) { printf \ "%s: display or change status of newsgroup reception.\n"\ "%s\n"\ "If no flags are given, %s displays the reception status of the named\n"\ "newsgroups.\n"\ "If no newsgroups are named, the standard input is read for group names.\n"\ "Options:\n"\ "-a: Add any groups that do not exist in the active file, with the\n"\ " given status (y, n, or x). Note that this will be incorrect for any\n"\ " groups that should be moderated.\n"\ "-g: Act on all groups. No group names should be given with this option.\n"\ "-A: Use instead of %s.\n"\ "-t: Test. Leave updated active file in active-file-name+; do not move it\n"\ " in place of the original file.\n"\ "-h: Print this help.\n"\ "-x: Turn off reception of the named newsgroups. y and n are changed to x,\n"\ " m is changed to xm.\n"\ "-y: Turn on reception of the named newsgroups. x and n are changed to y,\n"\ " xm is changed to m.\n"\ "-n: Mark the newsgroups as local posting disallowed. y is changed to n.\n"\ "-r: Remove the named newsgroups from the active file.\n"\ "-s: Give a specific status translation table, in the\n"\ " form old=new[:old=new...], where each old=new pair gives a new status\n"\ " that each group with the old status should be set to. A new status of\n"\ " 'r' means that groups with the given old status will be removed from\n"\ " the active file.\n"\ "-[XYNRS]: Like -[xynrs], but act on groups *not* named. One of these can\n"\ " be used to set the \"default\" reception status at the same time that\n"\ " one of -[xynrs] is used to set a specific status for the named groups.\n"\ "-d: Turn on debugging. 1 prints minimal debugging info;\n"\ " 2 prints per-group debugging info.\n"\ "-q: Do not warn about groups whose status is not changed.\n", Name,Usage,Name,active exit 0 } Quiet = "q" in Options if ((CreationStatus = Options["a"]) != "") { if (CreationStatus !~ /^([ynmx]|xm)$/) { printf "Bad status '%s' given with -a. Exiting.\n", CreationStatus > "/dev/stderr" exit 1 } else Report = 0 } if ("d" in Options) Debug = Options["d"] Test = "t" in Options if ((GlobAct = ("g" in Options)) && ARGC > 1) { print "No group names should be given with -g. Exiting.\n" \ > "/dev/stderr" exit 1 } if ("A" in Options) active = Options["A"] NewActive = active "+" if (!GlobAct) { if (ARGC > 1) { for (i = 1; i < ARGC; i++) Groups[ARGV[i]] } else while (getline < "/dev/stdin" == 1) MakeSet(Groups,$0,FS) delete Groups[""] # Do not rely on the return value of MakeSet, # since groups might be named more than once. NumGroups = NumElem(Groups) if (!NumGroups) { printf "%s: No groups specified.\n",Name > "/dev/stderr" exit 1 } } InitArr(VerboseState,"x:m:xm:y:n:r","not spooled:moderated:"\ "not spooled (moderated):spooled:spooled (local posting disabled):"\ "removed from active",":") Report = \ MakeTable(NewStatus,"x","y","n","r","s",Options,VerboseState) && Report UseNSTable = !MakeTable(NSTable,"X","Y","N","R","S",Options,VerboseState) Report = !UseNSTable && Report if (Debug) printf "Report=%d Test=%d GlobAct=%d\n",Report,Test,GlobAct \ > "/dev/stderr" } # Returns 0 if any of x y n r s is in Options, else returns 1. function MakeTable(Table,x,y,n,r,s,Options,States, Report,Pair,Pairs,Elem,i,Type) { Report = 1 if (x == "X") Type = "non-" if (y in Options) { Table["x"] = "y" Table["n"] = "y" Table["xm"] = "m" Report-- } if (x in Options) { Table["y"] = "x" Table["n"] = "x" Table["m"] = "xm" Report-- } if (n in Options) { Table["y"] = "n" Report-- } if (r in Options) { Table["y"] = "r" Table["n"] = "r" Table["m"] = "r" Table["x"] = "r" Table["xm"] = "r" Report-- } if (s in Options) { split(Options[s],Pairs,":") for (Pair in Pairs) { if (split(Pairs[Pair],Elem,"=") != 2) { printf "Bad format in -%s translation table.\n", s > "/dev/stderr" exit 1 } if (!(Elem[1] in States) || !(Elem[2] in States)) { printf \ "Unknown state in -%s translation table; must be one of [x m xm y n r]\n",s \ > "/dev/stderr" exit 1 } Table[Elem[1]] = Elem[2] } Report-- } if (Debug) { printf "Status translation table for %sspecified groups:\n", Type > "/dev/stderr" for (i in Table) printf "%s -> %s\n",i,Table[i] > "/dev/stderr" } if (Report < 0) { printf "Error in new status for %sspecified groups:"\ " must give only one of -[%s%s%s%s].\n",Type,x,y,n,s > "/dev/stderr" exit 1 } return Report } # Return value: 0 if no change, 1 if status changed. # Writes a line for group Group to file NewActive. # The first three fields of the line written are Group, c1, and c2. # The 4th field is the translation for Status. function ModifyGroup(Group,c1,c2,Status,XTable,NewActive, NewState) { if (!(Status in XTable)) { if (Quiet != 1) printf "%s not modified; had status: %s\n",Group, Status in VerboseState ? VerboseState[Status] : Status \ > "/dev/stderr" print Group " " c1 " " c2 " " Status > NewActive return 0 } else { NewState = XTable[Status] printf "%s: %s -> %s\n",Group,VerboseState[Status], VerboseState[NewState] > "/dev/stderr" if (NewState != "r") printf "%s %s %s %s\n",Group,c1,c2,NewState > NewActive return 1 } } # InitArr: Initialize an array with values. # Ind and Vals are separated into lists on Sep. # For each item in Ind, an index with that name is created in Arr[], # and the value with the same position in Vals is stored in it. # Global variables: none. function InitArr(Arr,Ind,Vals,sep, numind,indnames,values) { split(Ind,indnames,sep) split(Vals,values,sep) for (numind in indnames) Arr[indnames[numind]] = values[numind] } function ClearArr(Arr, Elem) { for (Elem in Arr) delete Arr[Elem] } # Subtract the values in Subtrahend from those in Minuend function SubtractArr(Minuend,Subtrahend, Elem) { for (Elem in Subtrahend) Minuend[Elem] -= Subtrahend[Elem] } # For each element of the array In, an element is created in Out having # an index equal to the value of the element in In and a value equal to # the index of the element in In. function Invert(In,Out,Index) { for (Index in In) Out[In[Index]] = Index } # Assign: make an array from a list of assignments. # An index with the name of each variable in the list is created in the array. # Its value is set to the value given for the # Input variables: # Elements is a string containing the list of variable-value pairs. # Sep is the string that separates the pairs in the list. # AssignOp is the string that separates variables from values. # Output variables: # Arr is the array. # Return value: the number of elements added to the set. # Example: # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=") function Assign(Arr,Elements,Sep,AssignOp, Num,Names,Elem,Assignments,Assignment) { Num = split(Elements,Assignments,Sep) for (; Num; Num--) { Assignment = Assignments[Num] Ind = index(Assignment,AssignOp) Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1) } return Num } # Returns 1 if Set is empty, 0 if not. function IsEmpty(Set, i) { for (i in Set) return 0 return 1 } # MakeSet: make a set from a list. # An index with the name of each element of the list # is created in the given array. # Input variables: # Elements is a string containing the list of elements. # Sep is the character that separates the elements of the list. # Output variables: # Set is the array. # Return value: the number of elements added to the set. function MakeSet(Set,Elements,Sep, i,Num,Names) { Num = split(Elements,Names,Sep) for (i = 1; i <= Num; i++) Set[Names[i]] return Num } # Returns the number of elements in set Set function NumElem(Set, elem,Num) { for (elem in Set) Num++ return Num } # Note: removed the code that treats -x specially from InitOpts() # so that -x can be used as a regular option for filegroup. # @(#) ProcArgs 1.2.2 94/09/23 # 92/02/29 john h. dubois iii (john@armory.com) # 93/07/18 Added "#" arg type # 93/09/26 Do not count -h against MinArgs # 94/01/01 Stop scanning at first non-option arg. Added ">" option type. # Removed meaning of "+" or "-" by itself. # 94/03/08 Added & option and *()< option types. # 94/04/02 Added NoRCopt to Opts() # 94/06/11 Mark numeric variables as such. # 94/07/08 Opts(): Don't require any args if 'h' option is given. # 94/09/23 Fixed bug that caused fail if -opt given as last arg. # optlist is a string which contains all of the possible command line options. # A character followed by certain characters indicates that the option takes # an argument, with type as follows: # : String argument # * Floating point argument # ( Non-negative floating point argument # ) Positive floating point argument # # Integer argument # < Non-negative integer argument # > Positive integer argument # The only difference the type of argument makes is in the runtime argument # error checking that is done. # The & option is a special case used to get numeric options without the # user having to give an option character. It is shorthand for [-+.0-9]. # If & is included in optlist and an option string that begins with one of # these characters is seen, the value given to "&" will include the first # char of the option. & must be followed by a type character other than ":". # Note that if e.g. &> is given, an option of -.5 will produce an error. # Strings in argv[] which begin with "-" or "+" are taken to be # strings of options, except that a string which consists solely of "-" # or "+" is taken to be a non-option string; like other non-option strings, # it stops the scanning of argv and is left in argv[]. # An argument of "--" or "++" also stops the scanning of argv[] but is removed. # If an option takes an argument, the argument may either immediately # follow it or be given separately. # "-" and "+" options are treated the same. "+" is allowed because most awks # take any -options to be arguments to themselves. gawk 2.15 was enhanced to # stop scanning when it encounters an unrecognized option, though until 2.15.5 # this feature had a bug that caused problems in some cases. # If an option that does not take an argument is given, # an index with its name is created in Options and its value is set to "1". # If an option that does take an argument is given, # an index with its name is created in Options and its value # is set to the value of the argument given for it. # Options and their arguments are deleted from argv. # Note that this means that there may be gaps left in the indices of argv[]. # If compress is nonzero, argv[] is packed by moving its elements so that # they have contiguous integer indices starting with 0. # argv[0] is not examined. # The number of arguments left in argc is returned. # If an error occurs, the global string OptErr is set to an error message # and -1 is returned. function ProcArgs(argc,argv,OptList,Options,compress, ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue, NeedNextOpt,GotValue) { # ArgNum is the index of the argument being processed. # ArgsLeft is the number of arguments left in argv. # Arg is the argument being processed. # ArgLen is the length of the argument being processed. # ArgInd is the position of the character in Arg being processed. # Option is the character in Arg being processed. # Pos is the position in OptList of the option being processed. # NumOpt is true if a numeric option may be given. ArgsLeft = argc NumOpt = index(OptList,"&") for (ArgNum = 1; ArgNum < argc; ArgNum++) { if ((Arg = argv[ArgNum]) !~ /^[-+]./) # Not an option; quit break delete argv[ArgNum] ArgsLeft-- if ((Arg == "--") || (Arg == "++")) break ArgLen = length(Arg) for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) { Option = substr(Arg,ArgInd,1) if (NumOpt && Option ~ /[-+.0-9]/) { Option = "&" Arg = "&" Arg ArgLen++ Pos = NumOpt } else if (!(Pos = index(OptList,Option)) || Option == "&") { OptErr = "Invalid option: -" Option return -1 } # Find what the value of the option will be if it needs one # NeedNextOpt is true if the option specifier is the last char of # this arg, which means that if the option requires a value it is # the next arg. if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg if (GotValue = ArgNum + 1 < argc) Value = argv[ArgNum+1] } else { # Value is included with option Value = substr(Arg,ArgInd + 1) GotValue = 1 } if (HadValue = AssignVal(Option,Value,Options, substr(OptList,Pos + 1,1),GotValue)) { if (HadValue == -1) return -1 if (NeedNextOpt) { delete argv[++ArgNum] ArgsLeft-- } break # Used up this option } } } if (compress != 0) PackArr(argv,ArgsLeft) return ArgsLeft } # Global variables: OptErr # Return value: -1 on error, 0 if option did not require an argument, # 1 if it did. function AssignVal(Option,Value,Options,ArgType,GotValue,Name, UsedValue,Err) { # If option takes a value... if (UsedValue = (ArgType ~ "[:*()#<>]")) { if (!GotValue) { if (Name != "") OptErr = "Variable requires a value -- " Name else OptErr = "option requires an argument -- " Option return -1 } if ((Err = CheckType(ArgType,Value,Option,Name)) != "") { OptErr = Err return -1 } # Mark this as a numeric variable; will be propogated to Options[] val. if (ArgType != ":") Value += 0 } else Value = 1 if (!(Option in Options)) # Do not overwrite previously assigned values Options[Option] = Value return UsedValue } # Option is the option letter # Value is the value being assigned # Name is the var name of the option, if any # ArgType is one of: # : String argument # * Floating point argument # ( Non-negative floating point argument # ) Positive floating point argument # # Integer argument # < Non-negative integer argument # > Positive integer argument # Returns null on success, err string on error function CheckType(ArgType,Value,Option,Name, Err) { if (ArgType == ":") return "" # A number begins with option + or -, and is followed by a string of # digits or a decimal with digits before it, after it, or both if (Value !~ /^[-+]?([0-9]+|[0-9]+?\.[0-9]+|[0-9]+\.)$/) Err = "must be a number" else if (ArgType ~ "[#<>]" && Value ~ /\./) Err = "may not include a fraction" else if (ArgType ~ "[()<>]" && Value < 0) Err = "may not be negative" else if (ArgType ~ "[)>]" && Value == 0) Err = "must be a positive number" if (Err != "") { if (Name != "") return "Value assigned to variable " Name " " Err else { if (Option == "&") Option = Value return "Value assigned to option -" Option " " Err } } else return "" } # Packs Arr to indices starting with 0 # Num should be the number of elements in Arr function PackArr(Arr,Num, NewInd,OldInd) { NewInd = OldInd = 0 for (; Num; Num--) { while (!(OldInd in Arr)) OldInd++ if (NewInd != OldInd) { Arr[NewInd] = Arr[OldInd] delete Arr[OldInd] } OldInd++ NewInd++ } } # Note: only the above functions are needed by ProcArgs. # The rest of these functions call ProcArgs() and also do other # option-processing stuff. # Opts: Process command line arguments. # Opts processes command line arguments using ProcArgs() # and checks for errors. If an error occurs, a message is printed # and the program is exited. # # Input variables: # Name is the name of the program, for error messages. # Usage is a usage message, for error messages. # OptList the option description string, as used by ProcArgs(). # MinArgs is the minimum number of non-option arguments that this # program should have, non including ARGV[0] and +h. # If the program does not require any non-option arguments, # MinArgs should be omitted or given as 0. # rcFile, if given, is the name of a file to read for variable initialization. # Values given in it will not override values given on the command line. # VarNames is a comma-separated list of variable names to map to options, # in the same order as the options are given in OptList. # If UseEnv is given and nonzero, the variables will also be searched for in # the environment. Values given in the environment will override those given # in the rcfile but not those given on the command line. # NoRCopt, if given, is an additional letter option that if given on the # command line prevents the rcfile from being read. # Special options: # If x is made an option and is given, some debugging info is output. # h is assumed to be the help option. # Global variables: # The command line arguments are taken from ARGV[]. # The arguments that are option specifiers and values are removed from # ARGV[], leaving only ARGV[0] and the non-option arguments. # The number of elements in ARGV[] should be in ARGC. # After processing, ARGC is set to the number of elements left in ARGV[]. # The option values are put in Options[]. # On error, Err is set to 1 so it can be checked for in an END block. # Return value: The number of elements left in ARGV is returned. function Opts(Name,Usage,OptList,MinArgs,rcFile,VarNames,UseEnv,NoRCopt, ArgsLeft) { if (MinArgs == "") MinArgs = 0 ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1) # if ((ArgsLeft + ("h" in Options)) < (MinArgs+1)) { if (ArgsLeft < (MinArgs+1) && !("h" in Options)) { if (ArgsLeft != -1) OptErr = "Not enough arguments" print Name ": " OptErr ". Use -h for help." print Usage Err = 1 exit 1 } if (rcFile != "" && (NoRCopt == "" || !(NoRCopt in Options)) && InitOpts(rcFile,Options,OptList,VarNames,UseEnv) == -1) { print Name ": " OptErr ". Use -h for help." Err = 1 exit 1 } return ArgsLeft } # Global vars: sets OptErr; uses ENVIRON[]; # if anything is read from the rcFile, sets READ_RCFILE to 1 function InitOpts(rcFile,Options,OptTypes,VarNames,UseEnv, Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret) { NumVars = split(VarNames,Vars,",") TypesInd = Ret = 0 for (i = 1; i <= NumVars; i++) { Var = Vars[i] CharOpt = substr(OptTypes,++TypesInd,1) if (CharOpt ~ "^[:*()#<>&]$") CharOpt = substr(OptTypes,++TypesInd,1) Map[Var] = CharOpt Types[Var] = Type = substr(OptTypes,TypesInd+1,1) # Do not overwrite entries from environment if (UseEnv && Var in ENVIRON && AssignVal(CharOpt,ENVIRON[Var],Options, Type,1,Var) == -1) return -1 } if (rcFile ~ "^~/") rcFile = ENVIRON["HOME"] substr(rcFile,2) while ((getline Line < rcFile) == 1) { READ_RCFILE = 1 if (Line !~ /^#/ && Line !~ "^[ \t]*$") { if (Pos = index(Line,"=")) Var = substr(Line,1,Pos-1) else Var = Line # If no value, var is entire line if (Var in Map) { if (AssignVal(Map[Var],substr(Line,Pos+1),Options, Types[Var],Pos != 0,Var) == -1) return -1 } else { OptErr = sprintf("Unknown var \"%s\" set in %s",Var,rcFile) Ret = -1 } } } return Ret }