summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Client/Tools
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Client/Tools')
-rw-r--r--src/lib/Bcfg2/Client/Tools/APK.py4
-rw-r--r--src/lib/Bcfg2/Client/Tools/APT.py46
-rw-r--r--src/lib/Bcfg2/Client/Tools/Action.py9
-rw-r--r--src/lib/Bcfg2/Client/Tools/Chkconfig.py46
-rw-r--r--src/lib/Bcfg2/Client/Tools/DebInit.py5
-rw-r--r--src/lib/Bcfg2/Client/Tools/OpenCSW.py33
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX.py943
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/Device.py66
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/Directory.py90
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/File.py225
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/Hardlink.py43
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/Nonexistent.py45
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/Permissions.py11
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/Symlink.py46
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/__init__.py151
-rw-r--r--src/lib/Bcfg2/Client/Tools/POSIX/base.py642
-rw-r--r--src/lib/Bcfg2/Client/Tools/Portage.py27
-rw-r--r--src/lib/Bcfg2/Client/Tools/RPMng.py124
-rw-r--r--src/lib/Bcfg2/Client/Tools/RcUpdate.py40
-rw-r--r--src/lib/Bcfg2/Client/Tools/SELinux.py716
-rw-r--r--src/lib/Bcfg2/Client/Tools/SMF.py5
-rw-r--r--src/lib/Bcfg2/Client/Tools/Systemd.py17
-rw-r--r--src/lib/Bcfg2/Client/Tools/Upstart.py5
-rw-r--r--src/lib/Bcfg2/Client/Tools/YUM24.py21
-rw-r--r--src/lib/Bcfg2/Client/Tools/YUMng.py121
-rw-r--r--src/lib/Bcfg2/Client/Tools/__init__.py117
-rw-r--r--src/lib/Bcfg2/Client/Tools/launchd.py5
-rwxr-xr-xsrc/lib/Bcfg2/Client/Tools/rpmtools.py23
28 files changed, 2320 insertions, 1306 deletions
diff --git a/src/lib/Bcfg2/Client/Tools/APK.py b/src/lib/Bcfg2/Client/Tools/APK.py
index aaaf2472f..d70916792 100644
--- a/src/lib/Bcfg2/Client/Tools/APK.py
+++ b/src/lib/Bcfg2/Client/Tools/APK.py
@@ -24,8 +24,8 @@ class APK(Bcfg2.Client.Tools.PkgTool):
for pkg in zip(names, nameversions):
pkgname = pkg[0]
version = pkg[1][len(pkgname) + 1:]
- self.logger.debug(" pkgname: %s\n version: %s" %
- (pkgname, version))
+ self.logger.debug(" pkgname: %s" % pkgname)
+ self.logger.debug(" version: %s" % version)
self.installed[pkgname] = version
def VerifyPackage(self, entry, modlist):
diff --git a/src/lib/Bcfg2/Client/Tools/APT.py b/src/lib/Bcfg2/Client/Tools/APT.py
index 6b839ffbc..ce7e9701f 100644
--- a/src/lib/Bcfg2/Client/Tools/APT.py
+++ b/src/lib/Bcfg2/Client/Tools/APT.py
@@ -6,22 +6,7 @@ warnings.filterwarnings("ignore", "apt API not stable yet",
FutureWarning)
import apt.cache
import os
-
import Bcfg2.Client.Tools
-import Bcfg2.Options
-
-# Options for tool locations
-opts = {'install_path': Bcfg2.Options.CLIENT_APT_TOOLS_INSTALL_PATH,
- 'var_path': Bcfg2.Options.CLIENT_APT_TOOLS_VAR_PATH,
- 'etc_path': Bcfg2.Options.CLIENT_SYSTEM_ETC_PATH}
-setup = Bcfg2.Options.OptionParser(opts)
-setup.parse([])
-install_path = setup['install_path']
-var_path = setup['var_path']
-etc_path = setup['etc_path']
-DEBSUMS = '%s/bin/debsums' % install_path
-APTGET = '%s/bin/apt-get' % install_path
-DPKG = '%s/bin/dpkg' % install_path
class APT(Bcfg2.Client.Tools.Tool):
"""The Debian toolset implements package and service operations and inherits
@@ -29,18 +14,26 @@ class APT(Bcfg2.Client.Tools.Tool):
"""
name = 'APT'
- __execs__ = [DEBSUMS, APTGET, DPKG]
+ __execs__ = []
__handles__ = [('Package', 'deb'), ('Path', 'ignore')]
__req__ = {'Package': ['name', 'version'], 'Path': ['type']}
def __init__(self, logger, setup, config):
Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config)
+
+ self.install_path = setup.get('apt_install_path', '/usr')
+ self.var_path = setup.get('apt_var_path', '/var')
+ self.etc_path = setup.get('apt_etc_path', '/etc')
+ self.debsums = '%s/bin/debsums' % self.install_path
+ self.aptget = '%s/bin/apt-get' % self.install_path
+ self.dpkg = '%s/bin/dpkg' % self.install_path
+ self.__execs__ = [self.debsums, self.aptget, self.dpkg]
+
path_entries = os.environ['PATH'].split(':')
for reqdir in ['/sbin', '/usr/sbin']:
if reqdir not in path_entries:
os.environ['PATH'] = os.environ['PATH'] + ':' + reqdir
- self.pkgcmd = '%s ' % APTGET + \
- '-o DPkg::Options::=--force-overwrite ' + \
+ self.pkgcmd = '%s ' % self.aptget + \
'-o DPkg::Options::=--force-confold ' + \
'-o DPkg::Options::=--force-confmiss ' + \
'--reinstall ' + \
@@ -53,21 +46,21 @@ class APT(Bcfg2.Client.Tools.Tool):
if entry.tag == 'Path' and \
entry.get('type') == 'ignore']
self.__important__ = self.__important__ + \
- ["%s/cache/debconf/config.dat" % var_path,
- "%s/cache/debconf/templates.dat" % var_path,
+ ["%s/cache/debconf/config.dat" % self.var_path,
+ "%s/cache/debconf/templates.dat" % self.var_path,
'/etc/passwd', '/etc/group',
- '%s/apt/apt.conf' % etc_path,
- '%s/dpkg/dpkg.cfg' % etc_path] + \
+ '%s/apt/apt.conf' % self.etc_path,
+ '%s/dpkg/dpkg.cfg' % self.etc_path] + \
[entry.get('name') for struct in config for entry in struct \
if entry.tag == 'Path' and \
- entry.get('name').startswith('%s/apt/sources.list' % etc_path)]
+ entry.get('name').startswith('%s/apt/sources.list' % self.etc_path)]
self.nonexistent = [entry.get('name') for struct in config for entry in struct \
if entry.tag == 'Path' and entry.get('type') == 'nonexistent']
os.environ["DEBIAN_FRONTEND"] = 'noninteractive'
self.actions = {}
if self.setup['kevlar'] and not self.setup['dryrun']:
- self.cmd.run("%s --force-confold --configure --pending" % DPKG)
- self.cmd.run("%s clean" % APTGET)
+ self.cmd.run("%s --force-confold --configure --pending" % self.dpkg)
+ self.cmd.run("%s clean" % self.aptget)
try:
self.pkg_cache = apt.cache.Cache()
except SystemError:
@@ -95,7 +88,8 @@ class APT(Bcfg2.Client.Tools.Tool):
for (name, version) in extras]
def VerifyDebsums(self, entry, modlist):
- output = self.cmd.run("%s -as %s" % (DEBSUMS, entry.get('name')))[1]
+ output = self.cmd.run("%s -as %s" % (self.debsums,
+ entry.get('name')))[1]
if len(output) == 1 and "no md5sums for" in output[0]:
self.logger.info("Package %s has no md5sums. Cannot verify" % \
entry.get('name'))
diff --git a/src/lib/Bcfg2/Client/Tools/Action.py b/src/lib/Bcfg2/Client/Tools/Action.py
index dc49347e9..52d4e6a3f 100644
--- a/src/lib/Bcfg2/Client/Tools/Action.py
+++ b/src/lib/Bcfg2/Client/Tools/Action.py
@@ -2,6 +2,7 @@
import Bcfg2.Client.Tools
from Bcfg2.Client.Frame import matches_white_list, passes_black_list
+from Bcfg2.Bcfg2Py3k import input
"""
<Action timing='pre|post|both'
@@ -44,11 +45,7 @@ class Action(Bcfg2.Client.Tools.Tool):
if self.setup['interactive']:
prompt = ('Run Action %s, %s: (y/N): ' %
(entry.get('name'), entry.get('command')))
- # py3k compatibility
- try:
- ans = raw_input(prompt)
- except NameError:
- ans = input(prompt)
+ ans = input(prompt)
if ans not in ['y', 'Y']:
return False
if self.setup['servicemode'] == 'build':
@@ -64,7 +61,7 @@ class Action(Bcfg2.Client.Tools.Tool):
else:
return rc == 0
else:
- self.logger.debug("In dryrun mode: not running action:\n %s" %
+ self.logger.debug("In dryrun mode: not running action: %s" %
(entry.get('name')))
return False
diff --git a/src/lib/Bcfg2/Client/Tools/Chkconfig.py b/src/lib/Bcfg2/Client/Tools/Chkconfig.py
index 12ea5f132..0169b12da 100644
--- a/src/lib/Bcfg2/Client/Tools/Chkconfig.py
+++ b/src/lib/Bcfg2/Client/Tools/Chkconfig.py
@@ -45,30 +45,14 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool):
except IndexError:
onlevels = []
+ pstatus = self.check_service(entry)
if entry.get('status') == 'on':
- status = (len(onlevels) > 0)
+ status = (len(onlevels) > 0 and pstatus)
command = 'start'
else:
- status = (len(onlevels) == 0)
+ status = (len(onlevels) == 0 and not pstatus)
command = 'stop'
- if entry.get('mode', 'default') == 'supervised':
- # turn on or off the service in supervised mode
- pstatus = self.cmd.run('/sbin/service %s status' % \
- entry.get('name'))[0]
- needs_modification = ((command == 'start' and pstatus) or \
- (command == 'stop' and not pstatus))
- if (not self.setup.get('dryrun') and
- self.setup['servicemode'] != 'disabled' and
- needs_modification):
- self.cmd.run(self.get_svc_command(entry, command))
- # service was modified, so it failed
- pstatus = False
-
- # chkconfig/init.d service
- if entry.get('status') == 'on':
- status = status and not pstatus
-
if not status:
if entry.get('status') == 'on':
entry.set('current_status', 'off')
@@ -78,22 +62,22 @@ class Chkconfig(Bcfg2.Client.Tools.SvcTool):
def InstallService(self, entry):
"""Install Service entry."""
- # don't take any actions for mode='manual'
- if entry.get('mode', 'default') == 'manual':
- self.logger.info("Service %s mode set to manual. Skipping "
- "installation." % (entry.get('name')))
- return False
rcmd = "/sbin/chkconfig %s %s"
self.cmd.run("/sbin/chkconfig --add %s" % (entry.attrib['name']))
self.logger.info("Installing Service %s" % (entry.get('name')))
- pass1 = True
+ rv = True
if entry.get('status') == 'off':
- rc = self.cmd.run(rcmd % (entry.get('name'),
- entry.get('status')) + \
- " --level 0123456")[0]
- pass1 = rc == 0
- rc = self.cmd.run(rcmd % (entry.get('name'), entry.get('status')))[0]
- return pass1 and rc == 0
+ rv &= self.cmd.run(rcmd + " --level 0123456" %
+ (entry.get('name'),
+ entry.get('status')))[0] == 0
+ if entry.get("current_status") == "on":
+ rv &= self.stop_service(entry)
+ else:
+ rv &= self.cmd.run(rcmd % (entry.get('name'),
+ entry.get('status')))[0] == 0
+ if entry.get("current_status") == "off":
+ rv &= self.start_service(entry)
+ return rv
def FindExtra(self):
"""Locate extra chkconfig Services."""
diff --git a/src/lib/Bcfg2/Client/Tools/DebInit.py b/src/lib/Bcfg2/Client/Tools/DebInit.py
index ca6fc439e..7d5af1127 100644
--- a/src/lib/Bcfg2/Client/Tools/DebInit.py
+++ b/src/lib/Bcfg2/Client/Tools/DebInit.py
@@ -76,11 +76,6 @@ class DebInit(Bcfg2.Client.Tools.SvcTool):
def InstallService(self, entry):
"""Install Service for entry."""
- # don't take any actions for mode='manual'
- if entry.get('mode', 'default') == 'manual':
- self.logger.info("Service %s mode set to manual. Skipping "
- "installation." % (entry.get('name')))
- return False
self.logger.info("Installing Service %s" % (entry.get('name')))
try:
os.stat('/etc/init.d/%s' % entry.get('name'))
diff --git a/src/lib/Bcfg2/Client/Tools/OpenCSW.py b/src/lib/Bcfg2/Client/Tools/OpenCSW.py
new file mode 100644
index 000000000..6aafe316f
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/OpenCSW.py
@@ -0,0 +1,33 @@
+# This is the bcfg2 support for opencsw packages (pkgutil)
+"""This provides Bcfg2 support for OpenCSW packages."""
+
+import tempfile
+import Bcfg2.Client.Tools.SYSV
+
+
+class OpenCSW(Bcfg2.Client.Tools.SYSV.SYSV):
+ """Support for OpenCSW packages."""
+ pkgtype = 'opencsw'
+ pkgtool = ("/opt/csw/bin/pkgutil -y -i %s", ("%s", ["bname"]))
+ name = 'OpenCSW'
+ __execs__ = ['/opt/csw/bin/pkgutil', "/usr/bin/pkginfo"]
+ __handles__ = [('Package', 'opencsw')]
+ __ireq__ = {'Package': ['name', 'version', 'bname']}
+
+ def __init__(self, logger, setup, config):
+ # dont use the sysv constructor
+ Bcfg2.Client.Tools.PkgTool.__init__(self, logger, setup, config)
+ noaskfile = tempfile.NamedTemporaryFile()
+ self.noaskname = noaskfile.name
+ try:
+ noaskfile.write(Bcfg2.Client.Tools.SYSV.noask)
+ except:
+ pass
+
+ # VerifyPackage comes from Bcfg2.Client.Tools.SYSV
+ # Install comes from Bcfg2.Client.Tools.PkgTool
+ # Extra comes from Bcfg2.Client.Tools.Tool
+ # Remove comes from Bcfg2.Client.Tools.SYSV
+ def FindExtraPackages(self):
+ """Pass through to null FindExtra call."""
+ return []
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX.py b/src/lib/Bcfg2/Client/Tools/POSIX.py
deleted file mode 100644
index 0d67dbbab..000000000
--- a/src/lib/Bcfg2/Client/Tools/POSIX.py
+++ /dev/null
@@ -1,943 +0,0 @@
-"""All POSIX Type client support for Bcfg2."""
-
-import binascii
-from datetime import datetime
-import difflib
-import errno
-import grp
-import logging
-import os
-import pwd
-import shutil
-import stat
-import sys
-import time
-# py3k compatibility
-if sys.hexversion >= 0x03000000:
- unicode = str
-
-import Bcfg2.Client.Tools
-import Bcfg2.Options
-from Bcfg2.Client import XML
-
-log = logging.getLogger('POSIX')
-
-# map between dev_type attribute and stat constants
-device_map = {'block': stat.S_IFBLK,
- 'char': stat.S_IFCHR,
- 'fifo': stat.S_IFIFO}
-
-
-def calcPerms(initial, perms):
- """This compares ondisk permissions with specified ones."""
- pdisp = [{1:stat.S_ISVTX, 2:stat.S_ISGID, 4:stat.S_ISUID},
- {1:stat.S_IXUSR, 2:stat.S_IWUSR, 4:stat.S_IRUSR},
- {1:stat.S_IXGRP, 2:stat.S_IWGRP, 4:stat.S_IRGRP},
- {1:stat.S_IXOTH, 2:stat.S_IWOTH, 4:stat.S_IROTH}]
- tempperms = initial
- if len(perms) == 3:
- perms = '0%s' % (perms)
- pdigits = [int(perms[digit]) for digit in range(4)]
- for index in range(4):
- for (num, perm) in list(pdisp[index].items()):
- if pdigits[index] & num:
- tempperms |= perm
- return tempperms
-
-
-def normGid(entry):
- """
- This takes a group name or gid and
- returns the corresponding gid or False.
- """
- try:
- try:
- return int(entry.get('group'))
- except:
- return int(grp.getgrnam(entry.get('group'))[2])
- except (OSError, KeyError):
- log.error('GID normalization failed for %s. Does group %s exist?'
- % (entry.get('name'), entry.get('group')))
- return False
-
-
-def normUid(entry):
- """
- This takes a user name or uid and
- returns the corresponding uid or False.
- """
- try:
- try:
- return int(entry.get('owner'))
- except:
- return int(pwd.getpwnam(entry.get('owner'))[2])
- except (OSError, KeyError):
- log.error('UID normalization failed for %s. Does owner %s exist?'
- % (entry.get('name'), entry.get('owner')))
- return False
-
-
-def isString(strng, encoding):
- """
- Returns true if the string contains no ASCII control characters
- and can be decoded from the specified encoding.
- """
- for char in strng:
- if ord(char) < 9 or ord(char) > 13 and ord(char) < 32:
- return False
- try:
- strng.decode(encoding)
- return True
- except:
- return False
-
-
-class POSIX(Bcfg2.Client.Tools.Tool):
- """POSIX File support code."""
- name = 'POSIX'
- __handles__ = [('Path', 'device'),
- ('Path', 'directory'),
- ('Path', 'file'),
- ('Path', 'hardlink'),
- ('Path', 'nonexistent'),
- ('Path', 'permissions'),
- ('Path', 'symlink')]
- __req__ = {'Path': ['name', 'type']}
-
- # grab paranoid options from /etc/bcfg2.conf
- opts = {'ppath': Bcfg2.Options.PARANOID_PATH,
- 'max_copies': Bcfg2.Options.PARANOID_MAX_COPIES}
- setup = Bcfg2.Options.OptionParser(opts)
- setup.parse([])
- ppath = setup['ppath']
- max_copies = setup['max_copies']
-
- def canInstall(self, entry):
- """Check if entry is complete for installation."""
- if Bcfg2.Client.Tools.Tool.canInstall(self, entry):
- if (entry.tag,
- entry.get('type'),
- entry.text,
- entry.get('empty', 'false')) == ('Path',
- 'file',
- None,
- 'false'):
- return False
- return True
- else:
- return False
-
- def gatherCurrentData(self, entry):
- if entry.tag == 'Path' and entry.get('type') == 'file':
- try:
- ondisk = os.stat(entry.get('name'))
- except OSError:
- entry.set('current_exists', 'false')
- self.logger.debug("%s %s does not exist" %
- (entry.tag, entry.get('name')))
- return False
- try:
- entry.set('current_owner', str(ondisk[stat.ST_UID]))
- entry.set('current_group', str(ondisk[stat.ST_GID]))
- except (OSError, KeyError):
- pass
- entry.set('perms', str(oct(ondisk[stat.ST_MODE])[-4:]))
-
- def Verifydevice(self, entry, _):
- """Verify device entry."""
- if entry.get('dev_type') == None or \
- entry.get('owner') == None or \
- entry.get('group') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % (entry.get('name')))
- return False
- if entry.get('dev_type') in ['block', 'char']:
- # check if major/minor are properly specified
- if entry.get('major') == None or \
- entry.get('minor') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % (entry.get('name')))
- return False
- try:
- # check for file existence
- filestat = os.stat(entry.get('name'))
- except OSError:
- entry.set('current_exists', 'false')
- self.logger.debug("%s %s does not exist" %
- (entry.tag, entry.get('name')))
- return False
-
- try:
- # attempt to verify device properties as specified in config
- dev_type = entry.get('dev_type')
- mode = calcPerms(device_map[dev_type],
- entry.get('mode', '0600'))
- owner = normUid(entry)
- group = normGid(entry)
- if dev_type in ['block', 'char']:
- # check for incompletely specified entries
- if entry.get('major') == None or \
- entry.get('minor') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % (entry.get('name')))
- return False
- major = int(entry.get('major'))
- minor = int(entry.get('minor'))
- if major == os.major(filestat.st_rdev) and \
- minor == os.minor(filestat.st_rdev) and \
- mode == filestat.st_mode and \
- owner == filestat.st_uid and \
- group == filestat.st_gid:
- return True
- else:
- return False
- elif dev_type == 'fifo' and \
- mode == filestat.st_mode and \
- owner == filestat.st_uid and \
- group == filestat.st_gid:
- return True
- else:
- self.logger.info('Device properties for %s incorrect' % \
- entry.get('name'))
- return False
- except OSError:
- self.logger.debug("%s %s failed to verify" %
- (entry.tag, entry.get('name')))
- return False
-
- def Installdevice(self, entry):
- """Install device entries."""
- try:
- # check for existing paths and remove them
- os.lstat(entry.get('name'))
- try:
- os.unlink(entry.get('name'))
- exists = False
- except OSError:
- self.logger.info('Failed to unlink %s' % \
- entry.get('name'))
- return False
- except OSError:
- exists = False
-
- if not exists:
- try:
- dev_type = entry.get('dev_type')
- mode = calcPerms(device_map[dev_type],
- entry.get('mode', '0600'))
- if dev_type in ['block', 'char']:
- # check if major/minor are properly specified
- if entry.get('major') == None or \
- entry.get('minor') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % (entry.get('name')))
- return False
- major = int(entry.get('major'))
- minor = int(entry.get('minor'))
- device = os.makedev(major, minor)
- os.mknod(entry.get('name'), mode, device)
- else:
- os.mknod(entry.get('name'), mode)
- """
- Python uses the OS mknod(2) implementation which modifies the
- mode based on the umask of the running process. Therefore, the
- following chmod(2) call is needed to make sure the permissions
- are set as specified by the user.
- """
- os.chmod(entry.get('name'), mode)
- os.chown(entry.get('name'), normUid(entry), normGid(entry))
- return True
- except KeyError:
- self.logger.error('Failed to install %s' % entry.get('name'))
- except OSError:
- self.logger.error('Failed to install %s' % entry.get('name'))
- return False
-
- def Verifydirectory(self, entry, modlist):
- """Verify Path type='directory' entry."""
- if entry.get('perms') == None or \
- entry.get('owner') == None or \
- entry.get('group') == None:
- self.logger.error("POSIX: Entry %s not completely specified. "
- "Try running bcfg2-lint." % (entry.get('name')))
- return False
- while len(entry.get('perms', '')) < 4:
- entry.set('perms', '0' + entry.get('perms', ''))
- try:
- ondisk = os.stat(entry.get('name'))
- except OSError:
- entry.set('current_exists', 'false')
- self.logger.info("POSIX: %s %s does not exist" %
- (entry.tag, entry.get('name')))
- return False
- try:
- owner = str(ondisk[stat.ST_UID])
- group = str(ondisk[stat.ST_GID])
- except (OSError, KeyError):
- self.logger.info("POSIX: User/Group resolution failed "
- "for path %s" % entry.get('name'))
- owner = 'root'
- group = '0'
- finfo = os.stat(entry.get('name'))
- perms = oct(finfo[stat.ST_MODE])[-4:]
- if entry.get('mtime', '-1') != '-1':
- mtime = str(finfo[stat.ST_MTIME])
- else:
- mtime = '-1'
- pTrue = ((owner == str(normUid(entry))) and
- (group == str(normGid(entry))) and
- (perms == entry.get('perms')) and
- (mtime == entry.get('mtime', '-1')))
-
- pruneTrue = True
- ex_ents = []
- if entry.get('prune', 'false') == 'true' \
- and (entry.tag == 'Path' and entry.get('type') == 'directory'):
- # check for any extra entries when prune='true' attribute is set
- try:
- entries = ['/'.join([entry.get('name'), ent]) \
- for ent in os.listdir(entry.get('name'))]
- ex_ents = [e for e in entries if e not in modlist]
- if ex_ents:
- pruneTrue = False
- self.logger.info("POSIX: Directory %s contains "
- "extra entries:" % entry.get('name'))
- self.logger.info(ex_ents)
- nqtext = entry.get('qtext', '') + '\n'
- nqtext += "Directory %s contains extra entries: " % \
- entry.get('name')
- nqtext += ":".join(ex_ents)
- entry.set('qtest', nqtext)
- [entry.append(XML.Element('Prune', path=x)) \
- for x in ex_ents]
- except OSError:
- ex_ents = []
- pruneTrue = True
-
- if not pTrue:
- if owner != str(normUid(entry)):
- entry.set('current_owner', owner)
- self.logger.debug("%s %s ownership wrong" % \
- (entry.tag, entry.get('name')))
- nqtext = entry.get('qtext', '') + '\n'
- nqtext += "%s owner wrong. is %s should be %s" % \
- (entry.get('name'), owner, entry.get('owner'))
- entry.set('qtext', nqtext)
- if group != str(normGid(entry)):
- entry.set('current_group', group)
- self.logger.debug("%s %s group wrong" % \
- (entry.tag, entry.get('name')))
- nqtext = entry.get('qtext', '') + '\n'
- nqtext += "%s group is %s should be %s" % \
- (entry.get('name'), group, entry.get('group'))
- entry.set('qtext', nqtext)
- if perms != entry.get('perms'):
- entry.set('current_perms', perms)
- self.logger.debug("%s %s permissions are %s should be %s" %
- (entry.tag,
- entry.get('name'),
- perms,
- entry.get('perms')))
- nqtext = entry.get('qtext', '') + '\n'
- nqtext += "%s %s perms are %s should be %s" % \
- (entry.tag,
- entry.get('name'),
- perms,
- entry.get('perms'))
- entry.set('qtext', nqtext)
- if mtime != entry.get('mtime', '-1'):
- entry.set('current_mtime', mtime)
- self.logger.debug("%s %s mtime is %s should be %s" \
- % (entry.tag, entry.get('name'), mtime,
- entry.get('mtime')))
- nqtext = entry.get('qtext', '') + '\n'
- nqtext += "%s mtime is %s should be %s" % \
- (entry.get('name'), mtime, entry.get('mtime'))
- entry.set('qtext', nqtext)
- if entry.get('type') != 'file':
- nnqtext = entry.get('qtext')
- nnqtext += '\nInstall %s %s: (y/N) ' % (entry.get('type'),
- entry.get('name'))
- entry.set('qtext', nnqtext)
- return pTrue and pruneTrue
-
- def Installdirectory(self, entry):
- """Install Path type='directory' entry."""
- if entry.get('perms') == None or \
- entry.get('owner') == None or \
- entry.get('group') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % \
- (entry.get('name')))
- return False
- self.logger.info("Installing directory %s" % (entry.get('name')))
- try:
- fmode = os.lstat(entry.get('name'))
- if not stat.S_ISDIR(fmode[stat.ST_MODE]):
- self.logger.debug("Found a non-directory entry at %s" % \
- (entry.get('name')))
- try:
- os.unlink(entry.get('name'))
- exists = False
- except OSError:
- self.logger.info("Failed to unlink %s" % \
- (entry.get('name')))
- return False
- else:
- self.logger.debug("Found a pre-existing directory at %s" % \
- (entry.get('name')))
- exists = True
- except OSError:
- # stat failed
- exists = False
-
- if not exists:
- parent = "/".join(entry.get('name').split('/')[:-1])
- if parent:
- try:
- os.stat(parent)
- except:
- self.logger.debug('Creating parent path for directory %s' % (entry.get('name')))
- for idx in range(len(parent.split('/')[:-1])):
- current = '/'+'/'.join(parent.split('/')[1:2+idx])
- try:
- sloc = os.stat(current)
- except OSError:
- try:
- os.mkdir(current)
- continue
- except OSError:
- return False
- if not stat.S_ISDIR(sloc[stat.ST_MODE]):
- try:
- os.unlink(current)
- os.mkdir(current)
- except OSError:
- return False
-
- try:
- os.mkdir(entry.get('name'))
- except OSError:
- self.logger.error('Failed to create directory %s' % \
- (entry.get('name')))
- return False
- if entry.get('prune', 'false') == 'true' and entry.get("qtest"):
- for pent in entry.findall('Prune'):
- pname = pent.get('path')
- ulfailed = False
- if os.path.isdir(pname):
- self.logger.info("Not removing extra directory %s, "
- "please check and remove manually" % pname)
- continue
- try:
- self.logger.debug("Unlinking file %s" % pname)
- os.unlink(pname)
- except OSError:
- self.logger.error("Failed to unlink path %s" % pname)
- ulfailed = True
- if ulfailed:
- return False
- return self.Installpermissions(entry)
-
- def Verifyfile(self, entry, _):
- """Verify Path type='file' entry."""
- # permissions check + content check
- permissionStatus = self.Verifydirectory(entry, _)
- tbin = False
- if entry.text == None and entry.get('empty', 'false') == 'false':
- self.logger.error("Cannot verify incomplete Path type='%s' %s" %
- (entry.get('type'), entry.get('name')))
- return False
- if entry.get('encoding', 'ascii') == 'base64':
- tempdata = binascii.a2b_base64(entry.text)
- tbin = True
- elif entry.get('empty', 'false') == 'true':
- tempdata = ''
- else:
- tempdata = entry.text
- if type(tempdata) == unicode:
- try:
- tempdata = tempdata.encode(self.setup['encoding'])
- except UnicodeEncodeError:
- e = sys.exc_info()[1]
- self.logger.error("Error encoding file %s:\n %s" % \
- (entry.get('name'), e))
-
- different = False
- content = None
- if not os.path.exists(entry.get("name")):
- # first, see if the target file exists at all; if not,
- # they're clearly different
- different = True
- content = ""
- else:
- # next, see if the size of the target file is different
- # from the size of the desired content
- try:
- estat = os.stat(entry.get('name'))
- except OSError:
- err = sys.exc_info()[1]
- self.logger.error("Failed to stat %s: %s" %
- (err.filename, err))
- return False
- if len(tempdata) != estat[stat.ST_SIZE]:
- different = True
- else:
- # finally, read in the target file and compare them
- # directly. comparison could be done with a checksum,
- # which might be faster for big binary files, but
- # slower for everything else
- try:
- content = open(entry.get('name')).read()
- except IOError:
- err = sys.exc_info()[1]
- self.logger.error("Failed to read %s: %s" %
- (err.filename, err))
- return False
- different = content != tempdata
-
- if different:
- if self.setup['interactive']:
- prompt = [entry.get('qtext', '')]
- if not tbin and content is None:
- # it's possible that we figured out the files are
- # different without reading in the local file. if
- # the supplied version of the file is not binary,
- # we now have to read in the local file to figure
- # out if _it_ is binary, and either include that
- # fact or the diff in our prompts for -I
- try:
- content = open(entry.get('name')).read()
- except IOError:
- err = sys.exc_info()[1]
- self.logger.error("Failed to read %s: %s" %
- (err.filename, err))
- return False
- if tbin or not isString(content, self.setup['encoding']):
- # don't compute diffs if the file is binary
- prompt.append('Binary file, no printable diff')
- else:
- diff = self._diff(content, tempdata,
- difflib.unified_diff,
- filename=entry.get("name"))
- if diff:
- udiff = '\n'.join(diff)
- try:
- prompt.append(udiff.decode(self.setup['encoding']))
- except UnicodeDecodeError:
- prompt.append("Binary file, no printable diff")
- else:
- prompt.append("Diff took too long to compute, no "
- "printable diff")
- prompt.append("Install %s %s: (y/N): " % (entry.tag,
- entry.get('name')))
- entry.set("qtext", "\n".join(prompt))
-
- if entry.get('sensitive', 'false').lower() != 'true':
- if content is None:
- # it's possible that we figured out the files are
- # different without reading in the local file. we
- # now have to read in the local file to figure out
- # if _it_ is binary, and either include the whole
- # file or the diff for reports
- try:
- content = open(entry.get('name')).read()
- except IOError:
- err = sys.exc_info()[1]
- self.logger.error("Failed to read %s: %s" %
- (err.filename, err))
- return False
-
- if tbin or not isString(content, self.setup['encoding']):
- # don't compute diffs if the file is binary
- entry.set('current_bfile', binascii.b2a_base64(content))
- else:
- diff = self._diff(content, tempdata, difflib.ndiff,
- filename=entry.get("name"))
- if diff:
- entry.set("current_bdiff",
- binascii.b2a_base64("\n".join(diff)))
- elif not tbin and isString(content, self.setup['encoding']):
- entry.set('current_bfile', binascii.b2a_base64(content))
- elif permissionStatus == False and self.setup['interactive']:
- prompt = [entry.get('qtext', '')]
- prompt.append("Install %s %s: (y/N): " % (entry.tag,
- entry.get('name')))
- entry.set("qtext", "\n".join(prompt))
-
-
- return permissionStatus and not different
-
- def Installfile(self, entry):
- """Install Path type='file' entry."""
- self.logger.info("Installing file %s" % (entry.get('name')))
-
- parent = "/".join(entry.get('name').split('/')[:-1])
- if parent:
- try:
- os.stat(parent)
- except:
- self.logger.debug('Creating parent path for config file %s' % \
- (entry.get('name')))
- current = '/'
- for next in parent.split('/')[1:]:
- current += next + '/'
- try:
- sloc = os.stat(current)
- try:
- if not stat.S_ISDIR(sloc[stat.ST_MODE]):
- self.logger.debug('%s is not a directory; recreating' \
- % (current))
- os.unlink(current)
- os.mkdir(current)
- except OSError:
- return False
- except OSError:
- try:
- self.logger.debug("Creating non-existent path %s" % current)
- os.mkdir(current)
- except OSError:
- return False
-
- # If we get here, then the parent directory should exist
- if (entry.get("paranoid", False) in ['true', 'True']) and \
- self.setup.get("paranoid", False) and not \
- (entry.get('current_exists', 'true') == 'false'):
- bkupnam = entry.get('name').replace('/', '_')
- # current list of backups for this file
- try:
- bkuplist = [f for f in os.listdir(self.ppath) if
- f.startswith(bkupnam)]
- except OSError:
- e = sys.exc_info()[1]
- self.logger.error("Failed to create backup list in %s: %s" %
- (self.ppath, e.strerror))
- return False
- bkuplist.sort()
- while len(bkuplist) >= int(self.max_copies):
- # remove the oldest backup available
- oldest = bkuplist.pop(0)
- self.logger.info("Removing %s" % oldest)
- try:
- os.remove("%s/%s" % (self.ppath, oldest))
- except:
- self.logger.error("Failed to remove %s/%s" % \
- (self.ppath, oldest))
- return False
- try:
- # backup existing file
- shutil.copy(entry.get('name'),
- "%s/%s_%s" % (self.ppath, bkupnam,
- datetime.isoformat(datetime.now())))
- self.logger.info("Backup of %s saved to %s" %
- (entry.get('name'), self.ppath))
- except IOError:
- e = sys.exc_info()[1]
- self.logger.error("Failed to create backup file for %s" % \
- (entry.get('name')))
- self.logger.error(e)
- return False
- try:
- newfile = open("%s.new"%(entry.get('name')), 'w')
- if entry.get('encoding', 'ascii') == 'base64':
- filedata = binascii.a2b_base64(entry.text)
- elif entry.get('empty', 'false') == 'true':
- filedata = ''
- else:
- if type(entry.text) == unicode:
- filedata = entry.text.encode(self.setup['encoding'])
- else:
- filedata = entry.text
- newfile.write(filedata)
- newfile.close()
- try:
- os.chown(newfile.name, normUid(entry), normGid(entry))
- except KeyError:
- self.logger.error("Failed to chown %s to %s:%s" %
- (newfile.name, entry.get('owner'),
- entry.get('group')))
- os.chown(newfile.name, 0, 0)
- except OSError:
- err = sys.exc_info()[1]
- self.logger.error("Could not chown %s: %s" % (newfile.name,
- err))
- os.chmod(newfile.name, calcPerms(stat.S_IFREG, entry.get('perms')))
- os.rename(newfile.name, entry.get('name'))
- if entry.get('mtime', '-1') != '-1':
- try:
- os.utime(entry.get('name'), (int(entry.get('mtime')),
- int(entry.get('mtime'))))
- except:
- self.logger.error("File %s mtime fix failed" \
- % (entry.get('name')))
- return False
- return True
- except (OSError, IOError):
- err = sys.exc_info()[1]
- if err.errno == errno.EACCES:
- self.logger.info("Failed to open %s for writing" % (entry.get('name')))
- else:
- print(err)
- return False
-
- def Verifyhardlink(self, entry, _):
- """Verify HardLink entry."""
- if entry.get('to') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % \
- (entry.get('name')))
- return False
- try:
- if os.path.samefile(entry.get('name'), entry.get('to')):
- return True
- self.logger.debug("Hardlink %s is incorrect" % \
- entry.get('name'))
- entry.set('qtext', "Link %s to %s? [y/N] " % \
- (entry.get('name'),
- entry.get('to')))
- return False
- except OSError:
- entry.set('current_exists', 'false')
- entry.set('qtext', "Link %s to %s? [y/N] " % \
- (entry.get('name'),
- entry.get('to')))
- return False
-
- def Installhardlink(self, entry):
- """Install HardLink entry."""
- if entry.get('to') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % \
- (entry.get('name')))
- return False
- self.logger.info("Installing Hardlink %s" % (entry.get('name')))
- if os.path.lexists(entry.get('name')):
- try:
- fmode = os.lstat(entry.get('name'))[stat.ST_MODE]
- if stat.S_ISREG(fmode) or stat.S_ISLNK(fmode):
- self.logger.debug("Non-directory entry already exists at "
- "%s. Unlinking entry." % (entry.get('name')))
- os.unlink(entry.get('name'))
- elif stat.S_ISDIR(fmode):
- self.logger.debug("Directory already exists at %s" % \
- (entry.get('name')))
- self.cmd.run("mv %s/ %s.bak" % \
- (entry.get('name'),
- entry.get('name')))
- else:
- os.unlink(entry.get('name'))
- except OSError:
- self.logger.info("Hardlink %s cleanup failed" % \
- (entry.get('name')))
- try:
- os.link(entry.get('to'), entry.get('name'))
- return True
- except OSError:
- return False
-
- def Verifynonexistent(self, entry, _):
- """Verify nonexistent entry."""
- # return true if path does _not_ exist
- return not os.path.lexists(entry.get('name'))
-
- def Installnonexistent(self, entry):
- '''Remove nonexistent entries'''
- ename = entry.get('name')
- if entry.get('recursive') in ['True', 'true']:
- # ensure that configuration spec is consistent first
- if [e for e in self.buildModlist() \
- if e.startswith(ename) and e != ename]:
- self.logger.error('Not installing %s. One or more files '
- 'in this directory are specified in '
- 'your configuration.' % ename)
- return False
- try:
- shutil.rmtree(ename)
- except OSError:
- e = sys.exc_info()[1]
- self.logger.error('Failed to remove %s: %s' % (ename,
- e.strerror))
- else:
- if os.path.isdir(ename):
- try:
- os.rmdir(ename)
- return True
- except OSError:
- e = sys.exc_info()[1]
- self.logger.error('Failed to remove %s: %s' % (ename,
- e.strerror))
- return False
- try:
- os.remove(ename)
- return True
- except OSError:
- e = sys.exc_info()[1]
- self.logger.error('Failed to remove %s: %s' % (ename,
- e.strerror))
- return False
-
- def Verifypermissions(self, entry, _):
- """Verify Path type='permissions' entry"""
- if entry.get('perms') == None or \
- entry.get('owner') == None or \
- entry.get('group') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % (entry.get('name')))
- return False
- if entry.get('recursive') in ['True', 'true']:
- # verify ownership information recursively
- owner = normUid(entry)
- group = normGid(entry)
-
- for root, dirs, files in os.walk(entry.get('name')):
- for p in dirs + files:
- path = os.path.join(root, p)
- pstat = os.stat(path)
- if owner != pstat.st_uid:
- # owner mismatch for path
- entry.set('current_owner', str(pstat.st_uid))
- self.logger.debug("%s %s ownership wrong" % \
- (entry.tag, path))
- nqtext = entry.get('qtext', '') + '\n'
- nqtext += ("Owner for path %s is incorrect. "
- "Current owner is %s but should be %s\n" % \
- (path, pstat.st_uid, entry.get('owner')))
- nqtext += ("\nInstall %s %s: (y/N): " %
- (entry.tag, entry.get('name')))
- entry.set('qtext', nqtext)
- return False
- if group != pstat.st_gid:
- # group mismatch for path
- entry.set('current_group', str(pstat.st_gid))
- self.logger.debug("%s %s group wrong" % \
- (entry.tag, path))
- nqtext = entry.get('qtext', '') + '\n'
- nqtext += ("Group for path %s is incorrect. "
- "Current group is %s but should be %s\n" % \
- (path, pstat.st_gid, entry.get('group')))
- nqtext += ("\nInstall %s %s: (y/N): " %
- (entry.tag, entry.get('name')))
- entry.set('qtext', nqtext)
- return False
- return self.Verifydirectory(entry, _)
-
- def _diff(self, content1, content2, difffunc, filename=None):
- rv = []
- start = time.time()
- longtime = False
- for diffline in difffunc(content1.split('\n'),
- content2.split('\n')):
- now = time.time()
- rv.append(diffline)
- if now - start > 5 and not longtime:
- if filename:
- self.logger.info("Diff of %s taking a long time" %
- filename)
- else:
- self.logger.info("Diff taking a long time")
- longtime = True
- elif now - start > 30:
- if filename:
- self.logger.error("Diff of %s took too long; giving up" %
- filename)
- else:
- self.logger.error("Diff took too long; giving up")
- return False
- return rv
-
- def Installpermissions(self, entry):
- """Install POSIX permissions"""
- if entry.get('perms') == None or \
- entry.get('owner') == None or \
- entry.get('group') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % (entry.get('name')))
- return False
- plist = [entry.get('name')]
- if entry.get('recursive') in ['True', 'true']:
- # verify ownership information recursively
- owner = normUid(entry)
- group = normGid(entry)
-
- for root, dirs, files in os.walk(entry.get('name')):
- for p in dirs + files:
- path = os.path.join(root, p)
- pstat = os.stat(path)
- if owner != pstat.st_uid or group != pstat.st_gid:
- # owner mismatch for path
- plist.append(path)
- try:
- for p in plist:
- os.chown(p, normUid(entry), normGid(entry))
- os.chmod(p, calcPerms(stat.S_IFDIR, entry.get('perms')))
- return True
- except (OSError, KeyError):
- self.logger.error('Permission fixup failed for %s' % \
- (entry.get('name')))
- return False
-
- def Verifysymlink(self, entry, _):
- """Verify Path type='symlink' entry."""
- if entry.get('to') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % \
- (entry.get('name')))
- return False
- try:
- sloc = os.readlink(entry.get('name'))
- if sloc == entry.get('to'):
- return True
- self.logger.debug("Symlink %s points to %s, should be %s" % \
- (entry.get('name'), sloc, entry.get('to')))
- entry.set('current_to', sloc)
- entry.set('qtext', "Link %s to %s? [y/N] " % (entry.get('name'),
- entry.get('to')))
- return False
- except OSError:
- entry.set('current_exists', 'false')
- entry.set('qtext', "Link %s to %s? [y/N] " % (entry.get('name'),
- entry.get('to')))
- return False
-
- def Installsymlink(self, entry):
- """Install Path type='symlink' entry."""
- if entry.get('to') == None:
- self.logger.error('Entry %s not completely specified. '
- 'Try running bcfg2-lint.' % \
- (entry.get('name')))
- return False
- self.logger.info("Installing symlink %s" % (entry.get('name')))
- if os.path.lexists(entry.get('name')):
- try:
- fmode = os.lstat(entry.get('name'))[stat.ST_MODE]
- if stat.S_ISREG(fmode) or stat.S_ISLNK(fmode):
- self.logger.debug("Non-directory entry already exists at "
- "%s. Unlinking entry." % \
- (entry.get('name')))
- os.unlink(entry.get('name'))
- elif stat.S_ISDIR(fmode):
- self.logger.debug("Directory already exists at %s" %\
- (entry.get('name')))
- self.cmd.run("mv %s/ %s.bak" % \
- (entry.get('name'),
- entry.get('name')))
- else:
- os.unlink(entry.get('name'))
- except OSError:
- self.logger.info("Symlink %s cleanup failed" %\
- (entry.get('name')))
- try:
- os.symlink(entry.get('to'), entry.get('name'))
- return True
- except OSError:
- return False
-
- def InstallPath(self, entry):
- """Dispatch install to the proper method according to type"""
- ret = getattr(self, 'Install%s' % entry.get('type'))
- return ret(entry)
-
- def VerifyPath(self, entry, _):
- """Dispatch verify to the proper method according to type"""
- ret = getattr(self, 'Verify%s' % entry.get('type'))
- return ret(entry, _)
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Device.py b/src/lib/Bcfg2/Client/Tools/POSIX/Device.py
new file mode 100644
index 000000000..0ea4128f7
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/Device.py
@@ -0,0 +1,66 @@
+import os
+import sys
+try:
+ from base import POSIXTool, device_map
+except ImportError:
+ # py3k, incompatible syntax with py2.4
+ exec("from .base import POSIXTool, device_map")
+
+class POSIXDevice(POSIXTool):
+ __req__ = ['name', 'dev_type', 'perms', 'owner', 'group']
+
+ def fully_specified(self, entry):
+ if entry.get('dev_type') in ['block', 'char']:
+ # check if major/minor are properly specified
+ if (entry.get('major') == None or
+ entry.get('minor') == None):
+ return False
+ return True
+
+ def verify(self, entry, modlist):
+ """Verify device entry."""
+ ondisk = self._exists(entry)
+ if not ondisk:
+ return False
+
+ # attempt to verify device properties as specified in config
+ rv = True
+ dev_type = entry.get('dev_type')
+ if dev_type in ['block', 'char']:
+ major = int(entry.get('major'))
+ minor = int(entry.get('minor'))
+ if major != os.major(ondisk.st_rdev):
+ msg = ("Major number for device %s is incorrect. "
+ "Current major is %s but should be %s" %
+ (entry.get("name"), os.major(ondisk.st_rdev), major))
+ self.logger.debug('POSIX: ' + msg)
+ entry.set('qtext', entry.get('qtext', '') + "\n" + msg)
+ rv = False
+
+ if minor != os.minor(ondisk.st_rdev):
+ msg = ("Minor number for device %s is incorrect. "
+ "Current minor is %s but should be %s" %
+ (entry.get("name"), os.minor(ondisk.st_rdev), minor))
+ self.logger.debug('POSIX: ' + msg)
+ entry.set('qtext', entry.get('qtext', '') + "\n" + msg)
+ rv = False
+ return POSIXTool.verify(self, entry, modlist) and rv
+
+ def install(self, entry):
+ if not self._exists(entry, remove=True):
+ try:
+ dev_type = entry.get('dev_type')
+ mode = device_map[dev_type] | int(entry.get('perms'), 8)
+ if dev_type in ['block', 'char']:
+ major = int(entry.get('major'))
+ minor = int(entry.get('minor'))
+ device = os.makedev(major, minor)
+ os.mknod(entry.get('name'), mode, device)
+ else:
+ os.mknod(entry.get('name'), mode)
+ except (KeyError, OSError, ValueError):
+ err = sys.exc_info()[1]
+ self.logger.error('POSIX: Failed to install %s: %s' %
+ (entry.get('name'), err))
+ return False
+ return POSIXTool.install(self, entry)
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Directory.py b/src/lib/Bcfg2/Client/Tools/POSIX/Directory.py
new file mode 100644
index 000000000..d2d383f66
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/Directory.py
@@ -0,0 +1,90 @@
+import os
+import sys
+import stat
+import shutil
+import Bcfg2.Client.XML
+try:
+ from base import POSIXTool
+except ImportError:
+ # py3k, incompatible syntax with py2.4
+ exec("from .base import POSIXTool")
+
+class POSIXDirectory(POSIXTool):
+ __req__ = ['name', 'perms', 'owner', 'group']
+
+ def verify(self, entry, modlist):
+ ondisk = self._exists(entry)
+ if not ondisk:
+ return False
+
+ if not stat.S_ISDIR(ondisk[stat.ST_MODE]):
+ self.logger.info("POSIX: %s is not a directory" % entry.get('name'))
+ return False
+
+ pruneTrue = True
+ if entry.get('prune', 'false').lower() == 'true':
+ # check for any extra entries when prune='true' attribute is set
+ try:
+ extras = [os.path.join(entry.get('name'), ent)
+ for ent in os.listdir(entry.get('name'))
+ if os.path.join(entry.get('name'),
+ ent) not in modlist]
+ if extras:
+ pruneTrue = False
+ msg = "Directory %s contains extra entries: %s" % \
+ (entry.get('name'), "; ".join(extras))
+ self.logger.info("POSIX: " + msg)
+ entry.set('qtext', entry.get('qtext', '') + '\n' + msg)
+ for extra in extras:
+ Bcfg2.Client.XML.SubElement(entry, 'Prune', path=extra)
+ except OSError:
+ pruneTrue = True
+
+ return POSIXTool.verify(self, entry, modlist) and pruneTrue
+
+ def install(self, entry):
+ """Install device entries."""
+ fmode = self._exists(entry)
+
+ if fmode and not stat.S_ISDIR(fmode[stat.ST_MODE]):
+ self.logger.info("POSIX: Found a non-directory entry at %s, "
+ "removing" % entry.get('name'))
+ try:
+ os.unlink(entry.get('name'))
+ fmode = False
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to unlink %s: %s" %
+ (entry.get('name'), err))
+ return False
+ elif fmode:
+ self.logger.debug("POSIX: Found a pre-existing directory at %s" %
+ entry.get('name'))
+
+ rv = True
+ if not fmode:
+ rv &= self._makedirs(entry)
+
+ if entry.get('prune', 'false') == 'true':
+ ulfailed = False
+ for pent in entry.findall('Prune'):
+ pname = pent.get('path')
+ ulfailed = False
+ if os.path.isdir(pname):
+ rm = shutil.rmtree
+ else:
+ rm = os.unlink
+ try:
+ self.logger.debug("POSIX: Removing %s" % pname)
+ rm(pname)
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to unlink %s: %s" %
+ (pname, err))
+ ulfailed = True
+ if ulfailed:
+ # even if prune failed, we still want to install the
+ # entry to make sure that we get permissions and
+ # whatnot set
+ rv = False
+ return POSIXTool.install(self, entry) and rv
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/File.py b/src/lib/Bcfg2/Client/Tools/POSIX/File.py
new file mode 100644
index 000000000..26550078e
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/File.py
@@ -0,0 +1,225 @@
+import os
+import sys
+import stat
+import time
+import difflib
+import tempfile
+try:
+ from base import POSIXTool
+except ImportError:
+ # py3k, incompatible syntax with py2.4
+ exec("from .base import POSIXTool")
+from Bcfg2.Bcfg2Py3k import unicode, b64encode, b64decode
+
+class POSIXFile(POSIXTool):
+ __req__ = ['name', 'perms', 'owner', 'group']
+
+ def fully_specified(self, entry):
+ return entry.text is not None or entry.get('empty', 'false') == 'true'
+
+ def _is_string(self, strng, encoding):
+ """ Returns true if the string contains no ASCII control
+ characters and can be decoded from the specified encoding. """
+ for char in strng:
+ if ord(char) < 9 or ord(char) > 13 and ord(char) < 32:
+ return False
+ if not hasattr(strng, "decode"):
+ # py3k
+ return True
+ try:
+ strng.decode(encoding)
+ return True
+ except:
+ return False
+
+ def _get_data(self, entry):
+ is_binary = False
+ if entry.get('encoding', 'ascii') == 'base64':
+ tempdata = b64decode(entry.text)
+ is_binary = True
+
+ elif entry.get('empty', 'false') == 'true':
+ tempdata = ''
+ else:
+ tempdata = entry.text
+ if isinstance(tempdata, unicode) and unicode != str:
+ try:
+ tempdata = tempdata.encode(self.setup['encoding'])
+ except UnicodeEncodeError:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Error encoding file %s: %s" %
+ (entry.get('name'), err))
+ return (tempdata, is_binary)
+
+ def verify(self, entry, modlist):
+ ondisk = self._exists(entry)
+ tempdata, is_binary = self._get_data(entry)
+
+ different = False
+ content = None
+ if not ondisk:
+ # first, see if the target file exists at all; if not,
+ # they're clearly different
+ different = True
+ content = ""
+ elif len(tempdata) != ondisk[stat.ST_SIZE]:
+ # next, see if the size of the target file is different
+ # from the size of the desired content
+ different = True
+ else:
+ # finally, read in the target file and compare them
+ # directly. comparison could be done with a checksum,
+ # which might be faster for big binary files, but slower
+ # for everything else
+ try:
+ content = open(entry.get('name')).read()
+ except IOError:
+ self.logger.error("POSIX: Failed to read %s: %s" %
+ (entry.get("name"), sys.exc_info()[1]))
+ return False
+ different = content != tempdata
+
+ if different:
+ self.logger.debug("POSIX: %s has incorrect contents" %
+ entry.get("name"))
+ self._get_diffs(
+ entry, interactive=self.setup['interactive'],
+ sensitive=entry.get('sensitive', 'false').lower() == 'true',
+ is_binary=is_binary, content=content)
+ return POSIXTool.verify(self, entry, modlist) and not different
+
+ def _write_tmpfile(self, entry):
+ filedata, _ = self._get_data(entry)
+ # get a temp file to write to that is in the same directory as
+ # the existing file in order to preserve any permissions
+ # protections on that directory, and also to avoid issues with
+ # /tmp set nosetuid while creating files that are supposed to
+ # be setuid
+ try:
+ (newfd, newfile) = \
+ tempfile.mkstemp(prefix=os.path.basename(entry.get("name")),
+ dir=os.path.dirname(entry.get("name")))
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to create temp file in %s: %s" %
+ (os.path.dirname(entry.get('name')), err))
+ return False
+ try:
+ os.fdopen(newfd, 'w').write(filedata)
+ except (OSError, IOError):
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to open temp file %s for writing "
+ "%s: %s" %
+ (newfile, entry.get("name"), err))
+ return False
+ return newfile
+
+ def _rename_tmpfile(self, newfile, entry):
+ try:
+ os.rename(newfile, entry.get('name'))
+ return True
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to rename temp file %s to %s: %s" %
+ (newfile, entry.get('name'), err))
+ try:
+ os.unlink(newfile)
+ except:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Could not remove temp file %s: %s" %
+ (newfile, err))
+ return False
+
+ def install(self, entry):
+ """Install device entries."""
+ if not os.path.exists(os.path.dirname(entry.get('name'))):
+ if not self._makedirs(entry,
+ path=os.path.dirname(entry.get('name'))):
+ return False
+ newfile = self._write_tmpfile(entry)
+ if not newfile:
+ return False
+ rv = self._set_perms(entry, path=newfile)
+ if not self._rename_tmpfile(newfile, entry):
+ return False
+
+ return POSIXTool.install(self, entry) and rv
+
+ def _get_diffs(self, entry, interactive=False, sensitive=False,
+ is_binary=False, content=None):
+ if not interactive and sensitive:
+ return
+
+ prompt = [entry.get('qtext', '')]
+ attrs = dict()
+ if content is None:
+ # it's possible that we figured out the files are
+ # different without reading in the local file. if the
+ # supplied version of the file is not binary, we now have
+ # to read in the local file to figure out if _it_ is
+ # binary, and either include that fact or the diff in our
+ # prompts for -I and the reports
+ try:
+ content = open(entry.get('name')).read()
+ except IOError:
+ self.logger.error("POSIX: Failed to read %s: %s" %
+ (entry.get("name"), sys.exc_info()[1]))
+ return False
+ if not is_binary:
+ is_binary |= not self._is_string(content, self.setup['encoding'])
+ if is_binary:
+ # don't compute diffs if the file is binary
+ prompt.append('Binary file, no printable diff')
+ attrs['current_bfile'] = b64encode(content)
+ else:
+ if interactive:
+ diff = self._diff(content, self._get_data(entry)[0],
+ difflib.unified_diff,
+ filename=entry.get("name"))
+ if diff:
+ udiff = ''.join(diff)
+ if hasattr(udiff, "decode"):
+ udiff = udiff.decode(self.setup['encoding'])
+ try:
+ prompt.append(udiff)
+ except UnicodeEncodeError:
+ prompt.append("Could not encode diff")
+ else:
+ prompt.append("Diff took too long to compute, no "
+ "printable diff")
+ if not sensitive:
+ diff = self._diff(content, self._get_data(entry)[0],
+ difflib.ndiff, filename=entry.get("name"))
+ if diff:
+ attrs["current_bdiff"] = b64encode("\n".join(diff))
+ else:
+ attrs['current_bfile'] = b64encode(content)
+ if interactive:
+ entry.set("qtext", "\n".join(prompt))
+ if not sensitive:
+ for attr, val in attrs.items():
+ entry.set(attr, val)
+
+ def _diff(self, content1, content2, difffunc, filename=None):
+ rv = []
+ start = time.time()
+ longtime = False
+ for diffline in difffunc(content1.split('\n'),
+ content2.split('\n')):
+ now = time.time()
+ rv.append(diffline)
+ if now - start > 5 and not longtime:
+ if filename:
+ self.logger.info("POSIX: Diff of %s taking a long time" %
+ filename)
+ else:
+ self.logger.info("POSIX: Diff taking a long time")
+ longtime = True
+ elif now - start > 30:
+ if filename:
+ self.logger.error("POSIX: Diff of %s took too long; giving "
+ "up" % filename)
+ else:
+ self.logger.error("POSIX: Diff took too long; giving up")
+ return False
+ return rv
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Hardlink.py b/src/lib/Bcfg2/Client/Tools/POSIX/Hardlink.py
new file mode 100644
index 000000000..ca7a23717
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/Hardlink.py
@@ -0,0 +1,43 @@
+import os
+import sys
+try:
+ from base import POSIXTool
+except ImportError:
+ # py3k, incompatible syntax with py2.4
+ exec("from .base import POSIXTool")
+
+class POSIXHardlink(POSIXTool):
+ __req__ = ['name', 'to']
+
+ def verify(self, entry, modlist):
+ rv = True
+
+ try:
+ if not os.path.samefile(entry.get('name'), entry.get('to')):
+ msg = "Hardlink %s is incorrect" % entry.get('name')
+ self.logger.debug("POSIX: " + msg)
+ entry.set('qtext', "\n".join([entry.get('qtext', ''), msg]))
+ rv = False
+ except OSError:
+ self.logger.debug("POSIX: %s %s does not exist" %
+ (entry.tag, entry.get("name")))
+ entry.set('current_exists', 'false')
+ return False
+
+ return POSIXTool.verify(self, entry, modlist) and rv
+
+ def install(self, entry):
+ ondisk = self._exists(entry, remove=True)
+ if ondisk:
+ self.logger.info("POSIX: Hardlink %s cleanup failed" %
+ entry.get('name'))
+ try:
+ os.link(entry.get('to'), entry.get('name'))
+ rv = True
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to create hardlink %s to %s: %s" %
+ (entry.get('name'), entry.get('to'), err))
+ rv = False
+ return POSIXTool.install(self, entry) and rv
+
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Nonexistent.py b/src/lib/Bcfg2/Client/Tools/POSIX/Nonexistent.py
new file mode 100644
index 000000000..c870ca0ed
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/Nonexistent.py
@@ -0,0 +1,45 @@
+import os
+import sys
+import shutil
+try:
+ from base import POSIXTool
+except ImportError:
+ # py3k, incompatible syntax with py2.4
+ exec("from .base import POSIXTool")
+
+class POSIXNonexistent(POSIXTool):
+ __req__ = ['name']
+
+ def verify(self, entry, _):
+ if os.path.lexists(entry.get('name')):
+ self.logger.debug("POSIX: %s exists but should not" %
+ entry.get("name"))
+ return False
+ return True
+
+ def install(self, entry):
+ ename = entry.get('name')
+ if entry.get('recursive', '').lower() == 'true':
+ # ensure that configuration spec is consistent first
+ for struct in self.config.getchildren():
+ for entry in struct.getchildren():
+ if (entry.tag == 'Path' and
+ entry.get('type') != 'nonexistent' and
+ entry.get('name').startswith(ename)):
+ self.logger.error('POSIX: Not removing %s. One or '
+ 'more files in this directory are '
+ 'specified in your configuration.' %
+ ename)
+ return False
+ rm = shutil.rmtree
+ elif os.path.isdir(ename):
+ rm = os.rmdir
+ else:
+ rm = os.remove
+ try:
+ rm(ename)
+ return True
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error('POSIX: Failed to remove %s: %s' % (ename, err))
+ return False
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Permissions.py b/src/lib/Bcfg2/Client/Tools/POSIX/Permissions.py
new file mode 100644
index 000000000..321376b98
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/Permissions.py
@@ -0,0 +1,11 @@
+import os
+import sys
+try:
+ from base import POSIXTool
+except ImportError:
+ # py3k, incompatible syntax with py2.4
+ exec("from .base import POSIXTool")
+
+class POSIXPermissions(POSIXTool):
+ __req__ = ['name', 'perms', 'owner', 'group']
+
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/Symlink.py b/src/lib/Bcfg2/Client/Tools/POSIX/Symlink.py
new file mode 100644
index 000000000..fb303bdbe
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/Symlink.py
@@ -0,0 +1,46 @@
+import os
+import sys
+try:
+ from base import POSIXTool
+except ImportError:
+ # py3k, incompatible syntax with py2.4
+ exec("from .base import POSIXTool")
+
+class POSIXSymlink(POSIXTool):
+ __req__ = ['name', 'to']
+
+ def verify(self, entry, modlist):
+ rv = True
+
+ try:
+ sloc = os.readlink(entry.get('name'))
+ if sloc != entry.get('to'):
+ entry.set('current_to', sloc)
+ msg = ("Symlink %s points to %s, should be %s" %
+ (entry.get('name'), sloc, entry.get('to')))
+ self.logger.debug("POSIX: " + msg)
+ entry.set('qtext', "\n".join([entry.get('qtext', ''), msg]))
+ rv = False
+ except OSError:
+ self.logger.debug("POSIX: %s %s does not exist" %
+ (entry.tag, entry.get("name")))
+ entry.set('current_exists', 'false')
+ return False
+
+ return POSIXTool.verify(self, entry, modlist) and rv
+
+ def install(self, entry):
+ ondisk = self._exists(entry, remove=True)
+ if ondisk:
+ self.logger.info("POSIX: Symlink %s cleanup failed" %
+ entry.get('name'))
+ try:
+ os.symlink(entry.get('to'), entry.get('name'))
+ rv = True
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to create symlink %s to %s: %s" %
+ (entry.get('name'), entry.get('to'), err))
+ rv = False
+ return POSIXTool.install(self, entry) and rv
+
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py
new file mode 100644
index 000000000..46631eb06
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/__init__.py
@@ -0,0 +1,151 @@
+"""All POSIX Type client support for Bcfg2."""
+
+import os
+import re
+import sys
+import shutil
+import pkgutil
+from datetime import datetime
+import Bcfg2.Client.Tools
+try:
+ from base import POSIXTool
+except ImportError:
+ # py3k, incompatible syntax with py2.4
+ exec("from .base import POSIXTool")
+
+class POSIX(Bcfg2.Client.Tools.Tool):
+ """POSIX File support code."""
+ name = 'POSIX'
+
+ def __init__(self, logger, setup, config):
+ Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config)
+ self.ppath = setup['ppath']
+ self.max_copies = setup['max_copies']
+ self._load_handlers()
+ self.logger.debug("POSIX: Handlers loaded: %s" %
+ (", ".join(self._handlers.keys())))
+ self.__req__ = dict(Path=dict())
+ for etype, hdlr in self._handlers.items():
+ self.__req__['Path'][etype] = hdlr.__req__
+ self.__handles__.append(('Path', etype))
+ # Tool.__init__() sets up the list of handled entries, but we
+ # need to do it again after __handles__ has been populated. we
+ # can't populate __handles__ when the class is created because
+ # _load_handlers() _must_ be called at run-time, not at
+ # compile-time.
+ for struct in config:
+ self.handled = [e for e in struct if self.handlesEntry(e)]
+
+ def _load_handlers(self):
+ # this must be called at run-time, not at compile-time, or we
+ # get wierd circular import issues.
+ self._handlers = dict()
+ if hasattr(pkgutil, 'walk_packages'):
+ submodules = pkgutil.walk_packages(path=__path__)
+ else:
+ # python 2.4
+ import glob
+ submodules = []
+ for path in __path__:
+ for submodule in glob.glob(os.path.join(path, "*.py")):
+ mod = os.path.splitext(os.path.basename(submodule))[0]
+ if mod not in ['__init__']:
+ submodules.append((None, mod, True))
+
+ for submodule in submodules:
+ if submodule[1] == 'base':
+ continue
+ module = getattr(__import__("%s.%s" %
+ (__name__,
+ submodule[1])).Client.Tools.POSIX,
+ submodule[1])
+ hdlr = getattr(module, "POSIX" + submodule[1])
+ if POSIXTool in hdlr.__mro__:
+ # figure out what entry type this handler handles
+ etype = hdlr.__name__[5:].lower()
+ self._handlers[etype] = hdlr(self.logger,
+ self.setup,
+ self.config)
+
+ def canVerify(self, entry):
+ if not Bcfg2.Client.Tools.Tool.canVerify(self, entry):
+ return False
+ if not self._handlers[entry.get("type")].fully_specified(entry):
+ self.logger.error('POSIX: Cannot verify incomplete entry %s. '
+ 'Try running bcfg2-lint.' %
+ entry.get('name'))
+ return False
+ return True
+
+ def canInstall(self, entry):
+ """Check if entry is complete for installation."""
+ if not Bcfg2.Client.Tools.Tool.canInstall(self, entry):
+ return False
+ if not self._handlers[entry.get("type")].fully_specified(entry):
+ self.logger.error('POSIX: Cannot install incomplete entry %s. '
+ 'Try running bcfg2-lint.' %
+ entry.get('name'))
+ return False
+ return True
+
+ def InstallPath(self, entry):
+ """Dispatch install to the proper method according to type"""
+ self.logger.debug("POSIX: Installing entry %s:%s:%s" %
+ (entry.tag, entry.get("type"), entry.get("name")))
+ self._paranoid_backup(entry)
+ return self._handlers[entry.get("type")].install(entry)
+
+ def VerifyPath(self, entry, modlist):
+ """Dispatch verify to the proper method according to type"""
+ self.logger.debug("POSIX: Verifying entry %s:%s:%s" %
+ (entry.tag, entry.get("type"), entry.get("name")))
+ ret = self._handlers[entry.get("type")].verify(entry, modlist)
+ if self.setup['interactive'] and not ret:
+ entry.set('qtext',
+ '%s\nInstall %s %s: (y/N) ' %
+ (entry.get('qtext', ''),
+ entry.get('type'), entry.get('name')))
+ return ret
+
+ def _prune_old_backups(self, entry):
+ bkupnam = entry.get('name').replace('/', '_')
+ bkup_re = re.compile(bkupnam + \
+ r'_\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{6}$')
+ # current list of backups for this file
+ try:
+ bkuplist = [f for f in os.listdir(self.ppath) if
+ bkup_re.match(f)]
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to create backup list in %s: %s" %
+ (self.ppath, err))
+ return
+ bkuplist.sort()
+ while len(bkuplist) >= int(self.max_copies):
+ # remove the oldest backup available
+ oldest = bkuplist.pop(0)
+ self.logger.info("POSIX: Removing old backup %s" % oldest)
+ try:
+ os.remove(os.path.join(self.ppath, oldest))
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to remove old backup %s: %s" %
+ (os.path.join(self.ppath, oldest), err))
+
+ def _paranoid_backup(self, entry):
+ if (entry.get("paranoid", 'false').lower() == 'true' and
+ self.setup.get("paranoid", False) and
+ entry.get('current_exists', 'true') == 'true' and
+ not os.path.isdir(entry.get("name"))):
+ self._prune_old_backups(entry)
+ bkupnam = "%s_%s" % (entry.get('name').replace('/', '_'),
+ datetime.isoformat(datetime.now()))
+ bfile = os.path.join(self.ppath, bkupnam)
+ try:
+ shutil.copy(entry.get('name'), bfile)
+ self.logger.info("POSIX: Backup of %s saved to %s" %
+ (entry.get('name'), bfile))
+ except IOError:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to create backup file for %s: "
+ "%s" % (entry.get('name'), err))
diff --git a/src/lib/Bcfg2/Client/Tools/POSIX/base.py b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
new file mode 100644
index 000000000..6952d0f7b
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/POSIX/base.py
@@ -0,0 +1,642 @@
+import os
+import sys
+import pwd
+import grp
+import stat
+import shutil
+import Bcfg2.Client.Tools
+import Bcfg2.Client.XML
+
+try:
+ import selinux
+ has_selinux = True
+except ImportError:
+ has_selinux = False
+
+try:
+ import posix1e
+ has_acls = True
+
+ # map between permissions characters and numeric ACL constants
+ acl_map = dict(r=posix1e.ACL_READ,
+ w=posix1e.ACL_WRITE,
+ x=posix1e.ACL_EXECUTE)
+except ImportError:
+ has_acls = False
+ acl_map = dict(r=4, w=2, x=1)
+
+# map between dev_type attribute and stat constants
+device_map = dict(block=stat.S_IFBLK,
+ char=stat.S_IFCHR,
+ fifo=stat.S_IFIFO)
+
+
+class POSIXTool(Bcfg2.Client.Tools.Tool):
+ def fully_specified(self, entry):
+ # checking is done by __req__
+ return True
+
+ def verify(self, entry, modlist):
+ if not self._verify_metadata(entry):
+ return False
+ if entry.get('recursive', 'false').lower() == 'true':
+ # verify ownership information recursively
+ for root, dirs, files in os.walk(entry.get('name')):
+ for p in dirs + files:
+ if not self._verify_metadata(entry,
+ path=os.path.join(root, p)):
+ return False
+ return True
+
+ def install(self, entry):
+ plist = [entry.get('name')]
+ rv = True
+ rv &= self._set_perms(entry)
+ if entry.get('recursive', 'false').lower() == 'true':
+ # set metadata recursively
+ for root, dirs, files in os.walk(entry.get('name')):
+ for path in dirs + files:
+ rv &= self._set_perms(entry, path=os.path.join(root, path))
+ return rv
+
+ def _exists(self, entry, remove=False):
+ try:
+ # check for existing paths and optionally remove them
+ ondisk = os.lstat(entry.get('name'))
+ if remove:
+ if os.path.isdir(entry.get('name')):
+ rm = shutil.rmtree
+ else:
+ rm = os.unlink
+ try:
+ rm(entry.get('name'))
+ return False
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.warning('POSIX: Failed to unlink %s: %s' %
+ (entry.get('name'), err))
+ return ondisk # probably still exists
+ else:
+ return ondisk
+ except OSError:
+ return False
+
+ def _set_perms(self, entry, path=None):
+ if path is None:
+ path = entry.get("name")
+
+ rv = True
+ if entry.get("owner") and entry.get("group"):
+ try:
+ self.logger.debug("POSIX: Setting ownership of %s to %s:%s" %
+ (path,
+ self._norm_entry_uid(entry),
+ self._norm_entry_gid(entry)))
+ os.chown(path, self._norm_entry_uid(entry),
+ self._norm_entry_gid(entry))
+ except KeyError:
+ self.logger.error('POSIX: Failed to change ownership of %s' %
+ path)
+ rv = False
+ os.chown(path, 0, 0)
+ except OSError:
+ self.logger.error('POSIX: Failed to change ownership of %s' %
+ path)
+ rv = False
+
+ if entry.get("perms"):
+ configPerms = int(entry.get('perms'), 8)
+ if entry.get('dev_type'):
+ configPerms |= device_map[entry.get('dev_type')]
+ try:
+ self.logger.debug("POSIX: Setting permissions on %s to %s" %
+ (path, oct(configPerms)))
+ os.chmod(path, configPerms)
+ except (OSError, KeyError):
+ self.logger.error('POSIX: Failed to change permissions on %s' %
+ path)
+ rv = False
+
+ if entry.get('mtime'):
+ try:
+ os.utime(entry.get('name'), (int(entry.get('mtime')),
+ int(entry.get('mtime'))))
+ except OSError:
+ self.logger.error("POSIX: Failed to set mtime of %s" % path)
+ rv = False
+
+ rv &= self._set_secontext(entry, path=path)
+ rv &= self._set_acls(entry, path=path)
+ return rv
+
+
+ def _set_acls(self, entry, path=None):
+ """ set POSIX ACLs on the file on disk according to the config """
+ if not has_acls:
+ if entry.findall("ACL"):
+ self.logger.debug("POSIX: ACLs listed for %s but no pylibacl "
+ "library installed" % entry.get('name'))
+ return True
+
+ if path is None:
+ path = entry.get("name")
+
+ try:
+ acl = posix1e.ACL(file=path)
+ except IOError:
+ err = sys.exc_info()[1]
+ if err.errno == 95:
+ # fs is mounted noacl
+ self.logger.error("POSIX: Cannot set ACLs on filesystem "
+ "mounted without ACL support: %s" % path)
+ else:
+ self.logger.error("POSIX: Error getting current ACLS on %s: %s"
+ % (path, err))
+ return False
+ # clear ACLs out so we start fresh -- way easier than trying
+ # to add/remove/modify ACLs
+ for aclentry in acl:
+ if aclentry.tag_type in [posix1e.ACL_USER, posix1e.ACL_GROUP]:
+ acl.delete_entry(aclentry)
+ if os.path.isdir(path):
+ defacl = posix1e.ACL(filedef=path)
+ if not defacl.valid():
+ # when a default ACL is queried on a directory that
+ # has no default ACL entries at all, you get an empty
+ # ACL, which is not valid. in this circumstance, we
+ # just copy the access ACL to get a base valid ACL
+ # that we can add things to.
+ defacl = posix1e.ACL(acl=acl)
+ else:
+ for aclentry in defacl:
+ if aclentry.tag_type in [posix1e.ACL_USER,
+ posix1e.ACL_GROUP]:
+ defacl.delete_entry(aclentry)
+ else:
+ defacl = None
+
+ for aclkey, perms in self._list_entry_acls(entry).items():
+ atype, scope, qualifier = aclkey
+ if atype == "default":
+ if defacl is None:
+ self.logger.warning("POSIX: Cannot set default ACLs on "
+ "non-directory %s" % path)
+ continue
+ entry = posix1e.Entry(defacl)
+ else:
+ entry = posix1e.Entry(acl)
+ for perm in acl_map.values():
+ if perm & perms:
+ entry.permset.add(perm)
+ entry.tag_type = scope
+ try:
+ if scope == posix1e.ACL_USER:
+ scopename = "user"
+ entry.qualifier = self._norm_uid(qualifier)
+ elif scope == posix1e.ACL_GROUP:
+ scopename = "group"
+ entry.qualifier = self._norm_gid(qualifier)
+ except (OSError, KeyError):
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Could not resolve %s %s: %s" %
+ (scopename, qualifier, err))
+ continue
+ acl.calc_mask()
+
+ def _apply_acl(acl, path, atype=posix1e.ACL_TYPE_ACCESS):
+ if atype == posix1e.ACL_TYPE_ACCESS:
+ atype_str = "access"
+ else:
+ atype_str = "default"
+ if acl.valid():
+ self.logger.debug("POSIX: Applying %s ACL to %s:" % (atype_str,
+ path))
+ for line in str(acl).splitlines():
+ self.logger.debug(" " + line)
+ try:
+ acl.applyto(path, atype)
+ return True
+ except:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to set ACLs on %s: %s" %
+ (path, err))
+ return False
+ else:
+ self.logger.warning("POSIX: %s ACL created for %s was invalid:"
+ % (atype_str.title(), path))
+ for line in str(acl).splitlines():
+ self.logger.warning(" " + line)
+ return False
+
+ rv = _apply_acl(acl, path)
+ if defacl:
+ defacl.calc_mask()
+ rv &= _apply_acl(defacl, path, posix1e.ACL_TYPE_DEFAULT)
+ return rv
+
+ def _set_secontext(self, entry, path=None):
+ """ set the SELinux context of the file on disk according to the
+ config"""
+ if not has_selinux:
+ return True
+
+ if path is None:
+ path = entry.get("name")
+ context = entry.get("secontext")
+ if context is None:
+ # no context listed
+ return True
+
+ if context == '__default__':
+ try:
+ selinux.restorecon(path)
+ rv = True
+ except:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to restore SELinux context "
+ "for %s: %s" % (path, err))
+ rv = False
+ else:
+ try:
+ rv = selinux.lsetfilecon(path, context) == 0
+ except:
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Failed to restore SELinux context "
+ "for %s: %s" % (path, err))
+ rv = False
+ return rv
+
+ def _norm_gid(self, gid):
+ """ This takes a group name or gid and returns the
+ corresponding gid. """
+ try:
+ return int(gid)
+ except ValueError:
+ return int(grp.getgrnam(gid)[2])
+
+ def _norm_entry_gid(self, entry):
+ try:
+ return self._norm_gid(entry.get('group'))
+ except (OSError, KeyError):
+ err = sys.exc_info()[1]
+ self.logger.error('POSIX: GID normalization failed for %s on %s: %s'
+ % (entry.get('group'), entry.get('name'), err))
+ return 0
+
+ def _norm_uid(self, uid):
+ """ This takes a username or uid and returns the
+ corresponding uid. """
+ try:
+ return int(uid)
+ except ValueError:
+ return int(pwd.getpwnam(uid)[2])
+
+ def _norm_entry_uid(self, entry):
+ try:
+ return self._norm_uid(entry.get("owner"))
+ except (OSError, KeyError):
+ err = sys.exc_info()[1]
+ self.logger.error('POSIX: UID normalization failed for %s on %s: %s'
+ % (entry.get('owner'), entry.get('name'), err))
+ return 0
+
+ def _norm_acl_perms(self, perms):
+ """ takes a representation of an ACL permset and returns a digit
+ representing the permissions entailed by it. representations can
+ either be a single octal digit, a string of up to three 'r',
+ 'w', 'x', or '-' characters, or a posix1e.Permset object"""
+ if hasattr(perms, 'test'):
+ # Permset object
+ return sum([p for p in acl_map.values()
+ if perms.test(p)])
+
+ try:
+ # single octal digit
+ rv = int(perms)
+ if rv > 0 and rv < 8:
+ return rv
+ else:
+ self.logger.error("POSIX: Permissions digit out of range in "
+ "ACL: %s" % perms)
+ return 0
+ except ValueError:
+ # couldn't be converted to an int; process as a string
+ if len(perms) > 3:
+ self.logger.error("POSIX: Permissions string too long in ACL: "
+ "%s" % perms)
+ return 0
+ rv = 0
+ for char in perms:
+ if char == '-':
+ continue
+ elif char not in acl_map:
+ self.logger.warning("POSIX: Unknown permissions character "
+ "in ACL: %s" % char)
+ elif rv & acl_map[char]:
+ self.logger.warning("POSIX: Duplicate permissions "
+ "character in ACL: %s" % perms)
+ else:
+ rv |= acl_map[char]
+ return rv
+
+ def _acl2string(self, aclkey, perms):
+ atype, scope, qualifier = aclkey
+ acl_str = []
+ if atype == 'default':
+ acl_str.append(atype)
+ if scope == posix1e.ACL_USER:
+ acl_str.append("user")
+ elif scope == posix1e.ACL_GROUP:
+ acl_str.append("group")
+ acl_str.append(qualifier)
+ acl_str.append(self._acl_perm2string(perms))
+ return ":".join(acl_str)
+
+ def _acl_perm2string(self, perm):
+ rv = []
+ for char in 'rwx':
+ if acl_map[char] & perm:
+ rv.append(char)
+ else:
+ rv.append('-')
+ return ''.join(rv)
+
+ def _gather_data(self, path):
+ try:
+ ondisk = os.stat(path)
+ except OSError:
+ self.logger.debug("POSIX: %s does not exist" % path)
+ return (False, None, None, None, None, None)
+
+ try:
+ owner = str(ondisk[stat.ST_UID])
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.debug("POSIX: Could not get current owner of %s: %s" %
+ (path, err))
+ owner = None
+ except KeyError:
+ self.logger.error('POSIX: User resolution failed for %s' % path)
+ owner = None
+
+ try:
+ group = str(ondisk[stat.ST_GID])
+ except (OSError, KeyError):
+ err = sys.exc_info()[1]
+ self.logger.debug("POSIX: Could not get current group of %s: %s" %
+ (path, err))
+ group = None
+ except KeyError:
+ self.logger.error('POSIX: Group resolution failed for %s' % path)
+ group = None
+
+ try:
+ perms = oct(ondisk[stat.ST_MODE])[-4:]
+ except (OSError, KeyError, TypeError):
+ err = sys.exc_info()[1]
+ self.logger.debug("POSIX: Could not get current permissions of %s: "
+ "%s" % (path, err))
+ perms = None
+
+ if has_selinux:
+ try:
+ secontext = selinux.getfilecon(path)[1].split(":")[2]
+ except (OSError, KeyError):
+ err = sys.exc_info()[1]
+ self.logger.debug("POSIX: Could not get current SELinux "
+ "context of %s: %s" % (path, err))
+ secontext = None
+ else:
+ secontext = None
+
+ if has_acls:
+ acls = self._list_file_acls(path)
+ else:
+ acls = None
+ return (ondisk, owner, group, perms, secontext, acls)
+
+ def _verify_metadata(self, entry, path=None):
+ """ generic method to verify perms, owner, group, secontext, acls,
+ and mtime """
+ # allow setting an alternate path for recursive permissions checking
+ if path is None:
+ path = entry.get('name')
+ attrib = dict()
+ ondisk, attrib['current_owner'], attrib['current_group'], \
+ attrib['current_perms'], attrib['current_secontext'], acls = \
+ self._gather_data(path)
+
+ if not ondisk:
+ entry.set('current_exists', 'false')
+ return False
+
+ # we conditionally verify every bit of metadata only if it's
+ # specified on the entry. consequently, canVerify() and
+ # fully_specified() are preconditions of _verify_metadata(),
+ # since they will ensure that everything that needs to be
+ # specified actually is. this lets us gracefully handle
+ # symlink and hardlink entries, which have SELinux contexts
+ # but not other permissions, optional secontext and mtime
+ # attrs, and so on.
+ configOwner, configGroup, configPerms, mtime = None, None, None, -1
+ if entry.get('mtime', '-1') != '-1':
+ mtime = str(ondisk[stat.ST_MTIME])
+ if entry.get("owner"):
+ configOwner = str(self._norm_entry_uid(entry))
+ if entry.get("group"):
+ configGroup = str(self._norm_entry_gid(entry))
+ if entry.get("perms"):
+ while len(entry.get('perms', '')) < 4:
+ entry.set('perms', '0' + entry.get('perms', ''))
+ configPerms = int(entry.get('perms'), 8)
+
+ errors = []
+ if configOwner and attrib['current_owner'] != configOwner:
+ errors.append("Owner for path %s is incorrect. "
+ "Current owner is %s but should be %s" %
+ (path, attrib['current_owner'], entry.get('owner')))
+
+ if configGroup and attrib['current_group'] != configGroup:
+ errors.append("Group for path %s is incorrect. "
+ "Current group is %s but should be %s" %
+ (path, attrib['current_group'], entry.get('group')))
+
+ if (configPerms and
+ oct(int(attrib['current_perms'], 8)) != oct(configPerms)):
+ errors.append("Permissions for path %s are incorrect. "
+ "Current permissions are %s but should be %s" %
+ (path, attrib['current_perms'], entry.get('perms')))
+
+ if entry.get('mtime'):
+ attrib['current_mtime'] = mtime
+ if mtime != entry.get('mtime', '-1'):
+ errors.append("mtime for path %s is incorrect. "
+ "Current mtime is %s but should be %s" %
+ (path, mtime, entry.get('mtime')))
+
+ if has_selinux and entry.get("secontext"):
+ if entry.get("secontext") == "__default__":
+ configContext = selinux.matchpathcon(path, 0)[1].split(":")[2]
+ else:
+ configContext = entry.get("secontext")
+ if attrib['current_secontext'] != configContext:
+ errors.append("SELinux context for path %s is incorrect. "
+ "Current context is %s but should be %s" %
+ (path, attrib['current_secontext'],
+ configContext))
+
+ if errors:
+ for error in errors:
+ self.logger.debug("POSIX: " + error)
+ entry.set('qtext', "\n".join([entry.get('qtext', '')] + errors))
+ if path == entry.get("name"):
+ for attr, val in attrib.items():
+ if val is not None:
+ entry.set(attr, str(val))
+
+ aclVerifies = self._verify_acls(entry, path=path)
+ return aclVerifies and len(errors) == 0
+
+ def _list_entry_acls(self, entry):
+ wanted = dict()
+ for acl in entry.findall("ACL"):
+ if acl.get("scope") == "user":
+ scope = posix1e.ACL_USER
+ elif acl.get("scope") == "group":
+ scope = posix1e.ACL_GROUP
+ else:
+ self.logger.error("POSIX: Unknown ACL scope %s" %
+ acl.get("scope"))
+ continue
+ wanted[(acl.get("type"), scope, acl.get(acl.get("scope")))] = \
+ self._norm_acl_perms(acl.get('perms'))
+ return wanted
+
+ def _list_file_acls(self, path):
+ def _process_acl(acl, atype):
+ try:
+ if acl.tag_type == posix1e.ACL_USER:
+ qual = pwd.getpwuid(acl.qualifier)[0]
+ elif acl.tag_type == posix1e.ACL_GROUP:
+ qual = grp.getgrgid(acl.qualifier)[0]
+ else:
+ return
+ except (OSError, KeyError):
+ err = sys.exc_info()[1]
+ self.logger.error("POSIX: Lookup of %s %s failed: %s" %
+ (scope, acl.qualifier, err))
+ qual = acl.qualifier
+ existing[(atype, acl.tag_type, qual)] = \
+ self._norm_acl_perms(acl.permset)
+
+ existing = dict()
+ try:
+ for acl in posix1e.ACL(file=path):
+ _process_acl(acl, "access")
+ except IOError:
+ err = sys.exc_info()[1]
+ if err.errno == 95:
+ # fs is mounted noacl
+ self.logger.debug("POSIX: Filesystem mounted without ACL "
+ "support: %s" % path)
+ else:
+ self.logger.error("POSIX: Error getting current ACLS on %s: %s"
+ % (path, err))
+ return existing
+
+ if os.path.isdir(path):
+ for acl in posix1e.ACL(filedef=path):
+ _process_acl(acl, "default")
+ return existing
+
+ def _verify_acls(self, entry, path=None):
+ if not has_acls:
+ if entry.findall("ACL"):
+ self.logger.debug("POSIX: ACLs listed for %s but no pylibacl "
+ "library installed" % entry.get('name'))
+ return True
+
+ if path is None:
+ path = entry.get("name")
+
+ # create lists of normalized representations of the ACLs we want
+ # and the ACLs we have. this will make them easier to compare
+ # than trying to mine that data out of the ACL objects and XML
+ # objects and compare it at the same time.
+ wanted = self._list_entry_acls(entry)
+ existing = self._list_file_acls(path)
+
+ missing = []
+ extra = []
+ wrong = []
+ for aclkey, perms in wanted.items():
+ if aclkey not in existing:
+ missing.append(self._acl2string(aclkey, perms))
+ elif existing[aclkey] != perms:
+ wrong.append((self._acl2string(aclkey, perms),
+ self._acl2string(aclkey, existing[aclkey])))
+ if path == entry.get("name"):
+ atype, scope, qual = aclkey
+ aclentry = Bcfg2.Client.XML.Element("ACL", type=atype,
+ perms=str(perms))
+ if scope == posix1e.ACL_USER:
+ aclentry.set("scope", "user")
+ elif scope == posix1e.ACL_GROUP:
+ aclentry.set("scope", "group")
+ else:
+ self.logger.debug("POSIX: Unknown ACL scope %s on %s" %
+ (scope, path))
+ continue
+ aclentry.set(aclentry.get("scope"), qual)
+ entry.append(aclentry)
+
+ for aclkey, perms in existing.items():
+ if aclkey not in wanted:
+ extra.append(self._acl2string(aclkey, perms))
+
+ msg = []
+ if missing:
+ msg.append("%s ACLs are missing: %s" % (len(missing),
+ ", ".join(missing)))
+ if wrong:
+ msg.append("%s ACLs are wrong: %s" %
+ (len(wrong),
+ "; ".join(["%s should be %s" % (e, w)
+ for w, e in wrong])))
+ if extra:
+ msg.append("%s extra ACLs: %s" % (len(extra), ", ".join(extra)))
+
+ if msg:
+ msg.insert(0, "POSIX: ACLs for %s are incorrect." % path)
+ self.logger.debug(msg[0])
+ for line in msg[1:]:
+ self.logger.debug(" " + line)
+ entry.set('qtext', "\n".join([entry.get("qtext", '')] + msg))
+ return False
+ return True
+
+ def _makedirs(self, entry, path=None):
+ """ os.makedirs helpfully creates all parent directories for
+ us, but it sets permissions according to umask, which is
+ probably wrong. we need to find out which directories were
+ created and set permissions on those
+ (http://trac.mcs.anl.gov/projects/bcfg2/ticket/1125) """
+ created = []
+ if path is None:
+ path = entry.get("name")
+ cur = path
+ while cur != '/':
+ if not os.path.exists(cur):
+ created.append(cur)
+ cur = os.path.dirname(cur)
+ rv = True
+ try:
+ os.makedirs(path)
+ except OSError:
+ err = sys.exc_info()[1]
+ self.logger.error('POSIX: Failed to create directory %s: %s' %
+ (path, err))
+ rv = False
+ for cpath in created:
+ rv &= self._set_perms(entry, path=cpath)
+ return rv
diff --git a/src/lib/Bcfg2/Client/Tools/Portage.py b/src/lib/Bcfg2/Client/Tools/Portage.py
index 4516f419d..36d48b8d3 100644
--- a/src/lib/Bcfg2/Client/Tools/Portage.py
+++ b/src/lib/Bcfg2/Client/Tools/Portage.py
@@ -2,8 +2,6 @@
import re
import Bcfg2.Client.Tools
-from Bcfg2.Bcfg2Py3k import ConfigParser
-
class Portage(Bcfg2.Client.Tools.PkgTool):
"""The Gentoo toolset implements package and service operations and
@@ -27,30 +25,11 @@ class Portage(Bcfg2.Client.Tools.PkgTool):
self._ebuild_pattern = re.compile('(ebuild|binary)')
self.cfg = cfg
self.installed = {}
- self._binpkgonly = True
-
- # Used to get options from configuration file
- parser = ConfigParser.ConfigParser()
- parser.read(self.setup.get('setup'))
- for opt in ['binpkgonly']:
- if parser.has_option(self.name, opt):
- setattr(self, ('_%s' % opt),
- self._StrToBoolIfBool(parser.get(self.name, opt)))
-
+ self._binpkgonly = self.setup.get('portage_binpkgonly', False)
if self._binpkgonly:
self.pkgtool = self._binpkgtool
self.RefreshPackages()
- def _StrToBoolIfBool(self, s):
- """Returns a boolean if the string specifies a boolean value.
- Returns a string otherwise"""
- if s.lower() in ('true', 'yes', 't', 'y', '1'):
- return True
- elif s.lower() in ('false', 'no', 'f', 'n', '0'):
- return False
- else:
- return s
-
def RefreshPackages(self):
"""Refresh memory hashes of packages."""
if not self._initialised:
@@ -83,8 +62,8 @@ class Portage(Bcfg2.Client.Tools.PkgTool):
entry.set('current_version', version)
if not self.setup['quick']:
- if ('verify' not in entry.attrib) or \
- self._StrToBoolIfBool(entry.get('verify')):
+ if ('verify' not in entry.attrib or
+ entry.get('verify').lower == 'true'):
# Check the package if:
# - Not running in quick mode
diff --git a/src/lib/Bcfg2/Client/Tools/RPMng.py b/src/lib/Bcfg2/Client/Tools/RPMng.py
index 00dd00d71..91e2180ae 100644
--- a/src/lib/Bcfg2/Client/Tools/RPMng.py
+++ b/src/lib/Bcfg2/Client/Tools/RPMng.py
@@ -4,8 +4,6 @@ import os.path
import rpm
import rpmtools
import Bcfg2.Client.Tools
-# Compatibility import
-from Bcfg2.Bcfg2Py3k import ConfigParser
class RPMng(Bcfg2.Client.Tools.PkgTool):
"""Support for RPM packages."""
@@ -44,82 +42,42 @@ class RPMng(Bcfg2.Client.Tools.PkgTool):
self.modlists = {}
self.gpg_keyids = self.getinstalledgpg()
- # Process thee RPMng section from the config file.
- RPMng_CP = ConfigParser.ConfigParser()
- RPMng_CP.read(self.setup.get('setup'))
-
- # installonlypackages
- self.installOnlyPkgs = []
- if RPMng_CP.has_option(self.name, 'installonlypackages'):
- for i in RPMng_CP.get(self.name, 'installonlypackages').split(','):
- self.installOnlyPkgs.append(i.strip())
- if self.installOnlyPkgs == []:
- self.installOnlyPkgs = ['kernel', 'kernel-bigmem', 'kernel-enterprise', 'kernel-smp',
- 'kernel-modules', 'kernel-debug', 'kernel-unsupported',
- 'kernel-source', 'kernel-devel', 'kernel-default',
- 'kernel-largesmp-devel', 'kernel-largesmp', 'kernel-xen',
- 'gpg-pubkey']
+ opt_prefix = self.name.lower()
+ self.installOnlyPkgs = self.setup["%s_installonly" % opt_prefix]
if 'gpg-pubkey' not in self.installOnlyPkgs:
self.installOnlyPkgs.append('gpg-pubkey')
- self.logger.debug('installOnlyPackages = %s' % self.installOnlyPkgs)
-
- # erase_flags
- self.erase_flags = []
- if RPMng_CP.has_option(self.name, 'erase_flags'):
- for i in RPMng_CP.get(self.name, 'erase_flags').split(','):
- self.erase_flags.append(i.strip())
- if self.erase_flags == []:
- self.erase_flags = ['allmatches']
- self.logger.debug('erase_flags = %s' % self.erase_flags)
-
- # pkg_checks
- if RPMng_CP.has_option(self.name, 'pkg_checks'):
- self.pkg_checks = RPMng_CP.get(self.name, 'pkg_checks').lower()
- else:
- self.pkg_checks = 'true'
- self.logger.debug('pkg_checks = %s' % self.pkg_checks)
-
- # pkg_verify
- if RPMng_CP.has_option(self.name, 'pkg_verify'):
- self.pkg_verify = RPMng_CP.get(self.name, 'pkg_verify').lower()
- else:
- self.pkg_verify = 'true'
- self.logger.debug('pkg_verify = %s' % self.pkg_verify)
-
- # installed_action
- if RPMng_CP.has_option(self.name, 'installed_action'):
- self.installed_action = RPMng_CP.get(self.name, 'installed_action').lower()
- else:
- self.installed_action = 'install'
- self.logger.debug('installed_action = %s' % self.installed_action)
-
- # version_fail_action
- if RPMng_CP.has_option(self.name, 'version_fail_action'):
- self.version_fail_action = RPMng_CP.get(self.name, 'version_fail_action').lower()
- else:
- self.version_fail_action = 'upgrade'
- self.logger.debug('version_fail_action = %s' % self.version_fail_action)
-
- # verify_fail_action
- if self.name == "RPMng":
- if RPMng_CP.has_option(self.name, 'verify_fail_action'):
- self.verify_fail_action = RPMng_CP.get(self.name, 'verify_fail_action').lower()
- else:
- self.verify_fail_action = 'reinstall'
- else: # yum can't reinstall packages.
- self.verify_fail_action = 'none'
- self.logger.debug('verify_fail_action = %s' % self.verify_fail_action)
-
- # version_fail_action
- if RPMng_CP.has_option(self.name, 'verify_flags'):
- self.verify_flags = RPMng_CP.get(self.name, 'verify_flags').lower().split(',')
- else:
- self.verify_flags = []
+ self.erase_flags = self.setup['%s_erase_flags' % opt_prefix]
+ self.pkg_checks = self.setup['%s_pkg_checks' % opt_prefix]
+ self.pkg_verify = self.setup['%s_pkg_verify' % opt_prefix]
+ self.installed_action = self.setup['%s_installed_action' % opt_prefix]
+ self.version_fail_action = self.setup['%s_version_fail_action' %
+ opt_prefix]
+ self.verify_fail_action = self.setup['%s_verify_fail_action' %
+ opt_prefix]
+ self.verify_flags = self.setup['%s_verify_flags' % opt_prefix]
if '' in self.verify_flags:
self.verify_flags.remove('')
- self.logger.debug('version_fail_action = %s' % self.version_fail_action)
+
+ self.logger.debug('%s: installOnlyPackages = %s' %
+ (self.name, self.installOnlyPkgs))
+ self.logger.debug('%s: erase_flags = %s' %
+ (self.name, self.erase_flags))
+ self.logger.debug('%s: pkg_checks = %s' %
+ (self.name, self.pkg_checks))
+ self.logger.debug('%s: pkg_verify = %s' %
+ (self.name, self.pkg_verify))
+ self.logger.debug('%s: installed_action = %s' %
+ (self.name, self.installed_action))
+ self.logger.debug('%s: version_fail_action = %s' %
+ (self.name, self.version_fail_action))
+ self.logger.debug('%s: verify_fail_action = %s' %
+ (self.name, self.verify_fail_action))
+ self.logger.debug('%s: verify_flags = %s' %
+ (self.name, self.verify_flags))
+
# Force a re- prelink of all packages if prelink exists.
- # Many, if not most package verifies can be caused by out of date prelinking.
+ # Many, if not most package verifies can be caused by out of
+ # date prelinking.
if os.path.isfile('/usr/sbin/prelink') and not self.setup['dryrun']:
cmdrc, output = self.cmd.run('/usr/sbin/prelink -a -mR')
if cmdrc == 0:
@@ -193,7 +151,8 @@ class RPMng(Bcfg2.Client.Tools.PkgTool):
instance = Bcfg2.Client.XML.SubElement(entry, 'Package')
for attrib in list(entry.attrib.keys()):
instance.attrib[attrib] = entry.attrib[attrib]
- if self.pkg_checks == 'true' and entry.get('pkg_checks', 'true') == 'true':
+ if (self.pkg_checks and
+ entry.get('pkg_checks', 'true').lower() == 'true'):
if 'any' in [entry.get('version'), pinned_version]:
version, release = 'any', 'any'
elif entry.get('version') == 'auto':
@@ -215,7 +174,8 @@ class RPMng(Bcfg2.Client.Tools.PkgTool):
if entry.get('name') in self.installed:
# There is at least one instance installed.
- if self.pkg_checks == 'true' and entry.get('pkg_checks', 'true') == 'true':
+ if (self.pkg_checks and
+ entry.get('pkg_checks', 'true').lower() == 'true'):
rpmTs = rpm.TransactionSet()
rpmHeader = None
for h in rpmTs.dbMatch(rpm.RPMTAG_NAME, entry.get('name')):
@@ -243,8 +203,8 @@ class RPMng(Bcfg2.Client.Tools.PkgTool):
self.logger.debug(" %s" % self.str_evra(inst))
self.instance_status[inst]['installed'] = True
- if self.pkg_verify == 'true' and \
- inst.get('pkg_verify', 'true') == 'true':
+ if (self.pkg_verify and
+ inst.get('pkg_verify', 'true').lower() == 'true'):
flags = inst.get('verify_flags', '').split(',') + self.verify_flags
if pkg.get('gpgkeyid', '')[-8:] not in self.gpg_keyids and \
entry.get('name') != 'gpg-pubkey':
@@ -302,8 +262,8 @@ class RPMng(Bcfg2.Client.Tools.PkgTool):
self.logger.debug(" %s" % self.str_evra(inst))
self.instance_status[inst]['installed'] = True
- if self.pkg_verify == 'true' and \
- inst.get('pkg_verify', 'true') == 'true':
+ if (self.pkg_verify and
+ inst.get('pkg_verify', 'true').lower() == 'true'):
flags = inst.get('verify_flags', '').split(',') + self.verify_flags
if pkg.get('gpgkeyid', '')[-8:] not in self.gpg_keyids and \
'nosignature' not in flags:
@@ -520,7 +480,7 @@ class RPMng(Bcfg2.Client.Tools.PkgTool):
self.extra = self.FindExtraPackages()
def FixInstance(self, instance, inst_status):
- """"
+ """
Control if a reinstall of a package happens or not based on the
results from RPMng.VerifyPackage().
@@ -824,8 +784,8 @@ class RPMng(Bcfg2.Client.Tools.PkgTool):
return False
# We don't want to do any checks so we don't care what the entry has in it.
- if self.pkg_checks == 'false' or \
- entry.get('pkg_checks', 'true').lower() == 'false':
+ if (not self.pkg_checks or
+ entry.get('pkg_checks', 'true').lower() == 'false'):
return True
instances = entry.findall('Instance')
diff --git a/src/lib/Bcfg2/Client/Tools/RcUpdate.py b/src/lib/Bcfg2/Client/Tools/RcUpdate.py
index 1b9a29478..ddf9c1f2d 100644
--- a/src/lib/Bcfg2/Client/Tools/RcUpdate.py
+++ b/src/lib/Bcfg2/Client/Tools/RcUpdate.py
@@ -23,22 +23,18 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool):
rc = self.cmd.run(cmd % entry.get('name'))[0]
is_enabled = (rc == 0)
- if entry.get('mode', 'default') == 'supervised':
- # check if init script exists
- try:
- os.stat('/etc/init.d/%s' % entry.get('name'))
- except OSError:
- self.logger.debug('Init script for service %s does not exist' %
- entry.get('name'))
- return False
+ # check if init script exists
+ try:
+ os.stat('/etc/init.d/%s' % entry.get('name'))
+ except OSError:
+ self.logger.debug('Init script for service %s does not exist' %
+ entry.get('name'))
+ return False
- # check if service is enabled
- cmd = '/etc/init.d/%s status | grep started'
- rc = self.cmd.run(cmd % entry.attrib['name'])[0]
- is_running = (rc == 0)
- else:
- # we don't care
- is_running = is_enabled
+ # check if service is enabled
+ cmd = '/etc/init.d/%s status | grep started'
+ rc = self.cmd.run(cmd % entry.attrib['name'])[0]
+ is_running = (rc == 0)
if entry.get('status') == 'on' and not (is_enabled and is_running):
entry.set('current_status', 'off')
@@ -53,19 +49,11 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool):
def InstallService(self, entry):
"""
Install Service entry
- In supervised mode we also take care it's (not) running.
"""
- # don't take any actions for mode='manual'
- if entry.get('mode', 'default') == 'manual':
- self.logger.info("Service %s mode set to manual. Skipping "
- "installation." % (entry.get('name')))
- return False
self.logger.info('Installing Service %s' % entry.get('name'))
if entry.get('status') == 'on':
- # make sure it's running if in supervised mode
- if entry.get('mode', 'default') == 'supervised' \
- and entry.get('current_status') == 'off':
+ if entry.get('current_status') == 'off':
self.start_service(entry)
# make sure it's enabled
cmd = '/sbin/rc-update add %s default'
@@ -73,9 +61,7 @@ class RcUpdate(Bcfg2.Client.Tools.SvcTool):
return (rc == 0)
elif entry.get('status') == 'off':
- # make sure it's not running if in supervised mode
- if entry.get('mode', 'default') == 'supervised' \
- and entry.get('current_status') == 'on':
+ if entry.get('current_status') == 'on':
self.stop_service(entry)
# make sure it's disabled
cmd = '/sbin/rc-update del %s default'
diff --git a/src/lib/Bcfg2/Client/Tools/SELinux.py b/src/lib/Bcfg2/Client/Tools/SELinux.py
new file mode 100644
index 000000000..1c0db904b
--- /dev/null
+++ b/src/lib/Bcfg2/Client/Tools/SELinux.py
@@ -0,0 +1,716 @@
+import os
+import re
+import sys
+import copy
+import glob
+import struct
+import socket
+import selinux
+import seobject
+import Bcfg2.Client.XML
+import Bcfg2.Client.Tools
+import Bcfg2.Client.Tools.POSIX
+
+def pack128(int_val):
+ """ pack a 128-bit integer in big-endian format """
+ max_int = 2 ** (128) - 1
+ max_word_size = 2 ** 32 - 1
+
+ if int_val <= max_word_size:
+ return struct.pack('>L', int_val)
+
+ words = []
+ for i in range(4):
+ word = int_val & max_word_size
+ words.append(int(word))
+ int_val >>= 32
+ words.reverse()
+ return struct.pack('>4I', *words)
+
+def netmask_itoa(netmask, proto="ipv4"):
+ """ convert an integer netmask (e.g., /16) to dotted-quad
+ notation (255.255.0.0) or IPv6 prefix notation (ffff::) """
+ if proto == "ipv4":
+ size = 32
+ family = socket.AF_INET
+ else: # ipv6
+ size = 128
+ family = socket.AF_INET6
+ try:
+ int(netmask)
+ except ValueError:
+ return netmask
+
+ if netmask > size:
+ raise ValueError("Netmask too large: %s" % netmask)
+
+ res = 0L
+ for n in range(netmask):
+ res |= 1 << (size - n - 1)
+ netmask = socket.inet_ntop(family, pack128(res))
+ return netmask
+
+
+class SELinux(Bcfg2.Client.Tools.Tool):
+ """ SELinux boolean and module support """
+ name = 'SELinux'
+ __handles__ = [('SELinux', 'boolean'),
+ ('SELinux', 'port'),
+ ('SELinux', 'fcontext'),
+ ('SELinux', 'node'),
+ ('SELinux', 'login'),
+ ('SELinux', 'user'),
+ ('SELinux', 'interface'),
+ ('SELinux', 'permissive'),
+ ('SELinux', 'module')]
+ __req__ = dict(SELinux=dict(boolean=['name', 'value'],
+ module=['name'],
+ port=['name', 'selinuxtype'],
+ fcontext=['name', 'selinuxtype'],
+ node=['name', 'selinuxtype', 'proto'],
+ login=['name', 'selinuxuser'],
+ user=['name', 'roles', 'prefix'],
+ interface=['name', 'selinuxtype'],
+ permissive=['name']))
+
+ def __init__(self, logger, setup, config):
+ Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config)
+ self.handlers = {}
+ for handles in self.__handles__:
+ etype = handles[1]
+ self.handlers[etype] = \
+ globals()["SELinux%sHandler" % etype.title()](self, logger,
+ setup, config)
+
+ def BundleUpdated(self, _, states):
+ for handler in self.handlers.values():
+ handler.BundleUpdated(states)
+
+ def FindExtra(self):
+ extra = []
+ for handler in self.handlers.values():
+ extra.extend(handler.FindExtra())
+ return extra
+
+ def canInstall(self, entry):
+ return (Bcfg2.Client.Tools.Tool.canInstall(self, entry) and
+ self.handlers[entry.get('type')].canInstall(entry))
+
+ def primarykey(self, entry):
+ """ return a string that should be unique amongst all entries
+ in the specification """
+ return self.handlers[entry.get('type')].primarykey(entry)
+
+ def Install(self, entries, states):
+ # start a transaction
+ sr = seobject.semanageRecords("")
+ if hasattr(sr, "start"):
+ self.logger.debug("Starting SELinux transaction")
+ sr.start()
+ else:
+ self.logger.debug("SELinux transactions not supported; this may "
+ "slow things down considerably")
+ Bcfg2.Client.Tools.Tool.Install(self, entries, states)
+ if hasattr(sr, "finish"):
+ self.logger.debug("Committing SELinux transaction")
+ sr.finish()
+
+ def InstallSELinux(self, entry):
+ """Dispatch install to the proper method according to type"""
+ return self.handlers[entry.get('type')].Install(entry)
+
+ def VerifySELinux(self, entry, _):
+ """Dispatch verify to the proper method according to type"""
+ rv = self.handlers[entry.get('type')].Verify(entry)
+ if entry.get('qtext') and self.setup['interactive']:
+ entry.set('qtext',
+ '%s\nInstall SELinux %s %s: (y/N) ' %
+ (entry.get('qtext'),
+ entry.get('type'),
+ self.handlers[entry.get('type')].tostring(entry)))
+ return rv
+
+ def Remove(self, entries):
+ """Dispatch verify to the proper removal method according to type"""
+ # sort by type
+ types = list()
+ for entry in entries:
+ if entry.get('type') not in types:
+ types.append(entry.get('type'))
+
+ for etype in types:
+ self.handlers[entry.get('type')].Remove([e for e in entries
+ if e.get('type') == etype])
+
+
+class SELinuxEntryHandler(object):
+ etype = None
+ key_format = ("name",)
+ value_format = ()
+ str_format = '%(name)s'
+ custom_re = re.compile(' (?P<name>\S+)$')
+ custom_format = None
+
+ def __init__(self, tool, logger, setup, config):
+ self.tool = tool
+ self.logger = logger
+ self._records = None
+ self._all = None
+ if not self.custom_format:
+ self.custom_format = self.key_format
+
+ @property
+ def records(self):
+ if self._records is None:
+ self._records = getattr(seobject, "%sRecords" % self.etype)("")
+ return self._records
+
+ @property
+ def all_records(self):
+ if self._all is None:
+ self._all = self.records.get_all()
+ return self._all
+
+ @property
+ def custom_records(self):
+ if hasattr(self.records, "customized") and self.custom_re:
+ return dict([(k, self.all_records[k]) for k in self.custom_keys])
+ else:
+ # ValueError is really a pretty dumb exception to raise,
+ # but that's what the seobject customized() method raises
+ # if it's defined but not implemented. yeah, i know, wtf.
+ raise ValueError("custom_records")
+
+ @property
+ def custom_keys(self):
+ keys = []
+ for cmd in self.records.customized():
+ match = self.custom_re.search(cmd)
+ if match:
+ if (len(self.custom_format) == 1 and
+ self.custom_format[0] == "name"):
+ keys.append(match.group("name"))
+ else:
+ keys.append(tuple([match.group(k)
+ for k in self.custom_format]))
+ return keys
+
+ def tostring(self, entry):
+ return self.str_format % entry.attrib
+
+ def keytostring(self, key):
+ return self.str_format % self._key2attrs(key)
+
+ def _key(self, entry):
+ if len(self.key_format) == 1 and self.key_format[0] == "name":
+ return entry.get("name")
+ else:
+ rv = []
+ for key in self.key_format:
+ rv.append(entry.get(key))
+ return tuple(rv)
+
+ def _key2attrs(self, key):
+ if isinstance(key, tuple):
+ rv = dict((self.key_format[i], key[i])
+ for i in range(len(self.key_format))
+ if self.key_format[i])
+ else:
+ rv = dict(name=key)
+ if self.value_format:
+ vals = self.all_records[key]
+ rv.update(dict((self.value_format[i], vals[i])
+ for i in range(len(self.value_format))
+ if self.value_format[i]))
+ return rv
+
+ def key2entry(self, key):
+ attrs = self._key2attrs(key)
+ attrs["type"] = self.etype
+ return Bcfg2.Client.XML.Element("SELinux", **attrs)
+
+ def _args(self, entry, method):
+ if hasattr(self, "_%sargs" % method):
+ return getattr(self, "_%sargs" % method)(entry)
+ elif hasattr(self, "_defaultargs"):
+ # default args
+ return self._defaultargs(entry)
+ else:
+ raise NotImplementedError
+
+ def _deleteargs(self, entry):
+ return (self._key(entry))
+
+ def canInstall(self, entry):
+ return bool(self._key(entry))
+
+ def primarykey(self, entry):
+ return ":".join([entry.tag, entry.get("type"), entry.get("name")])
+
+ def exists(self, entry):
+ if self._key(entry) not in self.all_records:
+ self.logger.debug("SELinux %s %s does not exist" %
+ (self.etype, self.tostring(entry)))
+ return False
+ return True
+
+ def Verify(self, entry):
+ if not self.exists(entry):
+ entry.set('current_exists', 'false')
+ return False
+
+ errors = []
+ current_attrs = self._key2attrs(self._key(entry))
+ desired_attrs = entry.attrib
+ for attr in self.value_format:
+ if not attr:
+ continue
+ if current_attrs[attr] != desired_attrs[attr]:
+ entry.set('current_%s' % attr, current_attrs[attr])
+ errors.append("SELinux %s %s has wrong %s: %s, should be %s" %
+ (self.etype, self.tostring(entry), attr,
+ current_attrs[attr], desired_attrs[attr]))
+
+ if errors:
+ for error in errors:
+ self.logger.debug(error)
+ entry.set('qtext', "\n".join([entry.get('qtext', '')] + errors))
+ return False
+ else:
+ return True
+
+ def Install(self, entry, method=None):
+ if not method:
+ if self.exists(entry):
+ method = "modify"
+ else:
+ method = "add"
+ self.logger.debug("%s SELinux %s %s" %
+ (method.title(), self.etype, self.tostring(entry)))
+
+ try:
+ getattr(self.records, method)(*self._args(entry, method))
+ self._all = None
+ return True
+ except ValueError:
+ err = sys.exc_info()[1]
+ self.logger.debug("Failed to %s SELinux %s %s: %s" %
+ (method, self.etype, self.tostring(entry), err))
+ return False
+
+ def Remove(self, entries):
+ for entry in entries:
+ try:
+ self.records.delete(*self._args(entry, "delete"))
+ self._all = None
+ except ValueError:
+ err = sys.exc_info()[1]
+ self.logger.info("Failed to remove SELinux %s %s: %s" %
+ (self.etype, self.tostring(entry), err))
+
+ def FindExtra(self):
+ specified = [self._key(e)
+ for e in self.tool.getSupportedEntries()
+ if e.get("type") == self.etype]
+ try:
+ records = self.custom_records
+ except ValueError:
+ records = self.all_records
+ return [self.key2entry(key)
+ for key in records.keys()
+ if key not in specified]
+
+ def BundleUpdated(self, states):
+ pass
+
+
+class SELinuxBooleanHandler(SELinuxEntryHandler):
+ etype = "boolean"
+ value_format = ("value",)
+
+ @property
+ def all_records(self):
+ # older versions of selinux return a single 0/1 value for each
+ # bool, while newer versions return a list of three 0/1 values
+ # representing various states. we don't care about the latter
+ # two values, but it's easier to coerce the older format into
+ # the newer format as far as interoperation with the rest of
+ # SELinuxEntryHandler goes
+ rv = SELinuxEntryHandler.all_records.fget(self)
+ if rv.values()[0] in [0, 1]:
+ for key, val in rv.items():
+ rv[key] = [val, val, val]
+ return rv
+
+ def _key2attrs(self, key):
+ rv = SELinuxEntryHandler._key2attrs(self, key)
+ status = self.all_records[key][0]
+ if status:
+ rv['value'] = "on"
+ else:
+ rv['value'] = "off"
+ return rv
+
+ def _defaultargs(self, entry):
+ # the only values recognized by both new and old versions of
+ # selinux are the strings "0" and "1". old selinux accepts
+ # ints or bools as well, new selinux accepts "on"/"off"
+ if entry.get("value").lower() == "on":
+ value = "1"
+ else:
+ value = "0"
+ return (entry.get("name"), value)
+
+ def canInstall(self, entry):
+ if entry.get("value").lower() not in ["on", "off"]:
+ self.logger.debug("SELinux %s %s has a bad value: %s" %
+ (self.etype, self.tostring(entry),
+ entry.get("value")))
+ return False
+ return (self.exists(entry) and
+ SELinuxEntryHandler.canInstall(self, entry))
+
+
+class SELinuxPortHandler(SELinuxEntryHandler):
+ etype = "port"
+ value_format = ('selinuxtype', None)
+ custom_re = re.compile(r'-p (?P<proto>tcp|udp).*? (?P<start>\d+)(?:-(?P<end>\d+))?$')
+
+ @property
+ def custom_keys(self):
+ keys = []
+ for cmd in self.records.customized():
+ match = self.custom_re.search(cmd)
+ if match:
+ if match.group('end'):
+ keys.append((int(match.group('start')),
+ int(match.group('end')),
+ match.group('proto')))
+ else:
+ keys.append((int(match.group('start')),
+ int(match.group('start')),
+ match.group('proto')))
+ return keys
+
+ @property
+ def all_records(self):
+ if self._all is None:
+ # older versions of selinux use (startport, endport) as
+ # they key for the ports.get_all() dict, and (type, proto,
+ # level) as the value; this is obviously broken, so newer
+ # versions use (startport, endport, proto) as the key, and
+ # (type, level) as the value. abstracting around this
+ # sucks.
+ ports = self.records.get_all()
+ if len(ports.keys()[0]) == 3:
+ self._all = ports
+ else:
+ # uglist list comprehension ever?
+ self._all = dict([((k[0], k[1], v[1]), (v[0], v[2]))
+ for k, v in ports.items()])
+ return self._all
+
+ def _key(self, entry):
+ try:
+ (port, proto) = entry.get("name").split("/")
+ except ValueError:
+ self.logger.error("Invalid SELinux node %s: no protocol specified" %
+ entry.get("name"))
+ return
+ if "-" in port:
+ start, end = port.split("-")
+ else:
+ start = port
+ end = port
+ return (int(start), int(end), proto)
+
+ def _key2attrs(self, key):
+ if key[0] == key[1]:
+ port = str(key[0])
+ else:
+ port = "%s-%s" % (key[0], key[1])
+ vals = self.all_records[key]
+ return dict(name="%s/%s" % (port, key[2]), selinuxtype=vals[0])
+
+ def _defaultargs(self, entry):
+ (port, proto) = entry.get("name").split("/")
+ return (port, proto, '', entry.get("selinuxtype"))
+
+ def _deleteargs(self, entry):
+ return tuple(entry.get("name").split("/"))
+
+
+class SELinuxFcontextHandler(SELinuxEntryHandler):
+ etype = "fcontext"
+ key_format = ("name", "filetype")
+ value_format = (None, None, "selinuxtype", None)
+ filetypeargs = dict(all="",
+ regular="--",
+ directory="-d",
+ symlink="-l",
+ pipe="-p",
+ socket="-s",
+ block="-b",
+ char="-c",
+ door="-D")
+ filetypenames = dict(all="all files",
+ regular="regular file",
+ directory="directory",
+ symlink="symbolic link",
+ pipe="named pipe",
+ socket="socket",
+ block="block device",
+ char="character device",
+ door="door")
+ filetypeattrs = dict([v, k] for k, v in filetypenames.iteritems())
+ custom_re = re.compile(r'-f \'(?P<filetype>[a-z ]+)\'.*? \'(?P<name>.*)\'')
+
+ @property
+ def all_records(self):
+ if self._all is None:
+ # on older selinux, fcontextRecords.get_all() returns a
+ # list of tuples of (filespec, filetype, seuser, serole,
+ # setype, level); on newer selinux, get_all() returns a
+ # dict of (filespec, filetype) => (seuser, serole, setype,
+ # level).
+ fcontexts = self.records.get_all()
+ if isinstance(fcontexts, dict):
+ self._all = fcontexts
+ else:
+ self._all = dict([(f[0:2], f[2:]) for f in fcontexts])
+ return self._all
+
+ def _key(self, entry):
+ ftype = entry.get("filetype", "all")
+ return (entry.get("name"),
+ self.filetypenames.get(ftype, ftype))
+
+ def _key2attrs(self, key):
+ rv = dict(name=key[0], filetype=self.filetypeattrs[key[1]])
+ vals = self.all_records[key]
+ # in older versions of selinux, an fcontext with no selinux
+ # type is the single value None; in newer versions, it's a
+ # tuple whose 0th (and only) value is None.
+ if vals and vals[0]:
+ rv["selinuxtype"] = vals[2]
+ else:
+ rv["selinuxtype"] = "<<none>>"
+ return rv
+
+ def canInstall(self, entry):
+ return (entry.get("filetype", "all") in self.filetypeargs and
+ SELinuxEntryHandler.canInstall(self, entry))
+
+ def _defaultargs(self, entry):
+ return (entry.get("name"), entry.get("selinuxtype"),
+ self.filetypeargs[entry.get("filetype", "all")],
+ '', '')
+
+ def primarykey(self, entry):
+ return ":".join([entry.tag, entry.get("type"), entry.get("name"),
+ entry.get("filetype", "all")])
+
+
+class SELinuxNodeHandler(SELinuxEntryHandler):
+ etype = "node"
+ value_format = (None, None, "selinuxtype", None)
+ str_format = '%(name)s (%(proto)s)'
+ custom_re = re.compile(r'-M (?P<netmask>\S+).*?-p (?P<proto>ipv\d).*? (?P<addr>\S+)$')
+ custom_format = ('addr', 'netmask', 'proto')
+
+ def _key(self, entry):
+ try:
+ (addr, netmask) = entry.get("name").split("/")
+ except ValueError:
+ self.logger.error("Invalid SELinux node %s: no netmask specified" %
+ entry.get("name"))
+ return
+ netmask = netmask_itoa(netmask, proto=entry.get("proto"))
+ return (addr, netmask, entry.get("proto"))
+
+ def _key2attrs(self, key):
+ vals = self.all_records[key]
+ return dict(name="%s/%s" % (key[0], key[1]), proto=key[2],
+ selinuxtype=vals[2])
+
+ def _defaultargs(self, entry):
+ (addr, netmask) = entry.get("name").split("/")
+ return (addr, netmask, entry.get("proto"), "", entry.get("selinuxtype"))
+
+
+class SELinuxLoginHandler(SELinuxEntryHandler):
+ etype = "login"
+ value_format = ("selinuxuser", None)
+
+ def _defaultargs(self, entry):
+ return (entry.get("name"), entry.get("selinuxuser"), "")
+
+
+class SELinuxUserHandler(SELinuxEntryHandler):
+ etype = "user"
+ value_format = ("prefix", None, None, "roles")
+
+ def __init__(self, tool, logger, setup, config):
+ SELinuxEntryHandler.__init__(self, tool, logger, setup, config)
+ self.needs_prefix = False
+
+ @property
+ def records(self):
+ if self._records is None:
+ self._records = seobject.seluserRecords()
+ return self._records
+
+ def Install(self, entry):
+ # in older versions of selinux, modify() is broken if you
+ # provide a prefix _at all_, so we try to avoid giving the
+ # prefix. however, in newer versions, prefix is _required_,
+ # so we a) try without a prefix; b) catch TypeError, which
+ # indicates that we had the wrong number of args (ValueError
+ # is thrown by the bug in older versions of selinux); and c)
+ # try with prefix.
+ try:
+ SELinuxEntryHandler.Install(self, entry)
+ except TypeError:
+ self.needs_prefix = True
+ SELinuxEntryHandler.Install(self, entry)
+
+ def _defaultargs(self, entry):
+ # in older versions of selinux, modify() is broken if you
+ # provide a prefix _at all_, so we try to avoid giving the
+ # prefix. see the comment in Install() above for more
+ # details.
+ rv = [entry.get("name"),
+ entry.get("roles", "").replace(" ", ",").split(",")]
+ if self.needs_prefix:
+ rv.extend(['', '', entry.get("prefix")])
+ else:
+ key = self._key(entry)
+ if key in self.all_records:
+ attrs = self._key2attrs(key)
+ if attrs['prefix'] != entry.get("prefix"):
+ rv.extend(['', '', entry.get("prefix")])
+ return tuple(rv)
+
+
+class SELinuxInterfaceHandler(SELinuxEntryHandler):
+ etype = "interface"
+ value_format = (None, None, "selinuxtype", None)
+
+ def _defaultargs(self, entry):
+ return (entry.get("name"), '', entry.get("selinuxtype"))
+
+
+class SELinuxPermissiveHandler(SELinuxEntryHandler):
+ etype = "permissive"
+
+ @property
+ def records(self):
+ try:
+ return SELinuxEntryHandler.records.fget(self)
+ except AttributeError:
+ self.logger.info("Permissive domains not supported by this version "
+ "of SELinux")
+ self._records = False
+ return self._records
+
+ @property
+ def all_records(self):
+ if self._all is None:
+ if self.records == False:
+ self._all = dict()
+ else:
+ # permissiveRecords.get_all() returns a list, so we just
+ # make it into a dict so that the rest of
+ # SELinuxEntryHandler works
+ self._all = dict([(d, d) for d in self.records.get_all()])
+ return self._all
+
+ def _defaultargs(self, entry):
+ return (entry.get("name"),)
+
+
+class SELinuxModuleHandler(SELinuxEntryHandler):
+ etype = "module"
+ value_format = (None, "disabled")
+
+ def __init__(self, tool, logger, setup, config):
+ SELinuxEntryHandler.__init__(self, tool, logger, setup, config)
+ self.posixtool = Bcfg2.Client.Tools.POSIX.POSIX(logger, setup, config)
+ try:
+ self.setype = selinux.selinux_getpolicytype()[1]
+ except IndexError:
+ self.logger.error("Unable to determine SELinux policy type")
+ self.setype = None
+
+ @property
+ def all_records(self):
+ if self._all is None:
+ # we get a list of tuples back; coerce it into a dict
+ self._all = dict([(m[0], (m[1], m[2]))
+ for m in self.records.get_all()])
+ return self._all
+
+ def _key2attrs(self, key):
+ rv = SELinuxEntryHandler._key2attrs(self, key)
+ status = self.all_records[key][1]
+ if status:
+ rv['disabled'] = "false"
+ else:
+ rv['disabled'] = "true"
+ return rv
+
+ def _filepath(self, entry):
+ return os.path.join("/usr/share/selinux", self.setype,
+ "%s.pp" % entry.get("name"))
+
+ def _pathentry(self, entry):
+ pathentry = copy.deepcopy(entry)
+ pathentry.set("name", self._filepath(pathentry))
+ pathentry.set("perms", "0644")
+ pathentry.set("owner", "root")
+ pathentry.set("group", "root")
+ pathentry.set("secontext", "__default__")
+ return pathentry
+
+ def Verify(self, entry):
+ if not entry.get("disabled"):
+ entry.set("disabled", "false")
+ return (SELinuxEntryHandler.Verify(self, entry) and
+ self.posixtool.Verifyfile(self._pathentry(entry), None))
+
+ def canInstall(self, entry):
+ return (entry.text and self.setype and
+ SELinuxEntryHandler.canInstall(self, entry))
+
+ def Install(self, entry):
+ rv = self.posixtool.Installfile(self._pathentry(entry))
+ try:
+ rv = rv and SELinuxEntryHandler.Install(self, entry)
+ except NameError:
+ # some versions of selinux have a bug in seobject that
+ # makes modify() calls fail. add() seems to have the same
+ # effect as modify, but without the bug
+ if self.exists(entry):
+ rv = rv and SELinuxEntryHandler.Install(self, entry,
+ method="add")
+
+ if entry.get("disabled", "false").lower() == "true":
+ method = "disable"
+ else:
+ method = "enable"
+ return rv and SELinuxEntryHandler.Install(self, entry, method=method)
+
+ def _addargs(self, entry):
+ return (self._filepath(entry),)
+
+ def _defaultargs(self, entry):
+ return (entry.get("name"),)
+
+ def FindExtra(self):
+ specified = [self._key(e)
+ for e in self.tool.getSupportedEntries()
+ if e.get("type") == self.etype]
+ return [self.key2entry(os.path.basename(f)[:-3])
+ for f in glob.glob(os.path.join("/usr/share/selinux",
+ self.setype, "*.pp"))
+ if f not in specified]
diff --git a/src/lib/Bcfg2/Client/Tools/SMF.py b/src/lib/Bcfg2/Client/Tools/SMF.py
index f824410ad..3e0a9da13 100644
--- a/src/lib/Bcfg2/Client/Tools/SMF.py
+++ b/src/lib/Bcfg2/Client/Tools/SMF.py
@@ -73,11 +73,6 @@ class SMF(Bcfg2.Client.Tools.SvcTool):
def InstallService(self, entry):
"""Install SMF Service entry."""
- # don't take any actions for mode='manual'
- if entry.get('mode', 'default') == 'manual':
- self.logger.info("Service %s mode set to manual. Skipping "
- "installation." % (entry.get('name')))
- return False
self.logger.info("Installing Service %s" % (entry.get('name')))
if entry.get('status') == 'off':
if entry.get("FMRI").startswith('lrc'):
diff --git a/src/lib/Bcfg2/Client/Tools/Systemd.py b/src/lib/Bcfg2/Client/Tools/Systemd.py
index e3f6a4169..a295bc608 100644
--- a/src/lib/Bcfg2/Client/Tools/Systemd.py
+++ b/src/lib/Bcfg2/Client/Tools/Systemd.py
@@ -42,18 +42,11 @@ class Systemd(Bcfg2.Client.Tools.SvcTool):
def InstallService(self, entry):
"""Install Service entry."""
- # don't take any actions for mode = 'manual'
- if entry.get('mode', 'default') == 'manual':
- self.logger.info("Service %s mode set to manual. Skipping "
- "installation." % (entry.get('name')))
- return True
-
if entry.get('status') == 'on':
- pstatus = self.cmd.run(self.get_svc_command(entry, 'enable'))[0]
- pstatus = self.cmd.run(self.get_svc_command(entry, 'start'))[0]
-
+ rv = self.cmd.run(self.get_svc_command(entry, 'enable'))[0] == 0
+ rv &= self.cmd.run(self.get_svc_command(entry, 'start'))[0] == 0
else:
- pstatus = self.cmd.run(self.get_svc_command(entry, 'stop'))[0]
- pstatus = self.cmd.run(self.get_svc_command(entry, 'disable'))[0]
+ rv = self.cmd.run(self.get_svc_command(entry, 'stop'))[0] == 0
+ rv &= self.cmd.run(self.get_svc_command(entry, 'disable'))[0] == 0
- return not pstatus
+ return rv
diff --git a/src/lib/Bcfg2/Client/Tools/Upstart.py b/src/lib/Bcfg2/Client/Tools/Upstart.py
index 7afc8edd7..aa5a921a6 100644
--- a/src/lib/Bcfg2/Client/Tools/Upstart.py
+++ b/src/lib/Bcfg2/Client/Tools/Upstart.py
@@ -69,11 +69,6 @@ class Upstart(Bcfg2.Client.Tools.SvcTool):
def InstallService(self, entry):
"""Install Service for entry."""
- # don't take any actions for mode='manual'
- if entry.get('mode', 'default') == 'manual':
- self.logger.info("Service %s mode set to manual. Skipping "
- "installation." % (entry.get('name')))
- return False
if entry.get('status') == 'on':
pstatus = self.cmd.run(self.get_svc_command(entry, 'start'))[0]
elif entry.get('status') == 'off':
diff --git a/src/lib/Bcfg2/Client/Tools/YUM24.py b/src/lib/Bcfg2/Client/Tools/YUM24.py
index 4e488b9da..2bc821db3 100644
--- a/src/lib/Bcfg2/Client/Tools/YUM24.py
+++ b/src/lib/Bcfg2/Client/Tools/YUM24.py
@@ -6,20 +6,6 @@ import sys
import yum
import Bcfg2.Client.XML
import Bcfg2.Client.Tools.RPMng
-# Compatibility import
-from Bcfg2.Bcfg2Py3k import ConfigParser
-
-YAD = True
-CP = ConfigParser.ConfigParser()
-try:
- if '-C' in sys.argv:
- CP.read([sys.argv[sys.argv.index('-C') + 1]])
- else:
- CP.read(['/etc/bcfg2.conf'])
- if CP.get('YUMng', 'autodep').lower() == 'false':
- YAD = False
-except:
- pass
if not hasattr(Bcfg2.Client.Tools.RPMng, 'RPMng'):
raise ImportError
@@ -79,6 +65,7 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng):
(entry.get('name').startswith('/etc/yum.d') \
or entry.get('name').startswith('/etc/yum.repos.d')) \
or entry.get('name') == '/etc/yum.conf']
+ self.autodep = setup.get("yum24_autodep")
self.yum_avail = dict()
self.yum_installed = dict()
self.yb = yum.YumBase()
@@ -273,7 +260,7 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng):
if len(install_pkgs) > 0:
self.logger.info("Attempting to install packages")
- if YAD:
+ if self.autodep:
pkgtool = "/usr/bin/yum -d0 -y install %s"
else:
pkgtool = "/usr/bin/yum -d0 install %s"
@@ -309,7 +296,7 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng):
if len(upgrade_pkgs) > 0:
self.logger.info("Attempting to upgrade packages")
- if YAD:
+ if self.autodep:
pkgtool = "/usr/bin/yum -d0 -y update %s"
else:
pkgtool = "/usr/bin/yum -d0 update %s"
@@ -359,7 +346,7 @@ class YUM24(Bcfg2.Client.Tools.RPMng.RPMng):
"""
self.logger.debug('Running YUMng.RemovePackages()')
- if YAD:
+ if self.autodep:
pkgtool = "/usr/bin/yum -d0 -y erase %s"
else:
pkgtool = "/usr/bin/yum -d0 erase %s"
diff --git a/src/lib/Bcfg2/Client/Tools/YUMng.py b/src/lib/Bcfg2/Client/Tools/YUMng.py
index 244b66cf4..34029b9fe 100644
--- a/src/lib/Bcfg2/Client/Tools/YUMng.py
+++ b/src/lib/Bcfg2/Client/Tools/YUMng.py
@@ -12,9 +12,6 @@ import yum.misc
import rpmUtils.arch
import Bcfg2.Client.XML
import Bcfg2.Client.Tools
-# Compatibility import
-from Bcfg2.Bcfg2Py3k import ConfigParser
-
def build_yname(pkgname, inst):
"""Build yum appropriate package name."""
@@ -58,20 +55,6 @@ def nevraString(p):
return ret
-class Parser(ConfigParser.ConfigParser):
-
- def get(self, section, option, default):
- """
- Override ConfigParser.get: If the request option is not in the
- config file then return the value of default rather than raise
- an exception. We still raise exceptions on missing sections.
- """
- try:
- return ConfigParser.ConfigParser.get(self, section, option)
- except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
- return default
-
-
class RPMDisplay(yum.rpmtrans.RPMBaseCallback):
"""We subclass the default RPM transaction callback so that we
can control Yum's verbosity and pipe it through the right logger."""
@@ -224,38 +207,24 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
def _loadConfig(self):
# Process the YUMng section from the config file.
- CP = Parser()
- CP.read(self.setup.get('setup'))
- truth = ['true', 'yes', '1']
-
# These are all boolean flags, either we do stuff or we don't
- self.pkg_checks = CP.get(self.name, "pkg_checks", "true").lower() \
- in truth
- self.pkg_verify = CP.get(self.name, "pkg_verify", "true").lower() \
- in truth
- self.doInstall = CP.get(self.name, "installed_action",
- "install").lower() == "install"
- self.doUpgrade = CP.get(self.name,
- "version_fail_action", "upgrade").lower() == "upgrade"
- self.doReinst = CP.get(self.name, "verify_fail_action",
- "reinstall").lower() == "reinstall"
- self.verifyFlags = CP.get(self.name, "verify_flags",
- "").lower().replace(' ', ',')
+ self.pkg_checks = self.setup["yumng_pkg_checks"]
+ self.pkg_verify = self.setup["yumng_pkg_verify"]
+ self.doInstall = self.setup["yumng_installed_action"] == "install"
+ self.doUpgrade = self.setup["yumng_version_fail_action"] == "upgrade"
+ self.doReinst = self.setup["yumng_verify_fail_action"] == "reinstall"
+ self.verifyFlags = self.setup["yumng_verify_flags"]
self.installOnlyPkgs = self.yb.conf.installonlypkgs
if 'gpg-pubkey' not in self.installOnlyPkgs:
self.installOnlyPkgs.append('gpg-pubkey')
- self.logger.debug("YUMng: Install missing: %s" \
- % self.doInstall)
+ self.logger.debug("YUMng: Install missing: %s" % self.doInstall)
self.logger.debug("YUMng: pkg_checks: %s" % self.pkg_checks)
self.logger.debug("YUMng: pkg_verify: %s" % self.pkg_verify)
- self.logger.debug("YUMng: Upgrade on version fail: %s" \
- % self.doUpgrade)
- self.logger.debug("YUMng: Reinstall on verify fail: %s" \
- % self.doReinst)
- self.logger.debug("YUMng: installOnlyPkgs: %s" \
- % str(self.installOnlyPkgs))
+ self.logger.debug("YUMng: Upgrade on version fail: %s" % self.doUpgrade)
+ self.logger.debug("YUMng: Reinstall on verify fail: %s" % self.doReinst)
+ self.logger.debug("YUMng: installOnlyPkgs: %s" % self.installOnlyPkgs)
self.logger.debug("YUMng: verify_flags: %s" % self.verifyFlags)
def _fixAutoVersion(self, entry):
@@ -425,8 +394,8 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
if entry.get('version', False) == 'auto':
self._fixAutoVersion(entry)
- self.logger.debug("Verifying package instances for %s" \
- % entry.get('name'))
+ self.logger.debug("Verifying package instances for %s" %
+ entry.get('name'))
self.verifyCache = {} # Used for checking multilib packages
self.modlists[entry] = modlist
@@ -450,8 +419,8 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
POs = self.yb.rpmdb.searchProvides(entry.get('name'))
if len(POs) > 0:
virtPkg = True
- self.logger.info("%s appears to be provided by:" \
- % entry.get('name'))
+ self.logger.info("%s appears to be provided by:" %
+ entry.get('name'))
for p in POs:
self.logger.info(" %s" % p)
@@ -473,8 +442,13 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
stat['verify_fail'] = False
stat['pkg'] = entry
stat['modlist'] = modlist
- verify_flags = inst.get('verify_flags', self.verifyFlags)
- verify_flags = verify_flags.lower().replace(' ', ',').split(',')
+ if inst.get('verify_flags'):
+ # this splits on either space or comma
+ verify_flags = \
+ inst.get('verify_flags').lower().replace(' ',
+ ',').split(',')
+ else:
+ verify_flags = self.verifyFlags
if 'arch' in nevra:
# If arch is specified use it to select the package
@@ -483,6 +457,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
_POs = POs
if len(_POs) == 0:
# Package (name, arch) not installed
+ entry.set('current_exists', 'false')
self.logger.debug(" %s is not installed" % nevraString(nevra))
stat['installed'] = False
package_fail = True
@@ -494,8 +469,23 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
# Check EVR
if virtPkg:
- self.logger.debug(" Not checking version for virtual package")
- _POs = [po for po in POs] # Make a copy
+ # we need to make sure that the version of the symbol
+ # provided matches the one required in the
+ # configuration
+ vlist = []
+ for attr in ["epoch", "version", "release"]:
+ vlist.append(nevra.get(attr))
+ if tuple(vlist) == (None, None, None):
+ # we just require the package name, no particular
+ # version, so just make a copy of POs since every
+ # package that provides this symbol satisfies the
+ # requirement
+ _POs = [po for po in POs]
+ else:
+ _POs = [po for po in POs
+ if po.checkPrco('provides',
+ (nevra["name"], 'EQ',
+ tuple(vlist)))]
elif entry.get('name') == 'gpg-pubkey':
if 'version' not in nevra:
m = "Skipping verify: gpg-pubkey without an RPM version."
@@ -513,10 +503,33 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
package_fail = True
stat['version_fail'] = True
# Just chose the first pkg for the error message
- self.logger.info(" %s: Wrong version installed. "
- "Want %s, but have %s" % (entry.get("name"),
- nevraString(nevra),
- nevraString(POs[0])))
+ if virtPkg:
+ provTuple = \
+ [p for p in POs[0].provides
+ if p[0] == entry.get("name")][0]
+ entry.set('current_version', "%s:%s-%s" % provTuple[2])
+ self.logger.info(" %s: Wrong version installed. "
+ "Want %s, but %s provides %s" %
+ (entry.get("name"),
+ nevraString(nevra),
+ nevraString(POs[0]),
+ yum.misc.prco_tuple_to_string(provTuple)))
+ else:
+ entry.set('current_version', "%s:%s-%s.%s" %
+ (POs[0].epoch,
+ POs[0].version,
+ POs[0].release,
+ POs[0].arch))
+ self.logger.info(" %s: Wrong version installed. "
+ "Want %s, but have %s" %
+ (entry.get("name"),
+ nevraString(nevra),
+ nevraString(POs[0])))
+ entry.set('version', "%s:%s-%s.%s" %
+ (nevra.get('epoch', 'any'),
+ nevra.get('version', 'any'),
+ nevra.get('release', 'any'),
+ nevra.get('arch', 'any')))
qtext_versions.append("U(%s)" % str(POs[0]))
continue
@@ -547,7 +560,7 @@ class YUMng(Bcfg2.Client.Tools.PkgTool):
package_fail = True
continue
- # Now take out the Yum specific objects / modlists / unproblmes
+ # Now take out the Yum specific objects / modlists / unproblems
ignores = [ig.get('name') for ig in entry.findall('Ignore')] + \
[ig.get('name') for ig in inst.findall('Ignore')] + \
self.ignores
diff --git a/src/lib/Bcfg2/Client/Tools/__init__.py b/src/lib/Bcfg2/Client/Tools/__init__.py
index c6cb6e239..026c7ade0 100644
--- a/src/lib/Bcfg2/Client/Tools/__init__.py
+++ b/src/lib/Bcfg2/Client/Tools/__init__.py
@@ -1,16 +1,27 @@
"""This contains all Bcfg2 Tool modules"""
import os
-import stat
import sys
-from subprocess import Popen, PIPE
+import stat
import time
+import pkgutil
+from subprocess import Popen, PIPE
import Bcfg2.Client.XML
-
-__all__ = [tool.split('.')[0] \
- for tool in os.listdir(os.path.dirname(__file__)) \
- if tool.endswith(".py") and tool != "__init__.py"]
-
+from Bcfg2.Bcfg2Py3k import input
+
+if hasattr(pkgutil, 'walk_packages'):
+ submodules = pkgutil.walk_packages(path=__path__)
+else:
+ # python 2.4
+ import glob
+ submodules = []
+ for path in __path__:
+ for submodule in glob.glob(os.path.join(path, "*.py")):
+ mod = os.path.splitext(os.path.basename(submodule))[0]
+ if mod not in ['__init__']:
+ submodules.append((None, mod, True))
+
+__all__ = [m[1] for m in submodules]
drivers = [item for item in __all__ if item not in ['rpmtools']]
default = [item for item in drivers if item not in ['RPM', 'Yum']]
@@ -36,7 +47,7 @@ class executor:
return (p.returncode, output.splitlines())
-class Tool:
+class Tool(object):
"""
All tools subclass this. It defines all interfaces that need to be defined.
"""
@@ -47,10 +58,6 @@ class Tool:
__important__ = []
def __init__(self, logger, setup, config):
- self.__important__ = [entry.get('name') \
- for struct in config for entry in struct \
- if entry.tag == 'Path' and \
- entry.get('important') in ['true', 'True']]
self.setup = setup
self.logger = logger
if not hasattr(self, '__ireq__'):
@@ -59,8 +66,15 @@ class Tool:
self.cmd = executor(logger)
self.modified = []
self.extra = []
- self.handled = [entry for struct in self.config for entry in struct \
- if self.handlesEntry(entry)]
+ self.__important__ = []
+ self.handled = []
+ for struct in config:
+ for entry in struct:
+ if (entry.tag == 'Path' and
+ entry.get('important', 'false').lower() == 'true'):
+ self.__important__.append(entry.get('name'))
+ if self.handlesEntry(entry):
+ self.handled.append(entry)
for filename in self.__execs__:
try:
mode = stat.S_IMODE(os.stat(filename)[stat.ST_MODE])
@@ -130,12 +144,24 @@ class Tool:
'''Build a list of potentially modified POSIX paths for this entry'''
return [entry.get('name') for struct in self.config.getchildren() \
for entry in struct.getchildren() \
- if entry.tag in ['Ignore', 'Path']]
+ if entry.tag == 'Path']
def gatherCurrentData(self, entry):
"""Default implementation of the information gathering routines."""
pass
+ def missing_attrs(self, entry):
+ required = self.__req__[entry.tag]
+ if isinstance(required, dict):
+ required = ["type"]
+ try:
+ required.extend(self.__req__[entry.tag][entry.get("type")])
+ except KeyError:
+ pass
+
+ return [attr for attr in required
+ if attr not in entry.attrib or not entry.attrib[attr]]
+
def canVerify(self, entry):
"""Test if entry has enough information to be verified."""
if not self.handlesEntry(entry):
@@ -148,13 +174,12 @@ class Tool:
entry.get('failure')))
return False
- missing = [attr for attr in self.__req__[entry.tag] \
- if attr not in entry.attrib]
+ missing = self.missing_attrs(entry)
if missing:
- self.logger.error("Incomplete information for entry %s:%s; cannot verify" \
- % (entry.tag, entry.get('name')))
- self.logger.error("\t... due to absence of %s attribute(s)" % \
- (":".join(missing)))
+ self.logger.error("Cannot verify entry %s:%s due to missing "
+ "required attribute(s): %s" %
+ (entry.tag, entry.get('name'),
+ ", ".join(missing)))
try:
self.gatherCurrentData(entry)
except:
@@ -167,6 +192,11 @@ class Tool:
"""Return a list of extra entries."""
return []
+ def primarykey(self, entry):
+ """ return a string that should be unique amongst all entries
+ in the specification """
+ return "%s:%s" % (entry.tag, entry.get("name"))
+
def canInstall(self, entry):
"""Test if entry has enough information to be installed."""
if not self.handlesEntry(entry):
@@ -177,13 +207,12 @@ class Tool:
(entry.tag, entry.get('name')))
return False
- missing = [attr for attr in self.__ireq__[entry.tag] \
- if attr not in entry.attrib or not entry.attrib[attr]]
+ missing = self.missing_attrs(entry)
if missing:
- self.logger.error("Incomplete information for entry %s:%s; cannot install" \
- % (entry.tag, entry.get('name')))
- self.logger.error("\t... due to absence of %s attribute" % \
- (":".join(missing)))
+ self.logger.error("Incomplete information for entry %s:%s; cannot "
+ "install due to absence of attribute(s): %s" %
+ (entry.tag, entry.get('name'),
+ ", ".join(missing)))
return False
return True
@@ -305,8 +334,7 @@ class SvcTool(Tool):
return self.cmd.run(self.get_svc_command(service, restart_target))[0]
def check_service(self, service):
- # not supported for this driver
- return 0
+ return self.cmd.run(self.get_svc_command(service, 'status'))[0] == 0
def Remove(self, services):
""" Dummy implementation of service removal method """
@@ -321,13 +349,12 @@ class SvcTool(Tool):
return
for entry in [ent for ent in bundle if self.handlesEntry(ent)]:
- mode = entry.get('mode', 'default')
- if (mode == 'manual' or
- (mode == 'interactive_only' and
+ restart = entry.get("restart", "true")
+ if (restart.lower() == "false" or
+ (restart.lower == "interactive" and
not self.setup['interactive'])):
continue
- # need to handle servicemode = (build|default)
- # need to handle mode = (default|supervised)
+
rc = None
if entry.get('status') == 'on':
if self.setup['servicemode'] == 'build':
@@ -336,11 +363,7 @@ class SvcTool(Tool):
if self.setup['interactive']:
prompt = ('Restart service %s?: (y/N): ' %
entry.get('name'))
- # py3k compatibility
- try:
- ans = raw_input(prompt)
- except NameError:
- ans = input(prompt)
+ ans = input(prompt)
if ans not in ['y', 'Y']:
continue
rc = self.restart_service(entry)
@@ -351,3 +374,19 @@ class SvcTool(Tool):
if rc:
self.logger.error("Failed to manipulate service %s" %
(entry.get('name')))
+
+ def Install(self, entries, states):
+ """Install all entries in sublist."""
+ for entry in entries:
+ if entry.get('install', 'true').lower() == 'false':
+ self.logger.info("Service %s installation is false. Skipping "
+ "installation." % (entry.get('name')))
+ continue
+ try:
+ func = getattr(self, "Install%s" % (entry.tag))
+ states[entry] = func(entry)
+ if states[entry]:
+ self.modified.append(entry)
+ except:
+ self.logger.error("Unexpected failure of install method for entry type %s"
+ % (entry.tag), exc_info=1)
diff --git a/src/lib/Bcfg2/Client/Tools/launchd.py b/src/lib/Bcfg2/Client/Tools/launchd.py
index c022d32ae..6f08559a2 100644
--- a/src/lib/Bcfg2/Client/Tools/launchd.py
+++ b/src/lib/Bcfg2/Client/Tools/launchd.py
@@ -88,11 +88,6 @@ class launchd(Bcfg2.Client.Tools.Tool):
def InstallService(self, entry):
"""Enable or disable launchd item."""
- # don't take any actions for mode='manual'
- if entry.get('mode', 'default') == 'manual':
- self.logger.info("Service %s mode set to manual. Skipping "
- "installation." % (entry.get('name')))
- return False
name = entry.get('name')
if entry.get('status') == 'on':
self.logger.error("Installing service %s" % name)
diff --git a/src/lib/Bcfg2/Client/Tools/rpmtools.py b/src/lib/Bcfg2/Client/Tools/rpmtools.py
index 7441b2c06..32a04262d 100755
--- a/src/lib/Bcfg2/Client/Tools/rpmtools.py
+++ b/src/lib/Bcfg2/Client/Tools/rpmtools.py
@@ -43,7 +43,6 @@ try:
isprelink_imported = True
except ImportError:
isprelink_imported = False
- #print '*********************** isprelink not loaded ***********************'
# If the prelink command is installed on the system then we need to do
# prelink -y on files.
@@ -333,7 +332,6 @@ def prelink_size_check(filename):
fsize += len(data)
elif whitelist_re.search(filename) and not blacklist_re.search(filename):
- # print "***** Warning isprelink extension failed to import ******"
plf.close()
cmd = '/usr/sbin/prelink -y %s 2> /dev/null' \
% (re.escape(filename))
@@ -601,7 +599,6 @@ def rpm_verify_package(vp_ts, header, verify_options):
omitmask |= VERIFY_RDEV
omitmask = ((~omitmask & VERIFY_ATTRS) ^ VERIFY_ATTRS)
- #print 'omitmask =', omitmask
package_results = {}
@@ -754,58 +751,41 @@ class Rpmtscallback(object):
"""
if reason == rpm.RPMCALLBACK_INST_OPEN_FILE:
pass
- #print 'rpm.RPMCALLBACK_INST_OPEN_FILE'
elif reason == rpm.RPMCALLBACK_INST_CLOSE_FILE:
pass
- #print 'rpm.RPMCALLBACK_INST_CLOSE_FILE'
elif reason == rpm.RPMCALLBACK_INST_START:
pass
- #print 'rpm.RPMCALLBACK_INST_START'
elif reason == rpm.RPMCALLBACK_TRANS_PROGRESS or \
reason == rpm.RPMCALLBACK_INST_PROGRESS:
pass
- #print 'rpm.RPMCALLBACK_TRANS_PROGRESS or \
# rpm.RPMCALLBACK_INST_PROGRESS'
elif reason == rpm.RPMCALLBACK_TRANS_START:
pass
- #print 'rpm.RPMCALLBACK_TRANS_START'
elif reason == rpm.RPMCALLBACK_TRANS_STOP:
pass
- #print 'rpm.RPMCALLBACK_TRANS_STOP'
elif reason == rpm.RPMCALLBACK_REPACKAGE_START:
pass
- #print 'rpm.RPMCALLBACK_REPACKAGE_START'
elif reason == rpm.RPMCALLBACK_REPACKAGE_PROGRESS:
pass
- #print 'rpm.RPMCALLBACK_REPACKAGE_PROGRESS'
elif reason == rpm.RPMCALLBACK_REPACKAGE_STOP:
pass
- #print 'rpm.RPMCALLBACK_REPACKAGE_STOP'
elif reason == rpm.RPMCALLBACK_UNINST_PROGRESS:
pass
- #print 'rpm.RPMCALLBACK_UNINST_PROGRESS'
elif reason == rpm.RPMCALLBACK_UNINST_START:
pass
- #print 'rpm.RPMCALLBACK_UNINST_START'
elif reason == rpm.RPMCALLBACK_UNINST_STOP:
pass
- #print 'rpm.RPMCALLBACK_UNINST_STOP'
- #print '***Package ', key, ' deleted ***'
# How do we get at this?
# RPM.modified += key
elif reason == rpm.RPMCALLBACK_UNPACK_ERROR:
pass
- #print 'rpm.RPMCALLBACK_UNPACK_ERROR'
elif reason == rpm.RPMCALLBACK_CPIO_ERROR:
pass
- #print 'rpm.RPMCALLBACK_CPIO_ERROR'
elif reason == rpm.RPMCALLBACK_UNKNOWN:
pass
- #print 'rpm.RPMCALLBACK_UNKNOWN'
else:
print('ERROR - Fell through callBack')
- #print reason, amount, total, key, client_data
def rpm_erase(erase_pkgspecs, erase_flags):
"""
@@ -836,7 +816,6 @@ def rpm_erase(erase_pkgspecs, erase_flags):
erase_ts.addErase(idx)
#for te in erase_ts:
- # print "%s %s:%s-%s.%s" % (te.N(), te.E(), te.V(), te.R(), te.A())
erase_problems = []
if 'nodeps' not in erase_flags:
@@ -847,8 +826,6 @@ def rpm_erase(erase_pkgspecs, erase_flags):
erase_callback = Rpmtscallback()
erase_ts.run(erase_callback.callback, 'Erase')
#else:
- # print 'ERROR - Dependency failures on package erase'
- # print erase_problems
erase_ts.closeDB()
del erase_ts