|
|
|
#!/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. #
|
|
|
|
# #
|
|
|
|
###########################################################################
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import os.path
|
|
|
|
from python_tqt.qt import *
|
|
|
|
from tdeui import *
|
|
|
|
from tdecore import *
|
|
|
|
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 = TDECModule
|
|
|
|
|
|
|
|
# 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
|
|
|
|
TDEGlobal.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:
|
|
|
|
TDECModule.__init__(self,parent,name)
|
|
|
|
self.setButtons(1)
|
|
|
|
self.aboutdata = MakeAboutData()
|
|
|
|
|
|
|
|
# Create a configuration object.
|
|
|
|
self.config = TDEConfig("serviceconfigrc")
|
|
|
|
TDEGlobal.iconLoader().addAppDir("guidance")
|
|
|
|
|
|
|
|
self.updatingGUI = False
|
|
|
|
self.context = getServiceContext()
|
|
|
|
self.servicestolistitems = {} # Map service names to TQListViewItems
|
|
|
|
self.currentrunlevel = self.context.currentRunLevel()
|
|
|
|
|
|
|
|
self.context.loadInfo()
|
|
|
|
|
|
|
|
self.aboutus = TDEAboutApplication(self)
|
|
|
|
|
|
|
|
if standalone:
|
|
|
|
toplayout = TQVBoxLayout( self.plainPage(), 0, KDialog.spacingHint() )
|
|
|
|
tophb = TQSplitter(TQt.Horizontal, self.plainPage())
|
|
|
|
else:
|
|
|
|
toplayout = TQVBoxLayout( self, 0, KDialog.spacingHint() )
|
|
|
|
tophb = TQSplitter(TQt.Horizontal, self)
|
|
|
|
|
|
|
|
toplayout.addWidget(tophb)
|
|
|
|
|
|
|
|
vb = TQVBox(tophb)
|
|
|
|
vb.setSpacing(KDialog.spacingHint())
|
|
|
|
|
|
|
|
hb = TQHBox(vb)
|
|
|
|
hb.setSpacing(KDialog.spacingHint())
|
|
|
|
vb.setStretchFactor(hb,0)
|
|
|
|
|
|
|
|
label = TQLabel(hb)
|
|
|
|
label.setPixmap(UserIcon("hi32-app-daemons"))
|
|
|
|
hb.setStretchFactor(label,0)
|
|
|
|
|
|
|
|
label = TQLabel(i18n("Run level:"),hb)
|
|
|
|
hb.setStretchFactor(label,0)
|
|
|
|
self.runlevelcombo = TQComboBox(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 = TQWidget(hb)
|
|
|
|
hb.setStretchFactor(widget,1)
|
|
|
|
|
|
|
|
self.servicelistview = TDEListView(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(TQListView.Single)
|
|
|
|
self.connect(self.servicelistview, SIGNAL("selectionChanged(TQListViewItem *)"), self.slotListClicked)
|
|
|
|
|
|
|
|
# Right hand side of the dialog.
|
|
|
|
vb = TQVBox(tophb)
|
|
|
|
vb.setSpacing(KDialog.spacingHint())
|
|
|
|
|
|
|
|
hgb = TQHGroupBox(i18n("Service Details"),vb)
|
|
|
|
vb.setStretchFactor(hgb,1)
|
|
|
|
vb2 = TQVBox(hgb)
|
|
|
|
vb2.setSpacing(KDialog.spacingHint())
|
|
|
|
|
|
|
|
label = TQLabel(i18n("Description:"),vb2)
|
|
|
|
vb2.setStretchFactor(label,0)
|
|
|
|
self.descriptiontextedit = TQTextEdit(vb2)
|
|
|
|
vb2.setStretchFactor(self.descriptiontextedit,2)
|
|
|
|
self.descriptiontextedit.setReadOnly(True)
|
|
|
|
self.startatbootcheckbox = TQCheckBox(i18n("Start during boot"),vb2)
|
|
|
|
vb2.setStretchFactor(self.startatbootcheckbox,0)
|
|
|
|
self.connect(self.startatbootcheckbox, SIGNAL("toggled(bool)"), self.slotBootChanged)
|
|
|
|
|
|
|
|
label = TQLabel(i18n("Status:"),vb2)
|
|
|
|
vb2.setStretchFactor(label,0)
|
|
|
|
self.statustext = TQTextEdit(vb2)
|
|
|
|
self.statustext.setReadOnly(True)
|
|
|
|
vb2.setStretchFactor(self.statustext,1)
|
|
|
|
|
|
|
|
hb2 = TQHBox(vb2)
|
|
|
|
hb2.setSpacing(KDialog.spacingHint())
|
|
|
|
vb2.setStretchFactor(hb2,0)
|
|
|
|
self.startbutton = TQPushButton(i18n("Start"),hb2)
|
|
|
|
hb2.setStretchFactor(self.startbutton,1)
|
|
|
|
self.connect(self.startbutton, SIGNAL("clicked()"), self.slotStartButton)
|
|
|
|
self.stopbutton = TQPushButton(i18n("Stop"),hb2)
|
|
|
|
hb2.setStretchFactor(self.stopbutton,1)
|
|
|
|
self.connect(self.stopbutton, SIGNAL("clicked()"), self.slotStopButton)
|
|
|
|
self.restartbutton = TQPushButton(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 = TQPushButton(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(TDEListView*,TQListViewItem*,const TQPoint&)"),
|
|
|
|
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 = TDEPopupMenu(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)):
|
|
|
|
TQTimer.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 = TQListViewItem(self.servicelistview,item.filename,i18n("Yes"),status)
|
|
|
|
else:
|
|
|
|
lvi = TQListViewItem(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
|
|
|
|
|
|
|
|
TQTimer.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 TDEAboutData object which we created during initialisation.
|
|
|
|
return self.aboutdata
|
|
|
|
|
|
|
|
def buttons(self):
|
|
|
|
# Only supply a Help button. Other choices are Default and Apply.
|
|
|
|
return TDECModule.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 = TQHBox(vbox)
|
|
|
|
hbox.setSpacing(self.spacingHint())
|
|
|
|
|
|
|
|
tmplabel = TQLabel(hbox)
|
|
|
|
tmplabel.setPixmap(UserIcon("laserwarn"))
|
|
|
|
hbox.setStretchFactor(tmplabel,0)
|
|
|
|
|
|
|
|
self.headinglabel = TQLabel(hbox)
|
|
|
|
hbox.setStretchFactor(self.headinglabel,1)
|
|
|
|
|
|
|
|
self.outputtextview = TQTextView(vbox)
|
|
|
|
self.outputtextview.setTextFormat(TQTextView.PlainText)
|
|
|
|
|
|
|
|
self.kid = TQProcess()
|
|
|
|
self.kid.setCommunication(TQProcess.Stdout|TQProcess.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 = TQStringList()
|
|
|
|
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 = TDEApplication.kApplication()
|
|
|
|
return SysVInitApp(parent, name)
|
|
|
|
|
|
|
|
############################################################################
|
|
|
|
def MakeAboutData():
|
|
|
|
aboutdata = TDEAboutData("guidance",programname,version, \
|
|
|
|
"Services Configuration Tool", TDEAboutData.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()
|
|
|
|
TDECmdLineArgs.init(sys.argv,aboutdata)
|
|
|
|
|
|
|
|
kapp = TDEApplication()
|
|
|
|
sysvapp = SysVInitApp()
|
|
|
|
sysvapp.exec_loop()
|