diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-13 05:43:39 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-13 05:43:39 +0000 |
commit | 19ae07d0d443ff8b777f46bcbe97119483356bfd (patch) | |
tree | dae169167c23ba7c61814101995de21d6abac2e8 /serviceconfig/serviceconfig.py | |
download | tde-guidance-19ae07d0d443ff8b777f46bcbe97119483356bfd.tar.gz tde-guidance-19ae07d0d443ff8b777f46bcbe97119483356bfd.zip |
Added KDE3 version of KDE Guidance utilities
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kde-guidance@1102646 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'serviceconfig/serviceconfig.py')
-rwxr-xr-x | serviceconfig/serviceconfig.py | 1481 |
1 files changed, 1481 insertions, 0 deletions
diff --git a/serviceconfig/serviceconfig.py b/serviceconfig/serviceconfig.py new file mode 100755 index 0000000..0da85a0 --- /dev/null +++ b/serviceconfig/serviceconfig.py @@ -0,0 +1,1481 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- +########################################################################### +# serviceconfig.py - description # +# ------------------------------ # +# begin : Wed Apr 30 2003 # +# copyright : (C) 2003-2006 by Simon Edwards # +# email : [email protected] # +# # +########################################################################### +# # +# 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 2 of the License, or # +# (at your option) any later version. # +# # +########################################################################### + +from qt import * +from kdeui import * +from kdecore import * +import sys +import os +import os.path +import posix +import re +from pickle import Pickler,Unpickler +import locale + +programname = "Services Configuration" +version = "0.8.1" + +# Holding the name of the distribution, defaults to ... +DISTRO = "Mandrake" + +# These get overridden for Debian. +initdir = "/etc/init.d" +rcdir = "/etc/rc.d" + +chkconfigpath = "/sbin/chkconfig" +statusblacklist = ['iptables'] + +# Are we running as a separate standalone application or in KControl? +standalone = __name__=='__main__' + +# Running as the root user or not? +isroot = os.getuid()==0 + +#print "Warning:: root check is disabled" +#isroot = True + +############################################################################ +class DescriptionCache(object): + + proto = 2 ## Choose latest available version of Pickler + _lines = [] + data = {} + + def __init__(self,filename,path="/tmp"): + if not os.path.isdir(path): + print path, "is not a valid directory, can't cache." + + self.filename = os.path.join(path,filename) + + def loadCache(self): + try: + fhandle = open(self.filename, "r") + pickler = Unpickler(fhandle) + self.data = pickler.load() + fhandle.close() + except IOError, e: + print "Couldn't load file:", e + print "Not reading description cache data" + + def saveCache(self): + """ Save pickled dataobject to the file. We don't want the user to be able to save a datastructure that root + will read in since that would have implications for security.""" + if not isroot: + print "Not saving description cache data for security reasons, we're not root" + return + + try: + fhandle = open(self.filename, "w") + pickler = Pickler(fhandle, self.proto) + pickler.dump(self.data) + fhandle.close() + #print "Saved description cache data to ", self.filename + except IOError, e: + print "Can't cache:", e + + def add(self,filename,packagename,description): + self.data[filename] = (packagename,description) + + def readDescription(self,filename): + """ Tries to look up the package description, returns None if it can't find it.""" + try: + return self.data[filename][1] + except KeyError: + return None + + def readPackagename(self,filename): + """ Tries to look up the Packagename, returns None if it can't find it.""" + try: + return self.data[filename][0] + except KeyError: + return None + +############################################################################ +# Holds all of the info about a single service. +class Service(object): + ######################################################################## + def __init__(self,context,filename): + self.context = context + self.filename = filename + self.longname = "" + self.description = "" + self.runlevels = [] + self.gotStatus = False + self.statusable = False + self.status = unicode(i18n("?")) + self.startpriority = "50" + self.killpriority = "50" + + try: + fhandle = open(os.path.join(initdir,self.filename)) + place = 0 + for line in fhandle.readlines(): + line = line.strip() + if place==0: + if line.startswith("#!/bin/sh") or line.startswith("#! /bin/sh") or \ + line.startswith("#!/bin/bash") or line.startswith("#! /bin/bash"): + place = 1 + elif place==1: # Grab the short description line. + if line!="#": + if line.startswith("# chkconfig:"): + parts = line[12:].split() + if len(parts)>=1: + for c in parts[0]: + try: + rl = self.context.getRunLevelByNumber(int(c)) + if rl is not None: + self.runlevels.append(rl) + rl.availableservices.append(self) + except ValueError: + pass + if len(parts)>=2: + self.startpriority = parts[1] + if len(parts)>=3: + self.killpriority = parts[2] + else: + self.longname = line[2:] + place = 2 + elif place==2: # Look for the description line + if line.startswith("# chkconfig:"): + parts = line[12:].split() + if len(parts)>=1: + for c in parts[0]: + try: + rl = self.context.getRunLevelByNumber(int(c)) + if rl is not None: + self.runlevels.append(rl) + rl.availableservices.append(self) + except ValueError: + pass + if len(parts)>=2: + self.startpriority = parts[1] + if len(parts)>=3: + self.killpriority = parts[2] + elif line.startswith("# description:"): + self.description = line[15:] + if self.description[-1:]=="\\": + self.description = self.description[:-1] + place = 3 + else: + place = 2 + elif place==3: # Grab a description continuation line. + if line[0]=="#": + self.description += line[1:].strip() + if self.description[-1:]=="\\": + self.description = self.description[:-1] + else: + place = 2 + if line.startswith("status)"): + self.statusable = True + + fhandle.close() + if self.filename in statusblacklist: + self.statusable = False + except IOError: + pass + + if self.longname=="": + self.longname = self.filename + + if len(self.runlevels)==0: + self.runlevels = self.context.runlevels[:] + for rl in self.context.runlevels: + rl.availableservices.append(self) + + ######################################################################## + def isAvailableInRunlevel(self,level): + return level in self.runlevels + + ######################################################################## + def fetchStatus(self): + global initdir + if self.statusable: + self.status = os.popen(os.path.join(initdir,self.filename) + " status").read() + self.gotStatus = True + + ######################################################################## + def doStartCommand(self): + self.gotStatus = False + return "export CONSOLETYPE=serial && " + os.path.join(self.context.initdir,self.filename) + " start" + + ######################################################################## + def doStopCommand(self): + self.gotStatus = False + return "export CONSOLETYPE=serial && " + os.path.join(self.context.initdir,self.filename) + " stop" + + ######################################################################## + def doRestartCommand(self): + self.gotStatus = False + return "export CONSOLETYPE=serial && " + os.path.join(self.context.initdir,self.filename) + " restart" + +############################################################################ +class ServiceContext(object): + def __init__(self): + self.runlevels = [] + self.services = {} + + def loadInfo(self): pass + def currentRunLevel(self): pass + def newRunLevel(self): pass + def newService(self,file): pass + def getRunLevelByNumber(self,num): + for rl in self.runlevels: + if rl.levelnum==num: + return rl + return None + +############################################################################ +def getServiceContext(): + global DISTRO + # Detect if we are running on Debian, Mandrake or what. + + # Uncomment here to test Gentoo compatibility. + #DISTRO = "Gentoo"; return GentooServiceContext() + + # Check for Debian - is this the 'good' way? + etc_issue = '/etc/issue' + + # Knoppix and Kanotix have a symlink called /etc/issue + if os.path.islink(etc_issue): + etc_issue = posix.readlink(etc_issue) + + if os.path.isfile(etc_issue): + etc_issue = open(etc_issue) + system_name = etc_issue.readline() + etc_issue.close() + if system_name.startswith("Debian") or system_name.startswith("Ubuntu"): + DISTRO = "Debian" + return DebianServiceContext() + + # Might this be Gentoo Linux? + if os.path.isfile('/etc/gentoo-release'): + DISTRO = "Gentoo" + return GentooServiceContext() + + # Mandrake is default. + return MandrakeServiceContext() + +############################################################################ +class MandrakeServiceContext(ServiceContext): + ######################################################################## + def __init__(self): + ServiceContext.__init__(self) + self.initdir = "/etc/init.d" + self.rcdir = "/etc/rc.d" + self.runlevels.append(SysVRunLevel(self,0,i18n("Halt (0)"))) + self.runlevels.append(SysVRunLevel(self,1,i18n("Single User Mode (1)"))) + self.runlevels.append(SysVRunLevel(self,2,i18n("Multiuser mode with Networking (2)"))) + self.runlevels.append(SysVRunLevel(self,3,i18n("Multiuser mode (3)"))) + self.__currentrunlevel = SysVRunLevel(self,5,i18n("GUI Multiuser mode (5)")) + self.runlevels.append(self.__currentrunlevel) + self.runlevels.append(SysVRunLevel(self,6,i18n("Reboot (6)"))) + self._filenametoservice = {} + + ######################################################################## + def currentRunLevel(self): + return self.__currentrunlevel + + ######################################################################## + def loadInfo(self): + # Load in all of the service info. + for filename in os.listdir(self.initdir): + newservice = self.newService(filename) + self.services[filename] = newservice + self._filenametoservice[filename] = newservice + + # Now load in which services are active in which run level. + for rl in self.runlevels: + rl.loadInfo() + + ######################################################################## + def newService(self,file): + return Service(self,file) + +############################################################################ +class SysVRunLevel(object): + ######################################################################## + def __init__(self,context,levelnum,name): + self.context = context + self.levelnum = levelnum + self.name = name + self.availableservices = [] + self.activeservices = [] + self.leveldir = os.path.join(self.context.rcdir,"rc"+str(self.levelnum)+".d") + + ######################################################################## + def loadInfo(self): + #print "SysVRunLevel(object).loadInfo",self.leveldir + for filename in os.listdir(self.leveldir): + if filename.startswith("S") and os.path.islink(self.leveldir+"/"+filename): + target = os.path.basename(posix.readlink(self.leveldir+"/"+filename)) + if target in self.context._filenametoservice: + self.activeservices.append(self.context._filenametoservice[target]) + #else: + # print "Couldn't find service '%s' in runlevel %i." % (target, self.levelnum) + + ######################################################################## + def isActiveAtBoot(self,service): + return service in self.activeservices + + ######################################################################## + def setActiveAtBoot(self,service,activeflag): + leveldir = self.leveldir + + # Remove any existing links to the service script + for filename in os.listdir(leveldir): + link = os.path.join(leveldir,filename) + if os.path.islink(link): + target = os.path.basename(posix.readlink(os.path.join(leveldir,filename))) + if target==service.filename: + # Kill target. + #print "Killing link",link + os.remove(link) + + if activeflag: + #print "symlink(",leveldir+"/S"+service.startpriority+service.filename,",",self.context.initdir+"/"+service.filename,")" + posix.symlink( os.path.join(self.context.initdir,service.filename), + os.path.join(leveldir,"S"+service.startpriority+service.filename)) + self.activeservices.append(service) + else: + #print "symlink(", leveldir+"/K"+service.killpriority+service.filename,",",self.context.initdir+"/"+service.filename,")" + posix.symlink( os.path.join(self.context.initdir,service.filename), + os.path.join(leveldir,"K"+service.killpriority+service.filename)) + self.activeservices.remove(service) + +############################################################################ +# +# Here's the Debian specific stuff, a Service, a ServiceContext and a RunLevel +# +# Enjoy. ;-) + +class DebianService(Service): + """ Mapping for services which don't use a pidfile like /var/run/<service>.pid + Services not in here are lookup up "normally" """ + pidfiles = { 'acpid':'acpid.socket', + 'spamassassin':'spamd.pid', + 'dbus':'dbus/pid', + 'klogd':'klogd/klogd.pid', + 'samba':'samba/smbd.pid', + 'zope':'zope/default/Z2.pid', + 'mysql':'mysqld/mysqld.pid', + 'hald':'hal/hald.pid', + 'sysklogd':'syslogd.pid', + 'ssh':'sshd.pid', + 'cron':'crond.pid', + 'slapd':'slapd/slapd.pid', + 'laptop-mode':'laptop-mode-enabled', + 'cupsys':'cups/cupsd.pid' + } + + ######################################################################## + def __init__(self,context,filename): + self.context = context + self.filename = filename + self.path_and_filename = os.path.join(context.initdir, self.filename) + self.packagename = False + self.description = i18n("Description is being loaded.") + self.runlevels = [] + self.gotStatus = False + self.statusable = False + self.status = unicode(i18n("not running")) + self.startpriority = "50" + self.killpriority = "50" + self.getStatusFrom = "pidfile" + + self.fetchStatus() + + if len(self.runlevels)==0: + self.runlevels = self.context.runlevels[:] + for rl in self.context.runlevels: + rl.availableservices.append(self) + + def fetchDescription(self): + self.description = self.context.descriptioncache.readDescription(self.filename) + self.packagename = self.context.descriptioncache.readPackagename(self.filename) + if not self.description: + if not self.packagename: + self.fetchPackageName() + #print " packagename", self.packagename + if self.packagename: + # FIXME: don't assume english output! + command = "apt-cache show " + self.packagename + + self.description = "" + description_label = "Description:" + for line in os.popen(command).readlines(): + if line.startswith(description_label): + self.description = line.strip()[len(description_label):] + + self.context.descriptioncache.add(self.filename, self.packagename, self.description, ) + else: + self.description = i18n("Couldn't fetch a description from apt.") + + def fetchPackageName(self): + if os.path.isfile(self.path_and_filename): + command = "dpkg -S " + self.path_and_filename + self.packagename = None # as opposed to False( = not yet fetched) + for line in os.popen(command).readlines(): + if ":" in line: + self.packagename = line.strip().split(":")[0] + else: + print self.path_and_filename + " is no file or does not exist!" + + ######################################################################## + def fetchStatus(self): + if self.getStatusFrom == "pidfile": + self.fetchStatusFromPidFile() + elif self.getStatusFrom == "top": + # FIXME: not yet implemented + self.fetchStatusFromTop() + + ######################################################################## + def fetchStatusFromTop(self): + # FIXME, incomplete. + top = os.popen("ps -aux") + + ######################################################################## + def fetchStatusFromPidFile(self): + try: + if os.path.isfile(os.path.join('/var/run',self.pidfiles[self.filename])): + self.status = unicode(i18n("running")) + else: + self.status = unicode(i18n("not running")) + except KeyError: + if os.path.isfile(os.path.join('/var/run',self.filename + '.pid')): + self.status = unicode(i18n("running")) + else: + self.status = unicode(i18n("not running")) + self.gotStatus = True + +############################################################################ +class DebianServiceContext(ServiceContext): + """ bootscripts are scripts that are only running once at boot and where starting, + stopping and restarting does not really make sense, generally exclude these from + serviceconfig list.""" + bootscripts = ( 'README', + 'acpi-support', + 'xorg-common', + 'binfmt-support', + 'bootclean.sh', + 'bootmisc.sh', + 'checkfs.sh', + 'checkroot.sh', + 'console-screen.sh', + 'dns-clean', + 'glibc.sh', + 'halt', + 'hostname.sh', + 'hwclock.sh', + 'hwclockfirst.sh', + 'initrd-tools.sh', + 'keymap.sh', + 'makedev', + 'module-init-tools', + 'mountall.sh', + 'mountvirtfs', + 'mountnfs.sh', + 'nvidia-kernel', + 'procps.sh', + 'pppd-dns', + 'powernowd.early', + 'rc', + 'rc.local', + 'rcS', + 'readahead', + 'readahead-desktop', + 'reboot', + 'rmnologin', + 'screen-cleanup', + 'screen', + 'sendsigs', + 'single', + 'skeleton', + 'stop-bootlogd', + 'stop-readahead', + 'umountfs', + 'umountnfs.sh', + 'urandom' + ) + + def __init__(self): + """ + Debian uses the following runlevels: + + 1 (single-user mode), + 2 through 5 (multiuser modes), and + 0 (halt the system), + 6 (reboot the system). + + Runlevels 7, 8, and 9 can also be used but their rc directories are not populated + when packages are installed. They are intentionally left out here, but should be + easy to add. + """ + ServiceContext.__init__(self) + + self.initdir = "/etc/init.d" + self.rcdir = "/etc" + self.relative_initdir = "../init.d" + + deb_runlevels = { 0 : i18n("Halt (0)"), + 1 : i18n("Single User Mode (1)"), + "S" : i18n("Single User Mode (S)"), + 2 : i18n("Multiuser Mode (2)"), + 3 : i18n("Multiuser Mode (3)"), + 4 : i18n("Multiuser Mode (4)"), + 5 : i18n("Multiuser Mode (5)"), + 6 : i18n("Reboot (6)") } + + # Lookup what runlevel we're in. + shell_output = os.popen('/sbin/runlevel') + raw_runlevel = shell_output.readline() + shell_output.close() + cur_runlevel = raw_runlevel[2:-1] + + for num in deb_runlevels.keys(): + if cur_runlevel.isdigit(): + if num == int(cur_runlevel): + self.__currentrunlevel = DebianRunLevel(self, num, deb_runlevels[num]) + self.runlevels.append(self.__currentrunlevel) + else: + self.runlevels.append(DebianRunLevel(self, num, deb_runlevels[num])) + else: + if num == cur_runlevel: + self.__currentrunlevel = DebianRunLevel(self, num, deb_runlevels[num]) + self.runlevels.append(self.__currentrunlevel) + else: + self.runlevels.append(DebianRunLevel(self, num, deb_runlevels[num])) + self._filenametoservice = {} + + ######################################################################## + def currentRunLevel(self): + return self.__currentrunlevel + + ######################################################################## + def loadInfo(self): + # Load in all of the service info. + initscripts = os.listdir(self.initdir) + # Remove "bootscripts" from our list. + servicefiles = [] + self.services = [] + for script in initscripts: + if script not in self.bootscripts: + try: + # Exclude backup copies. + if script.split(".")[1] not in ("orig","dpkg-dist"): + servicefiles.append(script) + except IndexError: + servicefiles.append(script) + + for filename in servicefiles: + if filename not in self.bootscripts: + newservice = self.newService(filename) + self.services.append(newservice) + self._filenametoservice[filename] = newservice + + # Now load in which services are active in which run level. + for rl in self.runlevels: + rl.loadInfo() + + ######################################################################## + def newService(self,file): + return DebianService(self,file) + +############################################################################ +class DebianRunLevel(SysVRunLevel): + ######################################################################## + def setActiveAtBoot(self,service,activeflag): + """ Adds a Service to a runlevel. + + Activating a service adds start symlinks in the respective levels, and + maintains symlinks in levels 0, 1 and 6 (halt, single user and reboot). + """ + leveldir = self.context.rcdir + + def createSymlink(target, linkname): + """ Creates a symlink after having checked if it makes sense to do so. + We first change to the rcdir, then create a relative symlink and then + change back, sounds weird, but Debian's own scripts break when the + symlinks are not relative. + + Returns True or False and prints debugging message. + """ + odir = os.getcwd() + tmpdir = "/".join(linkname.split("/")[0:-1]) # FIXME use os.path + os.chdir(tmpdir) + + if not os.path.isfile(target) or os.path.islink(target): + #print target + " is not a valid filename. Can't create symlink." + os.chdir(odir) + return False + + if os.path.islink(linkname) and posix.readlink(linkname) == target: + #print "Symlink " + linkname + " -> " + target + " already exists." + os.chdir(odir) + return True + + if os.path.islink(linkname) and posix.readlink(linkname) != target: + #print "Removing symlink, " + linkname + ", the target does not match." + try: + posix.unlink(linkname) + except OSError, e: + print "Couldn't remove symlink " + linkname + " :: " + str(e) + + try: + posix.symlink(target, linkname) + #print "Created symlink " + linkname + " -> " + target + " successfully." + os.chdir(odir) + return True + except OSError, e: + #print "Creating symlink " + linkname + " -> " + target + " failed: " + str(e) + os.chdir(odir) + return False + + def removeSymlink(servicename, runleveldir, KorS): + if KorS not in ('K','S'): + print "OUCH, symlinks have to start with S or K!" + return + for link in os.listdir(runleveldir): + if (link[0] == KorS) and (link[3:] == servicename): + #print "Killing ...", runleveldir+link + posix.unlink(os.path.join(runleveldir,link)) + + # In these levels, the K symlinks are created. + stop_levels = (0,1,6) + l_num = str(self.levelnum) + + if activeflag: + target = os.path.join(self.context.relative_initdir,service.filename) + createSymlink(target, os.path.join(self.context.rcdir,"rc"+l_num+".d","S"+service.startpriority+service.filename)) + # Kill links: + for i in stop_levels: + createSymlink(target, os.path.join(self.context.rcdir,"rc"+str(i)+".d","K"+service.killpriority+service.filename)) + self.activeservices.append(service) + else: + + try: + s_link = os.path.join(leveldir,"rc"+l_num+".d","S"+service.startpriority+service.filename) + runleveldir = os.path.join(leveldir,"rc"+l_num+".d") + #print "Removing symlink " + s_link + removeSymlink(service.filename, runleveldir, "S") + except OSError, e: + print "Could not remove symlink " + s_link + " :: " + str(e) + + self.activeservices.remove(service) + + # check if service has to be started in other runlevels: + # Y: Don't touch links + # N: Remove symlinks + + #print "Should remove symlinks here." + for rl in self.context.runlevels: + if service in rl.activeservices: + #print "Service " + service.filename + " is still used in runlevel " + \ + # str(rl.levelnum) + ", not removing K-Links." + break + else: + # The service is not being used anywhere. We can remove it now. + + #print "Service completely inactive, removing K-links." + for i in stop_levels: + k_link = os.path.join(leveldir,"rc"+str(i)+".d","K"+service.killpriority+service.filename) + runleveldir = os.path.join(leveldir,"rc"+str(i)+".d") + + try: + #print "Removing " + k_link + removeSymlink(service.filename, runleveldir, "K") + except OSError, e: + print "Could not remove " + k_link + " :: " + str(e) + + +############################################################################ +# +# Here come all the Gentoo specific pieces. +# Gentoo Linux has a special way of organizing the init and runlevels stuff. + +class GentooService(DebianService): + """ GentooService + + Services in Gentoo are handled very much like the ones in Debian, except + that there is rc-status to check whether a service is running or not. + """ + ######################################################################## + def fetchStatus(self): + # rc-status is run everytime we check a service, this might be some + # more efficiently solved. + # FIXME: add check if 'rc-status' is in current PATH + rc_status_fhandle = os.popen('rc-status') + for line in rc_status_fhandle.readlines(): + parts = line.split() + if parts[0] == self.filename: + # Who needs Perl? ;-) + # FIXME: set the terminal type to serial when running rc-status. + self.status = line.split(';01m')[2].split('\x1b[')[0].strip() + rc_status_fhandle.close() + self.gotStatus = True + + def fetchDescription(self): + # Temporary vars. + description_lines = [] + first_block = True + + if os.path.isfile(self.path_and_filename): + fhandle = open(self.path_and_filename) + for line in fhandle.readlines(): + # Ignore blank lines and CVS Headers: + if len(line.strip()) > 1 and line[:2] != '#!' and line[:10] != '# $Header:': + # Cut off newline at the end. + line = line[:-1] + # The first commencted block might be the description. + if first_block: + if line[0] != '#': first_block = False + else: description_lines.append(line[1:].strip()) + fhandle.close() + else: + print self.path_and_filename + " is no file or does not exist!" + + if len(description_lines): + self.description = "\n".join(description_lines) + else: + self.description = i18n("Could not extract description.") + + ######################################################################## + def doZapCommand(self): + self.gotStatus = False + return "export CONSOLETYPE=serial && rc-status" + return "export CONSOLETYPE=serial && "+os.path.join(self.context.initdir,self.filename)+" zap" + +############################################################################ +class GentooServiceContext(ServiceContext): + ######################################################################## + def __init__(self): + """ + Gentoo uses customized runlevels, see the Gentoo Documentation for + the dirty details. The runlevels are defined in inittab, so we have + a look there. + + - Runlevel links reside in /etc/runlevels/${RUNLEVEL}/. + - Default existing runlevels are boot/, nonetwork/ and default/. + - Custom runlevels and default are defined in /etc/inittab. + + Dependencies between runscripts / initscrips are not handled here, + this is responsibility of rc-update. Also, after rc-update has run, + some items might be necessary to refresh. + + """ + ServiceContext.__init__(self) + + # Here comes the Gentoo specific stuff. + # First off, parsing inittab for runlevels available. + def parseInittab(): + if os.path.isfile(self.inittab): + inittab_fhandle = open(self.inittab, 'r') + rl_appended = [] + for line in inittab_fhandle.readlines(): + line = line[:-1] + # Ignore blank and commented lines. + if len(line.strip()) > 1 and line.strip()[0] != '#': + parts = line.split(':') + if len(parts) == 4 and parts[2] == 'wait': + rl_num, rl_label = parts[1], parts[3].split()[1] +' ('+parts[1]+')' + rl_name = parts[3].split()[1] + #print "Num: " + rl_num + " Label: " + rl_label + " Name: " + parts[3].split()[1] + # This is a runlevel in Gentoo, is it the current one? + if parts[1] == self.current_runlevelnum: + self.__currentrunlevel = GentooRunLevel(self, rl_num, rl_name, rl_label) + self.runlevels.append(self.__currentrunlevel) + rl_appended.append(rl_name) + else: + if rl_name not in rl_appended: + self.runlevels.append(GentooRunLevel(self, rl_num, rl_name, rl_label)) + rl_appended.append(rl_name) + elif len(parts) == 4 and parts[2] == 'bootwait': + # The boot runlevel does not have a 'real' runlevel number, so we use 0. + self.runlevels.append(GentooRunLevel(self, 0, parts[3].split()[1], 'boot')) + rl_appended.append('boot') + inittab_fhandle.close() + + def currentRunLevelNum(): + runlevelbin = "/sbin/runlevel" + if not os.path.isfile(runlevelbin): + print "Couldn't find %s, that sucks. :o" % runlevelbin + sys.exit(1) + shell_output = os.popen(runlevelbin) + raw_runlevel = shell_output.readline() + shell_output.close() + return raw_runlevel[2:-1] + + self.initdir = "/etc/init.d" + self.rcdir = "/etc/runlevels" + self.inittab = "/etc/inittab" + + #self.initdir = "/home/sebas/gentooinit/init.d" + #self.rcdir = "/home/sebas/gentooinit/runlevels" + #self.inittab = "/home/sebas/gentooinit/inittab" + + self.current_runlevelnum = currentRunLevelNum() + parseInittab() + self._filenametoservice = {} + + ######################################################################## + def currentRunLevel(self): + return self.__currentrunlevel + + ######################################################################## + def loadInfo(self): + """ Load in all of the service info for every file in the init.d directory. """ + for filename in os.listdir(self.initdir): + # Exclude backup files from portage and .sh files like shutdown, depscan, etc. + if (filename.find('._cfg')<0) and not filename.endswith(".sh"): + newservice = self.newService(filename) + self.services[filename] = newservice + self._filenametoservice[filename] = newservice + + # Now load in which services are active in which run level. + for rl in self.runlevels: + rl.loadInfo() + + ######################################################################## + def newService(self,file): + return GentooService(self,file) + + +############################################################################ +class GentooRunLevel(SysVRunLevel): + """ Gentoo Runlevel + + GentooRunLevel has an additional parameter. the 'label' gets displayed in the + 'Run level:' drop-down menu, the 'name' is used. 'name' is internally handled + in very much the same way as SysVRunLevel.levelnum. It corresponds to the + actual runlevels Gentoo uses, such as /etc/runlevels/default/, rather than + using SysVRunLevel.levelnum, as it would be in a SysV init. """ + + ######################################################################## + def __init__(self,context,levelnum,dirname,label): + SysVRunLevel.__init__(self,context,levelnum,label) + + self.dirname = dirname + + self.leveldir = self.context.rcdir+'/'+self.dirname + # Not all runlevels in Gentoo correspond to a runlevel directory. + self.no_dirs = [] + if not os.path.isdir(self.leveldir): + #self.no_dirs = ('reboot', 'shutdown', 'single') + if self.dirname not in self.no_dirs: + self.no_dirs.append(self.dirname) + print "Runlevel " + self.leveldir + " is not a valid path. '" + self.dirname + "'" + self.leveldir = False + return + + ######################################################################## + def loadInfo(self): + """ Only look up active services if runlevel path exists, else leave empty. """ + if self.leveldir: + print "GentooRunLevel.loadInfo() from " + self.leveldir + for filename in os.listdir(self.leveldir): + # Exclude backup files from portage and .sh files like shutdown, depscan, etc. + if (filename.find('._cfg')<0) and not filename.endswith('.sh'): + linkname = self.leveldir+"/"+filename + if os.path.islink(linkname): + target = os.path.basename(posix.readlink(linkname)) + if target in self.context._filenametoservice: + self.activeservices.append(self.context._filenametoservice[target]) + else: + print "Couldn't find service '%s'. " % target + else: + print "%s is not a valid symlink." % linkname + + ######################################################################## + def setActiveAtBoot(self,service,activeflag): + """ Runs rc-update to add and remove Services from RunLevels. + + Dependencies are checked from within runscript.sh, so we don't handle them here. """ + + # FIXME :: "Start at Boot" column does not properly get updated once it's "False". + # The commands issued might better be passed through via CommandRunner. + if self.name in self.no_dirs: + print "Runlevel has no corresponding path, running rc-update anyway." + if activeflag: + if not service in self.activeservices: + rc_add_cmd = "rc-update add %s %s" % (service.filename, self.dirname) + print rc_add_cmd + # The brave really run it yet. + os.system(rc_add_cmd) + self.activeservices.append(service) + else: + if service in self.activeservices: + rc_del_cmd = "rc-update del %s %s" % (service.filename, self.dirname) + print rc_del_cmd + # The brave really run it yet. + os.system(rc_dell_cmd) + self.activeservices.remove(service) + +############################################################################ + +# Try translating this code to C++. I dare ya! +if standalone: + programbase = KDialogBase +else: + programbase = KCModule + +# is_shown exists to prevent loadDescriptions from running two times, which is +# the case when we're running inside kcontrol. Yes, this is an ugly hack. :( +# It's set to True after show has finished once. It doesn't play a role when +# we're running standalone. +is_shown = False + +class SysVInitApp(programbase): + ######################################################################## + + def __init__(self,parent=None,name=None): + global standalone,isroot, DISTRO, is_shown + KGlobal.locale().insertCatalogue("guidance") + + if standalone: + KDialogBase.__init__(self,KJanusWidget.Plain,i18n("Service Configuration"), \ + KDialogBase.User1|KDialogBase.Close, KDialogBase.Close) + self.setButtonText(KDialogBase.User1,i18n("About")) + else: + KCModule.__init__(self,parent,name) + self.setButtons(1) + self.aboutdata = MakeAboutData() + + # Create a configuration object. + self.config = KConfig("serviceconfigrc") + KGlobal.iconLoader().addAppDir("guidance") + + self.updatingGUI = False + self.context = getServiceContext() + self.servicestolistitems = {} # Map service names to QListViewItems + self.currentrunlevel = self.context.currentRunLevel() + + self.context.loadInfo() + + self.aboutus = KAboutApplication(self) + + if standalone: + toplayout = QVBoxLayout( self.plainPage(), 0, KDialog.spacingHint() ) + tophb = QSplitter(Qt.Horizontal, self.plainPage()) + else: + toplayout = QVBoxLayout( self, 0, KDialog.spacingHint() ) + tophb = QSplitter(Qt.Horizontal, self) + + toplayout.addWidget(tophb) + + vb = QVBox(tophb) + vb.setSpacing(KDialog.spacingHint()) + + hb = QHBox(vb) + hb.setSpacing(KDialog.spacingHint()) + vb.setStretchFactor(hb,0) + + label = QLabel(hb) + label.setPixmap(UserIcon("hi32-app-daemons")) + hb.setStretchFactor(label,0) + + label = QLabel(i18n("Run level:"),hb) + hb.setStretchFactor(label,0) + self.runlevelcombo = QComboBox(hb) + + # Load up the runlevel combo box. + i = 0 + for runlevel in self.context.runlevels: + self.runlevelcombo.insertItem(runlevel.name) + if self.context.currentRunLevel() is runlevel: + self.runlevelcombo.setCurrentItem(i) + i += 1 + + hb.setStretchFactor(self.runlevelcombo,0) + + self.connect(self.runlevelcombo, SIGNAL("activated(int)"), self.slotRunLevelChanged) + + widget = QWidget(hb) + hb.setStretchFactor(widget,1) + + self.servicelistview = KListView(vb) + self.servicelistview.addColumn(i18n("Service")) + self.servicelistview.addColumn(i18n("Start at Boot")) + self.servicelistview.addColumn(i18n("Status")) + self.servicelistview.setAllColumnsShowFocus(True) + self.servicelistview.setSelectionMode(QListView.Single) + self.connect(self.servicelistview, SIGNAL("selectionChanged(QListViewItem *)"), self.slotListClicked) + + # Right hand side of the dialog. + vb = QVBox(tophb) + vb.setSpacing(KDialog.spacingHint()) + + hgb = QHGroupBox(i18n("Service Details"),vb) + vb.setStretchFactor(hgb,1) + vb2 = QVBox(hgb) + vb2.setSpacing(KDialog.spacingHint()) + + label = QLabel(i18n("Description:"),vb2) + vb2.setStretchFactor(label,0) + self.descriptiontextedit = QTextEdit(vb2) + vb2.setStretchFactor(self.descriptiontextedit,2) + self.descriptiontextedit.setReadOnly(True) + self.startatbootcheckbox = QCheckBox(i18n("Start during boot"),vb2) + vb2.setStretchFactor(self.startatbootcheckbox,0) + self.connect(self.startatbootcheckbox, SIGNAL("toggled(bool)"), self.slotBootChanged) + + label = QLabel(i18n("Status:"),vb2) + vb2.setStretchFactor(label,0) + self.statustext = QTextEdit(vb2) + self.statustext.setReadOnly(True) + vb2.setStretchFactor(self.statustext,1) + + hb2 = QHBox(vb2) + hb2.setSpacing(KDialog.spacingHint()) + vb2.setStretchFactor(hb2,0) + self.startbutton = QPushButton(i18n("Start"),hb2) + hb2.setStretchFactor(self.startbutton,1) + self.connect(self.startbutton, SIGNAL("clicked()"), self.slotStartButton) + self.stopbutton = QPushButton(i18n("Stop"),hb2) + hb2.setStretchFactor(self.stopbutton,1) + self.connect(self.stopbutton, SIGNAL("clicked()"), self.slotStopButton) + self.restartbutton = QPushButton(i18n("Restart"),hb2) + hb2.setStretchFactor(self.restartbutton,1) + self.connect(self.restartbutton, SIGNAL("clicked()"), self.slotRestartButton) + + if DISTRO == "Gentoo": + # Gentoo Linux gets an extra button. + self.zapbutton = QPushButton(i18n("Zap"),hb2) + hb2.setStretchFactor(self.zapbutton,1) + self.connect(self.zapbutton, SIGNAL("clicked()"), self.slotZapButton) + + if not isroot: + self.disableStuff() + else: + self.connect(self.servicelistview, SIGNAL("contextMenu(KListView*,QListViewItem*,const QPoint&)"), + self.slotServiceContextMenu) + + self.__fillListView(self.currentrunlevel) + self.__selectFirstService() + + self.cr = CommandRunner(None,"title") + self.timerid = None + + ######################################################################## + def __del__(self): + pass + + def disableStuff(self): + """Disable a couple of widgets when not running as root""" + self.startatbootcheckbox.setDisabled(True) + self.startbutton.setDisabled(True) + self.restartbutton.setDisabled(True) + self.stopbutton.setDisabled(True) + if DISTRO == "Gentoo": + self.zapbutton.setDisabled(True) + + def slotServiceContextMenu(self,l,v,p): + self.cmenu = KPopupMenu(self,"MyActions") + self.cmenu.insertItem(i18n("Start..."), self.slotStartButton) + self.cmenu.insertItem(i18n("Stop..."), self.slotStopButton) + self.cmenu.insertItem(i18n("Restart..."), self.slotRestartButton) + self.cmenu.insertItem(i18n("Toggle start during boot..."), self.slotBootChangedAndToggle) + + self.cmenu.exec_loop(p) + + def slotBootChangedAndToggle(self): + """Wrap slotBootChanged in order to pass the status of the checkbox, used from contextmenu.""" + self.startatbootcheckbox.toggle() + + ######################################################################## + # KDialogBase method + def exec_loop(self): + global programbase + + self.__loadOptions() + programbase.exec_loop(self) + self.__saveOptions() + + ######################################################################## + def __selectFirstService(self): + # Grab the first service in the list and select it. + services = self.currentrunlevel.availableservices[:] + services.sort(lambda x,y: cmp(x.filename,y.filename)) + self.selectedservice = None + try: + self.selectedservice = services[0] + lvi = self.servicestolistitems[self.selectedservice] + self.servicelistview.setSelected(lvi,True) + except IndexError: + pass + self.__selectService(self.selectedservice) + + ######################################################################## + def show(self): + global standalone,isroot, is_shown + + programbase.show(self) + self.updatingGUI = True + if isroot: + self.__selectFirstService() + self.updatingGUI = False + self.__checkServiceStatus() + + if DISTRO == "Debian" and (standalone or (not standalone and is_shown)): + QTimer.singleShot(0,self.__startLoadDescriptions) + is_shown = True + + + ######################################################################## + # KDialogBase method + def slotUser1(self): + self.aboutus.show() + + ######################################################################## + def __fillListView(self,runlevelobj): + self.servicelistview.clear() + self.servicestolistitems = {} + + services = self.currentrunlevel.availableservices[:] + services.sort(lambda x,y: cmp(x.filename,y.filename)) + + for item in services: + if item.isAvailableInRunlevel(runlevelobj): + status = item.status.strip().replace("\n",", ")[:32] + if item in runlevelobj.activeservices: + lvi = QListViewItem(self.servicelistview,item.filename,i18n("Yes"),status) + else: + lvi = QListViewItem(self.servicelistview,item.filename,i18n("No"),status) + self.servicestolistitems[item] = lvi + + ######################################################################## + def __selectService(self,service): + if service!=None: + self.descriptiontextedit.setEnabled(True) + self.descriptiontextedit.setText(service.description) + + if isroot: + self.startatbootcheckbox.setEnabled(True) + + self.startatbootcheckbox.setChecked(self.currentrunlevel.isActiveAtBoot(service)) + self.statustext.setEnabled(True) + self.statustext.setText(service.status) + else: + self.disableStuff() + self.descriptiontextedit.setText("") + self.descriptiontextedit.setEnabled(False) + self.statustext.setText("") + self.statustext.setEnabled(False) + + ######################################################################## + def slotRunLevelChanged(self,levelnum): + if self.updatingGUI: + return + self.updatingGUI = True + + self.currentrunlevel = self.context.runlevels[levelnum] + self.__fillListView(self.currentrunlevel) + self.__selectFirstService() + self.__checkServiceStatus() + + self.updatingGUI = False + + ######################################################################## + def slotListClicked(self,item): + if self.updatingGUI: + return + self.updatingGUI = True + + for service in self.servicestolistitems.keys(): + if self.servicestolistitems[service] is item: + self.selectedservice = service + self.__selectService(self.selectedservice) + break + self.updatingGUI = False + + ######################################################################## + def slotBootChanged(self,state): + if self.updatingGUI: + return + self.updatingGUI = True + level = self.currentrunlevel + level.setActiveAtBoot(self.selectedservice,state) + if level.isActiveAtBoot(self.selectedservice): + self.servicestolistitems[self.selectedservice].setText(1,i18n("Yes")) + else: + self.servicestolistitems[self.selectedservice].setText(1,i18n("No")) + self.updatingGUI = False + + ######################################################################## + def slotCloseButton(self): + self.close() + + ######################################################################## + def slotStartButton(self): + self.__runInitScript(i18n("Starting %1").arg(self.selectedservice.filename), \ + self.selectedservice,"start") + + ######################################################################## + def slotStopButton(self): + self.__runInitScript(i18n("Stopping %1").arg(self.selectedservice.filename), \ + self.selectedservice,"stop") + + ######################################################################## + def slotRestartButton(self): + self.__runInitScript(i18n("Restarting %1").arg(self.selectedservice.filename), \ + self.selectedservice,"restart") + + ######################################################################## + def slotZapButton(self): + """This button lets the Gentoo user use the zap command, if process + information has not properly been cleaned up by the init script. + """ + if DISTRO == "Gentoo": + self.__runInitScript(i18n("Zapping %1").arg(self.selectedservice.filename), \ + self.selectedservice,"zap") + + + ######################################################################## + def __startLoadDescriptions(self): + if DISTRO=="Debian": + cachepath = "/var/tmp" + cachefile = "guidance-packagedescriptioncache" + self.context.descriptioncache = DescriptionCache(cachefile,cachepath) + self.context.descriptioncache.loadCache() + self.__loadDescriptions() + + ######################################################################## + def __loadDescriptions(self): + """ + Loads the description of all services showing a progressbar if it takes longer, + tries to get the descriptions from cache first. + """ + for service in self.context.services: + if service.packagename is None: + continue + # Check if we want to fetch a description for the currently selected item + # before we go on fetching other descriptions. + if not self.selectedservice.packagename: + self.selectedservice.fetchDescription() + self.slotListClicked(self.servicelistview.currentItem()) + break + if not service.packagename: + service.fetchDescription() + break + else: + self.slotListClicked(self.servicelistview.currentItem()) + if DISTRO=="Debian": + self.context.descriptioncache.saveCache() + return + + QTimer.singleShot(0,self.__loadDescriptions) + + ######################################################################## + def __runInitScript(self,title,service,command): + global DISTRO + self.cr.setCaption(title) + self.cr.setHeading(title) + if command=="start": + cmd = service.doStartCommand() + elif command=="stop": + cmd = service.doStopCommand() + elif command=="restart": + cmd = service.doRestartCommand() + elif DISTRO == "Gentoo" and command=="zap": + cmd = service.doZapCommand() + + self.cr.run(["/bin/bash","-c",cmd]) + + # Does not seem to properly update ... + self.__checkServiceStatus() + + ######################################################################## + def __checkServiceStatus(self): + global kapp + global progcount + + # Put up the progress dialog. (User pacifier). + dialog = KProgressDialog(self,"statusprogress", + i18n("Querying System Service Status"), + i18n("Querying system service status")) + dialog.setLabel(i18n("Querying system service status")) + dialog.setAutoClose(True) + dialog.showCancelButton(False) + + services = self.currentrunlevel.availableservices[:] + services.sort(lambda x,y: cmp(x.filename,y.filename)) + dialog.progressBar().setTotalSteps(len(services)) + + kapp.processEvents() + self.updatingGUI = True + + for item in services: + if item.gotStatus==False: + item.fetchStatus() + lvi = self.servicestolistitems[item] + lvi.setText(2,item.status.strip().replace("\n",", ")[:32]) + if self.selectedservice is item: + self.statustext.setText(item.status) + dialog.progressBar().advance(1) + kapp.processEvents() + dialog.setMinimumDuration(2000000000) + dialog.hide() + self.updatingGUI = False + + ######################################################################## + def __loadOptions(self): + self.config.setGroup("General") + size = self.config.readSizeEntry("Geometry") + if size.isEmpty()==False: + self.resize(size) + size = self.config.readSizeEntry("CommandRunnerGeometry") + if size.isEmpty()==False: + self.cr.resize(size) + + ####################################################################### + def __saveOptions(self): + global isroot + if isroot: + return + self.config.setGroup("General") + self.config.writeEntry("Geometry", self.size()) + self.config.writeEntry("CommandRunnerGeometry",self.cr.size()) + self.config.sync() + + ####################################################################### + # KControl virtual void methods + def load(self): + pass + def save(self): + pass + def defaults(self): + pass + def sysdefaults(self): + pass + + def aboutData(self): + # Return the KAboutData object which we created during initialisation. + return self.aboutdata + + def buttons(self): + # Only supply a Help button. Other choices are Default and Apply. + return KCModule.Help + +############################################################################ +class CommandRunner(KDialogBase): + ######################################################################## + def __init__(self,parent,name): + KDialogBase.__init__(self,parent,name,1,"",KDialogBase.Ok) + self.output = "" + self.running = False + + self.resize(400,200) + vbox = self.makeVBoxMainWidget() + + hbox = QHBox(vbox) + hbox.setSpacing(self.spacingHint()) + + tmplabel = QLabel(hbox) + tmplabel.setPixmap(UserIcon("laserwarn")) + hbox.setStretchFactor(tmplabel,0) + + self.headinglabel = QLabel(hbox) + hbox.setStretchFactor(self.headinglabel,1) + + self.outputtextview = QTextView(vbox) + self.outputtextview.setTextFormat(QTextView.PlainText) + + self.kid = QProcess() + self.kid.setCommunication(QProcess.Stdout|QProcess.Stderr) + self.connect(self.kid,SIGNAL("processExited()"),self.slotProcessExited) + self.connect(self.kid,SIGNAL("readyReadStdout()"),self.slotReadyReadStdout) + self.connect(self.kid,SIGNAL("readyReadStderr()"),self.slotReadyReadStderr) + + ######################################################################## + def run(self,argslist): + self.kid.clearArguments() + for arg in argslist: + self.kid.addArgument(arg) + self.output = "" + self.outputtextview.setText(self.output) + self.bootstraptimer = self.startTimer(0) + self.running = True + self.enableButtonOK(False) + self.exec_loop() + + ######################################################################## + def timerEvent(self,timer): + self.killTimer(self.bootstraptimer) + + # Create a slightly new environment where TERM is vt100 + new_env = QStringList() + for key in os.environ: + if key=="TERM": + new_env.append("TERM=vt100") + else: + new_env.append(key + "=" + os.environ[key]) + + self.kid.launch("",new_env) + + ######################################################################## + def setHeading(self,heading): + self.headinglabel.setText(heading) + + ######################################################################## + def slotOKButton(self): + self.accept() + + ######################################################################## + def slotReadyReadStdout(self): + # Remove the colors used by some programs. + # FIXME: this probably isn't neccessary anymore. + uncolor = lambda text: re.compile('\\x1b\[[0-9]+;01m').sub("", \ + re.compile('\\x1b\[0m').sub("", re.compile('\\033\[1;[0-9]+m').sub("", \ + re.compile('\\033\[0m').sub("", text)))) + self.output += uncolor(unicode(self.kid.readStdout())) + self.outputtextview.setText(self.output) + self.outputtextview.ensureVisible(0,self.outputtextview.contentsHeight()) + + ######################################################################## + def slotReadyReadStderr(self): + self.output += unicode(self.kid.readStderr()) + self.outputtextview.setText(self.output) + self.outputtextview.ensureVisible(0,self.outputtextview.contentsHeight()) + + ######################################################################## + def slotProcessExited(self): + self.running = False + self.enableButtonOK(True) + +############################################################################ +# Factory function for KControl +def create_serviceconfig(parent,name): + global kapp + kapp = KApplication.kApplication() + return SysVInitApp(parent, name) + +############################################################################ +def MakeAboutData(): + aboutdata = KAboutData("guidance",programname,version, \ + "Services Configuration Tool", KAboutData.License_GPL, \ + "Copyright (C) 2003-2007 Simon Edwards", \ + "Thanks go to Phil Thompson, Jim Bublitz and David Boddie.") + aboutdata.addAuthor("Simon Edwards","Developer","[email protected]","http://www.simonzone.com/software/") + aboutdata.addAuthor("Sebastian Kügler","Developer","[email protected]","http://vizZzion.org"); + return aboutdata + +if standalone: + aboutdata = MakeAboutData() + KCmdLineArgs.init(sys.argv,aboutdata) + + kapp = KApplication() + sysvapp = SysVInitApp() + sysvapp.exec_loop() |