2
# ipachangeconf - configuration file manipulation classes and functions
3
# partially based on authconfig code
4
# Copyright (c) 1999-2007 Red Hat, Inc.
5
# Author: Simo Sorce <ssorce@redhat.com>
7
# This is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; version 2 only
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
# General Public License for more details.
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
28
def openLocked(filename, perms, create = True):
33
flags = flags | os.O_CREAT
36
fd = os.open(filename, flags, perms)
37
fcntl.lockf(fd, fcntl.LOCK_EX)
38
except OSError, (errno, strerr):
44
raise IOError(errno, strerr)
45
return os.fdopen(fd, "r+")
48
#TODO: add subsection as a concept
49
# (ex. REALM.NAME = { foo = x bar = y } )
50
#TODO: put section delimiters as separating element of the list
51
# so that we can process multiple sections in one go
52
#TODO: add a comment all but provided options as a section option
55
def __init__(self, name):
57
self.indent = ("","","")
58
self.assign = (" = ","=")
59
self.dassign = self.assign[0]
61
self.dcomment = self.comment[0]
63
self.deol = self.eol[0]
64
self.sectnamdel = ("[","]")
65
self.subsectdel = ("{","}")
66
self.backup_suffix = ".ipabkp"
68
def setProgName(self, name):
71
def setIndent(self, indent):
72
if type(indent) is tuple:
74
elif type(indent) is str:
75
self.indent = (indent, )
77
raise ValueError, 'Indent must be a list of strings'
79
def setOptionAssignment(self, assign):
80
if type(assign) is tuple:
83
self.assign = (assign, )
84
self.dassign = self.assign[0]
86
def setCommentPrefix(self, comment):
87
if type(comment) is tuple:
88
self.comment = comment
90
self.comment = (comment, )
91
self.dcomment = self.comment[0]
93
def setEndLine(self, eol):
94
if type(eol) is tuple:
98
self.deol = self.eol[0]
100
def setSectionNameDelimiters(self, delims):
101
self.sectnamdel = delims
103
def setSubSectionDelimiters(self, delims):
104
self.subsectdel = delims
106
def matchComment(self, line):
107
for v in self.comment:
108
if line.lstrip().startswith(v):
109
return line.lstrip()[len(v):]
112
def matchEmpty(self, line):
113
if line.strip() == "":
117
def matchSection(self, line):
118
cl = "".join(line.strip().split())
119
if len(self.sectnamdel) != 2:
121
if not cl.startswith(self.sectnamdel[0]):
123
if not cl.endswith(self.sectnamdel[1]):
125
return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]
127
def matchSubSection(self, line):
128
if self.matchComment(line):
131
parts = line.split(self.dassign, 1)
135
if parts[1].strip() == self.subsectdel[0]:
136
return parts[0].strip()
140
def matchSubSectionEnd(self, line):
141
if self.matchComment(line):
144
if line.strip() == self.subsectdel[1]:
149
def getSectionLine(self, section):
150
if len(self.sectnamdel) != 2:
152
return self.sectnamdel[0]+section+self.sectnamdel[1]+self.deol
154
def dump(self, options, level=0):
156
if level >= len(self.indent):
157
level = len(self.indent)-1
160
if o['type'] == "section":
161
output += self.sectnamdel[0]+o['name']+self.sectnamdel[1]+self.deol
162
output += self.dump(o['value'], level+1)
164
if o['type'] == "subsection":
165
output += self.indent[level]+o['name']+self.dassign+self.subsectdel[0]+self.deol
166
output += self.dump(o['value'], level+1)
167
output += self.indent[level]+self.subsectdel[1]+self.deol
169
if o['type'] == "option":
170
output += self.indent[level]+o['name']+self.dassign+o['value']+self.deol
172
if o['type'] == "comment":
173
output += self.dcomment+o['value']+self.deol
175
if o['type'] == "empty":
178
raise SyntaxError, 'Unknown type: ['+o['type']+']'
182
def parseLine(self, line):
184
if self.matchEmpty(line):
185
return {'name':'empty', 'type':'empty'}
187
value = self.matchComment(line)
189
return {'name':'comment', 'type':'comment', 'value':value.rstrip()}
191
parts = line.split(self.dassign, 1)
193
raise SyntaxError, 'Syntax Error: Unknown line format'
195
return {'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()}
197
def findOpts(self, opts, type, name, exclude_sections=False):
201
if o['type'] == type and o['name'] == name:
203
if exclude_sections and (o['type'] == "section" or o['type'] == "subsection"):
208
def commentOpts(self, inopts, level = 0):
212
if level >= len(self.indent):
213
level = len(self.indent)-1
216
if o['type'] == 'section':
217
no = self.commentOpts(o['value'], level+1)
218
val = self.dcomment+self.sectnamdel[0]+o['name']+self.sectnamdel[1]
219
opts.append({'name':'comment', 'type':'comment', 'value':val})
223
if o['type'] == 'subsection':
224
no = self.commentOpts(o['value'], level+1)
225
val = self.indent[level]+o['name']+self.dassign+self.subsectdel[0]
226
opts.append({'name':'comment', 'type':'comment', 'value':val})
229
val = self.indent[level]+self.subsectdel[1]
230
opts.append({'name':'comment', 'type':'comment', 'value':val})
232
if o['type'] == 'option':
233
val = self.indent[level]+o['name']+self.dassign+o['value']
234
opts.append({'name':'comment', 'type':'comment', 'value':val})
236
if o['type'] == 'comment':
239
if o['type'] == 'empty':
240
opts.append({'name':'comment', 'type':'comment', 'value':''})
242
raise SyntaxError, 'Unknown type: ['+o['type']+']'
246
def mergeOld(self, oldopts, newopts):
251
if o['type'] == "section" or o['type'] == "subsection":
252
(num, no) = self.findOpts(newopts, o['type'], o['name'])
256
if no['action'] == "set":
257
mo = self.mergeOld(o['value'], no['value'])
258
opts.append({'name':o['name'], 'type':o['type'], 'value':mo})
260
if no['action'] == "comment":
261
co = self.commentOpts(o['value'])
265
if no['action'] == "remove":
267
raise SyntaxError, 'Unknown action: ['+no['action']+']'
269
if o['type'] == "comment" or o['type'] == "empty":
273
if o['type'] == "option":
274
(num, no) = self.findOpts(newopts, 'option', o['name'], True)
278
if no['action'] == 'comment' or no['action'] == 'remove':
279
if no['value'] != None and o['value'] != no['value']:
282
if no['action'] == 'comment':
283
opts.append({'name':'comment', 'type':'comment',
284
'value':self.dcomment+o['name']+self.dassign+o['value']})
286
if no['action'] == 'set':
289
raise SyntaxError, 'Unknown action: ['+o['action']+']'
291
raise SyntaxError, 'Unknown type: ['+o['type']+']'
295
def mergeNew(self, opts, newopts):
301
if no['type'] == "section" or no['type'] == "subsection":
302
(num, o) = self.findOpts(opts, no['type'], no['name'])
304
if no['action'] == 'set':
307
if no['action'] == "set":
308
self.mergeNew(o['value'], no['value'])
313
if no['type'] == "option":
314
(num, o) = self.findOpts(opts, no['type'], no['name'], True)
316
if no['action'] == 'set':
322
if no['type'] == "comment" or no['type'] == "empty":
323
opts.insert(cline, no)
327
raise SyntaxError, 'Unknown type: ['+no['type']+']'
330
def merge(self, oldopts, newopts):
332
#Use a two pass strategy
333
#First we create a new opts tree from oldopts removing/commenting
334
# the options as indicated by the contents of newopts
335
#Second we fill in the new opts tree with options as indicated
336
# in the newopts tree (this is becaus eentire (sub)sections may
337
# exist in the newopts that do not exist in oldopts)
339
opts = self.mergeOld(oldopts, newopts)
340
self.mergeNew(opts, newopts)
343
#TODO: Make parse() recursive?
354
# Read in the old file.
357
# It's a section start.
358
value = self.matchSection(line)
360
if section is not None:
361
opts.append({'name':section, 'type':'section', 'value':sectopts})
364
fatheropts = sectopts
368
# It's a subsection start.
369
value = self.matchSubSection(line)
371
if subsection is not None:
372
raise SyntaxError, 'nested subsections are not supported yet'
374
curopts = subsectopts
378
value = self.matchSubSectionEnd(line)
380
if subsection is None:
381
raise SyntaxError, 'Unmatched end subsection terminator found'
382
fatheropts.append({'name':subsection, 'type':'subsection', 'value':subsectopts})
387
# Copy anything else as is.
388
curopts.append(self.parseLine(line))
390
#Add last section if any
391
if len(sectopts) is not 0:
392
opts.append({'name':section, 'type':'section', 'value':sectopts})
396
# Write settings to configuration file
398
# options is a set of dictionaries in the form:
399
# [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
400
# section is a section name like 'global'
401
def changeConf(self, file, newopts):
408
#Do not catch an unexisting file error, we want to fail in that case
409
shutil.copy2(file, file+self.backup_suffix)
411
f = openLocked(file, 0644)
413
oldopts = self.parse(f)
415
options = self.merge(oldopts, newopts)
417
output = self.dump(options)
419
# Write it out and close it.
431
# Write settings to new file, backup old
433
# options is a set of dictionaries in the form:
434
# [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
435
# section is a section name like 'global'
436
def newConf(self, file, options):
444
shutil.copy2(file, file+self.backup_suffix)
447
# The orign file did not exist
450
f = openLocked(file, 0644)
456
output = self.dump(options)
467
# A SSSD-specific subclass of IPAChangeConf
468
class SSSDChangeConf(IPAChangeConf):
470
r'(?P<option>[^:=\s][^:=]*)' # very permissive!
471
r'\s*=\s*' # any number of space/tab,
472
# followed by separator
473
# followed by any # space/tab
474
r'(?P<value>.*)$' # everything up to eol
478
IPAChangeConf.__init__(self, "SSSD")
479
self.comment = ("#",";")
480
self.backup_suffix = ".bak"
483
def parseLine(self, line):
485
Overrides IPAChangeConf parseLine so that lines are splitted
486
using any separator in self.assign, not just the default one
489
if self.matchEmpty(line):
490
return {'name':'empty', 'type':'empty'}
492
value = self.matchComment(line)
494
return {'name':'comment', 'type':'comment', 'value':value.rstrip()}
496
mo = self.OPTCRE.match(line)
498
raise SyntaxError, 'Syntax Error: Unknown line format'
501
name, value = mo.group('option', 'value')
503
raise SyntaxError, 'Syntax Error: Unknown line format'
505
return {'name':name.strip(), 'type':'option', 'value':value.strip()}
507
def readfp(self, fd):
508
self.opts.extend(self.parse(fd))
510
def read(self, filename):
511
fd = open(filename, 'r')
515
def get(self, section, name):
516
index, item = self.get_option_index(section, name)
520
def set(self, section, name, value):
521
modkw = { 'type' : 'section',
531
self.opts = self.merge(self.opts, [ modkw ])
533
def add_section(self, name, optkw, index=0):
534
optkw.append({'type':'empty', 'value':'empty'})
535
addkw = { 'type' : 'section',
539
self.opts.insert(index, addkw)
541
def delete_section(self, name):
542
self.delete_option('section', name)
545
return [ o for o in self.opts if o['type'] == 'section' ]
547
def has_section(self, section):
548
return len([ o for o in self.opts if o['type'] == 'section' if o['name'] == section ]) > 0
550
def options(self, section):
551
for opt in self.opts:
552
if opt['type'] == 'section' and opt['name'] == section:
555
def delete_option(self, type, name, exclude_sections=False):
556
return self.delete_option_subtree(self.opts, type, name)
558
def delete_option_subtree(self, subtree, type, name, exclude_sections=False):
559
index, item = self.findOpts(subtree, type, name, exclude_sections)
564
def has_option(self, section, name):
565
index, item = self.get_option_index(section, name)
566
if index != -1 and item != None:
570
def strip_comments_empty(self, optlist):
573
if opt['type'] in ('comment', 'empty'):
578
def get_option_index(self, parent_name, name, type='option'):
581
pindex, pdata = self.findOpts(self.opts, 'section', parent_name)
584
subtree = pdata['value']
587
return self.findOpts(subtree, type, name)