From dab1d03d81c538966d03fb9318a4588a9e803b44 Mon Sep 17 00:00:00 2001 From: Sol Jerome Date: Sat, 24 Mar 2012 11:20:07 -0500 Subject: Allow to run directly from a git checkout (#1037) Signed-off-by: Sol Jerome --- src/lib/Bcfg2/Client/Tools/launchd.py | 135 ++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/lib/Bcfg2/Client/Tools/launchd.py (limited to 'src/lib/Bcfg2/Client/Tools/launchd.py') diff --git a/src/lib/Bcfg2/Client/Tools/launchd.py b/src/lib/Bcfg2/Client/Tools/launchd.py new file mode 100644 index 000000000..700234cc8 --- /dev/null +++ b/src/lib/Bcfg2/Client/Tools/launchd.py @@ -0,0 +1,135 @@ +"""launchd support for Bcfg2.""" + +import os +import popen2 + +import Bcfg2.Client.Tools + + +class launchd(Bcfg2.Client.Tools.Tool): + """Support for Mac OS X launchd services.""" + __handles__ = [('Service', 'launchd')] + __execs__ = ['/bin/launchctl', '/usr/bin/defaults'] + name = 'launchd' + __req__ = {'Service': ['name', 'status']} + + ''' + Currently requires the path to the plist to load/unload, + and Name is acually a reverse-fqdn (or the label). + ''' + + def __init__(self, logger, setup, config): + Bcfg2.Client.Tools.Tool.__init__(self, logger, setup, config) + + '''Locate plist file that provides given reverse-fqdn name + /Library/LaunchAgents Per-user agents provided by the administrator. + /Library/LaunchDaemons System wide daemons provided by the administrator. + /System/Library/LaunchAgents Mac OS X Per-user agents. + /System/Library/LaunchDaemons Mac OS X System wide daemons.''' + plistLocations = ["/Library/LaunchDaemons", "/System/Library/LaunchDaemons"] + self.plistMapping = {} + for directory in plistLocations: + for daemon in os.listdir(directory): + try: + if daemon.endswith(".plist"): + d = daemon[:-6] + else: + d = daemon + (stdout, _) = popen2.popen2('defaults read %s/%s Label' % (directory, d)) + label = stdout.read().strip() + self.plistMapping[label] = "%s/%s" % (directory, daemon) + except KeyError: #perhaps this could be more robust + pass + + def FindPlist(self, entry): + return self.plistMapping.get(entry.get('name'), None) + + def os_version(self): + version = "" + try: + vers = self.cmd.run('sw_vers')[1] + except: + return version + + for line in vers: + if line.startswith("ProductVersion"): + version = line.split()[-1] + return version + + def VerifyService(self, entry, _): + """Verify launchd service entry.""" + try: + services = self.cmd.run("/bin/launchctl list")[1] + except IndexError:#happens when no services are running (should be never) + services = [] + # launchctl output changed in 10.5 + # It is now three columns, with the last column being the name of the # service + version = self.os_version() + if version.startswith('10.5') or version.startswith('10.6'): + services = [s.split()[-1] for s in services] + if entry.get('name') in services:#doesn't check if non-spawning services are Started + return entry.get('status') == 'on' + else: + self.logger.debug("Didn't find service Loaded (launchd running under same user as bcfg)") + return entry.get('status') == 'off' + + try: #Perhaps add the "-w" flag to load and unload to modify the file itself! + self.cmd.run("/bin/launchctl load -w %s" % self.FindPlist(entry)) + except IndexError: + return 'on' + return False + + 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) + cmdrc = self.cmd.run("/bin/launchctl load -w %s" % self.FindPlist(entry)) + cmdrc = self.cmd.run("/bin/launchctl start %s" % name) + else: + self.logger.error("Uninstalling service %s" % name) + cmdrc = self.cmd.run("/bin/launchctl stop %s" % name) + cmdrc = self.cmd.run("/bin/launchctl unload -w %s" % self.FindPlist(entry)) + return cmdrc[0] == 0 + + def Remove(self, svcs): + """Remove Extra launchd entries.""" + pass + + def FindExtra(self): + """Find Extra launchd services.""" + try: + allsrv = self.cmd.run("/bin/launchctl list")[1] + except IndexError: + allsrv = [] + + [allsrv.remove(svc) for svc in [entry.get("name") for entry + in self.getSupportedEntries()] if svc in allsrv] + return [Bcfg2.Client.XML.Element("Service", + type='launchd', + name=name, + status='on') for name in allsrv] + + def BundleUpdated(self, bundle, states): + """Reload launchd plist.""" + for entry in [entry for entry in bundle if self.handlesEntry(entry)]: + if not self.canInstall(entry): + self.logger.error("Insufficient information to restart service %s" % (entry.get('name'))) + else: + name = entry.get('name') + if entry.get('status') == 'on' and self.FindPlist(entry): + self.logger.info("Reloading launchd service %s" % name) + #stop? + self.cmd.run("/bin/launchctl stop %s" % name) + self.cmd.run("/bin/launchctl unload -w %s" % (self.FindPlist(entry)))#what if it disappeared? how do we stop services that are currently running but the plist disappeared?! + self.cmd.run("/bin/launchctl load -w %s" % (self.FindPlist(entry))) + self.cmd.run("/bin/launchctl start %s" % name) + else: + #only if necessary.... + self.cmd.run("/bin/launchctl stop %s" % name) + self.cmd.run("/bin/launchctl unload -w %s" % (self.FindPlist(entry))) -- cgit v1.2.3-1-g7c22