summaryrefslogtreecommitdiff
path: root/InitScriptCheck.py
blob: c77adbc30f22315801e0d7bfec2dab396ce46e0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# -*- coding: utf-8 -*-
#---------------------------------------------------------------
# Project         : Mandriva Linux
# Module          : rpmlint
# File            : InitScriptCheck.py
# Version         : $Id: InitScriptCheck.py 1893 2011-11-23 20:24:32Z scop $
# 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)
use_deflevels = Config.getOption('UseDefaultRunlevels', True)
lsb_tags_regex = re.compile('^# ([\w-]+):\s*(.*?)\s*$')
lsb_cont_regex = re.compile('^#(?:\t|  )(.*?)\s*$')
use_subsys = Config.getOption('UseVarLockSubsys', True)

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 not fname.startswith('/etc/init.d/') and \
                    not fname.startswith('/etc/rc.d/init.d/'):
                continue

            basename = os.path.basename(fname)
            initscript_list.append(basename)
            if pkgfile.mode & 0500 != 0500:
                printError(pkg, 'init-script-non-executable', fname)

            if "." in basename:
                printError(pkg, 'init-script-name-with-dot', fname)

            # check chkconfig call in %post and %preun
            postin = pkg[rpm.RPMTAG_POSTIN] or \
                pkg.scriptprog(rpm.RPMTAG_POSTINPROG)
            if not postin:
                printError(pkg, 'init-script-without-chkconfig-postin', fname)
            elif not chkconfig_regex.search(postin):
                printError(pkg, 'postin-without-chkconfig', fname)

            preun = pkg[rpm.RPMTAG_PREUN] or \
                pkg.scriptprog(rpm.RPMTAG_PREUNPROG)
            if not preun:
                printError(pkg, 'init-script-without-chkconfig-preun', fname)
            elif 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)
                    elif 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 use_subsys and 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 and use_subsys:
                printError(pkg, 'subsys-not-used', fname)
            elif subsys_regex_found and not use_subsys:
                printError(pkg, 'subsys-unsupported', fname)

        if len(initscript_list) == 1:
            pkgname = re.sub("-sysvinit$", "", pkg.name.lower())
            goodnames = (pkgname, pkgname + 'd')
            if 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 script.''',

'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 script.''',

'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-unsupported',
'''The init script uses /var/lock/subsys which is not supported by
this distribution.''',

'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