1#!/usr/bin/env python3 2# SPDX-License-Identifier: LGPL-2.1-or-later 3# 4# Simple udev rules syntax checker 5# 6# © 2010 Canonical Ltd. 7# Author: Martin Pitt <martin.pitt@ubuntu.com> 8 9import re 10import sys 11import os 12from glob import glob 13 14rules_files = sys.argv[1:] 15if not rules_files: 16 sys.exit('Specify files to test as arguments') 17 18quoted_string_re = r'"(?:[^\\"]|\\.)*"' 19no_args_tests = re.compile(r'(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|PROGRAM|RESULT|TEST)\s*(?:=|!)=\s*' + quoted_string_re + '$') 20# PROGRAM can also be specified as an assignment. 21program_assign = re.compile(r'PROGRAM\s*=\s*' + quoted_string_re + '$') 22args_tests = re.compile(r'(ATTRS?|ENV|CONST|TEST){([a-zA-Z0-9/_.*%-]+)}\s*(?:=|!)=\s*' + quoted_string_re + '$') 23no_args_assign = re.compile(r'(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|RUN|LABEL|GOTO|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*' + quoted_string_re + '$') 24args_assign = re.compile(r'(ATTR|ENV|IMPORT|RUN){([a-zA-Z0-9/_.*%-]+)}\s*(=|\+=)\s*' + quoted_string_re + '$') 25# Find comma-separated groups, but allow commas that are inside quoted strings. 26# Using quoted_string_re + '?' so that strings missing the last double quote 27# will still match for this part that splits on commas. 28comma_separated_group_re = re.compile(r'(?:[^,"]|' + quoted_string_re + '?)+') 29 30result = 0 31buffer = '' 32for path in rules_files: 33 print('# looking at {}'.format(path)) 34 lineno = 0 35 for line in open(path): 36 lineno += 1 37 38 # handle line continuation 39 if line.endswith('\\\n'): 40 buffer += line[:-2] 41 continue 42 else: 43 line = buffer + line 44 buffer = '' 45 46 # filter out comments and empty lines 47 line = line.strip() 48 if not line or line.startswith('#'): 49 continue 50 51 # Separator ',' is normally optional but we make it mandatory here as 52 # it generally improves the readability of the rules. 53 for clause_match in comma_separated_group_re.finditer(line): 54 clause = clause_match.group().strip() 55 if not (no_args_tests.match(clause) or args_tests.match(clause) or 56 no_args_assign.match(clause) or args_assign.match(clause) or 57 program_assign.match(clause)): 58 59 print('Invalid line {}:{}: {}'.format(path, lineno, line)) 60 print(' clause:', clause) 61 print() 62 result = 1 63 break 64 65sys.exit(result) 66