#!/usr/bin/python # -*- coding: UTF-8 -*- ########################################################################### # serviceconfig.py - description # # ------------------------------ # # begin : Wed Apr 30 2003 # # copyright : (C) 2003-2006 by Simon Edwards # # email : simon@simonzone.com # # # ########################################################################### # # # 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 tdeui import * from tdecore 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/.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","simon@simonzone.com","http://www.simonzone.com/software/") aboutdata.addAuthor("Sebastian Kügler","Developer","sebas@kde.nl","http://vizZzion.org"); return aboutdata if standalone: aboutdata = MakeAboutData() KCmdLineArgs.init(sys.argv,aboutdata) kapp = KApplication() sysvapp = SysVInitApp() sysvapp.exec_loop()