]> git.ipfire.org Git - thirdparty/systemd.git/blame - hwdb.d/parse_hwdb.py
Merge pull request #16058 from Werkov/fix-memory-protection-default
[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
62TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'),
63 'evdev': ('name', 'atkbd', 'input'),
ffac3034 64 'id-input': ('modalias'),
0c9836c0 65 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'),
c8ec393b 66 'joystick': ('i8042', 'rmi', 'bluetooth', 'usb'),
0c9836c0 67 'keyboard': ('name', ),
7fdc73af 68 'sensor': ('modalias', ),
8d9d1e3a 69 }
0c9836c0 70
1258f088 71@lru_cache()
0c9836c0
ZJS
72def hwdb_grammar():
73 ParserElement.setDefaultWhitespaceChars('')
74
75 prefix = Or(category + ':' + Or(conn) + ':'
76 for category, conn in TYPES.items())
77 matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL
78 propertyline = (White(' ', exact=1).suppress() +
7fdc73af 79 Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.!-;, "') - Optional(pythonStyleComment)) +
0c9836c0
ZJS
80 EOL)
81 propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
82
83 group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
84 OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
8d9d1e3a 85 (EMPTYLINE ^ stringEnd()).suppress())
0c9836c0
ZJS
86 commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
87
2382a2e3 88 grammar = OneOrMore(Group(group)('GROUPS*') ^ commentgroup) + stringEnd()
0c9836c0
ZJS
89
90 return grammar
91
1258f088 92@lru_cache()
0c9836c0
ZJS
93def property_grammar():
94 ParserElement.setDefaultWhitespaceChars(' ')
95
7fdc73af
ZJS
96 dpi_setting = (Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ'))('SETTINGS*')
97 mount_matrix_row = SIGNED_REAL + ',' + SIGNED_REAL + ',' + SIGNED_REAL
98 mount_matrix = (mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX')
99
100 props = (('MOUSE_DPI', Group(OneOrMore(dpi_setting))),
0c9836c0 101 ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER),
a6a8e60b 102 ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER),
9107a337
PH
103 ('MOUSE_WHEEL_CLICK_COUNT', INTEGER),
104 ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER),
ffac3034
PH
105 ('ID_INPUT', Literal('1')),
106 ('ID_INPUT_ACCELEROMETER', Literal('1')),
107 ('ID_INPUT_JOYSTICK', Literal('1')),
108 ('ID_INPUT_KEY', Literal('1')),
109 ('ID_INPUT_KEYBOARD', Literal('1')),
110 ('ID_INPUT_MOUSE', Literal('1')),
111 ('ID_INPUT_POINTINGSTICK', Literal('1')),
112 ('ID_INPUT_SWITCH', Literal('1')),
113 ('ID_INPUT_TABLET', Literal('1')),
114 ('ID_INPUT_TABLET_PAD', Literal('1')),
115 ('ID_INPUT_TOUCHPAD', Literal('1')),
116 ('ID_INPUT_TOUCHSCREEN', Literal('1')),
0c9836c0
ZJS
117 ('ID_INPUT_TRACKBALL', Literal('1')),
118 ('POINTINGSTICK_SENSITIVITY', INTEGER),
119 ('POINTINGSTICK_CONST_ACCEL', REAL),
c8ec393b 120 ('ID_INPUT_JOYSTICK_INTEGRATION', Or(('internal', 'external'))),
0c9836c0 121 ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
086c001e 122 ('XKB_FIXED_LAYOUT', STRING),
8d9d1e3a 123 ('XKB_FIXED_VARIANT', STRING),
d7d31692 124 ('XKB_FIXED_MODEL', STRING),
b698b5cf
BN
125 ('KEYBOARD_LED_NUMLOCK', Literal('0')),
126 ('KEYBOARD_LED_CAPSLOCK', Literal('0')),
7fdc73af 127 ('ACCEL_MOUNT_MATRIX', mount_matrix),
331e34fe 128 ('ACCEL_LOCATION', Or(('display', 'base'))),
1c5b427f 129 ('PROXIMITY_NEAR_LEVEL', INTEGER),
8d9d1e3a 130 )
0c9836c0
ZJS
131 fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
132 for name, val in props]
133 kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
134 - Suppress('=') -
135 ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
8d9d1e3a 136 ]
0c9836c0
ZJS
137 abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
138 - Suppress('=') -
139 Word(nums + ':')('VALUE')
8d9d1e3a 140 ]
0c9836c0 141
fe2a2a4f 142 grammar = Or(fixed_props + kbd_props + abs_props) + EOL
0c9836c0
ZJS
143
144 return grammar
145
146ERROR = False
147def error(fmt, *args, **kwargs):
148 global ERROR
149 ERROR = True
150 print(fmt.format(*args, **kwargs))
151
152def convert_properties(group):
153 matches = [m[0] for m in group.MATCHES]
154 props = [p[0] for p in group.PROPERTIES]
155 return matches, props
156
157def parse(fname):
158 grammar = hwdb_grammar()
159 try:
aeceb390
MP
160 with open(fname, 'r', encoding='UTF-8') as f:
161 parsed = grammar.parseFile(f)
0c9836c0
ZJS
162 except ParseBaseException as e:
163 error('Cannot parse {}: {}', fname, e)
164 return []
165 return [convert_properties(g) for g in parsed.GROUPS]
166
167def check_match_uniqueness(groups):
168 matches = sum((group[0] for group in groups), [])
169 matches.sort()
170 prev = None
171 for match in matches:
172 if match == prev:
173 error('Match {!r} is duplicated', match)
174 prev = match
175
176def check_one_default(prop, settings):
177 defaults = [s for s in settings if s.DEFAULT]
178 if len(defaults) > 1:
179 error('More than one star entry: {!r}', prop)
180
494d16aa
ZJS
181def check_one_mount_matrix(prop, value):
182 numbers = [s for s in value if s not in {';', ','}]
183 if len(numbers) != 9:
184 error('Wrong accel matrix: {!r}', prop)
185 try:
186 numbers = [abs(float(number)) for number in numbers]
187 except ValueError:
188 error('Wrong accel matrix: {!r}', prop)
189 bad_x, bad_y, bad_z = max(numbers[0:3]) == 0, max(numbers[3:6]) == 0, max(numbers[6:9]) == 0
190 if bad_x or bad_y or bad_z:
191 error('Mount matrix is all zero in {} row: {!r}',
192 'x' if bad_x else ('y' if bad_y else 'z'),
193 prop)
194
0c9836c0
ZJS
195def check_one_keycode(prop, value):
196 if value != '!' and ecodes is not None:
197 key = 'KEY_' + value.upper()
12c7d4d6
ZJS
198 if not (key in ecodes or
199 value.upper() in ecodes or
200 # new keys added in kernel 5.5
201 'KBD_LCD_MENU' in key):
202 error('Keycode {} unknown', key)
0c9836c0
ZJS
203
204def check_properties(groups):
205 grammar = property_grammar()
206 for matches, props in groups:
207 prop_names = set()
208 for prop in props:
209 # print('--', prop)
210 prop = prop.partition('#')[0].rstrip()
211 try:
212 parsed = grammar.parseString(prop)
213 except ParseBaseException as e:
214 error('Failed to parse: {!r}', prop)
215 continue
216 # print('{!r}'.format(parsed))
217 if parsed.NAME in prop_names:
218 error('Property {} is duplicated', parsed.NAME)
219 prop_names.add(parsed.NAME)
220 if parsed.NAME == 'MOUSE_DPI':
221 check_one_default(prop, parsed.VALUE.SETTINGS)
494d16aa
ZJS
222 elif parsed.NAME == 'ACCEL_MOUNT_MATRIX':
223 check_one_mount_matrix(prop, parsed.VALUE)
0c9836c0 224 elif parsed.NAME.startswith('KEYBOARD_KEY_'):
2382a2e3
ZJS
225 val = parsed.VALUE if isinstance(parsed.VALUE, str) else parsed.VALUE[0]
226 check_one_keycode(prop, val)
0c9836c0
ZJS
227
228def print_summary(fname, groups):
f3c80bc0
ZJS
229 n_matches = sum(len(matches) for matches, props in groups)
230 n_props = sum(len(props) for matches, props in groups)
0c9836c0 231 print('{}: {} match groups, {} matches, {} properties'
f3c80bc0
ZJS
232 .format(fname, len(groups), n_matches, n_props))
233
234 if n_matches == 0 or n_props == 0:
235 error('{}: no matches or props'.format(fname))
0c9836c0
ZJS
236
237if __name__ == '__main__':
b32ae3aa 238 args = sys.argv[1:] or sorted(glob.glob(os.path.dirname(sys.argv[0]) + '/[67][0-9]-*.hwdb'))
c3f6a561
ZJS
239
240 for fname in args:
0c9836c0
ZJS
241 groups = parse(fname)
242 print_summary(fname, groups)
243 check_match_uniqueness(groups)
244 check_properties(groups)
245
246 sys.exit(ERROR)