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