????
Current Path : /usr/lib/python2.6/site-packages/SSSDConfig/ |
Current File : //usr/lib/python2.6/site-packages/SSSDConfig/ipachangeconf.py |
# # ipachangeconf - configuration file manipulation classes and functions # partially based on authconfig code # Copyright (c) 1999-2007 Red Hat, Inc. # Author: Simo Sorce <ssorce@redhat.com> # # This is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 only # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # import fcntl import os import string import time import shutil import re def openLocked(filename, perms, create = True): fd = -1 flags = os.O_RDWR if create: flags = flags | os.O_CREAT try: fd = os.open(filename, flags, perms) fcntl.lockf(fd, fcntl.LOCK_EX) except OSError as err: errno, strerr = err.args if fd != -1: try: os.close(fd) except OSError: pass raise IOError(errno, strerr) return os.fdopen(fd, "r+") #TODO: add subsection as a concept # (ex. REALM.NAME = { foo = x bar = y } ) #TODO: put section delimiters as separating element of the list # so that we can process multiple sections in one go #TODO: add a comment all but provided options as a section option class IPAChangeConf: def __init__(self, name): self.progname = name self.indent = ("","","") self.assign = (" = ","=") self.dassign = self.assign[0] self.comment = ("#",) self.dcomment = self.comment[0] self.eol = ("\n",) self.deol = self.eol[0] self.sectnamdel = ("[","]") self.subsectdel = ("{","}") self.backup_suffix = ".ipabkp" def setProgName(self, name): self.progname = name def setIndent(self, indent): if type(indent) is tuple: self.indent = indent elif type(indent) is str: self.indent = (indent, ) else: raise ValueError('Indent must be a list of strings') def setOptionAssignment(self, assign): if type(assign) is tuple: self.assign = assign else: self.assign = (assign, ) self.dassign = self.assign[0] def setCommentPrefix(self, comment): if type(comment) is tuple: self.comment = comment else: self.comment = (comment, ) self.dcomment = self.comment[0] def setEndLine(self, eol): if type(eol) is tuple: self.eol = eol else: self.eol = (eol, ) self.deol = self.eol[0] def setSectionNameDelimiters(self, delims): self.sectnamdel = delims def setSubSectionDelimiters(self, delims): self.subsectdel = delims def matchComment(self, line): for v in self.comment: if line.lstrip().startswith(v): return line.lstrip()[len(v):] return False def matchEmpty(self, line): if line.strip() == "": return True return False def matchSection(self, line): cl = "".join(line.strip().split()) if len(self.sectnamdel) != 2: return False if not cl.startswith(self.sectnamdel[0]): return False if not cl.endswith(self.sectnamdel[1]): return False return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])] def matchSubSection(self, line): if self.matchComment(line): return False parts = line.split(self.dassign, 1) if len(parts) < 2: return False if parts[1].strip() == self.subsectdel[0]: return parts[0].strip() return False def matchSubSectionEnd(self, line): if self.matchComment(line): return False if line.strip() == self.subsectdel[1]: return True return False def getSectionLine(self, section): if len(self.sectnamdel) != 2: return section return self.sectnamdel[0]+section+self.sectnamdel[1]+self.deol def dump(self, options, level=0): output = "" if level >= len(self.indent): level = len(self.indent)-1 for o in options: if o['type'] == "section": output += self.sectnamdel[0]+o['name']+self.sectnamdel[1]+self.deol output += self.dump(o['value'], level+1) continue if o['type'] == "subsection": output += self.indent[level]+o['name']+self.dassign+self.subsectdel[0]+self.deol output += self.dump(o['value'], level+1) output += self.indent[level]+self.subsectdel[1]+self.deol continue if o['type'] == "option": output += self.indent[level]+o['name']+self.dassign+o['value']+self.deol continue if o['type'] == "comment": output += self.dcomment+o['value']+self.deol continue if o['type'] == "empty": output += self.deol continue raise SyntaxError('Unknown type: ['+o['type']+']') return output def parseLine(self, line): if self.matchEmpty(line): return {'name':'empty', 'type':'empty'} value = self.matchComment(line) if value: return {'name':'comment', 'type':'comment', 'value':value.rstrip()} parts = line.split(self.dassign, 1) if len(parts) < 2: raise SyntaxError('Syntax Error: Unknown line format') return {'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()} def findOpts(self, opts, type, name, exclude_sections=False): num = 0 for o in opts: if o['type'] == type and o['name'] == name: return (num, o) if exclude_sections and (o['type'] == "section" or o['type'] == "subsection"): return (num, None) num += 1 return (num, None) def commentOpts(self, inopts, level = 0): opts = [] if level >= len(self.indent): level = len(self.indent)-1 for o in inopts: if o['type'] == 'section': no = self.commentOpts(o['value'], level+1) val = self.dcomment+self.sectnamdel[0]+o['name']+self.sectnamdel[1] opts.append({'name':'comment', 'type':'comment', 'value':val}) for n in no: opts.append(n) continue if o['type'] == 'subsection': no = self.commentOpts(o['value'], level+1) val = self.indent[level]+o['name']+self.dassign+self.subsectdel[0] opts.append({'name':'comment', 'type':'comment', 'value':val}) for n in no: opts.append(n) val = self.indent[level]+self.subsectdel[1] opts.append({'name':'comment', 'type':'comment', 'value':val}) continue if o['type'] == 'option': val = self.indent[level]+o['name']+self.dassign+o['value'] opts.append({'name':'comment', 'type':'comment', 'value':val}) continue if o['type'] == 'comment': opts.append(o) continue if o['type'] == 'empty': opts.append({'name':'comment', 'type':'comment', 'value':''}) continue raise SyntaxError('Unknown type: ['+o['type']+']') return opts def mergeOld(self, oldopts, newopts): opts = [] for o in oldopts: if o['type'] == "section" or o['type'] == "subsection": (num, no) = self.findOpts(newopts, o['type'], o['name']) if not no: opts.append(o) continue if no['action'] == "set": mo = self.mergeOld(o['value'], no['value']) opts.append({'name':o['name'], 'type':o['type'], 'value':mo}) continue if no['action'] == "comment": co = self.commentOpts(o['value']) for c in co: opts.append(c) continue if no['action'] == "remove": continue raise SyntaxError('Unknown action: ['+no['action']+']') if o['type'] == "comment" or o['type'] == "empty": opts.append(o) continue if o['type'] == "option": (num, no) = self.findOpts(newopts, 'option', o['name'], True) if not no: opts.append(o) continue if no['action'] == 'comment' or no['action'] == 'remove': if no['value'] != None and o['value'] != no['value']: opts.append(o) continue if no['action'] == 'comment': opts.append({'name':'comment', 'type':'comment', 'value':self.dcomment+o['name']+self.dassign+o['value']}) continue if no['action'] == 'set': opts.append(no) continue raise SyntaxError('Unknown action: ['+o['action']+']') raise SyntaxError('Unknown type: ['+o['type']+']') return opts def mergeNew(self, opts, newopts): cline = 0 for no in newopts: if no['type'] == "section" or no['type'] == "subsection": (num, o) = self.findOpts(opts, no['type'], no['name']) if not o: if no['action'] == 'set': opts.append(no) continue if no['action'] == "set": self.mergeNew(o['value'], no['value']) continue cline = num+1 continue if no['type'] == "option": (num, o) = self.findOpts(opts, no['type'], no['name'], True) if not o: if no['action'] == 'set': opts.append(no) continue cline = num+1 continue if no['type'] == "comment" or no['type'] == "empty": opts.insert(cline, no) cline += 1 continue raise SyntaxError('Unknown type: ['+no['type']+']') def merge(self, oldopts, newopts): #Use a two pass strategy #First we create a new opts tree from oldopts removing/commenting # the options as indicated by the contents of newopts #Second we fill in the new opts tree with options as indicated # in the newopts tree (this is becaus eentire (sub)sections may # exist in the newopts that do not exist in oldopts) opts = self.mergeOld(oldopts, newopts) self.mergeNew(opts, newopts) return opts #TODO: Make parse() recursive? def parse(self, f): opts = [] sectopts = [] section = None subsectopts = [] subsection = None curopts = opts fatheropts = opts # Read in the old file. for line in f: # It's a section start. value = self.matchSection(line) if value: if section is not None: opts.append({'name':section, 'type':'section', 'value':sectopts}) sectopts = [] curopts = sectopts fatheropts = sectopts section = value continue # It's a subsection start. value = self.matchSubSection(line) if value: if subsection is not None: raise SyntaxError('nested subsections are not supported yet') subsectopts = [] curopts = subsectopts subsection = value continue value = self.matchSubSectionEnd(line) if value: if subsection is None: raise SyntaxError('Unmatched end subsection terminator found') fatheropts.append({'name':subsection, 'type':'subsection', 'value':subsectopts}) subsection = None curopts = fatheropts continue # Copy anything else as is. curopts.append(self.parseLine(line)) #Add last section if any if len(sectopts) is not 0: opts.append({'name':section, 'type':'section', 'value':sectopts}) return opts # Write settings to configuration file # file is a path # options is a set of dictionaries in the form: # [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}] # section is a section name like 'global' def changeConf(self, file, newopts): autosection = False savedsection = None done = False output = "" f = None try: #Do not catch an unexisting file error, we want to fail in that case shutil.copy2(file, file+self.backup_suffix) f = openLocked(file, 0o644) oldopts = self.parse(f) options = self.merge(oldopts, newopts) output = self.dump(options) # Write it out and close it. f.seek(0) f.truncate(0) f.write(output) finally: try: if f: f.close() except IOError: pass return True # Write settings to new file, backup old # file is a path # options is a set of dictionaries in the form: # [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}] # section is a section name like 'global' def newConf(self, file, options): autosection = False savedsection = None done = False output = "" f = None try: try: shutil.copy2(file, file+self.backup_suffix) except IOError as err: if err.errno == 2: # The orign file did not exist pass f = openLocked(file, 0o644) # Trunkate f.seek(0) f.truncate(0) output = self.dump(options) f.write(output) finally: try: if f: f.close() except IOError: pass return True # A SSSD-specific subclass of IPAChangeConf class SSSDChangeConf(IPAChangeConf): OPTCRE = re.compile( r'(?P<option>[^:=\s][^:=]*)' # very permissive! r'\s*=\s*' # any number of space/tab, # followed by separator # followed by any # space/tab r'(?P<value>.*)$' # everything up to eol ) def __init__(self): IPAChangeConf.__init__(self, "SSSD") self.comment = ("#",";") self.backup_suffix = ".bak" self.opts = [] def parseLine(self, line): """ Overrides IPAChangeConf parseLine so that lines are splitted using any separator in self.assign, not just the default one """ if self.matchEmpty(line): return {'name':'empty', 'type':'empty'} value = self.matchComment(line) if value: return {'name':'comment', 'type':'comment', 'value':value.rstrip()} mo = self.OPTCRE.match(line) if not mo: raise SyntaxError('Syntax Error: Unknown line format') try: name, value = mo.group('option', 'value') except IndexError: raise SyntaxError('Syntax Error: Unknown line format') return {'name':name.strip(), 'type':'option', 'value':value.strip()} def readfp(self, fd): self.opts.extend(self.parse(fd)) def read(self, filename): fd = open(filename, 'r') self.readfp(fd) fd.close() def get(self, section, name): index, item = self.get_option_index(section, name) if item: return item['value'] def set(self, section, name, value): modkw = { 'type' : 'section', 'name' : section, 'value' : [{ 'type' : 'option', 'name' : name, 'value' : value, 'action': 'set', }], 'action': 'set', } self.opts = self.merge(self.opts, [ modkw ]) def add_section(self, name, optkw, index=0): optkw.append({'type':'empty', 'value':'empty'}) addkw = { 'type' : 'section', 'name' : name, 'value' : optkw, } self.opts.insert(index, addkw) def delete_section(self, name): self.delete_option('section', name) def sections(self): return [ o for o in self.opts if o['type'] == 'section' ] def has_section(self, section): return len([ o for o in self.opts if o['type'] == 'section' if o['name'] == section ]) > 0 def options(self, section): for opt in self.opts: if opt['type'] == 'section' and opt['name'] == section: return opt['value'] def delete_option(self, type, name, exclude_sections=False): return self.delete_option_subtree(self.opts, type, name) def delete_option_subtree(self, subtree, type, name, exclude_sections=False): index, item = self.findOpts(subtree, type, name, exclude_sections) if item: del subtree[index] return index def has_option(self, section, name): index, item = self.get_option_index(section, name) if index != -1 and item != None: return True return False def strip_comments_empty(self, optlist): retlist = [] for opt in optlist: if opt['type'] in ('comment', 'empty'): continue retlist.append(opt) return retlist def get_option_index(self, parent_name, name, type='option'): subtree = None if parent_name: pindex, pdata = self.findOpts(self.opts, 'section', parent_name) if not pdata: return (-1, None) subtree = pdata['value'] else: subtree = self.opts return self.findOpts(subtree, type, name)