suse-check-optional-dependencies.diff
[platform/upstream/rpmlint.git] / PostCheck.py
1 # -*- coding: utf-8 -*-
2 #############################################################################
3 # Project         : Mandriva Linux
4 # Module          : rpmlint
5 # File            : PostCheck.py
6 # Version         : $Id: PostCheck.py 1885 2011-09-13 18:15:29Z scop $
7 # Author          : Frederic Lepied
8 # Created On      : Wed Jul  5 13:30:17 2000
9 # Purpose         : Check post/pre scripts
10 #############################################################################
11
12 import os
13 import re
14 import types
15
16 import rpm
17
18 from Filter import addDetails, printError, printWarning
19 import AbstractCheck
20 import Config
21 import Pkg
22
23
24 DEFAULT_VALID_SHELLS = ('<lua>',
25                         '/bin/sh',
26                         '/bin/bash',
27                         '/sbin/sash',
28                         '/usr/bin/perl',
29                         '/sbin/ldconfig',
30                         )
31
32 DEFAULT_EMPTY_SHELLS = ('/sbin/ldconfig',
33                         )
34
35 valid_shells = Config.getOption('ValidShells', DEFAULT_VALID_SHELLS)
36 empty_shells = Config.getOption('ValidEmptyShells', DEFAULT_EMPTY_SHELLS)
37 # shells that grok the -n switch for debugging
38 syntaxcheck_shells = ('/bin/sh', '/bin/bash')
39
40 percent_regex = re.compile('^[^#]*%{?\w{3,}', re.MULTILINE)
41 bracket_regex = re.compile('^[^#]*if.*[^ :\]]\]', re.MULTILINE)
42 home_regex = re.compile('[^a-zA-Z]+~/|\${?HOME(\W|$)', re.MULTILINE)
43 dangerous_command_regex = re.compile("(^|[;\|`]|&&|$\()\s*(?:\S*/s?bin/)?(cp|mv|ln|tar|rpm|chmod|chown|rm|cpio|install|perl|userdel|groupdel)\s", re.MULTILINE)
44 selinux_regex = re.compile("(^|[;\|`]|&&|$\()\s*(?:\S*/s?bin/)?(chcon|runcon)\s", re.MULTILINE)
45 single_command_regex = re.compile("^[ \n]*([^ \n]+)[ \n]*$")
46 tmp_regex = re.compile('^[^#]*\s(/var)?/tmp', re.MULTILINE)
47 menu_regex = re.compile('^/usr/lib/menu/|^/etc/menu-methods/|^/usr/share/applications/')
48 bogus_var_regex = re.compile('(\${?RPM_BUILD_(ROOT|DIR)}?)')
49
50 prereq_assoc = (
51 #    ['chkconfig', ('chkconfig', '/sbin/chkconfig')],
52     ['chkfontpath', ('chkfontpath', '/usr/sbin/chkfontpath')],
53     ['rpm-helper', ('rpm-helper',)],
54     )
55
56 for p in prereq_assoc:
57     p[0] = re.compile('^[^#]+' + p[0], re.MULTILINE)
58
59 # pychecker fix
60 del p
61
62 script_tags = [
63     (rpm.RPMTAG_PREIN,          rpm.RPMTAG_PREINPROG,         '%pre'),
64     (rpm.RPMTAG_POSTIN,         rpm.RPMTAG_POSTINPROG,        '%post'),
65     (rpm.RPMTAG_PREUN,          rpm.RPMTAG_PREUNPROG,         '%preun'),
66     (rpm.RPMTAG_POSTUN,         rpm.RPMTAG_POSTUNPROG,        '%postun'),
67     (rpm.RPMTAG_TRIGGERSCRIPTS, rpm.RPMTAG_TRIGGERSCRIPTPROG, '%trigger'),
68     (rpm.RPMTAG_PRETRANS,       rpm.RPMTAG_PRETRANSPROG,      '%pretrans'),
69     (rpm.RPMTAG_POSTTRANS,      rpm.RPMTAG_POSTTRANSPROG,     '%posttrans'),
70     (rpm.RPMTAG_VERIFYSCRIPT,   rpm.RPMTAG_VERIFYSCRIPTPROG,  '%verifyscript'),
71     ]
72
73 def incorrect_shell_script(prog, shellscript):
74     if not shellscript:
75         return False
76     # TODO: test that "prog" is available/executable
77     tmpfile, tmpname = Pkg.mktemp()
78     try:
79         tmpfile.write(shellscript)
80         tmpfile.close()
81         ret = Pkg.getstatusoutput((prog, '-n', tmpname))
82     finally:
83         tmpfile.close()
84         os.remove(tmpname)
85     return ret[0]
86
87 def incorrect_perl_script(prog, perlscript):
88     if not perlscript:
89         return False
90     # TODO: test that "prog" is available/executable
91     tmpfile, tmpname = Pkg.mktemp()
92     try:
93         tmpfile.write(perlscript)
94         tmpfile.close()
95         ret = Pkg.getstatusoutput((prog, '-wc', tmpname))
96     finally:
97         tmpfile.close()
98         os.remove(tmpname)
99     return ret[0]
100
101 class PostCheck(AbstractCheck.AbstractCheck):
102
103     def __init__(self):
104         AbstractCheck.AbstractCheck.__init__(self, 'PostCheck')
105
106     def check(self, pkg):
107         # Check only binary package
108         if pkg.isSource():
109             return
110
111         prereq = [x[0] for x in pkg.prereq()]
112         files = pkg.files()
113
114         for tag in script_tags:
115             script = pkg[tag[0]]
116
117             if not isinstance(script, types.ListType):
118                 prog = pkg.scriptprog(tag[1])
119                 if prog:
120                     prog = prog.split()[0]
121                 self.check_aux(pkg, files, prog, script, tag[2], prereq)
122             else:
123                 prog = pkg[tag[1]]
124                 for idx in range(0, len(prog)):
125                     self.check_aux(
126                         pkg, files, prog[idx], script[idx], tag[2], prereq)
127
128         ghost_files = pkg.ghostFiles()
129         if ghost_files:
130             postin = pkg[rpm.RPMTAG_POSTIN]
131             prein = pkg[rpm.RPMTAG_PREIN]
132             if not postin and not prein:
133                 printWarning(pkg, 'ghost-files-without-postin')
134             else:
135                 for f in ghost_files:
136                     if (not postin or f not in postin) and \
137                        (not prein or f not in prein) and \
138                        f not in pkg.missingOkFiles():
139                         printWarning(pkg,
140                                      'postin-without-ghost-file-creation', f)
141
142     def check_aux(self, pkg, files, prog, script, tag, prereq):
143         if script:
144             if prog:
145                 if prog not in valid_shells:
146                     printError(pkg, 'invalid-shell-in-' + tag, prog)
147                 if prog in empty_shells:
148                     printError(pkg, 'non-empty-' + tag, prog)
149             if prog in syntaxcheck_shells or prog == '/usr/bin/perl':
150                 if percent_regex.search(script):
151                     printWarning(pkg, 'percent-in-' + tag)
152                 if bracket_regex.search(script):
153                     printWarning(pkg, 'spurious-bracket-in-' + tag)
154                 res = dangerous_command_regex.search(script)
155                 if res:
156                     printWarning(pkg, 'dangerous-command-in-' + tag,
157                                  res.group(2))
158                 res = selinux_regex.search(script)
159                 if res:
160                     printError(pkg, 'forbidden-selinux-command-in-' + tag,
161                                res.group(2))
162
163                 if 'update-menus' in script:
164                     menu_error = True
165                     for f in files:
166                         if menu_regex.search(f):
167                             menu_error = False
168                             break
169                     if menu_error:
170                         printError(pkg, 'update-menus-without-menu-file-in-' +
171                                    tag)
172                 if tmp_regex.search(script):
173                     printError(pkg, 'use-tmp-in-' + tag)
174                 for c in prereq_assoc:
175                     if c[0].search(script):
176                         found = False
177                         for p in c[1]:
178                             if p in prereq or p in files:
179                                 found = True
180                                 break
181                         if not found:
182                             printError(pkg, 'no-prereq-on', c[1][0])
183
184             if prog in syntaxcheck_shells:
185                 if incorrect_shell_script(prog, script):
186                     printError(pkg, 'shell-syntax-error-in-' + tag)
187                 if home_regex.search(script):
188                     printError(pkg, 'use-of-home-in-' + tag)
189                 res = bogus_var_regex.search(script)
190                 if res:
191                     printWarning(pkg, 'bogus-variable-use-in-' + tag,
192                                  res.group(1))
193
194             if prog == '/usr/bin/perl':
195                 if incorrect_perl_script(prog, script):
196                     printError(pkg, 'perl-syntax-error-in-' + tag)
197             elif prog.endswith('sh'):
198                 res = single_command_regex.search(script)
199                 if res:
200                     printWarning(pkg, 'one-line-command-in-' + tag,
201                                  res.group(1))
202
203         elif prog not in empty_shells and prog in valid_shells:
204             printWarning(pkg, 'empty-' + tag)
205
206 # Create an object to enable the auto registration of the test
207 check = PostCheck()
208
209 # Add information about checks
210 addDetails(
211 'postin-without-ghost-file-creation',
212 '''A file tagged as ghost is not created during %prein nor during %postin.''',
213 )
214 for scriptlet in (
215     '%pre', '%post', '%preun', '%postun', '%pretrans', '%posttrans',
216     '%trigger', '%triggerin', '%triggerprein', '%triggerun', '%triggerpostun',
217     '%verifyscript'):
218     addDetails(
219 'one-line-command-in-%s' % scriptlet,
220 '''You should use %s -p <command> instead of using:
221
222 %s
223 <command>
224
225 It will avoid the fork of a shell interpreter to execute your command as
226 well as allows rpm to automatically mark the dependency on your command
227 for the excecution of the scriptlet.''' % (scriptlet, scriptlet),
228
229 'percent-in-%s' % scriptlet,
230 '''The %s scriptlet contains a "%%" in a context which might indicate it being
231 fallout from an rpm macro/variable which was not expanded during build.
232 Investigate whether this is the case and fix if appropriate.''' % scriptlet,
233
234 'spurious-bracket-in-%s' % scriptlet,
235 '''The %s scriptlet contains an "if []" construct without a space before
236 the "]".''' % scriptlet,
237
238 'forbidden-selinux-command-in-%s' % scriptlet,
239 '''A command which requires intimate knowledge about a specific SELinux
240 policy type was found in the scriptlet. These types are subject to change
241 on a policy version upgrade. Use the restorecon command which queries the
242 currently loaded policy for the correct type instead.''',
243
244 'non-empty-%s' % scriptlet,
245 '''Scriptlets for the interpreter mentioned in the message should be empty.
246 One common case where they are unintentionally not is when the specfile
247 contains comments after the scriptlet and before the next section. Review
248 and clean up the scriptlet contents if appropriate.''',
249 )
250
251 # PostCheck.py ends here
252
253 # Local variables:
254 # indent-tabs-mode: nil
255 # py-indent-offset: 4
256 # End:
257 # ex: ts=4 sw=4 et