summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian C. Lane <bcl@redhat.com>2012-08-07 17:32:28 -0700
committerBrian C. Lane <bcl@redhat.com>2012-08-09 08:22:27 -0700
commit16a7c6e7af3df4816323a007f785155d307869bf (patch)
tree17c754675b3e20fa982a9710e247f601db86a374
parente7f28ffbb3f85bf72ac828e5838d99714ccc997f (diff)
downloadanaconda-16a7c6e7af3df4816323a007f785155d307869bf.tar.gz
anaconda-16a7c6e7af3df4816323a007f785155d307869bf.tar.xz
anaconda-16a7c6e7af3df4816323a007f785155d307869bf.zip
simpleconfig: rewrite to better support commented config files
This takes some of my code, and some of wwoods' code and rewrites SimpleConfigFile to support a wider range of uses. It can now preserve comments and whitespaces in config files and can quote (or not) values when reading and writing them.
-rw-r--r--pyanaconda/simpleconfig.py222
1 files changed, 143 insertions, 79 deletions
diff --git a/pyanaconda/simpleconfig.py b/pyanaconda/simpleconfig.py
index 4bd665ac8..db0dea22a 100644
--- a/pyanaconda/simpleconfig.py
+++ b/pyanaconda/simpleconfig.py
@@ -3,8 +3,10 @@
#
# Matt Wilson <msw@redhat.com>
# Jeremy Katz <katzj@redhat.com>
+# Will Woods <wwoods@redhat.com>
+# Brian C. Lane <bcl@redhat.com>
#
-# Copyright 1999-2002 Red Hat, Inc.
+# Copyright 1999-2012 Red Hat, Inc.
#
# This software may be freely redistributed under the terms of the GNU
# library public license.
@@ -13,81 +15,157 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
-
-import string
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(str):
+def uppercase_ASCII_string(s):
newstr = ""
- for i in range(0,len(str)):
- if str[i] in string.lowercase:
- newstr += chr(ord(str[i])-32)
- else:
- newstr += str[i]
-
+ for c in s:
+ if c in string.lowercase:
+ newstr += chr(ord(c)-32)
+ else:
+ newstr += c
return newstr
-class SimpleConfigFile:
- def __str__ (self):
- s = ""
- keys = self.info.keys ()
- keys.sort ()
- for key in keys:
- # FIXME - use proper escaping
- if type (self.info[key]) == type(""):
- s = s + key + "=\"" + self.info[key] + "\"\n"
- return s
+def unquote(s):
+ return ' '.join(shlex.split(s))
- def __init__ (self):
+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 write(self, file):
- f = open(file, "w")
- f.write(self.__str__())
- f.close()
-
- def read(self, file):
- if not os.access(file, os.R_OK):
- return
-
- f = open(file, "r")
- lines = f.readlines()
- f.close()
-
- for line in lines:
- fields = line[:-1].split('=', 2)
- if len(fields) < 2:
- # how am I supposed to know what to do here?
- continue
- key = uppercase_ASCII_string(fields[0])
- value = fields[1]
- # XXX hack
- value = value.replace('"', '')
- value = value.replace("'", '')
- self.info[key] = value
-
- def set (self, *args):
- for (key, data) in args:
- self.info[uppercase_ASCII_string(key)] = data
-
- def unset (self, *keys):
- for key in keys:
- key = uppercase_ASCII_string(key)
- if self.info.has_key (key):
+ 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)
+ m = os.stat(filename)
+ shutil.move(tmpf.name, filename)
+ os.chmod(filename, m.st_mode)
+
+ 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):
- key = uppercase_ASCII_string(key)
- return self.info.get(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):
+class IfcfgFile(SimpleConfigFile):
def __init__(self, dir, iface):
- SimpleConfigFile.__init__(self)
+ SimpleConfigFile.__init__(self, always_quote=True)
self.iface = iface
self.dir = dir
@@ -96,35 +174,21 @@ class IfcfgFile(SimpleConfigFile):
return os.path.join(self.dir, "ifcfg-%s" % self.iface)
def clear(self):
- self.info = {}
+ SimpleConfigFile.reset(self)
def read(self):
- """Reads values from ifcfg file.
+ """ Reads values from ifcfg file.
- returns: number of values read
+ returns: number of values read
"""
- f = open(self.path, "r")
- lines = f.readlines()
- f.close()
-
- for line in lines:
- line = line.strip()
- if line.startswith("#") or line == '':
- continue
- fields = line.split('=', 1)
- key = uppercase_ASCII_string(fields[0])
- value = fields[1]
- # XXX hack
- value = value.replace('"', '')
- value = value.replace("'", '')
- self.info[key] = value
-
+ 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."""
+ """ Writes values into ifcfg file.
+ """
if not dir:
path = self.path