# Copyright (C) 1996-2002 Red Hat, Inc. # Use of this software is subject to the terms of the GNU General # Public License # This module manages standard configuration file handling # These classes are available: # Conf: # This is the base class. This is good for working with just about # any line-oriented configuration file. # Currently does not deal with newline escaping; may never... # ConfShellVar(Conf): # This is a derived class which implements a dictionary for standard # VARIABLE=value # shell variable setting. # Limitations: # o one variable per line # o assumes everything on the line after the '=' is the value # ConfShellVarClone(ConfShellVar): # Takes a ConfShellVar instance and records in another ConfShellVar # "difference file" only those settings which conflict with the # original instance. The delete operator does delete the variable # text in the cloned instance file, but that will not really delete # the shell variable that occurs, because it does not put an "unset" # command in the file. # ConfESNetwork(ConfShellVar): # This is a derived class specifically intended for /etc/sysconfig/network # It is another dictionary, but magically fixes /etc/HOSTNAME when the # Hostname is changed. # ConfEHosts(Conf): # Yet another dictionary, this one for /etc/hosts # Dictionary keys are numeric IP addresses in string form, values are # 2-item lists, the first item of which is the canonical Hostname, # and the second of which is a list of nicknames. # ConfEResolv(Conf): # Yet another dictionary, this one for /etc/resolv.conf # This ugly file has two different kinds of entries. All but one # take the form "key list of arguments", but one entry (nameserver) # instead takes multiple lines of "key argument" pairs. # In this dictionary, all keys have the same name as the keys in # the file, EXCEPT that the multiple nameserver entries are all # stored under 'nameservers'. Each value (even singleton values) # is a list. # ConfESStaticRoutes(Conf): # Yet another dictionary, this one for /etc/sysconfig/static-routes # This file has a syntax similar to that of /etc/gateways; # the interface name is added and active/passive is deleted: # net netmask gw # The key is the interface, the value is a list of # [, , ] lists # ConfChat(Conf): # Not a dictionary! # This reads chat files, and writes a subset of chat files that # has all items enclosed in '' and has one expect/send pair on # each line. # Uses a list of two-element tuples. # ConfChatFile(ConfChat): # This class is a ConfChat which it interprets as a netcfg-written # chat file with a certain amount of structure. It interprets it # relative to information in an "ifcfg-" file (devconf) and has a # set of abortstrings that can be turned on and off. # It exports the following data items: # abortstrings list of standard strings on which to abort # abortlist list of alternative strings on which to abort # defabort boolean: use the default abort strings or not # dialcmd string containing dial command (ATDT, for instance) # phonenum string containing phone number # chatlist list containing chat script after CONNECT # chatfile ConfChat instance # ConfChatFileClone(ConfChatFile): # Creates a chatfile, then removes it if it is identical to the chat # file it clones. # ConfDIP: # This reads chat files, and writes a dip file based on that chat script. # Takes three arguments: # o The chatfile # o The name of the dipfile # o The ConfSHellVar instance from which to take variables in the dipfile # ConfModules(Conf) # This reads /etc/modprobe.d/dist.conf into a dictionary keyed on device type, # holding dictionaries: cm['eth0']['alias'] --> 'smc-ultra' # cm['eth0']['options'] --> {'io':'0x300', 'irq':'10'} # cm['eth0']['post-install'] --> ['/bin/foo', # 'arg1', 'arg2'] # path[*] entries are ignored (but not removed) # New entries are added at the end to make sure that they # come after any path[*] entries. # Comments are delimited by initial '#' # ConfModInfo(Conf) # This READ-ONLY class reads /boot/module-info. # The first line of /boot/module-info is "Version = "; # this class reads versions 0 and 1 module-info files. # ConfPw(Conf) # This class implements a dictionary based on a :-separated file. # It takes as arguments the filename and the field number to key on; # The data provided is a list including all fields including the key. # Has its own write method to keep files sane. # ConfPasswd(ConfPw) # This class presents a data-oriented class for making changes # to the /etc/passwd file. # ConfShadow(ConfPw) # This class presents a data-oriented class for making changes # to the /etc/shadow file. # ConfGroup(ConfPw) # This class presents a data-oriented class for making changes # to the /etc/group file. # May be replaced by a pwdb-based module, we hope. # ConfUnix() # This class presents a data-oriented class which uses the ConfPasswd # and ConfShadow classes (if /etc/shadow exists) to hold data. # Designed to be replaced by a pwdb module eventually, we hope. # ConfPAP(Conf): # Yet another dictionary, this one for /etc/ppp/pap-secrets # The key is the remotename, the value is a list of # [, ] lists # ConfCHAP(ConfPAP): # Yet another dictionary, this one for /etc/ppp/chap-secrets # The key is the remotename, the value is a list of # [, ] lists # ConfSecrets: # Has-a ConfPAP and ConfCHAP # Yet another dictionary, which reads from pap-secrets and # chap-secrets, and writes to both when an entry is set. # When conflicts occur while reading, the pap version is # used in preference to the chap version (this is arbitrary). # ConfSysctl: # Guess what? A dictionary, this time with key/value pairs for sysctl vars. # Duplicate keys get appended to existing values, and broken out again when # the file is written (does that even work?) # This library exports several Errors, including # FileMissing # Conf raises this error if create_if_missing == 0 and the file does not # exist # IndexError # ConfShVar raises this error if unbalanced quotes are found # BadFile # Raised to indicate improperly formatted files # WrongMethod # Raised to indicate that the wrong method is being called. May indicate # that a dictionary class should be written to through methods rather # than assignment. # VersionMismatch # An unsupported file version was found. # SystemFull # No more UIDs or GIDs are available import os import re class FileMissing(Exception): def __init__(self, filename): Exception.__init__(self) self.filename = filename def __str__(self): return self.filename + " does not exist." class ConfIndexError(IndexError): def __init__(self, filename, var): IndexError.__init__(self) self.filename = filename self.var = var def __str__(self): return "end quote not found in %s: %s" % (self.filename, self.var[0]) class BadFile(Exception): def __init__(self, msg): Exception.__init__(self) self.msg = msg def __str__(self): return self.msg WrongMethod = BadFile VersionMismatch = BadFile SystemFull = BadFile # Implementation: # A configuration file is a list of lines. # a line is a string. class Conf: def __init__(self, filename, commenttype='#', separators='\t ', separator='\t', merge=1, create_if_missing=1): self.commenttype = commenttype self.separators = separators self.separator = separator self.codedict = {} self.splitdict = {} self.merge = merge self.create_if_missing = create_if_missing self.line = 0 self.rcs = 0 self.mode = -1 # self.line is a "point" -- 0 is before the first line; # 1 is between the first and second lines, etc. # The "current" line is the line after the point. self.filename = filename self.lines = [] self.read() def rewind(self): self.line = 0 def fsf(self): self.line = len(self.lines) def tell(self): return self.line def seek(self, line): self.line = line def nextline(self): self.line = min([self.line + 1, len(self.lines)]) def findnextline(self, regexp=None): # returns False if no more lines matching pattern while self.line < len(self.lines): if regexp: if hasattr(regexp, "search"): if regexp.search(self.lines[self.line]): return 1 elif re.search(regexp, self.lines[self.line]): return 1 elif not regexp: return 1 self.line = self.line + 1 # if while loop terminated, pattern not found. return 0 def findnextcodeline(self): # optional whitespace followed by non-comment character # defines a codeline. blank lines, lines with only whitespace, # and comment lines do not count. if not self.codedict.has_key((self.separators, self.commenttype)): self.codedict[(self.separators, self.commenttype)] = \ re.compile('^[' + self.separators \ + ']*' + '[^' + \ self.commenttype + \ self.separators + ']+') codereg = self.codedict[(self.separators, self.commenttype)] return self.findnextline(codereg) def findlinewithfield(self, fieldnum, value): if self.merge: seps = '['+self.separators+']+' else: seps = '['+self.separators+']' rx = '^' #for i in range(fieldnum - 1): # rx = rx + '[^'+self.separators+']*' + seps rx += ([ ('[^'+self.separators+']*' + seps) ] * fieldnum).join() rx = rx + value + '\(['+self.separators+']\|$\)' return self.findnextline(rx) def getline(self): if self.line >= len(self.lines): return '' return self.lines[self.line] def getfields(self): # returns list of fields split by self.separators if self.line >= len(self.lines): return [] if self.merge: seps = '['+self.separators+']+' else: seps = '['+self.separators+']' # print "re.split(%s, %s) = %s" % (self.lines[self.line], # seps, # re.split(seps, self.lines[self.line])) if not self.splitdict.has_key(seps): self.splitdict[seps] = re.compile(seps) regexp = self.splitdict[seps] return regexp.split(self.lines[self.line]) def setfields(self, mlist): # replaces current line with line built from list # appends if off the end of the array if self.line < len(self.lines): self.deleteline() self.insertlinelist(mlist) def insertline(self, line=''): self.lines.insert(self.line, line) def insertlinelist(self, linelist): self.insertline(self.separator.join(linelist)) def sedline(self, pat, repl): if self.line < len(self.lines): self.lines[self.line] = re.sub(pat, repl, \ self.lines[self.line]) def changefield(self, fieldno, fieldtext): fields = self.getfields() fields[fieldno:fieldno+1] = [fieldtext] self.setfields(fields) def setline(self, line=None): if not line: line = [] self.deleteline() self.insertline(line) def deleteline(self): self.lines[self.line:self.line+1] = [] def chmod(self, mode=-1): self.mode = mode def read(self): file_exists = 0 if os.path.isfile(self.filename): file_exists = 1 if not self.create_if_missing and not file_exists: raise FileMissing, self.filename if file_exists and os.access(self.filename, os.R_OK): mfile = open(self.filename, 'r', -1) self.lines = mfile.readlines() # strip newlines for index in range(len(self.lines)): if len(self.lines[index]) and self.lines[index][-1] == '\n': self.lines[index] = self.lines[index][:-1] if len(self.lines[index]) and self.lines[index][-1] == '\r': self.lines[index] = self.lines[index][:-1] mfile.close() else: self.lines = [] def write(self): # rcs checkout/checkin errors are thrown away, because they # aren't this tool's fault, and there's nothing much it could # do about them. For example, if the file is already locked # by someone else, too bad! This code is for keeping a trail, # not for managing contention. Too many deadlocks that way... if self.rcs or os.path.exists(os.path.split(self.filename)[0]+'/RCS'): self.rcs = 1 os.system('/usr/bin/co -l ' + self.filename + ' /dev/null 2>&1') mfile = open(self.filename, 'w', -1) if self.mode >= 0: os.chmod(self.filename, self.mode) # add newlines for index in range(len(self.lines)): mfile.write(self.lines[index] + '\n') mfile.close() if self.rcs: mode = os.stat(self.filename)[0] os.system('/usr/bin/ci -u -m"control panel update" ' + self.filename+' /dev/null 2>&1') os.chmod(self.filename, mode) class odict(dict): def __init__(self, modict = None): self._keys = [] dict.__init__(self) if modict: dict.update(self, modict) def __delitem__(self, key): dict.__delitem__(self, key) self._keys.remove(key) def __setitem__(self, key, item): #print "[%s] = %s" % (str(key), str(item)) dict.__setitem__(self, key, item) if key not in self._keys: self._keys.append(key) def clear(self): dict.clear(self) self._keys = [] def copy(self): modict = dict.copy(self) modict._keys = self._keys[:] return modict def items(self): return zip(self._keys, self.values()) def keys(self): return self._keys def popitem(self): try: key = self._keys[-1] except IndexError: raise KeyError('dictionary is empty') val = self[key] del self[key] return (key, val) def setdefault(self, key, failobj = None): dict.setdefault(self, key, failobj) if key not in self._keys: self._keys.append(key) def update(self, mdict): dict.update(self, mdict) for key in mdict.keys(): if key not in self._keys: self._keys.append(key) def values(self): return [ self.get(x) for x in self._keys ] # return map(self.get, self._keys)