1
0
Fork 0

bootloader: add support for initrd overlays

The bootloader plugin now supports the following options:
  initrd_add_img=IMAGE
  initrd_add_dir=DIR
  initrd_dst_img=PATHNAME

The 'initrd_add_img' adds initrd overlay named IMAGE. The IMAGE is
added from the current profile directory. If IMAGE begins with '/' it's
taken as absolute path (e.g. initrd_add_img="/root/overlay.img").

The 'initrd_add_dir' creates initrd image from the DIR at first and then
adds the image as a overlay. The DIR is taken from the current profile
directory. If DIR begins with '/' it's taken as absolute path.

The 'initrd_dst_img' sets the name and location of the resulting initrd
image. Usually it is not needed to set it. By default the location of
initrd images is /boot and the name of the image is taken as a basename
of IMAGE or DIR. This can be overridden by 'initrd_dst_img'

Currently grub2-mkconfig doesn't support initrd overlays, so the initrd
settings are lost after 'grub2-mkconfig -o /boot/grub2/grub.cfg' is
issued. There is grub2 RFE bugzilla:
https://bugzilla.redhat.com/show_bug.cgi?id=1427899

Resolves: rhbz#1414098

Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
This commit is contained in:
Jaroslav Škarvada 2017-03-01 15:56:17 +01:00
parent 4c1ed69f4a
commit 047a7335b0
10 changed files with 227 additions and 44 deletions

View file

@ -26,3 +26,4 @@ tuned_bootcmdline_file=$tunedcfgdir/bootcmdline
. $tuned_bootcmdline_file
echo "set tuned_params=\"$TUNED_BOOT_CMDLINE\""
echo "set tuned_initrd=\"$TUNED_BOOT_INITRD_ADD\""

View file

@ -1,6 +1,6 @@
# This file specifies additional parameters to kernel boot command line.
# Its content is set by the Tuned bootloader plugin and sourced by the
# grub2-mkconfig (/etc/grub.d/00_tuned script).
# This file specifies additional parameters to kernel boot command line and
# initrd overlay images. Its content is set by the Tuned bootloader plugin
# and sourced by the grub2-mkconfig (/etc/grub.d/00_tuned script).
#
# Please do not edit this file. Content of this file can be overwritten by
# switch of Tuned profile.
@ -21,5 +21,17 @@
# grub2-mkconfig -o /boot/grub2/grub.cfg
#
# YOUR_ADDITIONAL_KERNEL_PARAMETERS will stay preserved.
#
# Similarly if you need to add initrd overlay image, create Tuned profile
# containing the following:
#
# [bootloader]
# initrd_add_img = INITRD_OVERLAY_IMAGE
#
# or to generate initrd overlay image from the directory:
#
# [bootloader]
# initrd_add_dir = INITRD_OVERLAY_DIRECTORY
TUNED_BOOT_CMDLINE=
TUNED_BOOT_INITRD_ADD=

View file

@ -11,6 +11,8 @@ DEFAULT_PROFILE = "balanced"
DEFAULT_STORAGE_FILE = "/run/tuned/save.pickle"
LOAD_DIRECTORIES = ["/usr/lib/tuned", "/etc/tuned"]
PERSISTENT_STORAGE_DIR = "/var/lib/tuned"
PLUGIN_MAIN_UNIT_NAME = "main"
BOOT_DIR = "/boot"
TMP_FILE_SUFFIX = ".tmp"
# max. number of consecutive errors to give up
@ -24,8 +26,11 @@ GRUB2_TUNED_TEMPLATE_PATH = GRUB2_CFG_DIR + "/" + GRUB2_TUNED_TEMPLATE_NAME
GRUB2_TEMPLATE_HEADER_BEGIN = "### BEGIN /etc/grub.d/" + GRUB2_TUNED_TEMPLATE_NAME + " ###"
GRUB2_TEMPLATE_HEADER_END = "### END /etc/grub.d/" + GRUB2_TUNED_TEMPLATE_NAME + " ###"
GRUB2_TUNED_VAR = "tuned_params"
GRUB2_TUNED_INITRD_VAR = "tuned_initrd"
GRUB2_DEFAULT_ENV_FILE = "/etc/default/grub"
INITRD_IMAGE_DIR = "/boot"
BOOT_CMDLINE_TUNED_VAR = "TUNED_BOOT_CMDLINE"
BOOT_CMDLINE_INITRD_ADD_VAR = "TUNED_BOOT_INITRD_ADD"
BOOT_CMDLINE_FILE = "/etc/tuned/bootcmdline"
# modules plugin configuration

View file

@ -40,7 +40,7 @@ class Application(object):
profile_factory = profiles.Factory()
profile_merger = profiles.Merger()
profile_locator = profiles.Locator(consts.LOAD_DIRECTORIES)
profile_loader = profiles.Loader(profile_locator, profile_factory, profile_merger, self.variables)
profile_loader = profiles.Loader(profile_locator, profile_factory, profile_merger, self.config, self.variables)
self._daemon = daemon.Daemon(unit_manager, profile_loader, profile_name, self.config, self)
self._controller = controller.Controller(self._daemon, self.config)

View file

@ -178,6 +178,12 @@ class Plugin(object):
for device in devices:
callback(instance, device)
def _instance_pre_static(self, instance, enabling):
pass
def _instance_post_static(self, instance, enabling):
pass
def instance_apply_tuning(self, instance):
"""
Apply static and dynamic tuning if the plugin instance is active.
@ -186,7 +192,9 @@ class Plugin(object):
return
if instance.has_static_tuning:
self._instance_pre_static(instance, True)
self._instance_apply_static(instance)
self._instance_post_static(instance, True)
if instance.has_dynamic_tuning and self._global_cfg.get(consts.CFG_DYNAMIC_TUNING, consts.CFG_DEF_DYNAMIC_TUNING):
self._run_for_each_device(instance, self._instance_apply_dynamic)
@ -219,7 +227,9 @@ class Plugin(object):
if instance.has_dynamic_tuning and self._global_cfg.get(consts.CFG_DYNAMIC_TUNING, consts.CFG_DEF_DYNAMIC_TUNING):
self._run_for_each_device(instance, self._instance_unapply_dynamic)
if instance.has_static_tuning:
self._instance_pre_static(instance, False)
self._instance_unapply_static(instance, profile_switch)
self._instance_post_static(instance, False)
def _instance_apply_static(self, instance):
self._execute_all_non_device_commands(instance)

View file

@ -7,6 +7,7 @@ import tuned.consts as consts
import os
import re
import tempfile
log = tuned.logs.get()
@ -27,6 +28,11 @@ class BootloaderPlugin(base.Plugin):
def _instance_init(self, instance):
instance._has_dynamic_tuning = False
instance._has_static_tuning = True
# controls grub2_cfg rewrites in _instance_post_static
self.update_grub2_cfg = False
self._initrd_dst_img = None
self._cmdline = ""
self._initrd = ""
self._grub2_cfg_file_name = self._get_grub2_cfg_file()
def _instance_cleanup(self, instance):
@ -36,7 +42,10 @@ class BootloaderPlugin(base.Plugin):
def _get_config_options(cls):
return {
"grub2_cfg_file": None,
"cmdline": "",
"initrd_dst_img": None,
"initrd_add_img": None,
"initrd_add_dir": None,
"cmdline": None,
}
def _get_effective_options(self, options):
@ -76,13 +85,12 @@ class BootloaderPlugin(base.Plugin):
return f
return None
def _patch_bootcmdline(self, value):
return self._cmd.replace_in_file(consts.BOOT_CMDLINE_FILE, r"\b(" + consts.BOOT_CMDLINE_TUNED_VAR + \
r"\s*=).*$", r"\1" + "\"" + str(value) + "\"")
def _patch_bootcmdline(self, d):
return self._cmd.add_modify_option_in_file(consts.BOOT_CMDLINE_FILE, d)
def _remove_grub2_tuning(self):
self._patch_bootcmdline("")
self._cmd.replace_in_file(self._grub2_cfg_file_name, r"\b(set\s+" + consts.GRUB2_TUNED_VAR + r"\s*=).*$", r"\1" + "\"\"")
self._patch_bootcmdline({consts.BOOT_CMDLINE_TUNED_VAR : "", consts.BOOT_CMDLINE_INITRD_ADD_VAR : ""})
self._cmd.add_modify_option_in_file(self._grub2_cfg_file_name, {"set\s+" + consts.GRUB2_TUNED_VAR : "", "set\s+" + consts.GRUB2_TUNED_INITRD_VAR : ""}, add = False)
def _instance_unapply_static(self, instance, profile_switch = False):
if profile_switch:
@ -93,19 +101,28 @@ class BootloaderPlugin(base.Plugin):
log.debug("unpatching grub.cfg")
cfg = re.sub(r"^\s*set\s+" + consts.GRUB2_TUNED_VAR + "\s*=.*\n", "", grub2_cfg, flags = re.MULTILINE)
grub2_cfg = re.sub(r" *\$" + consts.GRUB2_TUNED_VAR, "", cfg, flags = re.MULTILINE)
cfg = re.sub(r"^\s*set\s+" + consts.GRUB2_TUNED_INITRD_VAR + "\s*=.*\n", "", grub2_cfg, flags = re.MULTILINE)
grub2_cfg = re.sub(r" *\$" + consts.GRUB2_TUNED_INITRD_VAR, "", cfg, flags = re.MULTILINE)
cfg = re.sub(consts.GRUB2_TEMPLATE_HEADER_BEGIN + r"\n", "", grub2_cfg, flags = re.MULTILINE)
return re.sub(consts.GRUB2_TEMPLATE_HEADER_END + r"\n+", "", cfg, flags = re.MULTILINE)
def _grub2_cfg_patch_initial(self, grub2_cfg, value):
def _grub2_cfg_patch_initial(self, grub2_cfg, d):
log.debug("initial patching of grub.cfg")
cfg = re.sub(r"^(\s*###\s+END\s+[^#]+/00_header\s+### *)\n", r"\1\n\n" + consts.GRUB2_TEMPLATE_HEADER_BEGIN + "\nset " +
consts.GRUB2_TUNED_VAR + "=\"" + str(value) + "\"\n" + consts.GRUB2_TEMPLATE_HEADER_END + r"\n", grub2_cfg, flags = re.MULTILINE)
# add tuned parameters to all kernels
grub2_cfg = re.sub(r"^(\s*linux(16|efi)?\s+.*)$", r"\1 $" + consts.GRUB2_TUNED_VAR, cfg, flags = re.MULTILINE)
# remove tuned parameters from rescue kernels
cfg = re.sub(r"^(\s*linux(?:16|efi)?\s+\S+rescue.*)\$" + consts.GRUB2_TUNED_VAR + r" *(.*)$", r"\1\2", grub2_cfg, flags = re.MULTILINE)
# fix whitespaces in rescue kernels
return re.sub(r"^(\s*linux(?:16|efi)?\s+\S+rescue.*) +$", r"\1", cfg, flags = re.MULTILINE)
s = r"\1\n\n" + consts.GRUB2_TEMPLATE_HEADER_BEGIN + "\n"
for opt in d:
s += r"set " + self._cmd.escape(opt) + "=\"" + self._cmd.escape(d[opt]) + "\"\n"
s += consts.GRUB2_TEMPLATE_HEADER_END + r"\n"
grub2_cfg = re.sub(r"^(\s*###\s+END\s+[^#]+/00_header\s+### *)\n", s, grub2_cfg, flags = re.MULTILINE)
d2 = {"linux" : consts.GRUB2_TUNED_VAR, "initrd" : consts.GRUB2_TUNED_INITRD_VAR}
for i in d2:
# add tuned parameters to all kernels
grub2_cfg = re.sub(r"^(\s*" + i + r"(16|efi)?\s+.*)$", r"\1 $" + d2[i], grub2_cfg, flags = re.MULTILINE)
# remove tuned parameters from rescue kernels
grub2_cfg = re.sub(r"^(\s*" + i + r"(?:16|efi)?\s+\S+rescue.*)\$" + d2[i] + r" *(.*)$", r"\1\2", grub2_cfg, flags = re.MULTILINE)
# fix whitespaces in rescue kernels
grub2_cfg = re.sub(r"^(\s*" + i + r"(?:16|efi)?\s+\S+rescue.*) +$", r"\1", grub2_cfg, flags = re.MULTILINE)
return grub2_cfg
def _grub2_default_env_patch(self):
grub2_default_env = self._cmd.read_file(consts.GRUB2_DEFAULT_ENV_FILE)
@ -113,13 +130,20 @@ class BootloaderPlugin(base.Plugin):
log.error("error reading '%s'" % consts.GRUB2_DEFAULT_ENV_FILE)
return False
if re.search(r"^[^#]*\bGRUB_CMDLINE_LINUX_DEFAULT\s*=.*\\\$" + consts.GRUB2_TUNED_VAR + r"\b.*$", grub2_default_env, flags = re.MULTILINE) is None:
d = {"GRUB_CMDLINE_LINUX_DEFAULT" : consts.GRUB2_TUNED_VAR, "GRUB_INITRD_OVERLAY" : consts.GRUB2_TUNED_INITRD_VAR}
write = False
for i in d:
if re.search(r"^[^#]*\b" + i + r"\s*=.*\\\$" + d[i] + r"\b.*$", grub2_default_env, flags = re.MULTILINE) is None:
write = True
if grub2_default_env[-1] != "\n":
grub2_default_env += "\n"
grub2_default_env += i + "=\"${" + i + ":+$" + i + r" }\$" + d[i] + "\"\n"
if write:
log.debug("patching '%s'" % consts.GRUB2_DEFAULT_ENV_FILE)
self._cmd.write_to_file(consts.GRUB2_DEFAULT_ENV_FILE,
grub2_default_env + "GRUB_CMDLINE_LINUX_DEFAULT=\"${GRUB_CMDLINE_LINUX_DEFAULT:+$GRUB_CMDLINE_LINUX_DEFAULT }" + r"\$" + consts.GRUB2_TUNED_VAR + "\"\n")
self._cmd.write_to_file(consts.GRUB2_DEFAULT_ENV_FILE, grub2_default_env)
return True
def _grub2_cfg_patch(self, value):
def _grub2_cfg_patch(self, d):
log.debug("patching grub.cfg")
if self._grub2_cfg_file_name is None:
log.error("cannot find grub.cfg to patch, you need to regenerate it by hand by grub2-mkconfig")
@ -129,20 +153,96 @@ class BootloaderPlugin(base.Plugin):
log.error("error patching %s, you need to regenerate it by hand by grub2-mkconfig" % self._grub2_cfg_file_name)
return False
log.debug("adding boot command line parameters to '%s'" % self._grub2_cfg_file_name)
(grub2_cfg_new, nsubs) = re.subn(r"\b(set\s+" + consts.GRUB2_TUNED_VAR + "\s*=).*$", r"\1" + "\"" + str(value) + "\"", grub2_cfg, flags = re.MULTILINE)
if nsubs < 1 or re.search(r"\$" + consts.GRUB2_TUNED_VAR, grub2_cfg, flags = re.MULTILINE) is None:
grub2_cfg_new = self._grub2_cfg_patch_initial(self._grub2_cfg_unpatch(grub2_cfg), value)
grub2_cfg_new = grub2_cfg
patch_initial = False
for opt in d:
(grub2_cfg_new, nsubs) = re.subn(r"\b(set\s+" + opt + "\s*=).*$", r"\1" + "\"" + d[opt] + "\"", grub2_cfg_new, flags = re.MULTILINE)
if nsubs < 1 or re.search(r"\$" + opt, grub2_cfg, flags = re.MULTILINE) is None:
patch_initial = True
if patch_initial:
grub2_cfg_new = self._grub2_cfg_patch_initial(self._grub2_cfg_unpatch(grub2_cfg), d)
self._cmd.write_to_file(self._grub2_cfg_file_name, grub2_cfg_new)
self._grub2_default_env_patch()
return True
def _grub2_update(self):
self._grub2_cfg_patch({consts.GRUB2_TUNED_VAR : self._cmdline, consts.GRUB2_TUNED_INITRD_VAR : self._initrd})
self._patch_bootcmdline({consts.BOOT_CMDLINE_TUNED_VAR : self._cmdline, consts.BOOT_CMDLINE_INITRD_ADD_VAR : self._initrd})
def _init_initrd_dst_img(self, name):
if self._initrd_dst_img is None:
self._initrd_dst_img = os.path.join(consts.BOOT_DIR, os.path.basename(name))
def _install_initrd(self, img):
log.info("installing initrd image as '%s'" % self._initrd_dst_img)
img_name = os.path.basename(self._initrd_dst_img)
self._cmd.copy(img, self._initrd_dst_img)
self.update_grub2_cfg = True
self._initrd = "/" + img_name
@command_custom("grub2_cfg_file")
def _grub2_cfg_file(self, enabling, value, verify, ignore_missing):
# nothing to verify
if verify:
return None
if enabling and value is not None:
self._grub2_cfg_file_name = value
self._grub2_cfg_file_name = str(value)
@command_custom("initrd_dst_img")
def _initrd_dst_img(self, enabling, value, verify, ignore_missing):
# nothing to verify
if verify:
return None
if enabling and value is not None:
self._initrd_dst_img = str(value)
if self._initrd_dst_img == "":
return False
if self._initrd_dst_img[0] != "/":
self._initrd_dst_img = os.path.join(consts.BOOT_DIR, self._initrd_dst_img)
@command_custom("initrd_add_img", per_device = False, priority = 10)
def _initrd_add_img(self, enabling, value, verify, ignore_missing):
# nothing to verify
if verify:
return None
if enabling and value is not None:
src_img = str(value)
self._init_initrd_dst_img(src_img)
if src_img == "":
return False
if src_img[0] != "/":
src_img = os.path.join(os.path.dirname(self._global_cfg.get("profile_location", "")), src_img)
self._install_initrd(src_img)
@command_custom("initrd_add_dir", per_device = False, priority = 10)
def _initrd_add_dir(self, enabling, value, verify, ignore_missing):
# nothing to verify
if verify:
return None
if enabling and value is not None:
src_dir = str(value)
self._init_initrd_dst_img(src_dir)
if src_dir == "":
return False
if src_dir[0] != "/":
src_dir = os.path.join(os.path.dirname(self._global_cfg.get("profile_location", "./")), src_dir)
if not os.path.isdir(src_dir):
log.error("error: cannot create initrd image, source directory '%s' doesn't exist" % src_dir)
return False
log.info("generating initrd image from directory '%s'" % src_dir)
(fd, tmpfile) = tempfile.mkstemp(prefix = "tuned-bootloader-", suffix = ".tmp")
log.debug("writing initrd image to temporary file '%s'" % tmpfile)
os.close(fd)
(rc, out) = self._cmd.execute("find . | cpio -co > %s" % tmpfile, cwd = src_dir, shell = True)
log.debug("cpio log: %s" % out)
if rc != 0:
log.error("error generating initrd image")
self._cmd.unlink(tmpfile, no_error = True)
return False
self._install_initrd(tmpfile)
self._cmd.unlink(tmpfile)
@command_custom("cmdline", per_device = False, priority = 10)
def _cmdline(self, enabling, value, verify, ignore_missing):
@ -160,7 +260,12 @@ class BootloaderPlugin(base.Plugin):
else:
log.error(consts.STR_VERIFY_PROFILE_VALUE_FAIL % ("cmdline", str(cmdline_intersect), str(value_set)))
return False
if enabling:
if enabling and value is not None:
log.info("installing additional boot command line parameters to grub2")
self._grub2_cfg_patch(v)
self._patch_bootcmdline(v)
self.update_grub2_cfg = True
self._cmdline = v
def _instance_post_static(self, instance, enabling):
if enabling and self.update_grub2_cfg:
self._grub2_update()
self.update_grub2_cfg = False

View file

@ -1,6 +1,7 @@
import tuned.profiles.profile
import tuned.profiles.variables
from configobj import ConfigObj, ConfigObjError
import tuned.consts as consts
import os.path
import collections
import tuned.logs
@ -14,12 +15,13 @@ class Loader(object):
Profiles loader.
"""
__slots__ = ["_profile_locator", "_profile_merger", "_profile_factory", "_variables"]
__slots__ = ["_profile_locator", "_profile_merger", "_profile_factory", "_global_config", "_variables"]
def __init__(self, profile_locator, profile_factory, profile_merger, variables):
def __init__(self, profile_locator, profile_factory, profile_merger, global_config, variables):
self._profile_locator = profile_locator
self._profile_factory = profile_factory
self._profile_merger = profile_merger
self._global_config = global_config
self._variables = variables
def _create_profile(self, profile_name, config):
@ -87,10 +89,13 @@ class Loader(object):
for option in config_obj[section].keys():
config[section][option] = config_obj[section][option]
# hack to notify plugins about profile location
self._global_config.set("profile_location", file_name)
# TODO: HACK, this needs to be solved in a better way (better config parser)
dir_name = os.path.dirname(file_name)
for unit_name in config:
if "script" in config[unit_name] and config[unit_name].get("script", None) is not None:
dir_name = os.path.dirname(file_name)
script_path = os.path.join(dir_name, config[unit_name]["script"])
config[unit_name]["script"] = [os.path.normpath(script_path)]

View file

@ -73,8 +73,8 @@ class Locator(object):
config = self.parse_config(profile_name)
if config is None:
return [False, "", "", ""]
if "main" in config:
d = config["main"]
if consts.PLUGIN_MAIN_UNIT_NAME in config:
d = config[consts.PLUGIN_MAIN_UNIT_NAME]
else:
d = dict()
vals = [True, profile_name]

View file

@ -1,4 +1,5 @@
import tuned.profiles.unit
import tuned.consts as consts
import collections
class Profile(object):
@ -15,13 +16,13 @@ class Profile(object):
def _init_options(self, config):
self._options = {}
if "main" in config:
self._options = dict(config["main"])
if consts.PLUGIN_MAIN_UNIT_NAME in config:
self._options = dict(config[consts.PLUGIN_MAIN_UNIT_NAME])
def _init_units(self, config):
self._units = collections.OrderedDict()
for unit_name in config:
if unit_name != "main":
if unit_name != consts.PLUGIN_MAIN_UNIT_NAME:
new_unit = self._create_unit(unit_name, config[unit_name])
self._units[unit_name] = new_unit

View file

@ -2,6 +2,7 @@ import errno
import tuned.logs
import copy
import os
import shutil
import tuned.consts as consts
from configobj import ConfigObj, ConfigObjError
import re
@ -33,6 +34,10 @@ class commands:
def unquote(self, v):
return re.sub("^\"(.*)\"$", r"\1", v)
# escape escape character (by default '\')
def escape(self, s, what_escape = "\\", escape_by = "\\"):
return s.replace(what_escape, "%s%s" % (escape_by, what_escape))
# clear escape characters (by default '\')
def unescape(self, s, escape_char = "\\"):
return s.replace(escape_char, "")
@ -60,12 +65,16 @@ class commands:
# Do multiple regex replaces in 's' according to lookup table described by
# dictionary 'd', e.g.: d = {"re1": "replace1", "re2": "replace2", ...}
# r can be regex precompiled by re_lookup_compile for speedup
def multiple_re_replace(self, d, s, r = None):
if len(d) == 0 or s is None:
return s
def multiple_re_replace(self, d, s, r = None, flags = 0):
if d is None:
if r is None:
return s
else:
if len(d) == 0 or s is None:
return s
if r is None:
r = self.re_lookup_compile(d)
return r.sub(lambda mo: d.values()[mo.lastindex - 1], s)
return r.sub(lambda mo: d.values()[mo.lastindex - 1], s, flags)
# Do regex lookup on 's' according to lookup table described by
# dictionary 'd' and return corresponding value from the dictionary,
@ -133,14 +142,49 @@ class commands:
return False
return True
def copy(self, src, dst, no_error = False):
try:
log.debug("copying file '%s' to '%s'" % (src, dst))
shutil.copy(src, dst)
except IOError as e:
if not no_error:
log.error("cannot copy file '%s' to '%s': %s" % (src, dst, e))
def replace_in_file(self, f, pattern, repl):
data = self.read_file(f)
if len(data) <= 0:
return False;
return self.write_to_file(f, re.sub(pattern, repl, data, flags = re.MULTILINE))
# do multiple replaces in file 'f' by using dictionary 'd',
# e.g.: d = {"re1": val1, "re2": val2, ...}
def multiple_replace_in_file(self, f, d):
data = self.read_file(f)
if len(data) <= 0:
return False;
return self.write_to_file(f, self.multiple_re_replace(d, data, flags = re.MULTILINE))
# makes sure that options from 'd' are set to values from 'd' in file 'f',
# when needed it edits options or add new options if they don't
# exist and 'add' is set to True, 'd' has the following form:
# d = {"option_1": value_1, "option_2": value_2, ...}
def add_modify_option_in_file(self, f, d, add = True):
data = self.read_file(f)
for opt in d:
o = str(opt)
v = str(d[opt])
if re.search(r"\b" + o + r"\s*=.*$", data, flags = re.MULTILINE) is None:
if add:
if len(data) > 0 and data[-1] != "\n":
data += "\n"
data += "%s=\"%s\"\n" % (o, v)
else:
data = re.sub(r"\b(" + o + r"\s*=).*$", r"\1" + "\"" + v + "\"", data, flags = re.MULTILINE)
return self.write_to_file(f, data)
# "no_errors" can be list of return codes not treated as errors
def execute(self, args, no_errors = []):
def execute(self, args, shell = False, cwd = None, no_errors = []):
retcode = 0
if self._environment is None:
self._environment = os.environ.copy()
@ -149,7 +193,7 @@ class commands:
self._debug("Executing %s." % str(args))
out = ""
try:
proc = Popen(args, stdout=PIPE, stderr=PIPE, env=self._environment, close_fds=True)
proc = Popen(args, stdout = PIPE, stderr = PIPE, env = self._environment, shell = shell, cwd = cwd, close_fds = True)
out, err = proc.communicate()
retcode = proc.returncode