]> git.ipfire.org Git - thirdparty/systemd.git/blame - hwdb.d/parse_hwdb.py
Merge pull request #16782 from keszybz/seccomp-use-cleanup
[thirdparty/systemd.git] / hwdb.d / parse_hwdb.py
CommitLineData
3e67e5c9 1#!/usr/bin/env python3
d8a0bcfd 2# SPDX-License-Identifier: MIT
0c9836c0 3#
818bf546 4# This file is distributed under the MIT license, see below.
0c9836c0 5#
acb98601 6# The MIT License (MIT)
0c9836c0 7#
acb98601
ZJS
8# Permission is hereby granted, free of charge, to any person obtaining a copy
9# of this software and associated documentation files (the "Software"), to deal
10# in the Software without restriction, including without limitation the rights
11# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12# copies of the Software, and to permit persons to whom the Software is
13# furnished to do so, subject to the following conditions:
0c9836c0 14#
acb98601
ZJS
15# The above copyright notice and this permission notice shall be included in
16# all copies or substantial portions of the Software.
17#
18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24# SOFTWARE.
0c9836c0 25
c3f6a561
ZJS
26import glob
27import string
28import sys
29import os
0c9836c0
ZJS
30
31try:
af182e78 32 from pyparsing import (Word, White, Literal, ParserElement, Regex, LineEnd,
8d9d1e3a 33 OneOrMore, Combine, Or, Optional, Suppress, Group,
0c9836c0 34 nums, alphanums, printables,
086c001e 35 stringEnd, pythonStyleComment, QuotedString,
0c9836c0
ZJS
36 ParseBaseException)
37except ImportError:
c3f6a561
ZJS
38 print('pyparsing is not available')
39 sys.exit(77)
0c9836c0
ZJS
40
41try:
42 from evdev.ecodes import ecodes
43except ImportError:
44 ecodes = None
45 print('WARNING: evdev is not available')
46
1258f088
ZJS
47try:
48 from functools import lru_cache
49except ImportError:
50 # don't do caching on old python
51 lru_cache = lambda: (lambda f: f)
52
0c9836c0 53EOL = LineEnd().suppress()
f644a6da 54EMPTYLINE = LineEnd()
0c9836c0
ZJS
55COMMENTLINE = pythonStyleComment + EOL
56INTEGER = Word(nums)
086c001e 57STRING = QuotedString('"')
0c9836c0 58REAL = Combine((INTEGER + Optional('.' + Optional(INTEGER))) ^ ('.' + INTEGER))
7fdc73af 59SIGNED_REAL = Combine(Optional(Word('-+')) + REAL)
0c9836c0
ZJS
60UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_')
61
aa549ff3 62# Those patterns are used in type-specific matches
0c9836c0
ZJS
63TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'),
64 'evdev': ('name', 'atkbd', 'input'),
ffac3034 65 'id-input': ('modalias'),
0c9836c0 66 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'),
c8ec393b 67 'joystick': ('i8042', 'rmi', 'bluetooth', 'usb'),
0c9836c0 68 'keyboard': ('name', ),
7fdc73af 69 'sensor': ('modalias', ),
8d9d1e3a 70 }
0c9836c0 71
aa549ff3
ZJS
72# Patterns that are used to set general properties on a device
73GENERAL_MATCHES = {'acpi',
74 'bluetooth',
75 'usb',
76 'pci',
77 'sdio',
78 'vmbus',
79 'OUI',
80 }
81
77547d53
ZJS
82def upperhex_word(length):
83 return Word(nums + 'ABCDEF', exact=length)
84
1258f088 85@lru_cache()
0c9836c0
ZJS
86def hwdb_grammar():
87 ParserElement.setDefaultWhitespaceChars('')
88
89 prefix = Or(category + ':' + Or(conn) + ':'
90 for category, conn in TYPES.items())
aa549ff3
ZJS
91
92 matchline_typed = Combine(prefix + Word(printables + ' ' + '®'))
457763aa 93 matchline_general = Combine(Or(GENERAL_MATCHES) + ':' + Word(printables + ' ' + '®'))
aa549ff3
ZJS
94 matchline = (matchline_typed | matchline_general) + EOL
95
0c9836c0 96 propertyline = (White(' ', exact=1).suppress() +
7fdc73af 97 Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.!-;, "') - Optional(pythonStyleComment)) +
0c9836c0
ZJS
98 EOL)
99 propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
100
101 group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
102 OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
8d9d1e3a 103 (EMPTYLINE ^ stringEnd()).suppress())
0c9836c0
ZJS
104 commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
105
2382a2e3 106 grammar = OneOrMore(Group(group)('GROUPS*') ^ commentgroup) + stringEnd()
0c9836c0
ZJS
107
108 return grammar
109
1258f088 110@lru_cache()
0c9836c0
ZJS
111def property_grammar():
112 ParserElement.setDefaultWhitespaceChars(' ')
113
7fdc73af
ZJS
114 dpi_setting = (Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ'))('SETTINGS*')
115 mount_matrix_row = SIGNED_REAL + ',' + SIGNED_REAL + ',' + SIGNED_REAL
116 mount_matrix = (mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX')
117
118 props = (('MOUSE_DPI', Group(OneOrMore(dpi_setting))),
0c9836c0 119 ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER),
a6a8e60b 120 ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER),
9107a337
PH
121 ('MOUSE_WHEEL_CLICK_COUNT', INTEGER),
122 ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER),
aa549ff3 123 ('ID_AUTOSUSPEND', Literal('1')),
ffac3034
PH
124 ('ID_INPUT', Literal('1')),
125 ('ID_INPUT_ACCELEROMETER', Literal('1')),
126 ('ID_INPUT_JOYSTICK', Literal('1')),
127 ('ID_INPUT_KEY', Literal('1')),
128 ('ID_INPUT_KEYBOARD', Literal('1')),
129 ('ID_INPUT_MOUSE', Literal('1')),
130 ('ID_INPUT_POINTINGSTICK', Literal('1')),
131 ('ID_INPUT_SWITCH', Literal('1')),
132 ('ID_INPUT_TABLET', Literal('1')),
133 ('ID_INPUT_TABLET_PAD', Literal('1')),
134 ('ID_INPUT_TOUCHPAD', Literal('1')),
135 ('ID_INPUT_TOUCHSCREEN', Literal('1')),
0c9836c0
ZJS
136 ('ID_INPUT_TRACKBALL', Literal('1')),
137 ('POINTINGSTICK_SENSITIVITY', INTEGER),
138 ('POINTINGSTICK_CONST_ACCEL', REAL),
c8ec393b 139 ('ID_INPUT_JOYSTICK_INTEGRATION', Or(('internal', 'external'))),
0c9836c0 140 ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
086c001e 141 ('XKB_FIXED_LAYOUT', STRING),
8d9d1e3a 142 ('XKB_FIXED_VARIANT', STRING),
d7d31692 143 ('XKB_FIXED_MODEL', STRING),
b698b5cf
BN
144 ('KEYBOARD_LED_NUMLOCK', Literal('0')),
145 ('KEYBOARD_LED_CAPSLOCK', Literal('0')),
7fdc73af 146 ('ACCEL_MOUNT_MATRIX', mount_matrix),
331e34fe 147 ('ACCEL_LOCATION', Or(('display', 'base'))),
1c5b427f 148 ('PROXIMITY_NEAR_LEVEL', INTEGER),
8d9d1e3a 149 )
0c9836c0
ZJS
150 fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
151 for name, val in props]
152 kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
153 - Suppress('=') -
154 ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
8d9d1e3a 155 ]
0c9836c0
ZJS
156 abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
157 - Suppress('=') -
158 Word(nums + ':')('VALUE')
8d9d1e3a 159 ]
0c9836c0 160
fe2a2a4f 161 grammar = Or(fixed_props + kbd_props + abs_props) + EOL
0c9836c0
ZJS
162
163 return grammar
164
165ERROR = False
166def error(fmt, *args, **kwargs):
167 global ERROR
168 ERROR = True
169 print(fmt.format(*args, **kwargs))
170
171def convert_properties(group):
172 matches = [m[0] for m in group.MATCHES]
173 props = [p[0] for p in group.PROPERTIES]
174 return matches, props
175
176def parse(fname):
177 grammar = hwdb_grammar()
178 try:
aeceb390
MP
179 with open(fname, 'r', encoding='UTF-8') as f:
180 parsed = grammar.parseFile(f)
0c9836c0
ZJS
181 except ParseBaseException as e:
182 error('Cannot parse {}: {}', fname, e)
183 return []
184 return [convert_properties(g) for g in parsed.GROUPS]
185
77547d53 186def check_matches(groups):
0c9836c0 187 matches = sum((group[0] for group in groups), [])
77547d53
ZJS
188
189 # This is a partial check. The other cases could be also done, but those
190 # two are most commonly wrong.
191 grammars = { 'usb' : 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4)),
192 'pci' : 'v' + upperhex_word(8) + Optional('d' + upperhex_word(8)),
193 }
194
195 for match in matches:
196 prefix, rest = match.split(':', maxsplit=1)
197 gr = grammars.get(prefix)
198 if gr:
199 try:
200 gr.parseString(rest)
201 except ParseBaseException as e:
202 error('Pattern {!r} is invalid: {}', rest, e)
203 continue
204 if rest[-1] not in '*:':
205 error('pattern {} does not end with "*" or ":"', match)
206
0c9836c0
ZJS
207 matches.sort()
208 prev = None
209 for match in matches:
210 if match == prev:
211 error('Match {!r} is duplicated', match)
212 prev = match
213
214def check_one_default(prop, settings):
215 defaults = [s for s in settings if s.DEFAULT]
216 if len(defaults) > 1:
217 error('More than one star entry: {!r}', prop)
218
494d16aa
ZJS
219def check_one_mount_matrix(prop, value):
220 numbers = [s for s in value if s not in {';', ','}]
221 if len(numbers) != 9:
222 error('Wrong accel matrix: {!r}', prop)
223 try:
224 numbers = [abs(float(number)) for number in numbers]
225 except ValueError:
226 error('Wrong accel matrix: {!r}', prop)
227 bad_x, bad_y, bad_z = max(numbers[0:3]) == 0, max(numbers[3:6]) == 0, max(numbers[6:9]) == 0
228 if bad_x or bad_y or bad_z:
229 error('Mount matrix is all zero in {} row: {!r}',
230 'x' if bad_x else ('y' if bad_y else 'z'),
231 prop)
232
0c9836c0
ZJS
233def check_one_keycode(prop, value):
234 if value != '!' and ecodes is not None:
235 key = 'KEY_' + value.upper()
12c7d4d6
ZJS
236 if not (key in ecodes or
237 value.upper() in ecodes or
238 # new keys added in kernel 5.5
239 'KBD_LCD_MENU' in key):
240 error('Keycode {} unknown', key)
0c9836c0
ZJS
241
242def check_properties(groups):
243 grammar = property_grammar()
244 for matches, props in groups:
245 prop_names = set()
246 for prop in props:
247 # print('--', prop)
248 prop = prop.partition('#')[0].rstrip()
249 try:
250 parsed = grammar.parseString(prop)
251 except ParseBaseException as e:
252 error('Failed to parse: {!r}', prop)
253 continue
254 # print('{!r}'.format(parsed))
255 if parsed.NAME in prop_names:
256 error('Property {} is duplicated', parsed.NAME)
257 prop_names.add(parsed.NAME)
258 if parsed.NAME == 'MOUSE_DPI':
259 check_one_default(prop, parsed.VALUE.SETTINGS)
494d16aa
ZJS
260 elif parsed.NAME == 'ACCEL_MOUNT_MATRIX':
261 check_one_mount_matrix(prop, parsed.VALUE)
0c9836c0 262 elif parsed.NAME.startswith('KEYBOARD_KEY_'):
2382a2e3
ZJS
263 val = parsed.VALUE if isinstance(parsed.VALUE, str) else parsed.VALUE[0]
264 check_one_keycode(prop, val)
0c9836c0
ZJS
265
266def print_summary(fname, groups):
f3c80bc0
ZJS
267 n_matches = sum(len(matches) for matches, props in groups)
268 n_props = sum(len(props) for matches, props in groups)
0c9836c0 269 print('{}: {} match groups, {} matches, {} properties'
f3c80bc0
ZJS
270 .format(fname, len(groups), n_matches, n_props))
271
272 if n_matches == 0 or n_props == 0:
273 error('{}: no matches or props'.format(fname))
0c9836c0
ZJS
274
275if __name__ == '__main__':
b32ae3aa 276 args = sys.argv[1:] or sorted(glob.glob(os.path.dirname(sys.argv[0]) + '/[67][0-9]-*.hwdb'))
c3f6a561
ZJS
277
278 for fname in args:
0c9836c0
ZJS
279 groups = parse(fname)
280 print_summary(fname, groups)
77547d53 281 check_matches(groups)
0c9836c0
ZJS
282 check_properties(groups)
283
284 sys.exit(ERROR)