summaryrefslogtreecommitdiffstats
path: root/pyanaconda/simpleconfig.py
blob: 9fb128995a97acbdb40cfe8f0b95d5aee1172fd6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#
# simpleconifg.py - representation of a simple configuration file (sh-like)
#
# Matt Wilson <msw@redhat.com>
# Jeremy Katz <katzj@redhat.com>
# Will Woods <wwoods@redhat.com>
# Brian C. Lane <bcl@redhat.com>
#
# Copyright 1999-2012 Red Hat, Inc.
#
# This software may be freely redistributed under the terms of the GNU
# library public license.
#
# You should have received a copy of the GNU Library Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
import os
import shutil
import string
import shlex
from pipes import _safechars
import tempfile

# use our own ASCII only uppercase function to avoid locale issues
# not going to be fast but not important
def uppercase_ASCII_string(s):
    newstr = ""
    for c in s:
        if c in string.lowercase:
            newstr += chr(ord(c)-32)
        else:
            newstr += c
    return newstr

def unquote(s):
    return ' '.join(shlex.split(s))

def quote(s, always=False):
    """ If always is set it returns a quoted value
    """
    if not always:
        for c in s:
            if c not in _safechars:
                break
        else:
            return s
    return '"'+s.replace('"', '\\"')+'"'

class SimpleConfigFile(object):
    """ Edit values in a configuration file without changing comments.
        Supports KEY=VALUE lines and ignores everything else.
        Supports adding new keys.
        Supports deleting keys.
        Preserves comment, blank lines and comments on KEY lines
        Does not support duplicate key entries.
    """
    def __init__(self, filename=None, read_unquote=True, write_quote=True,
                 always_quote=False):
        self.filename = filename
        self.read_unquote = read_unquote
        self.write_quote = write_quote
        self.always_quote = always_quote
        self.reset()

    def reset(self):
        self._lines = []
        self.info = {}

    def read(self, filename=None):
        """ passing filename will override the filename passed to init.

            save the lines into self._lines and the key/value pairs into
            self.info
        """
        filename = filename or self.filename
        with open(filename) as f:
            for line in f:
                self._lines.append(line)
                key, value = self._parseline(line)
                if key:
                    self.info[key] = value

    def write(self, filename=None):
        """ passing filename will override the filename passed to init.
        """
        filename = filename or self.filename
        if not filename:
            return None

        tmpf = tempfile.NamedTemporaryFile(mode="w", delete=False)
        tmpf.write(str(self))
        tmpf.close()

        # Move the temporary file (with 0600 permissions) over the top of the
        # original and preserve the original's permissions
        filename = os.path.realpath(filename)
        if os.path.exists(filename):
            m = os.stat(filename).st_mode
        else:
            m = int('0100644', 8)
        shutil.move(tmpf.name, filename)
        os.chmod(filename, m)

    def set(self, *args):
        for key, value in args:
           self.info[uppercase_ASCII_string(key)] = value

    def unset(self, *keys):
        for key in [uppercase_ASCII_string(k) for k in keys]:
            if key in self.info:
               del self.info[key]

    def get(self, key):
        return self.info.get(uppercase_ASCII_string(key), "")

    def _parseline(self, line):
        """ parse a line into a key, value pair
            Handle comments and optionally unquote quoted strings
            Returns (key, value) or (None, None)
            key is always UPPERCASE
        """
        s = line.strip()
        if '#' in s:
            s = s[:s.find('#')] # remove from comment to EOL
            s = s.strip()       # and any unnecessary whitespace
        key, eq, val = s.partition('=')
        if self.read_unquote:
            val = unquote(val)
        if key != '' and eq == '=':
            return (uppercase_ASCII_string(key), val)
        else:
            return (None, None)

    def _kvpair(self, key, comment=""):
        value = self.info[key]
        if self.write_quote or self.always_quote:
            value = quote(value, self.always_quote)
        return key + '=' + value + comment + "\n"

    def __str__(self):
        """ Return the file that was read, replacing existing keys with new values
            removing keys that have been deleted and adding new keys.
        """
        oldkeys = []
        s = ""
        for line in self._lines:
            key, val = self._parseline(line)
            if key is None:
                s += line
            else:
                if key not in self.info:
                    continue
                oldkeys.append(key)
                if "#" in line:
                    comment = " " + line[line.find("#"):]
                else:
                    comment = ""
                s += self._kvpair(key, comment)

        # Add new keys
        for key in self.info:
            if key not in oldkeys:
                s += self._kvpair(key)

        return s


class IfcfgFile(SimpleConfigFile):
    def __init__(self, dir, iface):
        SimpleConfigFile.__init__(self, always_quote=True)
        self.iface = iface
        self.dir = dir

    @property
    def path(self):
        return os.path.join(self.dir, "ifcfg-%s" % self.iface)

    def clear(self):
        SimpleConfigFile.reset(self)

    def read(self):
        """ Reads values from ifcfg file.

            returns: number of values read
        """
        SimpleConfigFile.read(self, self.path)
        return len(self.info)

    # ifcfg-rh is using inotify IN_CLOSE_WRITE event
    # so we don't use temporary file for new configuration.
    def write(self, dir=None):
        """ Writes values into ifcfg file.
        """

        if not dir:
            path = self.path
        else:
            path = os.path.join(dir, os.path.basename(self.path))

        SimpleConfigFile.write(self, path)