aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuido Günther <agx@sigxcpu.org>2014-08-05 14:43:14 +0200
committerGuido Günther <agx@sigxcpu.org>2014-08-05 14:43:14 +0200
commit95620b53bcdf68843cd210306bb7228bfee3239c (patch)
tree71e1578814515cdc67cfd9fff230d98d4b5fd99f
parenta0aa1dd751453a3557f16912cc5cfe1225eb3253 (diff)
parent20df31b862f3fbb274a4bb4952a84a53b73238a0 (diff)
Merge tag 'debian/0.0.8-2' into bpo/wheezy
whatmaps Debian release 0.0.8-2
-rw-r--r--debian/changelog18
-rw-r--r--debian/config21
-rw-r--r--debian/control1
-rw-r--r--debian/gbp.conf1
-rw-r--r--debian/patches/0001-Another-import-bugfix.patch27
-rw-r--r--debian/patches/0002-Abort-early-if-we-found-a-match.patch24
-rw-r--r--debian/patches/series2
-rw-r--r--debian/po/POTFILES.in1
-rw-r--r--debian/po/templates.pot42
-rw-r--r--debian/postinst47
-rw-r--r--debian/templates11
-rw-r--r--debian/tests/control3
-rwxr-xr-xdebian/tests/smoke3
-rw-r--r--tests/test_debiandistro.py9
-rw-r--r--tests/test_distro.py20
-rw-r--r--tests/test_systemd.py62
-rwxr-xr-xwhatmaps/command.py184
-rw-r--r--whatmaps/debiandistro.py22
-rw-r--r--whatmaps/distro.py66
-rw-r--r--whatmaps/redhatdistro.py1
-rw-r--r--whatmaps/systemd.py41
21 files changed, 508 insertions, 98 deletions
diff --git a/debian/changelog b/debian/changelog
index bc3533e..e8923f7 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,21 @@
+whatmaps (0.0.8-2) unstable; urgency=medium
+
+ * [cca6b44] Add debconf question to enable automatic service restarts
+
+ -- Guido Günther <agx@sigxcpu.org> Tue, 29 Jul 2014 13:10:50 +0200
+
+whatmaps (0.0.8-1) unstable; urgency=medium
+
+ * [ac817c1] Print userfriendly message if not root (Closes: #751088)
+ * [c6168df] Add upstream-branch to gbp.conf
+ * [e1b9e7f] pkg_services: Check if a service is actually installed.
+ * [f758314] Add systemd support
+ * [d6dba76] Add simple autopkgtest
+ * [e048b5f] Abort early if we found a match. This speeds up the output and
+ avoids duplicate printouts
+
+ -- Guido Günther <agx@sigxcpu.org> Mon, 14 Jul 2014 16:58:19 +0200
+
whatmaps (0.0.7-1~bpo70+1) wheezy-backports; urgency=medium
* Rebuild for wheezy-backports.
diff --git a/debian/config b/debian/config
new file mode 100644
index 0000000..7497f7d
--- /dev/null
+++ b/debian/config
@@ -0,0 +1,21 @@
+#!/bin/sh -e
+
+APT_CONF=/etc/apt/apt.conf.d/20services
+
+. /usr/share/debconf/confmodule
+db_version 2.0
+
+if [ -f $APT_CONF ]; then
+ CURRENT=$(sed -ne 's,^\s*Whatmaps::Enable-Restart\s\+"\(\S\+\)"\s*;,\1,p' $APT_CONF)
+ case $CURRENT in
+ 1|true)
+ CURRENT="true"
+ ;;
+ *) CURRENT="false"
+ ;;
+ esac
+ db_set whatmaps/enable_service_restarts $CURRENT
+fi
+
+db_input medium whatmaps/enable_service_restarts || true
+db_go
diff --git a/debian/control b/debian/control
index 516e234..4711391 100644
--- a/debian/control
+++ b/debian/control
@@ -11,6 +11,7 @@ Standards-Version: 3.9.5
Homepage: https://honk.sigxcpu.org/piki/projects/whatmaps
Vcs-Git: git://honk.sigxcpu.org/git/whatmaps.git/
X-Python-Version: >= 2.7
+XS-Testsuite: autopkgtest
Package: whatmaps
Architecture: all
diff --git a/debian/gbp.conf b/debian/gbp.conf
index 0d61683..9678e28 100644
--- a/debian/gbp.conf
+++ b/debian/gbp.conf
@@ -1,3 +1,4 @@
[DEFAULT]
debian-branch = debian/master
upstream-tag = v%(version)s
+upstream-branch = master
diff --git a/debian/patches/0001-Another-import-bugfix.patch b/debian/patches/0001-Another-import-bugfix.patch
new file mode 100644
index 0000000..9cbb73e
--- /dev/null
+++ b/debian/patches/0001-Another-import-bugfix.patch
@@ -0,0 +1,27 @@
+From: =?utf-8?q?Guido_G=C3=BCnther?= <agx@sigxcpu.org>
+Date: Fri, 11 Jul 2014 20:53:31 +0200
+Subject: Another import bugfix
+
+---
+ whatmaps/debiandistro.py | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/whatmaps/debiandistro.py b/whatmaps/debiandistro.py
+index 9294c9f..3a3bf16 100644
+--- a/whatmaps/debiandistro.py
++++ b/whatmaps/debiandistro.py
+@@ -31,12 +31,12 @@ import subprocess
+ import sys
+ import string
+
+-import whatmaps.distro
++from . distro import Distro
+ from . debianpkg import DebianPkg
+ from . pkg import PkgError
+ from . systemd import Systemd
+
+-class DebianDistro(whatmaps.distro.Distro):
++class DebianDistro(Distro):
+ "Debian (dpkg) based distribution"
+ id = 'Debian'
+
diff --git a/debian/patches/0002-Abort-early-if-we-found-a-match.patch b/debian/patches/0002-Abort-early-if-we-found-a-match.patch
new file mode 100644
index 0000000..3513ad6
--- /dev/null
+++ b/debian/patches/0002-Abort-early-if-we-found-a-match.patch
@@ -0,0 +1,24 @@
+From: =?utf-8?q?Guido_G=C3=BCnther?= <agx@sigxcpu.org>
+Date: Mon, 14 Jul 2014 16:12:43 +0200
+Subject: Abort early if we found a match
+
+Once we found a shared object that a process maps we don't need to
+continue with the other shared objects since one is enough to require
+a process restart.
+---
+ whatmaps/command.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/whatmaps/command.py b/whatmaps/command.py
+index 7690ef8..e649cbb 100755
+--- a/whatmaps/command.py
++++ b/whatmaps/command.py
+@@ -39,7 +39,7 @@ def check_maps(procs, shared_objects):
+ restart_procs[proc.exe] += [ proc ]
+ else:
+ restart_procs[proc.exe] = [ proc ]
+- continue
++ break
+ return restart_procs
+
+
diff --git a/debian/patches/series b/debian/patches/series
new file mode 100644
index 0000000..219effd
--- /dev/null
+++ b/debian/patches/series
@@ -0,0 +1,2 @@
+0001-Another-import-bugfix.patch
+0002-Abort-early-if-we-found-a-match.patch
diff --git a/debian/po/POTFILES.in b/debian/po/POTFILES.in
new file mode 100644
index 0000000..cef83a3
--- /dev/null
+++ b/debian/po/POTFILES.in
@@ -0,0 +1 @@
+[type: gettext/rfc822deb] templates
diff --git a/debian/po/templates.pot b/debian/po/templates.pot
new file mode 100644
index 0000000..3c0783d
--- /dev/null
+++ b/debian/po/templates.pot
@@ -0,0 +1,42 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: whatmaps\n"
+"Report-Msgid-Bugs-To: whatmaps@packages.debian.org\n"
+"POT-Creation-Date: 2014-07-15 13:22+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#. Type: boolean
+#. Description
+#: ../templates:1001
+msgid "Automatically restart services after shared library security updates?"
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:1001
+msgid ""
+"Services need to be restarted to benefit from updates of shared libraries "
+"they depend on. Without that they remain vulnerable to security bugs fixed "
+"in these updates."
+msgstr ""
+
+#. Type: boolean
+#. Description
+#: ../templates:1001
+msgid ""
+"Automatic service restarts are only done if apt fetched the library from a "
+"source providing security updates. This also affects packages installed via "
+"unattended-upgrades."
+msgstr ""
diff --git a/debian/postinst b/debian/postinst
new file mode 100644
index 0000000..9965480
--- /dev/null
+++ b/debian/postinst
@@ -0,0 +1,47 @@
+#!/bin/bash
+
+set -e
+
+APT_CONF=/etc/apt/apt.conf.d/20services
+
+. /usr/share/debconf/confmodule
+db_version 2.0
+
+case "$1" in
+ configure)
+ db_get whatmaps/enable_service_restarts || true
+ DBVAL="$RET"
+
+ # Configuration file doesn't exist but user wants updates enabled so
+ # recreate it:
+ if [ ! -f $APT_CONF ] && [ $DBVAL = "true" ]; then
+ cat << EOF > $APT_CONF
+// Set to 1 or true to enable service restarts on security updates
+Whatmaps::Enable-Restart "1";
+
+// What updates are considered security updates
+Whatmaps::Security-Update-Origins {
+ "\${distro_id} stable";
+ "\${distro_id} \${distro_codename}-security";
+};
+EOF
+ else
+ case $DBVAL in
+ "true") VAL=1;;
+ *) VAL=0;;
+ esac
+ sed -ie "s,^\(\s*Whatmaps::Enable-Restart\s\+\"\)\S\+\(\"\s*;\),\1${VAL}\2," $APT_CONF
+ fi
+ ;;
+ abort-upgrade|abort-remove|abort-deconfigure)
+ ;;
+
+ *)
+ echo "postinst called with unknown argument \`$1'" >&2
+ exit 0
+ ;;
+esac
+
+#DEBHELPER#
+
+exit 0
diff --git a/debian/templates b/debian/templates
new file mode 100644
index 0000000..83e32c1
--- /dev/null
+++ b/debian/templates
@@ -0,0 +1,11 @@
+Template: whatmaps/enable_service_restarts
+Type: boolean
+Default: false
+_Description: Automatically restart services after shared library security updates?
+ Services need to be restarted to benefit from updates of shared libraries they
+ depend on. Without that they remain vulnerable to security bugs fixed in these
+ updates.
+ .
+ Automatic service restarts are only done if apt fetched the library from a
+ source providing security updates. This also affects packages installed
+ via unattended-upgrades.
diff --git a/debian/tests/control b/debian/tests/control
new file mode 100644
index 0000000..2c75b73
--- /dev/null
+++ b/debian/tests/control
@@ -0,0 +1,3 @@
+Tests: smoke
+Restrictions: needs-root
+Depends: libvirt-bin, @
diff --git a/debian/tests/smoke b/debian/tests/smoke
new file mode 100755
index 0000000..dde8c37
--- /dev/null
+++ b/debian/tests/smoke
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+whatmaps libvirt0 | grep -qs ^libvirt-bin
diff --git a/tests/test_debiandistro.py b/tests/test_debiandistro.py
index c01efd8..617005c 100644
--- a/tests/test_debiandistro.py
+++ b/tests/test_debiandistro.py
@@ -23,6 +23,12 @@ try:
except ImportError:
have_apt_pkg=False
+try:
+ import lsb_release
+ have_lsb_release=True
+except ImportError:
+ have_lsb_release=False
+
from whatmaps.debiandistro import DebianDistro
from whatmaps.debianpkg import DebianPkg
@@ -90,11 +96,12 @@ class TestDebianDistro(unittest.TestCase):
@patch('apt_pkg.init')
@patch('apt_pkg.Acquire')
@unittest.skipUnless(have_apt_pkg, "apt_pkg not installed")
+ @unittest.skipUnless(have_lsb_release, "lsb_release not installed")
def test_filter_security_updates(self, apt_pkg_acquire, apt_pkg_init):
pkgs = {'pkg1': DebianPkg('pkg1'),
'pkg2': DebianPkg('pkg2'),
}
- with patch('apt_pkg.Cache') as mock:
+ with patch('apt_pkg.Cache'):
DebianDistro.filter_security_updates(pkgs)
apt_pkg_init.assert_called_once_with()
apt_pkg_acquire.assert_called_once_with()
diff --git a/tests/test_distro.py b/tests/test_distro.py
index e22da68..88fa984 100644
--- a/tests/test_distro.py
+++ b/tests/test_distro.py
@@ -16,9 +16,15 @@
import unittest
-from whatmaps.distro import Distro
+from mock import patch
-from . import context
+try:
+ import lsb_release
+ have_lsb_release=True
+except ImportError:
+ have_lsb_release=False
+
+from whatmaps.distro import Distro, detect
class Pkg(object):
name = 'doesnotmatter'
@@ -38,3 +44,13 @@ class TestDistro(unittest.TestCase):
self.assertEqual(Distro.pkg_services(Pkg), [])
self.assertEqual(Distro.pkg_service_blacklist(Pkg), [])
self.assertFalse(Distro.has_apt())
+
+
+ @unittest.skipUnless(have_lsb_release, "lsb_release not installed")
+ def test_detect_via_lsb_release_module(self):
+ "Detect distro via lsb_release"
+ with patch('lsb_release.get_distro_information', return_value={'ID': 'Debian'}):
+ # Make sure we don't use the fallback
+ with patch('os.path.exists', return_value=False):
+ d = detect()
+ self.assertEqual(d.id, 'Debian')
diff --git a/tests/test_systemd.py b/tests/test_systemd.py
new file mode 100644
index 0000000..2fbdfdc
--- /dev/null
+++ b/tests/test_systemd.py
@@ -0,0 +1,62 @@
+# vim: set fileencoding=utf-8 :
+# (C) 2014 Guido Günther <agx@sigxcpu.org>
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""Test L{whatmaps.process} config"""
+
+import unittest
+
+from mock import patch
+
+from whatmaps.systemd import Systemd
+from whatmaps.process import Process
+
+class Process(object):
+ def __init__(self, pid):
+ self.pid = pid
+
+class TestSystemd(unittest.TestCase):
+ def test_is_init(self):
+ """Check if we create a systemd object if systemd is the init system in use"""
+ with patch('os.path.exists', return_value=True):
+ self.assertIsNotNone(Systemd())
+
+ def test_is_not_init(self):
+ """Check if we raise an exception if systemd isn't tthe init system in use"""
+ with patch('os.path.exists', return_value=False):
+ self.assertRaises(ValueError, Systemd)
+
+ def test_process_to_unit(self):
+ p = Process(952)
+ output = """libvirt-bin.service - Virtualization daemon
+ Loaded: loaded (/lib/systemd/system/libvirt-bin.service; enabled)
+ Active: active (running) since Fr 2014-07-11 16:10:55 CEST; 50min ago
+ Docs: man:libvirtd(8)
+ http://libvirt.org
+ Main PID: 952 (libvirtd)
+ CGroup: name=systemd:/system/libvirt-bin.service
+ ├─ 952 /usr/sbin/libvirtd
+ └─1355 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf
+ """
+ with patch('os.path.exists', return_value=True):
+ with patch('subprocess.Popen') as mock:
+ PopenMock = mock.return_value
+ PopenMock.communicate.return_value = [output]
+ PopenMock.returncode = 0
+ result = Systemd().process_to_unit(p)
+ self.assertEqual(result, 'libvirt-bin.service')
+
+ PopenMock.returncode = 1
+ result = Systemd().process_to_unit(p)
+ self.assertIsNone(result)
+
diff --git a/whatmaps/command.py b/whatmaps/command.py
index 3225f54..7690ef8 100755
--- a/whatmaps/command.py
+++ b/whatmaps/command.py
@@ -18,27 +18,17 @@
from __future__ import print_function
+import errno
import glob
import os
import logging
-import re
-import string
-import subprocess
import sys
-import errno
from optparse import OptionParser
-try:
- import lsb_release
-except ImportError:
- lsb_release = None
from . process import Process
-from . debiandistro import DebianDistro
-from . redhatdistro import FedoraDistro
-from . pkg import Pkg, PkgError
-from . debianpkg import DebianPkg
-from . rpmpkg import RpmPkg
-
+from . distro import Distro
+from . pkg import PkgError
+from . systemd import Systemd
def check_maps(procs, shared_objects):
restart_procs = {}
@@ -64,38 +54,6 @@ def get_all_pids():
return processes
-def detect_distro():
- id = None
-
- if lsb_release:
- id = lsb_release.get_distro_information()['ID']
- else:
- try:
- lsb_cmd = subprocess.Popen(['lsb_release', '--id', '-s'],
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- output = lsb_cmd.communicate()[0]
- if not lsb_cmd.returncode:
- id = output.strip()
- except OSError:
- # id is None in this case
- pass
-
- if id == DebianDistro.id:
- return DebianDistro
- elif id == FedoraDistro.id:
- return FedoraDistro
- else:
- if os.path.exists('/usr/bin/dpkg'):
- logging.warning("Unknown distro but dpkg found, assuming Debian")
- return DebianDistro
- elif os.path.exists('/bin/rpm'):
- logging.warning("Unknown distro but rpm found, assuming Fedora")
- return FedoraDistro
- else:
- return None
-
-
def write_cmd_file(services, cmd_file, distro):
"Write out commands needed to restart the services to a file"
out = open(cmd_file, 'w')
@@ -107,8 +65,67 @@ def write_cmd_file(services, cmd_file, distro):
os.chmod(cmd_file, 0o755)
+def find_pkgs(procs, distro):
+ """
+ Find packages that contain the binaries of the given processes
+ """
+ pkgs = {}
+ for proc in procs:
+ pkg = distro.pkg_by_file(proc)
+ if not pkg:
+ logging.warning("No package found for '%s' - restart manually" % proc)
+ else:
+ if pkg.name in pkgs:
+ pkgs[pkg.name].procs.append(proc)
+ else:
+ pkg.procs = [ proc ]
+ pkgs[pkg.name] = pkg
+
+ logging.info("Packages that ship the affected binaries:")
+ for pkg in pkgs.values():
+ logging.info(" Pkg: %s, binaries: %s" % (pkg.name, pkg.procs))
+
+ return pkgs
+
+
+def find_services(pkgs, distro):
+ """
+ Determine the services in pkgs honoring distro specific mappings
+ and blacklists
+ """
+ all_services = set()
+
+ for pkg in pkgs.values():
+ services = set(pkg.services + distro.pkg_services(pkg))
+ services -= set(distro.pkg_service_blacklist(pkg))
+ if not services:
+ logging.warning("No service script found in '%s' for '%s' "
+ "- restart manually" % (pkg.name, pkg.procs))
+ else:
+ all_services.update(services)
+ all_services -= distro.service_blacklist
+ return all_services
+
+
+def find_systemd_units(procmap, distro):
+ """Find systemd units that contain the given processes"""
+ units = set()
+
+ for dummy, procs in procmap.items():
+ for proc in procs:
+ unit = Systemd.process_to_unit(proc)
+ if not unit:
+ logging.warning("No systemd unit found for '%s'"
+ "- restart manually" % proc.exe)
+ else:
+ units.add(unit)
+ units -= set([ service + '.service' for service in distro.service_blacklist ])
+ return units
+
+
def main(argv):
shared_objects = []
+ services = None
parser = OptionParser(usage='%prog [options] pkg1 [pkg2 pkg3 pkg4]')
parser.add_option("--debug", action="store_true", dest="debug",
@@ -134,7 +151,7 @@ def main(argv):
logging.basicConfig(level=level,
format='%(levelname)s: %(message)s')
- distro = detect_distro()
+ distro = Distro.detect()
if not distro:
logging.error("Unsupported Distribution")
return 1
@@ -169,59 +186,50 @@ def main(argv):
logging.debug(" %s", so)
# Find processes that map them
- restart_procs = check_maps(get_all_pids(), shared_objects)
+ try:
+ restart_procs = check_maps(get_all_pids(), shared_objects)
+ except IOError as e:
+ if e.errno == errno.EACCES:
+ logging.error("Can't open process maps in '/proc/<pid>/maps', are you root?")
+ return 1
+ else:
+ raise
logging.debug("Processes that map them:")
for exe, pids in restart_procs.items():
logging.debug(" Exe: %s Pids: %s", exe, pids),
- # Find packages that contain the binaries of these processes
- pkgs = {}
- for proc in restart_procs:
- pkg = distro.pkg_by_file(proc)
- if not pkg:
- logging.warning("No package found for '%s' - restart manually" % proc)
- else:
- if pkg.name in pkgs:
- pkgs[pkg.name].procs.append(proc)
- else:
- pkg.procs = [ proc ]
- pkgs[pkg.name] = pkg
-
- logging.info("Packages that ship the affected binaries:")
- for pkg in pkgs.values():
- logging.info(" Pkg: %s, binaries: %s" % (pkg.name, pkg.procs))
+ if Systemd.is_running():
+ logging.debug("Detected Systemd")
+ services = find_systemd_units(restart_procs, distro)
+ else:
+ # Find the packages that contain the binaries the processes are
+ # executing
+ pkgs = find_pkgs(restart_procs, distro)
- all_services = set()
- try:
- for pkg in pkgs.values():
- services = set(pkg.services + distro.pkg_services(pkg))
- services -= set(distro.pkg_service_blacklist(pkg))
- if not services:
- logging.warning("No service script found in '%s' for '%s' "
- "- restart manually" % (pkg.name, pkg.procs))
+ # Find the services in these packages honoring distro specific
+ # mappings and blacklists
+ try:
+ services = find_services(pkgs, distro)
+ except NotImplementedError:
+ if level > logging.INFO:
+ logging.error("Getting Service listing not implemented "
+ "for distribution %s - rerun with --verbose to see a list"
+ "of binaries that map a shared objects from %s",
+ distro.id, args)
+ return 1
else:
- all_services.update(services)
- all_services -= distro.service_blacklist
- except NotImplementedError:
- if level > logging.INFO:
- logging.error("Getting Service listing not implemented "
- "for distribution %s - rerun with --verbose to see a list"
- "of binaries and packages to map a shared objects from %s",
- distro.id, args)
- return 1
- else:
- return 0
+ return 0
if options.restart:
- if options.print_cmds and all_services:
- write_cmd_file(all_services, options.print_cmds, distro)
+ if options.print_cmds and services:
+ write_cmd_file(services, options.print_cmds, distro)
else:
- for service in all_services:
+ for service in services:
logging.info("Restarting %s" % service)
distro.restart_service(service)
- elif all_services:
+ elif services:
print("Services that possibly need to be restarted:")
- for s in all_services:
+ for s in services:
print(s)
return 0
diff --git a/whatmaps/debiandistro.py b/whatmaps/debiandistro.py
index 4a3d0db..9294c9f 100644
--- a/whatmaps/debiandistro.py
+++ b/whatmaps/debiandistro.py
@@ -26,16 +26,18 @@ except ImportError:
lsb_release = None
import logging
-import string
+import os
import subprocess
import sys
+import string
-from . distro import Distro
+import whatmaps.distro
from . debianpkg import DebianPkg
from . pkg import PkgError
+from . systemd import Systemd
-class DebianDistro(Distro):
- "Debian (dpkg) based distribution"""
+class DebianDistro(whatmaps.distro.Distro):
+ "Debian (dpkg) based distribution"
id = 'Debian'
_pkg_services = { 'apache2-mpm-worker': [ 'apache2' ],
@@ -72,10 +74,20 @@ class DebianDistro(Distro):
return DebianPkg(pkg)
@classmethod
- def restart_service_cmd(klass, name):
+ def restart_service_cmd(klass, service):
+ """The command that should be used to start a service"""
+ if Systemd.is_running() and service.endswith('.service'):
+ name = service[:-len('.service')]
+ else:
+ name = service
return ['invoke-rc.d', name, 'restart']
@classmethod
+ def is_service_installed(klass, name):
+ """Whether the system has this service"""
+ return os.path.exists('/etc/init.d/%s' % name)
+
+ @classmethod
def has_apt(klass):
return True
diff --git a/whatmaps/distro.py b/whatmaps/distro.py
index e4f417f..f0f08eb 100644
--- a/whatmaps/distro.py
+++ b/whatmaps/distro.py
@@ -15,12 +15,28 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+import logging
+import os
import subprocess
+
+try:
+ import lsb_release
+except ImportError:
+ lsb_release = None
+
class Distro(object):
"""
A distribution
+
@cvar id: distro id as returned by lsb-release
+
+ @cvar service_blacklist: services that should never be restarted
+ @cvar _pkg_services: A C{dict} that maps packages to services. In
+ case we find binaries that match a key in this hash restart
+ the services listed in values.
+ @cvar _pkg_service_blacklist: if we find binaries in the package
+ listed as key don't restart services listed in values
"""
id = None
service_blacklist = set()
@@ -49,12 +65,18 @@ class Distro(object):
subprocess.call(klass.restart_service_cmd(service))
@classmethod
+ def is_service_installed(klass, service):
+ """Check wether a service exists on the system"""
+ return True
+
+ @classmethod
def pkg_services(klass, pkg):
"""
List of services that package pkg needs restarted that aren't part
of pkg itself
"""
- return klass._pkg_services.get(pkg.name, [])
+ return [ s for s in klass._pkg_services.get(pkg.name, [])
+ if klass.is_service_installed(s) ]
@classmethod
def pkg_service_blacklist(klass, pkg):
@@ -68,3 +90,45 @@ class Distro(object):
def has_apt(klass):
"""Does the distribution use apt"""
return False
+
+ @staticmethod
+ def detect():
+ return detect()
+
+import whatmaps.debiandistro
+import whatmaps.redhatdistro
+
+def detect():
+ """
+ Detect the distribution we run on. Returns C{None} if the
+ distribution is unknown.
+ """
+ id = None
+
+ if lsb_release:
+ id = lsb_release.get_distro_information()['ID']
+ else:
+ try:
+ lsb_cmd = subprocess.Popen(['lsb_release', '--id', '-s'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ output = lsb_cmd.communicate()[0]
+ if not lsb_cmd.returncode:
+ id = output.strip()
+ except OSError:
+ # id is None in this case
+ pass
+
+ if id == whatmaps.debiandistro.DebianDistro.id:
+ return whatmaps.debiandistro.DebianDistro
+ elif id == whatmaps.redhatdistro.FedoraDistro.id:
+ return whatmaps.redhatdistro.FedoraDistro
+ else:
+ if os.path.exists('/usr/bin/dpkg'):
+ logging.warning("Unknown distro but dpkg found, assuming Debian")
+ return whatmaps.debiandistro.DebianDistro
+ elif os.path.exists('/bin/rpm'):
+ logging.warning("Unknown distro but rpm found, assuming Fedora")
+ return whatmaps.debiandistro.FedoraDistro
+ else:
+ return None
diff --git a/whatmaps/redhatdistro.py b/whatmaps/redhatdistro.py
index 4d9f5cf..9300648 100644
--- a/whatmaps/redhatdistro.py
+++ b/whatmaps/redhatdistro.py
@@ -20,7 +20,6 @@ import re
import subprocess
from . distro import Distro
-from . pkg import PkgError
from . rpmpkg import RpmPkg
class RedHatDistro(Distro):
diff --git a/whatmaps/systemd.py b/whatmaps/systemd.py
new file mode 100644
index 0000000..add6d0e
--- /dev/null
+++ b/whatmaps/systemd.py
@@ -0,0 +1,41 @@
+# vim: set fileencoding=utf-8 :
+#
+# (C) 2014 Guido Günther <agx@sigxcpu.org>
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import os
+import subprocess
+
+
+class Systemd(object):
+ """Systemd init system"""
+
+ def __init__(self):
+ if not self.is_running():
+ raise ValueError("Systemd not running")
+
+ @staticmethod
+ def is_running():
+ return os.path.exists("/run/systemd/system")
+
+ @staticmethod
+ def process_to_unit(process):
+ cmd = ['systemctl', 'status', "%d" % process.pid]
+ systemctl_status = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ output = systemctl_status.communicate()[0]
+ if systemctl_status.returncode:
+ return None
+ else:
+ return output.split()[0]