~ubuntu-branches/ubuntu/raring/freeipa/raring

« back to all changes in this revision

Viewing changes to contrib/RHEL4/ipachangeconf.py

  • Committer: Package Import Robot
  • Author(s): Timo Aaltonen
  • Date: 2012-03-22 00:17:10 UTC
  • Revision ID: package-import@ubuntu.com-20120322001710-pyratwr2bfml6bin
Tags: upstream-2.1.4
ImportĀ upstreamĀ versionĀ 2.1.4

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
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>
 
6
#
 
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.
 
11
#
 
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.
 
16
#
 
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/>.
 
19
 
 
20
import fcntl
 
21
import os
 
22
import string
 
23
import time
 
24
import shutil
 
25
 
 
26
def openLocked(filename, perms):
 
27
    fd = -1
 
28
    try:
 
29
        fd = os.open(filename, os.O_RDWR | os.O_CREAT, perms)
 
30
        
 
31
        fcntl.lockf(fd, fcntl.LOCK_EX)
 
32
    except OSError, (errno, strerr):
 
33
        if fd != -1:
 
34
            try:
 
35
                os.close(fd)
 
36
            except OSError:
 
37
                pass
 
38
        raise IOError(errno, strerr)
 
39
    return os.fdopen(fd, "r+")
 
40
 
 
41
 
 
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
 
47
class IPAChangeConf:
 
48
 
 
49
    def __init__(self, name):
 
50
        self.progname = name
 
51
        self.indent = ("","","")
 
52
        self.assign = (" = ","=")
 
53
        self.dassign = self.assign[0]
 
54
        self.comment = ("#",)
 
55
        self.dcomment = self.comment[0]
 
56
        self.eol = ("\n",)
 
57
        self.deol = self.eol[0]
 
58
        self.sectnamdel = ("[","]")
 
59
        self.subsectdel = ("{","}")
 
60
 
 
61
    def setProgName(self, name):
 
62
        self.progname = name
 
63
 
 
64
    def setIndent(self, indent):
 
65
        if type(indent) is tuple:
 
66
            self.indent = indent
 
67
        elif type(indent) is str:
 
68
            self.indent = (indent, )
 
69
        else:
 
70
           raise ValueError, 'Indent must be a list of strings'
 
71
 
 
72
    def setOptionAssignment(self, assign):
 
73
        if type(assign) is tuple:
 
74
            self.assign = assign
 
75
        else:
 
76
            self.assign = (assign, )
 
77
        self.dassign = self.assign[0]
 
78
 
 
79
    def setCommentPrefix(self, comment):
 
80
        if type(comment) is tuple:
 
81
            self.comment = comment
 
82
        else:
 
83
            self.comment = (comment, )
 
84
        self.dcomment = self.comment[0]
 
85
 
 
86
    def setEndLine(self, eol):
 
87
        if type(eol) is tuple:
 
88
            self.eol = eol
 
89
        else:
 
90
            self.eol = (eol, )
 
91
        self.deol = self.eol[0]
 
92
 
 
93
    def setSectionNameDelimiters(self, delims):
 
94
        self.sectnamdel = delims
 
95
 
 
96
    def setSubSectionDelimiters(self, delims):
 
97
        self.subsectdel = delims
 
98
 
 
99
    def matchComment(self, line):
 
100
        for v in self.comment:
 
101
            if line.lstrip().startswith(v):
 
102
                return line.lstrip()[len(v):]
 
103
        return False
 
104
 
 
105
    def matchEmpty(self, line):
 
106
        if line.strip() == "":
 
107
            return True
 
108
        return False
 
109
 
 
110
    def matchSection(self, line):
 
111
        cl = "".join(line.strip().split()).lower()
 
112
        if len(self.sectnamdel) != 2:
 
113
            return False
 
114
        if not cl.startswith(self.sectnamdel[0]):
 
115
            return False
 
116
        if not cl.endswith(self.sectnamdel[1]):
 
117
            return False
 
118
        return cl[len(self.sectnamdel[0]):-len(self.sectnamdel[1])]            
 
119
 
 
120
    def matchSubSection(self, line):
 
121
        if self.matchComment(line):
 
122
            return False
 
123
 
 
124
        parts = line.split(self.dassign, 1)
 
125
        if len(parts) < 2:
 
126
            return False
 
127
 
 
128
        if parts[1].strip() == self.subsectdel[0]:
 
129
            return parts[0].strip()
 
130
 
 
131
        return False
 
132
 
 
133
    def matchSubSectionEnd(self, line):
 
134
        if self.matchComment(line):
 
135
            return False
 
136
 
 
137
        if line.strip() == self.subsectdel[1]:
 
138
            return True
 
139
 
 
140
        return False
 
141
 
 
142
    def getSectionLine(self, section):
 
143
        if len(self.sectnamdel) != 2:
 
144
            return section
 
145
        return self.sectnamdel[0]+section+self.sectnamdel[1]+self.deol
 
146
 
 
147
    def dump(self, options, level=0):
 
148
        output = ""
 
149
        if level >= len(self.indent):
 
150
            level = len(self.indent)-1
 
151
 
 
152
        for o in options:
 
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)
 
156
                continue
 
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
 
161
                continue
 
162
            if o['type'] == "option":
 
163
                output += self.indent[level]+o['name']+self.dassign+o['value']+self.deol
 
164
                continue
 
165
            if o['type'] == "comment":
 
166
                output += self.dcomment+o['value']+self.deol
 
167
                continue
 
168
            if o['type'] == "empty":
 
169
                output += self.deol
 
170
                continue
 
171
            raise SyntaxError, 'Unknown type: ['+o['type']+']'
 
172
 
 
173
        return output
 
174
 
 
175
    def parseLine(self, line):
 
176
 
 
177
        if self.matchEmpty(line):
 
178
            return {'name':'empty', 'type':'empty'}
 
179
 
 
180
        value = self.matchComment(line)
 
181
        if value:
 
182
            return {'name':'comment', 'type':'comment', 'value':value.rstrip()} #pylint: disable=E1103
 
183
 
 
184
        parts = line.split(self.dassign, 1)
 
185
        if len(parts) < 2:
 
186
            raise SyntaxError, 'Syntax Error: Unknown line format'
 
187
 
 
188
        return {'name':parts[0].strip(), 'type':'option', 'value':parts[1].rstrip()}
 
189
 
 
190
    def findOpts(self, opts, type, name, exclude_sections=False):
 
191
 
 
192
        num = 0
 
193
        for o in opts:
 
194
            if o['type'] == type and o['name'] == name:
 
195
                return (num, o)
 
196
            if exclude_sections and (o['type'] == "section" or o['type'] == "subsection"):
 
197
                return (num, None)
 
198
            num += 1
 
199
        return (num, None)
 
200
 
 
201
    def commentOpts(self, inopts, level = 0):
 
202
 
 
203
        opts = []
 
204
 
 
205
        if level >= len(self.indent):
 
206
            level = len(self.indent)-1
 
207
 
 
208
        for o in inopts:
 
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})
 
213
                for n in no:
 
214
                    opts.append(n)
 
215
                continue
 
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})
 
220
                for n in no:
 
221
                    opts.append(n)
 
222
                val = self.indent[level]+self.subsectdel[1]
 
223
                opts.append({'name':'comment', 'type':'comment', 'value':val})
 
224
                continue
 
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})
 
228
                continue
 
229
            if o['type'] == 'comment':
 
230
                opts.append(o)
 
231
                continue
 
232
            if o['type'] == 'empty':
 
233
                opts.append({'name':'comment', 'type':'comment', 'value':''})
 
234
                continue
 
235
            raise SyntaxError, 'Unknown type: ['+o['type']+']'
 
236
 
 
237
        return opts
 
238
 
 
239
    def mergeOld(self, oldopts, newopts):
 
240
 
 
241
        opts = []
 
242
 
 
243
        for o in oldopts:
 
244
            if o['type'] == "section" or o['type'] == "subsection":
 
245
                (num, no) = self.findOpts(newopts, o['type'], o['name'])
 
246
                if not no:
 
247
                    opts.append(o)
 
248
                    continue
 
249
                if no['action'] == "set":
 
250
                    mo = self.mergeOld(o['value'], no['value'])
 
251
                    opts.append({'name':o['name'], 'type':o['type'], 'value':mo})
 
252
                    continue
 
253
                if no['action'] == "comment":
 
254
                    co = self.commentOpts(o['value'])
 
255
                    for c in co:
 
256
                        opts.append(c)
 
257
                    continue
 
258
                if no['action'] == "remove":
 
259
                    continue
 
260
                raise SyntaxError, 'Unknown action: ['+no['action']+']'
 
261
 
 
262
            if o['type'] == "comment" or o['type'] == "empty":
 
263
                 opts.append(o)
 
264
                 continue
 
265
 
 
266
            if o['type'] == "option":
 
267
                (num, no) = self.findOpts(newopts, 'option', o['name'], True)
 
268
                if not no:
 
269
                    opts.append(o)
 
270
                    continue
 
271
                if no['action'] == 'comment' or no['action'] == 'remove':
 
272
                    if no['value'] != None and o['value'] != no['value']:
 
273
                        opts.append(o)
 
274
                        continue
 
275
                    if no['action'] == 'comment':
 
276
                       opts.append({'name':'comment', 'type':'comment',
 
277
                                    'value':self.dcomment+o['name']+self.dassign+o['value']})
 
278
                    continue
 
279
                if no['action'] == 'set':
 
280
                    opts.append(no)
 
281
                    continue
 
282
                raise SyntaxError, 'Unknown action: ['+o['action']+']'
 
283
 
 
284
            raise SyntaxError, 'Unknown type: ['+o['type']+']'
 
285
 
 
286
        return opts
 
287
 
 
288
    def mergeNew(self, opts, newopts):
 
289
 
 
290
        cline = 0
 
291
 
 
292
        for no in newopts:
 
293
 
 
294
            if no['type'] == "section" or no['type'] == "subsection":
 
295
                (num, o) = self.findOpts(opts, no['type'], no['name'])
 
296
                if not o:
 
297
                    if no['action'] == 'set':
 
298
                        opts.append(no)
 
299
                    continue
 
300
                if no['action'] == "set":
 
301
                    self.mergeNew(o['value'], no['value'])
 
302
                    continue
 
303
                cline = num+1
 
304
                continue
 
305
 
 
306
            if no['type'] == "option":
 
307
                (num, o) = self.findOpts(opts, no['type'], no['name'], True)
 
308
                if not o:
 
309
                    if no['action'] == 'set':
 
310
                        opts.append(no)
 
311
                    continue
 
312
                cline = num+1
 
313
                continue
 
314
 
 
315
            if no['type'] == "comment" or no['type'] == "empty":
 
316
                opts.insert(cline, no)
 
317
                cline += 1
 
318
                continue
 
319
 
 
320
            raise SyntaxError, 'Unknown type: ['+no['type']+']'
 
321
 
 
322
 
 
323
    def merge(self, oldopts, newopts):
 
324
 
 
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)
 
331
 
 
332
        opts = self.mergeOld(oldopts, newopts)
 
333
        self.mergeNew(opts, newopts)
 
334
        return opts
 
335
 
 
336
    #TODO: Make parse() recursive?
 
337
    def parse(self, f):
 
338
 
 
339
        opts = []
 
340
        sectopts = []
 
341
        section = None
 
342
        subsectopts = []
 
343
        subsection = None
 
344
        curopts = opts
 
345
        fatheropts = opts
 
346
 
 
347
        # Read in the old file.
 
348
        for line in f:
 
349
 
 
350
            # It's a section start.
 
351
            value = self.matchSection(line)
 
352
            if value:
 
353
                if section is not None:
 
354
                    opts.append({'name':section, 'type':'section', 'value':sectopts})
 
355
                sectopts = []
 
356
                curopts = sectopts
 
357
                fatheropts = sectopts
 
358
                section = value
 
359
                continue
 
360
 
 
361
            # It's a subsection start.
 
362
            value = self.matchSubSection(line)
 
363
            if value:
 
364
                if subsection is not None:
 
365
                    raise SyntaxError, 'nested subsections are not supported yet'
 
366
                subsectopts = []
 
367
                curopts = subsectopts
 
368
                subsection = value
 
369
                continue
 
370
 
 
371
            value = self.matchSubSectionEnd(line)
 
372
            if value:
 
373
                if subsection is None:
 
374
                    raise SyntaxError, 'Unmatched end subsection terminator found'
 
375
                fatheropts.append({'name':subsection, 'type':'subsection', 'value':subsectopts})
 
376
                subsection = None
 
377
                curopts = fatheropts
 
378
                continue
 
379
 
 
380
            # Copy anything else as is.
 
381
            curopts.append(self.parseLine(line))
 
382
 
 
383
        #Add last section if any
 
384
        if len(sectopts) is not 0:
 
385
            opts.append({'name':section, 'type':'section', 'value':sectopts})
 
386
 
 
387
        return opts
 
388
 
 
389
    # Write settings to configuration file
 
390
    # file is a path
 
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):
 
395
        autosection = False
 
396
        savedsection = None
 
397
        done = False
 
398
        output = ""
 
399
        f = None
 
400
        try:
 
401
            #Do not catch an unexisting file error, we want to fail in that case
 
402
            shutil.copy2(file, file+".ipabkp")
 
403
 
 
404
            f = openLocked(file, 0644)
 
405
 
 
406
            oldopts = self.parse(f)
 
407
 
 
408
            options = self.merge(oldopts, newopts)
 
409
 
 
410
            output = self.dump(options)
 
411
 
 
412
            # Write it out and close it.
 
413
            f.seek(0)
 
414
            f.truncate(0)
 
415
            f.write(output)
 
416
        finally:
 
417
            try:
 
418
                if f:
 
419
                    f.close()
 
420
            except IOError:
 
421
                pass
 
422
        return True
 
423
 
 
424
    # Write settings to new file, backup old
 
425
    # file is a path
 
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):
 
430
        autosection = False
 
431
        savedsection = None
 
432
        done = False
 
433
        output = ""
 
434
        f = None
 
435
        try:
 
436
            try:
 
437
                shutil.copy2(file, file+".ipabkp")
 
438
            except IOError, err:
 
439
                if err.errno == 2:
 
440
                    # The orign file did not exist
 
441
                    pass
 
442
 
 
443
            f = openLocked(file, 0644)
 
444
 
 
445
            # Trunkate
 
446
            f.seek(0)
 
447
            f.truncate(0)
 
448
 
 
449
            output = self.dump(options)
 
450
 
 
451
            f.write(output)
 
452
        finally:
 
453
            try:
 
454
                if f:
 
455
                    f.close()
 
456
            except IOError:
 
457
                pass
 
458
        return True