blob: 0e0da32ed3fd175f152ce8d802768beb96a90bce [file] [log] [blame]
Mike Frysingerb9743c62020-02-20 02:53:55 -05001#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# Copyright 2020 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Make sure packages don't create random paths outside of existing norms."""
8
9from __future__ import print_function
10
11import argparse
12import fnmatch
13import logging # pylint: disable=cros-logging-import
14import os
15import sys
16
17
18# NB: Do not add any new entries here without wider discussion.
19
20
21# Paths that are allowed in the / dir.
22VALID_ROOT = {
23 'bin', 'boot', 'dev', 'etc', 'home', 'lib', 'lib32', 'lib64', 'media',
24 'mnt', 'opt', 'proc', 'root', 'run', 'sbin', 'sys', 'tmp', 'usr', 'var',
25}
26
27# Paths that are allowed in the / dir for boards.
28VALID_BOARD_ROOT = {
29 'build', 'firmware',
30 # TODO(): We should clean this up.
31 'postinst',
32}
33
34# Paths that are allowed in the / dir for the SDK chroot.
35VALID_HOST_ROOT = set()
36
37# Paths under / that should not have any subdirs.
38NOSUBDIRS_ROOT = {
39 'bin', 'dev', 'proc', 'sbin', 'sys', 'tmp',
40}
41
42# Paths that are allowed in the /usr dir.
43VALID_USR = {
44 'bin', 'include', 'lib', 'lib32', 'lib64', 'libexec', 'sbin', 'share',
45 'src',
46}
47
48# Paths that are allowed in the /usr dir for boards.
49VALID_BOARD_USR = {
50 # Boards install into /usr/local for test images.
51 'local',
52}
53
54# Paths that are allowed in the /usr dir for the SDK chroot.
55VALID_HOST_USR = set()
56
57# Paths under /usr that should not have any subdirs.
58NOSUBDIRS_USR = {
59 'bin', 'sbin',
60}
61
62# Valid toolchain targets. We don't want to add any more non-standard ones.
63# targets that use *-cros-* as the vendor are OK to add more.
64KNOWN_TARGETS = {
65 'arm-none-eabi',
66 'i686-pc-linux-gnu',
67 'x86_64-pc-linux-gnu',
68 'arm*-cros-eabi',
69 '*-cros-linux-gnu*',
70}
71
72# These packages need fixing.
73# NB: Do *not* add more packages here.
74BAD_ROOT_PACKAGES = {
75 # TODO(crbug.com/1003107): Delete this.
76 'dev-go/go-tools',
Aileen Cheng3b891332020-03-03 09:03:23 -080077 # TODO(b/150694105): Delete this.
78 'net-wireless/wpantund',
Mike Frysingerb9743c62020-02-20 02:53:55 -050079}
80
81# These SDK packages need cleanup.
82# NB: Do *not* add more packages here.
83BAD_HOST_USR_LOCAL_PACKAGES = {
84 'app-crypt/nss',
85}
86
87# Ignore some packages installing into /var for now.
88# NB: Do *not* add more packages here.
89BAD_VAR_PACKAGES = {
90 'app-accessibility/brltty',
91 'app-admin/eselect',
92 'app-admin/puppet',
93 'app-admin/rsyslog',
94 'app-admin/sudo',
95 'app-admin/sysstat',
96 'app-admin/webapp-config',
97 'app-crypt/mit-krb5',
98 'app-crypt/trousers',
99 'app-emulation/containerd',
100 'app-emulation/lxc',
101 'chromeos-base/chromeos-initramfs',
102 # https://crbug.com/1054646
103 'chromeos-base/devserver',
104 # https://crbug.com/1007402
105 'chromeos-base/factory',
106 'chromeos-base/factory-board',
107 'dev-python/django',
108 'media-gfx/sane-backends',
109 'media-sound/alsa-utils',
110 'net-analyzer/netperf',
111 'net-dns/dnsmasq',
Aileen Cheng3b891332020-03-03 09:03:23 -0800112 # TODO(b/150694545): Delete this.
Aileen Chengc1e163a2020-03-02 14:47:00 -0800113 'net-firewall/ebtables',
Mike Frysingerb9743c62020-02-20 02:53:55 -0500114 'net-firewall/iptables',
Mike Frysinger88028fe2020-03-04 18:53:39 -0500115 'net-firewall/nftables',
Mike Frysingerb9743c62020-02-20 02:53:55 -0500116 'net-fs/samba',
Mike Frysinger0669e002020-03-02 16:56:24 -0500117 'net-misc/chrony',
Mike Frysingerb9743c62020-02-20 02:53:55 -0500118 'net-misc/dhcpcd',
119 'net-misc/openssh',
120 'net-print/cups',
121 'sys-apps/dbus',
122 'sys-apps/fwupd',
123 'sys-apps/iproute2',
124 'sys-apps/journald',
125 'sys-apps/portage',
126 'sys-apps/sandbox',
127 'sys-apps/systemd',
128 'sys-apps/usbguard',
129 'sys-kernel/loonix-initramfs',
130 'sys-libs/glibc',
131 'sys-process/audit',
132 'www-servers/nginx',
133 'x11-base/xwayland',
134}
135
136# Ignore some packages installing into /run for now.
137# NB: Do *not* add more packages here.
138BAD_RUN_PACKAGES = {
139 'app-accessibility/brltty',
140 'net-fs/samba',
141}
142
143# Ignore some packages installing into /tmp for now.
144# NB: Do *not* add more packages here.
145BAD_TMP_PACKAGES = {
146 # https://crbug.com/1057059
147 'chromeos-base/chromeos-bsp-caroline-private',
Mike Frysinger833a8222020-03-02 14:04:36 -0500148 'chromeos-base/chromeos-bsp-elm-private',
Mike Frysinger7dd8d682020-03-03 11:18:29 -0500149 'chromeos-base/chromeos-config-bsp',
Mike Frysinger833a8222020-03-02 14:04:36 -0500150 'chromeos-base/chromeos-config-bsp-coral',
151 'chromeos-base/chromeos-config-bsp-coral-private',
152 'chromeos-base/chromeos-config-bsp-nami',
153 'chromeos-base/chromeos-config-bsp-scarlet-private',
Mike Frysingerb9743c62020-02-20 02:53:55 -0500154 'chromeos-base/cros-config-test',
155}
156
157
158def has_subdirs(path):
159 """See if |path| has any subdirs."""
160 # These checks are helpful for manually running the script when debugging.
161 if os.path.ismount(path):
162 logging.warning('Ignoring mounted dir for subdir check: %s', path)
163 return False
164
165 if os.path.join(os.getenv('SYSROOT', '/'), 'tmp') == path:
166 logging.warning('Ignoring live dir: %s', path)
167 return False
168
169 for _, dirs, _ in os.walk(path):
170 if dirs:
171 logging.error('Subdirs found in a dir that should be empty:\n %s\n'
172 ' |-- %s', path, '\n |-- '.join(sorted(dirs)))
173 return True
174 break
175
176 return False
177
178
179def check_usr(usr, host=False):
180 """Check the /usr filesystem at |usr|."""
181 ret = True
182
183 # Not all packages install into /usr.
184 if not os.path.exists(usr):
185 return ret
186
187 atom = get_current_package()
188 paths = set(os.listdir(usr))
189 unknown = paths - VALID_USR
190 for target in KNOWN_TARGETS:
191 unknown = set(x for x in unknown if not fnmatch.fnmatch(x, target))
192 if host:
193 unknown -= VALID_HOST_USR
194
195 if atom in BAD_HOST_USR_LOCAL_PACKAGES:
196 logging.warning('Ignoring known bad /usr/local install for now')
197 unknown -= {'local'}
198 else:
199 unknown -= VALID_BOARD_USR
200
201 if atom in {'chromeos-base/ap-daemons'}:
202 logging.warning('Ignoring known bad /usr install for now')
203 unknown -= {'www'}
204
205 if unknown:
206 logging.error('Paths are not allowed in the /usr dir: %s', sorted(unknown))
207 ret = False
208
209 for path in NOSUBDIRS_USR:
210 if has_subdirs(os.path.join(usr, path)):
211 ret = False
212
213 return ret
214
215
216def check_root(root, host=False):
217 """Check the filesystem |root|."""
218 ret = True
219
220 atom = get_current_package()
221 paths = set(os.listdir(root))
222 unknown = paths - VALID_ROOT
223 if host:
224 unknown -= VALID_HOST_ROOT
225 else:
226 unknown -= VALID_BOARD_ROOT
227
228 if atom in BAD_ROOT_PACKAGES:
229 logging.warning('Ignoring known bad / install for now')
230 elif unknown:
231 logging.error('Paths are not allowed in the root dir:\n %s\n |-- %s',
232 root, '\n |-- '.join(sorted(unknown)))
233 ret = False
234
235 # Some of these may have subdirs at runtime, but not from package installs.
236 for path in NOSUBDIRS_ROOT:
237 if has_subdirs(os.path.join(root, path)):
238 if path == 'tmp' and atom in BAD_TMP_PACKAGES:
239 logging.warning('Ignoring known bad /tmp install for now')
240 else:
241 ret = False
242
243 # Special case /var due to so many misuses currently.
244 if has_subdirs(os.path.join(root, 'var')):
245 if atom in BAD_VAR_PACKAGES:
246 logging.warning('Ignoring known bad /var install for now')
247 elif os.environ.get('PORTAGE_REPO_NAME') == 'portage-stable':
248 logging.warning('Ignoring bad /var install with portage-stable package '
249 'for now')
250 else:
251 ret = False
252 else:
253 if atom in BAD_VAR_PACKAGES:
254 logging.warning('Package has improved; please update BAD_VAR_PACKAGES')
255
256 # Special case /run due to so many misuses currently.
257 if has_subdirs(os.path.join(root, 'run')):
258 if atom in BAD_RUN_PACKAGES:
259 logging.warning('Ignoring known bad /run install for now')
260 elif os.environ.get('PORTAGE_REPO_NAME') == 'portage-stable':
261 logging.warning('Ignoring bad /run install with portage-stable package '
262 'for now')
263 else:
264 ret = False
265 else:
266 if atom in BAD_RUN_PACKAGES:
267 logging.warning('Package has improved; please update BAD_RUN_PACKAGES')
268
269 if not check_usr(os.path.join(root, 'usr'), host):
270 ret = False
271
272 return ret
273
274
275def get_current_package():
276 """Figure out what package is being built currently."""
277 if 'CATEGORY' in os.environ and 'PN' in os.environ:
278 return f'{os.environ.get("CATEGORY")}/{os.environ.get("PN")}'
279 else:
280 return None
281
282
283def get_parser():
284 """Get a CLI parser."""
285 parser = argparse.ArgumentParser(description=__doc__)
286 parser.add_argument('--host', default=None, action='store_true',
287 help='the filesystem is the host SDK, not board sysroot')
288 parser.add_argument('--board', dest='host', action='store_false',
289 help='the filesystem is a board sysroot')
290 parser.add_argument('root', nargs='?',
291 help='the rootfs to scan')
292 return parser
293
294
295def main(argv):
296 """The main func!"""
297 parser = get_parser()
298 opts = parser.parse_args(argv)
299
300 # Default to common portage env vars.
301 if opts.root is None:
302 for var in ('ED', 'D', 'ROOT'):
303 if var in os.environ:
304 logging.debug('Scanning filesystem root via $%s', var)
305 opts.root = os.environ[var]
306 break
307 if not opts.root:
308 parser.error('Need a valid rootfs to scan, but unable to detect one')
309
310 if opts.host is None:
311 if os.getenv('BOARD') == 'amd64-host':
312 opts.host = True
313 else:
314 opts.host = not bool(os.getenv('SYSROOT'))
315
316 if not check_root(opts.root, opts.host):
317 logging.critical(
318 "This package does not conform to CrOS's filesystem conventions. "
319 'Please review the paths flagged above and adjust its layout.')
320 return 1
321 else:
322 return 0
323
324
325if __name__ == '__main__':
326 sys.exit(main(sys.argv[1:]))