272 lines
12 KiB
Python
272 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
|
#---------------------------------------------------------------
|
|
# Project : Mandriva Linux
|
|
# Module : rpmlint
|
|
# File : InitScriptCheck.py
|
|
# Version : $Id$
|
|
# Author : Frederic Lepied
|
|
# Created On : Fri Aug 25 09:26:37 2000
|
|
# Purpose : check init scripts (files in /etc/rc.d/init.d)
|
|
#---------------------------------------------------------------
|
|
|
|
import os
|
|
import re
|
|
|
|
import rpm
|
|
|
|
from Filter import addDetails, printError, printWarning
|
|
import AbstractCheck
|
|
import Config
|
|
import Pkg
|
|
|
|
|
|
chkconfig_content_regex = re.compile('^\s*#\s*chkconfig:\s*([-0-9]+)\s+[-0-9]+\s+[-0-9]+')
|
|
subsys_regex = re.compile('/var/lock/subsys/([^/"\'\n\s;&|]+)', re.MULTILINE)
|
|
chkconfig_regex = re.compile('^[^#]*(chkconfig|add-service|del-service)', re.MULTILINE)
|
|
status_regex = re.compile('^[^#]*status', re.MULTILINE)
|
|
reload_regex = re.compile('^[^#]*reload', re.MULTILINE)
|
|
dot_in_name_regex = re.compile('.*\..*')
|
|
use_deflevels = Config.getOption('UseDefaultRunlevels', True)
|
|
lsb_tags_regex = re.compile('^# ([\w-]+):\s*(.*?)\s*$')
|
|
lsb_cont_regex = re.compile('^#(?:\t| )(.*?)\s*$')
|
|
|
|
LSB_KEYWORDS = ('Provides', 'Required-Start', 'Required-Stop', 'Should-Start',
|
|
'Should-Stop', 'Default-Start', 'Default-Stop',
|
|
'Short-Description', 'Description')
|
|
RECOMMENDED_LSB_KEYWORDS = ('Provides', 'Required-Start', 'Required-Stop',
|
|
'Default-Stop', 'Short-Description')
|
|
|
|
class InitScriptCheck(AbstractCheck.AbstractCheck):
|
|
|
|
def __init__(self):
|
|
AbstractCheck.AbstractCheck.__init__(self, 'InitScriptCheck')
|
|
|
|
def check(self, pkg):
|
|
# Check only binary package
|
|
if pkg.isSource():
|
|
return
|
|
|
|
initscript_list = []
|
|
for fname, pkgfile in pkg.files().items():
|
|
if fname.startswith('/etc/init.d/') or \
|
|
fname.startswith('/etc/rc.d/init.d/'):
|
|
basename = os.path.basename(fname)
|
|
initscript_list.append(basename)
|
|
if pkgfile.mode & 0500 != 0500:
|
|
printError(pkg, 'init-script-non-executable', fname)
|
|
|
|
if dot_in_name_regex.match(basename):
|
|
printError(pkg, 'init-script-name-with-dot', fname)
|
|
# check chkconfig call in %post and %preun
|
|
postin = pkg[rpm.RPMTAG_POSTIN] or pkg[rpm.RPMTAG_POSTINPROG]
|
|
if not postin:
|
|
printError(pkg, 'init-script-without-chkconfig-postin', fname)
|
|
else:
|
|
if not chkconfig_regex.search(postin):
|
|
printError(pkg, 'postin-without-chkconfig', fname)
|
|
|
|
preun = pkg[rpm.RPMTAG_PREUN] or pkg[rpm.RPMTAG_PREUNPROG]
|
|
if not preun:
|
|
printError(pkg, 'init-script-without-chkconfig-preun', fname)
|
|
else:
|
|
if not chkconfig_regex.search(preun):
|
|
printError(pkg, 'preun-without-chkconfig', fname)
|
|
|
|
status_found = False
|
|
reload_found = False
|
|
chkconfig_content_found = False
|
|
subsys_regex_found = False
|
|
in_lsb_tag = False
|
|
in_lsb_description = False
|
|
lastline = ''
|
|
lsb_tags = {}
|
|
# check common error in file content
|
|
content = None
|
|
try:
|
|
content = Pkg.readlines(pkgfile.path)
|
|
except Exception, e:
|
|
printWarning(pkg, 'read-error', e)
|
|
continue
|
|
content_str = "".join(content)
|
|
for line in content:
|
|
line = line[:-1] # chomp
|
|
# TODO check if there is only one line like this
|
|
if line.startswith('### BEGIN INIT INFO'):
|
|
in_lsb_tag = True
|
|
continue
|
|
if line.endswith('### END INIT INFO'):
|
|
in_lsb_tag = False
|
|
for kw, vals in lsb_tags.items():
|
|
if len(vals) != 1:
|
|
printError(pkg, 'redundant-lsb-keyword', kw)
|
|
|
|
for kw in RECOMMENDED_LSB_KEYWORDS:
|
|
if kw not in lsb_tags:
|
|
printWarning(pkg,
|
|
'missing-lsb-keyword',
|
|
"%s in %s" % (kw, fname))
|
|
if in_lsb_tag:
|
|
# TODO maybe we do not have to handle this ?
|
|
if lastline.endswith('\\'):
|
|
line = lastline + line
|
|
else:
|
|
res = lsb_tags_regex.search(line)
|
|
if not res:
|
|
cres = lsb_cont_regex.search(line)
|
|
if not (in_lsb_description and cres):
|
|
in_lsb_description = False
|
|
printError(pkg, 'malformed-line-in-lsb-comment-block', line)
|
|
else:
|
|
lsb_tags["Description"][-1] += " " + cres.group(1)
|
|
else:
|
|
tag = res.group(1)
|
|
if not tag.startswith('X-') and \
|
|
tag not in LSB_KEYWORDS:
|
|
printError(pkg, 'unknown-lsb-keyword', line)
|
|
else:
|
|
in_lsb_description = (tag == 'Description')
|
|
if tag not in lsb_tags:
|
|
lsb_tags[tag] = []
|
|
lsb_tags[tag].append(res.group(2))
|
|
lastline = line
|
|
|
|
|
|
|
|
if not status_found and status_regex.search(line):
|
|
status_found = True
|
|
|
|
if not reload_found and reload_regex.search(line):
|
|
reload_found = True
|
|
|
|
res = chkconfig_content_regex.search(line)
|
|
if res:
|
|
chkconfig_content_found = True
|
|
if use_deflevels:
|
|
if res.group(1) == '-':
|
|
printWarning(pkg, 'no-default-runlevel', fname)
|
|
else:
|
|
if res.group(1) != '-':
|
|
printWarning(pkg, 'service-default-enabled', fname)
|
|
|
|
res = subsys_regex.search(line)
|
|
if res:
|
|
subsys_regex_found = True
|
|
name = res.group(1)
|
|
if name != basename:
|
|
error = True
|
|
if name[0] == '$':
|
|
value = Pkg.substitute_shell_vars(name, content_str)
|
|
if value == basename:
|
|
error = False
|
|
else:
|
|
i = name.find('}')
|
|
if i != -1:
|
|
name = name[0:i]
|
|
error = name != basename
|
|
if error and len(name):
|
|
if name[0] == '$':
|
|
printWarning(pkg, 'incoherent-subsys', fname, name)
|
|
else:
|
|
printError(pkg, 'incoherent-subsys', fname, name)
|
|
|
|
if "Default-Start" in lsb_tags:
|
|
if "".join(lsb_tags["Default-Start"]):
|
|
printWarning(pkg, 'service-default-enabled', fname)
|
|
|
|
if not status_found:
|
|
printError(pkg, 'no-status-entry', fname)
|
|
if not reload_found:
|
|
printWarning(pkg, 'no-reload-entry', fname)
|
|
if not chkconfig_content_found:
|
|
printError(pkg, 'no-chkconfig-line', fname)
|
|
if not subsys_regex_found:
|
|
printError(pkg, 'subsys-not-used', fname)
|
|
|
|
goodnames = (pkg.name.lower(), pkg.name.lower() + 'd')
|
|
if len(initscript_list) == 1 and initscript_list[0] not in goodnames:
|
|
printWarning(pkg, 'incoherent-init-script-name', initscript_list[0],
|
|
str(goodnames))
|
|
|
|
|
|
# Create an object to enable the auto registration of the test
|
|
check = InitScriptCheck()
|
|
|
|
addDetails(
|
|
'init-script-without-chkconfig-postin',
|
|
'''The package contains an init script but doesn't contain a %post with
|
|
a call to chkconfig.''',
|
|
|
|
'postin-without-chkconfig',
|
|
'''The package contains an init script but doesn't call chkconfig in its %post.''',
|
|
|
|
'init-script-without-chkconfig-preun',
|
|
'''The package contains an init script but doesn't contain a %preun with
|
|
a call to chkconfig.''',
|
|
|
|
'preun-without-chkconfig',
|
|
'''The package contains an init script but doesn't call chkconfig in its %preun.''',
|
|
|
|
'missing-lsb-keyword',
|
|
'''The package contains an init script that does not contain one of the LSB
|
|
init script comment block convention keywords that are recommendable for all
|
|
init scripts. If there is nothing to add to a keyword's value, include the
|
|
keyword in the script with an empty value. Note that as of version 3.2, the
|
|
LSB specification does not mandate presence of any keywords.''',
|
|
|
|
'no-status-entry',
|
|
'''In your init script (/etc/rc.d/init.d/your_file), you don't
|
|
have a 'status' entry, which is necessary for good functionality.''',
|
|
|
|
'no-reload-entry',
|
|
'''In your init script (/etc/rc.d/init.d/your_file), you don't
|
|
have a 'reload' entry, which is necessary for good functionality.''',
|
|
|
|
'no-chkconfig-line',
|
|
'''The init script doesn't contain a chkconfig line to specify the runlevels
|
|
at which to start and stop it.''',
|
|
|
|
'no-default-runlevel',
|
|
'''The default runlevel isn't specified in the init script.''',
|
|
|
|
'service-default-enabled',
|
|
'''The service is enabled by default after "chkconfig --add"; for security
|
|
reasons, most services should not be. Use "-" as the default runlevel in the
|
|
init script's "chkconfig:" line and/or remove the "Default-Start:" LSB keyword
|
|
to fix this if appropriate for this service.''',
|
|
|
|
'subsys-not-used',
|
|
'''While your daemon is running, you have to put a lock file in
|
|
/var/lock/subsys/. To see an example, look at this directory on your
|
|
machine and examine the corresponding init scripts.''',
|
|
|
|
'incoherent-subsys',
|
|
'''The filename of your lock file in /var/lock/subsys/ is incoherent
|
|
with your actual init script name. For example, if your script name
|
|
is httpd, you have to use 'httpd' as the filename in your subsys directory.
|
|
It is also possible that rpmlint gets this wrong, especially if the init
|
|
script contains nontrivial shell variables and/or assignments. These
|
|
cases usually manifest themselves when rpmlint reports that the subsys name
|
|
starts a with '$'; in these cases a warning instead of an error is reported
|
|
and you should check the script manually.''',
|
|
|
|
'incoherent-init-script-name',
|
|
'''The init script name should be the same as the package name in lower case,
|
|
or one with 'd' appended if it invokes a process by that name.''',
|
|
|
|
'init-script-name-with-dot',
|
|
'''The init script name should not contain a dot in its name. Some versions
|
|
of chkconfig don't work as expected with init script names like that.''',
|
|
|
|
'init-script-non-executable',
|
|
'''The init script should have at least the execution bit set for root
|
|
in order for it to run at boot time.''',
|
|
)
|
|
|
|
# InitScriptCheck.py ends here
|
|
|
|
# Local variables:
|
|
# indent-tabs-mode: nil
|
|
# py-indent-offset: 4
|
|
# End:
|
|
# ex: ts=4 sw=4 et
|