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
|
#!/usr/bin/env python3
import sys, os, subprocess, re, shutil
whitelist = (
# type is supported by dash
'if type systemctl >/dev/null 2>/dev/null; then',
'if type systemd-tmpfiles >/dev/null 2>/dev/null; then',
'if type update-rc.d >/dev/null 2>/dev/null; then',
'command -v',
# HOSTNAME is set locally
'buildhistory_single_commit "$CMDLINE" "$HOSTNAME"',
# False-positive, match is a grep not shell expression
'grep "^$groupname:[^:]*:[^:]*:\\([^,]*,\\)*$username\\(,[^,]*\\)*"',
# TODO verify dash's '. script args' behaviour
'. $target_sdk_dir/${oe_init_build_env_path} $target_sdk_dir >> $LOGFILE'
)
def is_whitelisted(s):
for w in whitelist:
if w in s:
return True
return False
def process(recipe, function, script):
import tempfile
if not script.startswith("#!"):
script = "#! /bin/sh\n" + script
fn = tempfile.NamedTemporaryFile(mode="w+t")
fn.write(script)
fn.flush()
try:
subprocess.check_output(("checkbashisms.pl", fn.name), universal_newlines=True, stderr=subprocess.STDOUT)
# No bashisms, so just return
return
except subprocess.CalledProcessError as e:
# TODO check exit code is 1
# Replace the temporary filename with the function and split it
output = e.output.replace(fn.name, function).splitlines()
if len(results) % 2 != 0:
print("Unexpected output from checkbashism: %s" % str(output))
return
# Turn the output into a list of (message, source) values
result = []
# Check the results against the whitelist
for message, source in zip(output[0::2], output[1::2]):
if not is_whitelisted(source):
result.append((message, source))
return result
def get_tinfoil():
scripts_path = os.path.dirname(os.path.realpath(__file__))
lib_path = scripts_path + '/lib'
sys.path = sys.path + [lib_path]
import scriptpath
scriptpath.add_bitbake_lib_path()
import bb.tinfoil
tinfoil = bb.tinfoil.Tinfoil()
tinfoil.prepare()
# tinfoil.logger.setLevel(logging.WARNING)
return tinfoil
if __name__=='__main__':
import shutil
if shutil.which("checkbashisms.pl") is None:
print("Cannot find checkbashisms.pl on $PATH")
sys.exit(1)
tinfoil = get_tinfoil()
# This is only the default configuration and should iterate over
# recipecaches to handle multiconfig environments
pkg_pn = tinfoil.cooker.recipecaches[""].pkg_pn
# TODO: use argparse and have --help
if len(sys.argv) > 1:
initial_pns = sys.argv[1:]
else:
initial_pns = sorted(pkg_pn)
pns = []
print("Generating file list...")
for pn in initial_pns:
for fn in pkg_pn[pn]:
# There's no point checking multiple BBCLASSEXTENDed variants of the same recipe
realfn, _, _ = bb.cache.virtualfn2realfn(fn)
if realfn not in pns:
pns.append(realfn)
def func(fn):
result = []
data = tinfoil.parse_recipe_file(fn)
for key in data.keys():
if data.getVarFlag(key, "func", True) and not data.getVarFlag(key, "python", True):
script = data.getVar(key, False)
if not script: continue
#print ("%s:%s" % (fn, key))
r = process(fn, key, script)
if r: result.extend(r)
return fn, result
print("Scanning scripts...\n")
import multiprocessing
pool = multiprocessing.Pool()
for pn,results in pool.imap(func, pns):
if results:
print(pn)
for message,source in results:
print(" %s\n %s" % (message, source))
print()
|