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 program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
26
def openLocked(filename, perms):
29
fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms)
31
fcntl.lockf(fd, fcntl.LOCK_EX)
32
except OSError, (errno, strerr):
38
raise IOError(errno, strerr)
39
return os.fdopen(fd, "r+")
42
#TODO: add subsection as a concept
43
# (ex. REALM.NAME = { foo = x bar = y } )
44
#TODO: put section delimiters as separating element of the list
45
# so that we can process multiple sections in one go
46
#TODO: add a comment all but provided options as a section option
49
def __init__(self, name):
51
self.indent = ("","","")
52
self.assign = (" = ","=")
53
self.dassign = self.assign[0]
55
self.dcomment = self.comment[0]
57
self.deol = self.eol[0]
58
self.sectnamdel = ("[","]")
59
self.subsectdel = ("{","}")
61
def setProgName(self, name):
64
def setIndent(self, indent):
65
if type(indent) is tuple:
67
elif type(indent) is str:
68
self.indent = (indent, )
70
raise ValueError, 'Indent must be a list of strings'
72
def setOptionAssignment(self, assign):
73
if type(assign) is tuple:
76
self.assign = (assign, )
77
self.dassign = self.assign[0]
79
def setCommentPrefix(self, comment):
80
if type(comment) is tuple:
81
self.comment = comment
83
self.comment = (comment, )
84
self.dcomment = self.comment[0]
86
def setEndLine(self, eol):
87
if type(eol) is tuple:
91
self.deol = self.eol[0]
93
def setSectionNameDelimiters(self, delims):
94
self.sectnamdel = delims
96
def setSubSectionDelimiters(self, delims):
97
self.subsectdel = delims
99
def matchComment(self, line):
100
for v in self.comment:
101
if line.lstrip().startswith(v):
102
return line.lstrip()[len(v):]
105
def matchEmpty(self, line):
106
if line.strip() == "":
110
def matchSection(self, line):
111
cl = "".join(line.strip().split()).lower()
112
if len(self.sectnamdel) != 2:
114
if not cl.startswith(self.sectnamdel[0]):
116
if not cl.endswith(self.sectnamdel[1]):
118
return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]
120
def matchSubSection(self, line):
121
if self.matchComment(line):
124
parts = line.split(self.dassign, 1)
128
if parts[1].strip() == self.subsectdel[0]:
129
return parts[0].strip()
133
def matchSubSectionEnd(self, line):
134
if self.matchComment(line):
137
if line.strip() == self.subsectdel[1]:
142
def getSectionLine(self, section):
143
if len(self.sectnamdel) != 2:
145
return self.sectnamdel[0]+section+self.sectnamdel[1]+self.deol
147
def dump(self, options, level=0):
149
if level >= len(self.indent):
150
level = len(self.indent)-1
153
if o['type'] == "section":
154
output += self.sectnamdel[0]+o['name']+self.sectnamdel[1]+self.deol
155
output += self.dump(o['value'], level+1)
157
if o['type'] == "subsection":
158
output += self.indent[level]+o['name']+self.dassign+self.subsectdel[0]+self.deol
159
output += self.dump(o['value'], level+1)
160
output += self.indent[level]+self.subsectdel[1]+self.deol
162
if o['type'] == "option":
163
output += self.indent[level]+o['name']+self.dassign+o['value']+self.deol
165
if o['type'] == "comment":
166
output += self.dcomment+o['value']+self.deol
168
if o['type'] == "empty":
171
raise SyntaxError, 'Unknown type: ['+o['type']+']'
175
def parseLine(self, line):
177
if self.matchEmpty(line):
178
return {'name':'empty', 'type':'empty'}
180
value = self.matchComment(line)
182
return {'name':'comment', 'type':'comment', 'value':value.rstrip()} #pylint: disable=E1103
184
parts = line.split(self.dassign, 1)
186
raise SyntaxError, 'Syntax Error: Unknown line format'
188
return {'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()}
190
def findOpts(self, opts, type, name, exclude_sections=False):
194
if o['type'] == type and o['name'] == name:
196
if exclude_sections and (o['type'] == "section" or o['type'] == "subsection"):
201
def commentOpts(self, inopts, level = 0):
205
if level >= len(self.indent):
206
level = len(self.indent)-1
209
if o['type'] == 'section':
210
no = self.commentOpts(o['value'], level+1)
211
val = self.dcomment+self.sectnamdel[0]+o['name']+self.sectnamdel[1]
212
opts.append({'name':'comment', 'type':'comment', 'value':val})
216
if o['type'] == 'subsection':
217
no = self.commentOpts(o['value'], level+1)
218
val = self.indent[level]+o['name']+self.dassign+self.subsectdel[0]
219
opts.append({'name':'comment', 'type':'comment', 'value':val})
222
val = self.indent[level]+self.subsectdel[1]
223
opts.append({'name':'comment', 'type':'comment', 'value':val})
225
if o['type'] == 'option':
226
val = self.indent[level]+o['name']+self.dassign+o['value']
227
opts.append({'name':'comment', 'type':'comment', 'value':val})
229
if o['type'] == 'comment':
232
if o['type'] == 'empty':
233
opts.append({'name':'comment', 'type':'comment', 'value':''})
235
raise SyntaxError, 'Unknown type: ['+o['type']+']'
239
def mergeOld(self, oldopts, newopts):
244
if o['type'] == "section" or o['type'] == "subsection":
245
(num, no) = self.findOpts(newopts, o['type'], o['name'])
249
if no['action'] == "set":
250
mo = self.mergeOld(o['value'], no['value'])
251
opts.append({'name':o['name'], 'type':o['type'], 'value':mo})
253
if no['action'] == "comment":
254
co = self.commentOpts(o['value'])
258
if no['action'] == "remove":
260
raise SyntaxError, 'Unknown action: ['+no['action']+']'
262
if o['type'] == "comment" or o['type'] == "empty":
266
if o['type'] == "option":
267
(num, no) = self.findOpts(newopts, 'option', o['name'], True)
271
if no['action'] == 'comment' or no['action'] == 'remove':
272
if no['value'] != None and o['value'] != no['value']:
275
if no['action'] == 'comment':
276
opts.append({'name':'comment', 'type':'comment',
277
'value':self.dcomment+o['name']+self.dassign+o['value']})
279
if no['action'] == 'set':
282
raise SyntaxError, 'Unknown action: ['+o['action']+']'
284
raise SyntaxError, 'Unknown type: ['+o['type']+']'
288
def mergeNew(self, opts, newopts):
294
if no['type'] == "section" or no['type'] == "subsection":
295
(num, o) = self.findOpts(opts, no['type'], no['name'])
297
if no['action'] == 'set':
300
if no['action'] == "set":
301
self.mergeNew(o['value'], no['value'])
306
if no['type'] == "option":
307
(num, o) = self.findOpts(opts, no['type'], no['name'], True)
309
if no['action'] == 'set':
315
if no['type'] == "comment" or no['type'] == "empty":
316
opts.insert(cline, no)
320
raise SyntaxError, 'Unknown type: ['+no['type']+']'
323
def merge(self, oldopts, newopts):
325
#Use a two pass strategy
326
#First we create a new opts tree from oldopts removing/commenting
327
# the options as indicated by the contents of newopts
328
#Second we fill in the new opts tree with options as indicated
329
# in the newopts tree (this is becaus eentire (sub)sections may
330
# exist in the newopts that do not exist in oldopts)
332
opts = self.mergeOld(oldopts, newopts)
333
self.mergeNew(opts, newopts)
336
#TODO: Make parse() recursive?
347
# Read in the old file.
350
# It's a section start.
351
value = self.matchSection(line)
353
if section is not None:
354
opts.append({'name':section, 'type':'section', 'value':sectopts})
357
fatheropts = sectopts
361
# It's a subsection start.
362
value = self.matchSubSection(line)
364
if subsection is not None:
365
raise SyntaxError, 'nested subsections are not supported yet'
367
curopts = subsectopts
371
value = self.matchSubSectionEnd(line)
373
if subsection is None:
374
raise SyntaxError, 'Unmatched end subsection terminator found'
375
fatheropts.append({'name':subsection, 'type':'subsection', 'value':subsectopts})
380
# Copy anything else as is.
381
curopts.append(self.parseLine(line))
383
#Add last section if any
384
if len(sectopts) is not 0:
385
opts.append({'name':section, 'type':'section', 'value':sectopts})
389
# Write settings to configuration file
391
# options is a set of dictionaries in the form:
392
# [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
393
# section is a section name like 'global'
394
def changeConf(self, file, newopts):
401
#Do not catch an unexisting file error, we want to fail in that case
402
shutil.copy2(file, file+".ipabkp")
404
f = openLocked(file, 0644)
406
oldopts = self.parse(f)
408
options = self.merge(oldopts, newopts)
410
output = self.dump(options)
412
# Write it out and close it.
424
# Write settings to new file, backup old
426
# options is a set of dictionaries in the form:
427
# [{'name': 'foo', 'value': 'bar', 'action': 'set/comment'}]
428
# section is a section name like 'global'
429
def newConf(self, file, options):
437
shutil.copy2(file, file+".ipabkp")
440
# The orign file did not exist
443
f = openLocked(file, 0644)
449
output = self.dump(options)