#!/usr/skunk/bin/gawk -f #!/usr/bin/awk -f # @(#) modems.awk 1.2 94/03/09 # 90/09/24 john h. dubois iii (john@armory.com) # 91/02/01 fixed bugs due to using PIDS instead of PIDs # and $0 instead of Devices[Device] # 91/04/04 added debug option; # fixed bug which caused lock file name to always end in 'a' # 91/04/08 fixed bug which prevented correct parsing of Sysfiles # 91/06/30 Added more output for debug option # Fixed regular expression used for parsing Sysfiles # (some awks require [charclass]+ where old awk let * be used) # 91/07/17 Added early reference to Sysfiles as array so awk will know it is # 91/11/27 modified to only lowercase last character of device when # generating lockfile name (per behaviour of uucico) # 92/02/16 added help # 92/03/15 fixed DevFiles[] reference # 92/04/14 Fixed to handle multiple device file names for a service # Changed to #!awk script (too large to be passed as arg) # Using awk instead of gawk because gawk has a bug in tolower() # and most recent awk has tolower(), which is all that was needed. # 92/05/05 Fixed so that all ttys are reported as free if none are in use # 92/08/25 Print device list in sorted order. # 93/05/01 Improved sysfiles parsing. # 93/09/29 Added -a option # 93/09/30 Added -w option # 93/11/09 Removed extraneous reading of Sysfiles. # Use all services listed in Sysfiles in addition to standard services. # 94/03/09 Use gawk so - options can be given BEGIN { if (ProcArgs(ARGC,ARGV,"ahwx:",Options,0) == -1) { printf "modems: %s\n",OptErr exit 1 } if ("h" in Options) { print \ "modems: print the status of all ttys listed as ACUs in the Devices files.\n"\ "Usage: modems [-ahw] [-x]\n"\ "The name of each tty is printed, along with its status: either free, or\n"\ "the process locking it as listed by \"ps\".\n"\ "Options:\n"\ "-a prints the status and type of all ttys listed in the Devices files,\n"\ " regardless of whether they are ACUs or not.\n"\ "-h prints this help info.\n"\ "-w uses the output of \"w\" instead of \"ps\" to determine what each tty is\n"\ " being used for. Information is only available for login processes.\n"\ " Other ttys in use are listed as being \"locked\".\n"\ "-x is used to turn on debugging.\n"\ " Higher values for give more debugging information." exit(0) } debug = Options["x"] split("",SysfilesDat) MakeSet(Services,"cu:ct:uucico",":") if (ReadSysfiles("/usr/lib/uucp/Sysfiles",SysfilesDat,"/usr/lib/uucp", Services) == -1) { if (debug) print \ "No Sysfiles file; services default to /usr/lib/uucp/Devices." FileList["/usr/lib/uucp/Devices"] } else for (Service in Services) MakeSet(FileList,SysfilesDat[Service,"devices"],":") if ("a" in Options) { All = 1 ReportLen = 64 Pat = "." } else { ReportLen = 71 Pat = "^ACU$" } FindDevices(FileList,Pat,Devices,Types) for (Device in Devices) { Low2Orig[tolower(Device)] = Device UsedNames[tolower(Device)] = Device } if ((PIDs = GetLockProcs(PID2Device,Low2Orig,FreeDevices)) != "") { if ("w" in Options) Header = wInfo(UsedNames,Reports) else Header = psInfo(PIDs,Reports,PID2Device,UsedNames) if (Header != "") { if (All) printf "%-6s ","" printf "%-7s %s\n","",Header } } # Make k[1..n] be the sorted lower-case names of devices NumDev = qsort_by_index(Low2Orig,k) for (i = 1; i <= NumDev; i++) { OrigName = Low2Orig[LowName = k[i]] UsedName = UsedNames[LowName] printf "%-7s ",UsedName if (All) if (UsedName in Types) printf "%-6s ",Types[UsedName] else printf "%-6s ","-" if (LowName in FreeDevices) print "free" else if (LowName in Reports) print substr(Reports[LowName],1,ReportLen) else print "locked" } } # Devices contains an index for the lower-case name for each modem device # For each device in use, its w output is put in Reports[lower-case-name], # and the tty name as actually used is assigned to its index in Devices function wInfo(Devices,Reports, Proc,Device) { Proc = "w" Proc | getline # Discard header Proc | getline # Fieldname header sub(" Tty...","") Header = $0 while ((Proc | getline) == 1) { OrigDev = $2 Device = tolower($2) if (Device in Devices) { sub(" tty....","") Reports[Device] = $0 # Change Devices entry to the capitalization of the device # name actually in use if (tolower(OrigDev) == Device) Devices[Device] = OrigDev } } return Header } # For each process id in PIDs, the ps output for that process is put # in Reports[PID2Device[pid]], and Devices[PID2Device[pid]] is set # to the tty name actually listed in the ps output. function psInfo(PIDs,Reports,PID2Device,Devices, Time,Proc,Device,tty) { # Get ps info on locking processes Proc = "ps -fp " PIDs Proc | getline # Discard header CmdInd = index($0,"COMMAND") TTYInd = index($0,"TTY") while ((Proc | getline) == 1) { Device = PID2Device[$2] if (!debug) { Time = $5 if (Time ~ "^[A-Z]") Time = Time " " $6 } Reports[Device] = \ sprintf("%-8s %5d %8s %s",$1,$2,Time,substr($0,CmdInd)) tty = "tty" substr($0,TTYInd,7) gsub(" +","",tty) # Only put controlling tty name in if it refers to the same device if (tolower(tty) == Device) Devices[Device] = tty } return "" } # Make PID2Device[] a table to look up a device name given a PID # Takes a set of devices in Devices[] (must be the lower-case names) # Returns a list of free devices in FreeDevices[] # Returns a comma-separated list of all PIDs function GetLockProcs(PID2Device,Devices,FreeDevices, Device,LockedName,PIDs) { for (Device in Devices) { # find pids of locking processes LockedName = "/usr/spool/uucp/LCK.." Device if ((getline < LockedName) == 1) { PID2Device[$1] = Device PIDs = PIDs "," $1 if (debug) printf("Lock file for %s is %s; locking process is %d.\n", Device,LockedName,$1) } else { if (debug) printf("Lock file for %s is %s; no locking process.\n", Device,LockedName) FreeDevices[Device] } } if (debug) print "" if (debug > 1) # remove leading comma printf "Locking processes: %s\n\n",substr(PIDs,2) return PIDs } # Put in Types[] the type of each tty found in Files[] that has a type # that matches Pattern. function FindDevices(Files,Pattern,Devices,Types, FName,DeviceList,Found) { # Convince awk these are arrays split("",Found,"") split("",Types,"") for (FName in Files) { DeviceList = "" while ((getline < FName) == 1) # Don't display non-tty (TCP, etc.) devices if ($1 !~ "^#" && $1 ~ Pattern && $2 ~ /^tty/) { if (!($2 in Types)) { Types[$2] = $1 } # Only put the first version of device name in Devices if (!(tolower($2) in Found)) { Devices[$2] Found[tolower($2)] if (debug) DeviceList = DeviceList "," $2 } } if (debug) printf "Device file %s lists devices: %s\n", FName,substr(DeviceList,2) } if (debug) print "" } # ReadRecFile: read file File in array Arr[]. # One logical line is stored in each element of Arr[]. # The elements of Arr[] have numeric indices starting with 1. # A logical line is extended onto another line if it ends with a backslash. # The backslash may be followed by whitespace. # The backslash and any trailing whitespace are converted into a single # space when the physical lines are joined into a logical line. # If an error occurs, -1 is returned. # Otherwise, the number of logical lines read is returned. function ReadRecFile(File,Arr, result,i,line) { i = 1 while ((result = (getline line < File)) == 1) { if (!(i in Arr) || (Arr[i] ~ /\\[ \t]*$/)) { sub(/\\[ \t]*$/," ",Arr[i]) Arr[i] = Arr[i] line } else Arr[++i] = line } close(File) if (result) return -1 else { if (!(i in Arr) && i > 0) i-- return i } } # 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. function MakeSet(Set,Elements,Sep, Num,Names) { Num = split(Elements,Names,Sep) for (; Num; Num--) Set[Names[Num]] } # ReadSysfiles: read & parse Sysfiles. # SysFileName should be the name of the sysfiles file, # usually /usr/lib/uucp/Sysfiles. # It is read and the files assigned to services are put in Sysfiles # indexed by service name and file type. # The value consists of filenames separated by colons in the same order # that they are given in the sysfiles file. # If DefDir is given, it is prepended to any non-absolute path names. # Usual service names are cu, ct, and uucico. # Usual file types are systems, devices, and dialers. # Services[] is an array containing service names as indexes. # Any other services found are also made indexes of Services. # The default values for systems, devices, and dialers are put in Sysfiles # for each service if they are not specified in the sysfiles file. # Example: to get the devices files used by cu, use Sysfiles["cu","devices"] # If the given sysfile does not exist or cannot be read, # the array is left empty and -1 is returned. # Otherwise, 0 is returned. # Example call: # ReadSysfiles("/usr/lib/uucp/Sysfiles",Sysfiles,"/usr/lib/uucp",Services) # where Services is an array with the indexes "cu", "ct", and "uucico" function ReadSysfiles(SysfileName,Sysfiles,DefDir,Services, NumServ,ServNames,FileTypes,Service,FileType, SysfilesLines,NumLines,Assignments,Assignment,i,j,Ind,Var,Val,Files,NumFiles) { split("",SysfilesLines) if ((NumLines = ReadRecFile(SysfileName,SysfilesLines)) == -1) return -1 if (DefDir ~ "[^/]$") DefDir = DefDir "/" for (i = 1; i <= NumLines; i++) { if (debug > 1) print "sysfiles line: " SysfilesLines[i] | "cat 1>&2" split(SysfilesLines[i],Assignments,"[ \t]+") split("",VarVal) for (j in Assignments) { if (debug > 1) print "Assignment: " Assignments[j] | "cat 1>&2" # More than one assignment for the same file type may be given # on a line Var = Val = Assignments[j] sub("=.*$","",Var) sub("^[^=]*=","",Val) if (Var in VarVal) VarVal[Var] = VarVal[Var] ":" Val else VarVal[Var] = Val } if (!("service" in VarVal)) { if (debug) print "No service given on Sysfiles line: " SysfilesLines[i] | \ "cat 1>&2" continue } # More than one service name may be given split("",ServNames) MakeSet(ServNames,VarVal["service"],":") for (Var in VarVal) if (Var != "service") { NumFiles = split(VarVal[Var],Files,":") for (j = 1; j <= NumFiles; j++) { Val = Files[j] if (Val !~ "^/") Val = DefDir Val for (Service in ServNames) { Services[Service] # Add to service name set Ind = Service SUBSEP Var if (Ind in Sysfiles) Sysfiles[Ind] = Sysfiles[Ind] ":" Val else Sysfiles[Ind] = Val if (debug > 1) printf "Sysfiles[%s,%s] = %s\n", Service,Var,Sysfiles[Ind] | "cat 1>&2" } } } } MakeSet(FileTypes,"systems:devices:dialers",":") for (Service in Services) { for (FileType in FileTypes) { Ind = Service SUBSEP FileType if (!(Ind in Sysfiles)) { Sysfiles[Ind] = DefDir toupper(substr(FileType,1,1)) \ substr(FileType,2) if (debug > 1) printf "Adding default: Sysfiles[%s,%s] = %s\n", Service,FileType,Sysfiles[Ind] | "cat 1>&2" } } } } # Arr is an array of values with arbitrary indices. # Array k is returned with numeric indices 1..n. # The values in k are the indices of array arr, # ordered so that if array arr is stepped through # in the order arr[k[1]] .. arr[k[n]], it will be stepped # through in order of the values of its indices. # The return value is the number of elements in the array (n). function qsort_by_index(arr,k, ArrInd,end) { end = 0 for (ArrInd in arr) k[++end] = ArrInd; qsort_num_ind(k,1,end); return end } # qsort_num_ind: sort an array # arr is the array to be sorted. # It must have contiguous numeric indexes. # start and end are the starting and ending indexes of the range to be sorted. function qsort_num_ind(arr,start,end, left,right,sepval,tmp,tmpe,tmps) { # handle two-element case explicitely for a tiny speedup if ((start - end) == 1) { if ((tmps = arr[start]) > (tmpe = arr[end])) { arr[start] = tmpe arr[end] = tmps } return } left = start; right = end; sepval = arr[int((left + right) / 2)] while (left < right) { while (arr[left] < sepval) left++ while (arr[right] > sepval) right-- if (left <= right) { tmp = arr[left] arr[left++] = arr[right] arr[right--] = tmp } } if (start < right) qsort_num_ind(arr,start,right) if (left < end) qsort_num_ind(arr,left,end) } # ProcArgs.awk john h. dubois iii 92/02/29 # optlist is a string which contains all of the possible command line options. # If a character is followed by a colon, # it indicates that that option takes an argument. # Strings in argv[] which begin with "-" or "+" are taken to be # strings of options, except that a string which consists solely of "-" or # "+" is not taken to be an option string (it is not acted on). # If an optoin takes an argument, the argument may either immedately # follow it or be given separately. # 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. # An argument of "--" or "++" stops the scanning of argv[]. # The number of arguments left in argc is returned. # If an error occurs, # the 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) { # 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. ArgsLeft = argc for (ArgNum = 1; ArgNum < argc; ArgNum++) { Arg = argv[ArgNum] if (Arg ~ "^[-+]") { if ((Arg == "-") || (Arg == "+")) continue delete argv[ArgNum] ArgsLeft-- if ((Arg == "--") || (Arg == "++")) break ArgLen = length(Arg) for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) { Option = substr(Arg,ArgInd,1) Pos = index(optlist,Option) if (!Pos) { OptErr = "Invalid option: -" Option return -1 } if (substr(optlist,Pos + 1,1) == ":") { if (ArgInd < ArgLen) { options[Option] = substr(Arg,ArgInd + 1) ArgInd = ArgLen } else { if (ArgNum < (argc - 1)) { options[Option] = argv[++ArgNum] delete argv[ArgNum] ArgsLeft-- } else { OptErr = "Option -" Option " requires an argument." return -1 } } } else options[Option] = 1 } } } if (compress != 0) PackArr(argv,ArgsLeft) return ArgsLeft } # 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++ } }