]> git.ipfire.org Git - thirdparty/systemd.git/blob - hwdb/parse_hwdb.py
Merge pull request #4372 from evverx/fix-synced-cgroup-hierarchy-perms
[thirdparty/systemd.git] / hwdb / parse_hwdb.py
1 #!/usr/bin/python3
2 # -*- Mode: python; coding: utf-8; indent-tabs-mode: nil -*- */
3 #
4 # This file is part of systemd. It is distrubuted under the MIT license, see
5 # below.
6 #
7 # Copyright 2016 Zbigniew Jędrzejewski-Szmek
8 #
9 # The MIT License (MIT)
10 #
11 # Permission is hereby granted, free of charge, to any person obtaining a copy
12 # of this software and associated documentation files (the "Software"), to deal
13 # in the Software without restriction, including without limitation the rights
14 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 # copies of the Software, and to permit persons to whom the Software is
16 # furnished to do so, subject to the following conditions:
17 #
18 # The above copyright notice and this permission notice shall be included in
19 # all copies or substantial portions of the Software.
20 #
21 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 # SOFTWARE.
28
29 import functools
30 import glob
31 import string
32 import sys
33 import os
34
35 try:
36 from pyparsing import (Word, White, Literal, ParserElement, Regex,
37 LineStart, LineEnd,
38 ZeroOrMore, OneOrMore, Combine, Or, Optional, Suppress, Group,
39 nums, alphanums, printables,
40 stringEnd, pythonStyleComment,
41 ParseBaseException)
42 except ImportError:
43 print('pyparsing is not available')
44 sys.exit(77)
45
46 try:
47 from evdev.ecodes import ecodes
48 except ImportError:
49 ecodes = None
50 print('WARNING: evdev is not available')
51
52 EOL = LineEnd().suppress()
53 EMPTYLINE = LineStart() + LineEnd()
54 COMMENTLINE = pythonStyleComment + EOL
55 INTEGER = Word(nums)
56 REAL = Combine((INTEGER + Optional('.' + Optional(INTEGER))) ^ ('.' + INTEGER))
57 UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_')
58
59 TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'),
60 'evdev': ('name', 'atkbd', 'input'),
61 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'),
62 'keyboard': ('name', ),
63 }
64
65 @functools.lru_cache()
66 def hwdb_grammar():
67 ParserElement.setDefaultWhitespaceChars('')
68
69 prefix = Or(category + ':' + Or(conn) + ':'
70 for category, conn in TYPES.items())
71 matchline = Combine(prefix + Word(printables + ' ' + '®')) + EOL
72 propertyline = (White(' ', exact=1).suppress() +
73 Combine(UDEV_TAG - '=' - Word(alphanums + '_=:@*.! ') - Optional(pythonStyleComment)) +
74 EOL)
75 propertycomment = White(' ', exact=1) + pythonStyleComment + EOL
76
77 group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) -
78 OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) -
79 (EMPTYLINE ^ stringEnd()).suppress() )
80 commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress()
81
82 grammar = OneOrMore(group('GROUPS*') ^ commentgroup) + stringEnd()
83
84 return grammar
85
86 @functools.lru_cache()
87 def property_grammar():
88 ParserElement.setDefaultWhitespaceChars(' ')
89
90 setting = Optional('*')('DEFAULT') + INTEGER('DPI') + Suppress('@') + INTEGER('HZ')
91 props = (('MOUSE_DPI', Group(OneOrMore(setting('SETTINGS*')))),
92 ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER),
93 ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER),
94 ('MOUSE_WHEEL_CLICK_COUNT', INTEGER),
95 ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER),
96 ('ID_INPUT_TRACKBALL', Literal('1')),
97 ('POINTINGSTICK_SENSITIVITY', INTEGER),
98 ('POINTINGSTICK_CONST_ACCEL', REAL),
99 ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))),
100 )
101 fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE')
102 for name, val in props]
103 kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME')
104 - Suppress('=') -
105 ('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE')
106 ]
107 abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME')
108 - Suppress('=') -
109 Word(nums + ':')('VALUE')
110 ]
111
112 grammar = Or(fixed_props + kbd_props + abs_props)
113
114 return grammar
115
116 ERROR = False
117 def error(fmt, *args, **kwargs):
118 global ERROR
119 ERROR = True
120 print(fmt.format(*args, **kwargs))
121
122 def convert_properties(group):
123 matches = [m[0] for m in group.MATCHES]
124 props = [p[0] for p in group.PROPERTIES]
125 return matches, props
126
127 def parse(fname):
128 grammar = hwdb_grammar()
129 try:
130 parsed = grammar.parseFile(fname)
131 except ParseBaseException as e:
132 error('Cannot parse {}: {}', fname, e)
133 return []
134 return [convert_properties(g) for g in parsed.GROUPS]
135
136 def check_match_uniqueness(groups):
137 matches = sum((group[0] for group in groups), [])
138 matches.sort()
139 prev = None
140 for match in matches:
141 if match == prev:
142 error('Match {!r} is duplicated', match)
143 prev = match
144
145 def check_one_default(prop, settings):
146 defaults = [s for s in settings if s.DEFAULT]
147 if len(defaults) > 1:
148 error('More than one star entry: {!r}', prop)
149
150 def check_one_keycode(prop, value):
151 if value != '!' and ecodes is not None:
152 key = 'KEY_' + value.upper()
153 if key not in ecodes:
154 error('Keycode {} unknown', key)
155
156 def check_properties(groups):
157 grammar = property_grammar()
158 for matches, props in groups:
159 prop_names = set()
160 for prop in props:
161 # print('--', prop)
162 prop = prop.partition('#')[0].rstrip()
163 try:
164 parsed = grammar.parseString(prop)
165 except ParseBaseException as e:
166 error('Failed to parse: {!r}', prop)
167 continue
168 # print('{!r}'.format(parsed))
169 if parsed.NAME in prop_names:
170 error('Property {} is duplicated', parsed.NAME)
171 prop_names.add(parsed.NAME)
172 if parsed.NAME == 'MOUSE_DPI':
173 check_one_default(prop, parsed.VALUE.SETTINGS)
174 elif parsed.NAME.startswith('KEYBOARD_KEY_'):
175 check_one_keycode(prop, parsed.VALUE)
176
177 def print_summary(fname, groups):
178 print('{}: {} match groups, {} matches, {} properties'
179 .format(fname,
180 len(groups),
181 sum(len(matches) for matches, props in groups),
182 sum(len(props) for matches, props in groups),
183 ))
184
185 if __name__ == '__main__':
186 args = sys.argv[1:] or glob.glob(os.path.dirname(sys.argv[0]) + '/[67]0-*.hwdb')
187
188 for fname in args:
189 groups = parse(fname)
190 print_summary(fname, groups)
191 check_match_uniqueness(groups)
192 check_properties(groups)
193
194 sys.exit(ERROR)