diff --git a/.dir-locals.el b/.dir-locals.el index 6944406da07ba..47da56f939021 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -22,6 +22,14 @@ (meson-mode . ((meson-indent-basic . 8))) (sh-mode . ((sh-indentation . 4))) (awk-mode . ((c-basic-offset . 8))) + (python-mode . ((indent-tabs-mode . nil) + (tab-width . 4) + (fill-column . 109) + (python-indent-def-block-scale . 1))) + (python-ts-mode . ((indent-tabs-mode . nil) + (tab-width . 4) + (fill-column . 109) + (python-indent-def-block-scale . 1))) (nil . ((indent-tabs-mode . nil) (tab-width . 8) (fill-column . 79))) ) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index ba293cf8be135..de60c26de637b 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -63,16 +63,25 @@ jobs: - name: Run ruff format run: | mkosi box -- ruff --version - if ! mkosi box -- ruff format --check src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/test/generate-sym-test.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py + # shellcheck disable=SC2046 + if ! mkosi box -- ruff format --check $(git ls-files -- *.py | sed -e '/gen_autosuspend_rules/d') then echo "Please run 'ruff format' on the above files or apply the diffs below manually" - mkosi box -- ruff format --check --quiet --diff src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/test/generate-sym-test.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py + # shellcheck disable=SC2046 + mkosi box -- ruff format --check --quiet --diff $(git ls-files -- *.py | sed -e '/gen_autosuspend_rules/d') fi - name: Run ruff check run: | mkosi box -- ruff --version - mkosi box -- ruff check src/core/generate-bpf-delegate-configs.py src/boot/generate-hwids-section.py src/test/generate-sym-test.py src/ukify/ukify.py test/integration-tests/integration-test-wrapper.py + # shellcheck disable=SC2046 + if ! mkosi box -- ruff check --ignore E501,E731 $(git ls-files -- *.py | sed -e '/gen_autosuspend_rules/d') + then + echo "Please fix the errors shown by 'ruff check' on the above files or add noqa comments were appropriate" + echo "Diffs for fixes that can be programmatically generated are shown below:" + # shellcheck disable=SC2046 + mkosi box -- ruff check --ignore E501,E731 --diff $(git ls-files -- *.py | sed -e '/gen_autosuspend_rules/d') + fi - name: Configure meson run: mkosi box -- env CC=clang CXX=clang++ meson setup build diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index 46b5ecd9d9abd..c64cc48a7be24 100755 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -43,11 +43,11 @@ import glob import os -import ycm_core +import ycm_core -SOURCE_EXTENSIONS = (".C", ".cpp", ".cxx", ".cc", ".c", ".m", ".mm") -HEADER_EXTENSIONS = (".H", ".h", ".hxx", ".hpp", ".hh") +SOURCE_EXTENSIONS = ('.C', '.cpp', '.cxx', '.cc', '.c', '.m', '.mm') +HEADER_EXTENSIONS = ('.H', '.h', '.hxx', '.hpp', '.hh') def DirectoryOfThisScript(): @@ -69,19 +69,18 @@ def GuessBuildDirectory(): containing '.ninja_log' file two levels above the current directory; returns this single directory only if there is one candidate. """ - result = os.path.join(DirectoryOfThisScript(), "build") + result = os.path.join(DirectoryOfThisScript(), 'build') if os.path.exists(result): return result - result = glob.glob(os.path.join(DirectoryOfThisScript(), - "..", "..", "*", ".ninja_log")) + result = glob.glob(os.path.join(DirectoryOfThisScript(), '..', '..', '*', '.ninja_log')) if not result: - return "" + return '' if 1 != len(result): - return "" + return '' return os.path.split(result[0])[0] @@ -106,9 +105,7 @@ def TraverseByDepth(root, include_extensions): # print(subdirs) if include_extensions: get_ext = os.path.splitext - subdir_extensions = { - get_ext(f)[-1] for f in file_list if get_ext(f)[-1] - } + subdir_extensions = {get_ext(f)[-1] for f in file_list if get_ext(f)[-1]} if subdir_extensions & include_extensions: result.add(root_dir) else: @@ -119,11 +116,11 @@ def TraverseByDepth(root, include_extensions): return result -_project_src_dir = os.path.join(DirectoryOfThisScript(), "src") -_include_dirs_set = TraverseByDepth(_project_src_dir, frozenset({".h"})) +_project_src_dir = os.path.join(DirectoryOfThisScript(), 'src') +_include_dirs_set = TraverseByDepth(_project_src_dir, frozenset({'.h'})) flags = [ - "-x", - "c" + '-x', + 'c', # The following flags are partially redundant due to the existence of # 'compile_commands.json'. # '-Wall', @@ -135,7 +132,7 @@ def TraverseByDepth(root, include_extensions): ] for include_dir in _include_dirs_set: - flags.append("-I" + include_dir) + flags.append('-I' + include_dir) # Set this to the absolute path to the folder (NOT the file!) containing the # compile_commands.json file to use that instead of 'flags'. See here for @@ -165,13 +162,13 @@ def MakeRelativePathsInFlagsAbsolute(flags, working_directory): return list(flags) new_flags = [] make_next_absolute = False - path_flags = ["-isystem", "-I", "-iquote", "--sysroot="] + path_flags = ['-isystem', '-I', '-iquote', '--sysroot='] for flag in flags: new_flag = flag if make_next_absolute: make_next_absolute = False - if not flag.startswith("/"): + if not flag.startswith('/'): new_flag = os.path.join(working_directory, flag) for path_flag in path_flags: @@ -180,7 +177,7 @@ def MakeRelativePathsInFlagsAbsolute(flags, working_directory): break if flag.startswith(path_flag): - path = flag[len(path_flag):] + path = flag[len(path_flag) :] new_flag = path_flag + os.path.join(working_directory, path) break @@ -213,8 +210,7 @@ def GetCompilationInfoForFile(filename): for extension in SOURCE_EXTENSIONS: replacement_file = basename + extension if os.path.exists(replacement_file): - compilation_info = \ - database.GetCompilationInfoForFile(replacement_file) + compilation_info = database.GetCompilationInfoForFile(replacement_file) if compilation_info.compiler_flags_: return compilation_info return None @@ -238,13 +234,14 @@ def FlagsForFile(filename, **kwargs): final_flags = MakeRelativePathsInFlagsAbsolute( compilation_info.compiler_flags_, - compilation_info.compiler_working_dir_) + compilation_info.compiler_working_dir_, + ) else: relative_to = DirectoryOfThisScript() final_flags = MakeRelativePathsInFlagsAbsolute(flags, relative_to) return { - "flags": final_flags, - "do_cache": True + 'flags': final_flags, + 'do_cache': True, } diff --git a/hwdb.d/acpi-update.py b/hwdb.d/acpi-update.py index 41670b32bbc7c..66a11d8502dbd 100755 --- a/hwdb.d/acpi-update.py +++ b/hwdb.d/acpi-update.py @@ -5,6 +5,7 @@ # pylint: disable=consider-using-with + def read_table(filename): table = list(reader(open(filename, newline=''))) table = table[1:] # Skip header @@ -15,6 +16,7 @@ def read_table(filename): # a mistake, strip it. print(f'\nacpi:{row[1].strip()}*:\n ID_VENDOR_FROM_DATABASE={row[0].strip()}') + print('''\ # This file is part of systemd. # diff --git a/hwdb.d/ids_parser.py b/hwdb.d/ids_parser.py index ed2c615508def..7efd934f466e8 100755 --- a/hwdb.d/ids_parser.py +++ b/hwdb.d/ids_parser.py @@ -3,11 +3,24 @@ import re import sys -from pyparsing import (Word, White, Literal, Regex, - LineEnd, SkipTo, - ZeroOrMore, OneOrMore, Combine, Optional, Suppress, - Group, ParserElement, - stringEnd, pythonStyleComment) + +from pyparsing import ( + Combine, + Group, + LineEnd, + Literal, + OneOrMore, + Optional, + ParserElement, + Regex, + SkipTo, + Suppress, + White, + Word, + ZeroOrMore, + pythonStyleComment, + stringEnd, +) EOL = LineEnd().suppress() NUM1 = Word('0123456789abcdefABCDEF', exact=1) @@ -22,7 +35,9 @@ ParserElement.setDefaultWhitespaceChars(' \n') + def klass_grammar(): + # fmt: off klass_line = Literal('C ').suppress() + NUM2('klass') + text_eol('text') subclass_line = TAB + NUM2('subclass') + text_eol('text') protocol_line = TAB + TAB + NUM2('protocol') + text_eol('name') @@ -32,9 +47,12 @@ def klass_grammar(): klass = (klass_line('KLASS') - ZeroOrMore(Group(subclass)('SUBCLASSES*') ^ COMMENTLINE.suppress())) + # fmt: on return klass + def usb_ids_grammar(): + # fmt: off vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') interface_line = TAB + TAB + NUM4('interface') + NUM4('interface2') + text_eol('text') @@ -55,11 +73,14 @@ def usb_ids_grammar(): grammar = OneOrMore(Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ other_group.suppress() ^ commentgroup) + stringEnd() + # fmt: on grammar.parseWithTabs() return grammar + def pci_ids_grammar(): + # fmt: off vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') subvendor_line = TAB + TAB + NUM4('a') + White(' ') + NUM4('b') + text_eol('name') @@ -75,11 +96,14 @@ def pci_ids_grammar(): grammar = OneOrMore(Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ commentgroup) + stringEnd() + # fmt: on grammar.parseWithTabs() return grammar + def sdio_ids_grammar(): + # fmt: off vendor_line = NUM4('vendor') + text_eol('text') device_line = TAB + NUM4('device') + text_eol('text') vendor = (vendor_line('VENDOR') + @@ -91,11 +115,14 @@ def sdio_ids_grammar(): grammar = OneOrMore(Group(vendor)('VENDORS*') ^ Group(klass)('CLASSES*') ^ commentgroup) + stringEnd() + # fmt: on grammar.parseWithTabs() return grammar + def oui_grammar(type): + # fmt: off prefix_line = (Combine(NUM2 - Suppress('-') - NUM2 - Suppress('-') - NUM2)('prefix') - Literal('(hex)') - text_eol('text')) if type == 'small': @@ -115,18 +142,23 @@ def oui_grammar(type): grammar = (Literal('OUI') + text_eol('header') + text_eol('header') + text_eol('header') + EMPTYLINE + OneOrMore(Group(vendor)('VENDORS*')) + stringEnd()) + # fmt: on grammar.parseWithTabs() return grammar def header(file, *sources): - print('''\ + sep = ' ' if len(sources) == 1 else '\n# ' + joined = sep + sep.join(sources) + print( + f'''\ # This file is part of systemd. # -# Data imported from:{}{}'''.format(' ' if len(sources) == 1 else '\n# ', - '\n# '.join(sources)), - file=file) +# Data imported from:{joined}''', + file=file, + ) + def add_item(items, key, value): if key in items: @@ -134,6 +166,7 @@ def add_item(items, key, value): else: items[key] = value + def usb_vendor_model(p): items = {} @@ -147,19 +180,19 @@ def usb_vendor_model(p): text = vendor_dev.text.strip() add_item(items, (vendor, device), text) - with open('20-usb-vendor-model.hwdb', 'wt') as out: + with open('20-usb-vendor-model.hwdb', 'w') as out: header(out, 'http://www.linux-usb.org/usb.ids') for key in sorted(items): if len(key) == 1: p, n = 'usb:v{}*', 'VENDOR' else: - p, n = 'usb:v{}p{}*', 'MODEL', - print('', p.format(*key), - f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + p, n = 'usb:v{}p{}*', 'MODEL' + print('', p.format(*key), f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def usb_classes(p): items = {} @@ -182,7 +215,7 @@ def usb_classes(p): if klass != '00' and not re.match(r'(\?|None|Unused)\s*$', text): add_item(items, (klass, subclass, protocol), text) - with open('20-usb-classes.hwdb', 'wt') as out: + with open('20-usb-classes.hwdb', 'w') as out: header(out, 'http://www.linux-usb.org/usb.ids') for key in sorted(items): @@ -192,11 +225,11 @@ def usb_classes(p): p, n = 'usb:v*p*d*dc{}dsc{}*', 'SUBCLASS' else: p, n = 'usb:v*p*d*dc{}dsc{}dp{}*', 'PROTOCOL' - print('', p.format(*key), - f' ID_USB_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_USB_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def pci_vendor_model(p): items = {} @@ -215,12 +248,12 @@ def pci_vendor_model(p): sub_model = subvendor_group.b.upper() sub_text = subvendor_group.name.strip() if sub_text.startswith(text): - sub_text = sub_text[len(text):].lstrip() + sub_text = sub_text[len(text) :].lstrip() if sub_text: sub_text = f' ({sub_text})' add_item(items, (vendor, device, sub_vendor, sub_model), text + sub_text) - with open('20-pci-vendor-model.hwdb', 'wt') as out: + with open('20-pci-vendor-model.hwdb', 'w') as out: header(out, 'http://pci-ids.ucw.cz/v2.2/pci.ids') for key in sorted(items): @@ -230,11 +263,11 @@ def pci_vendor_model(p): p, n = 'pci:v0000{}d0000{}*', 'MODEL' else: p, n = 'pci:v0000{}d0000{}sv0000{}sd0000{}*', 'MODEL' - print('', p.format(*key), - f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def pci_classes(p): items = {} @@ -253,7 +286,7 @@ def pci_classes(p): text = protocol_group.name.strip() add_item(items, (klass, subclass, protocol), text) - with open('20-pci-classes.hwdb', 'wt') as out: + with open('20-pci-classes.hwdb', 'w') as out: header(out, 'http://pci-ids.ucw.cz/v2.2/pci.ids') for key in sorted(items): @@ -263,11 +296,11 @@ def pci_classes(p): p, n = 'pci:v*d*sv*sd*bc{}sc{}*', 'SUBCLASS' else: p, n = 'pci:v*d*sv*sd*bc{}sc{}i{}*', 'INTERFACE' - print('', p.format(*key), - f' ID_PCI_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_PCI_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def sdio_vendor_model(p): items = {} @@ -281,7 +314,7 @@ def sdio_vendor_model(p): text = device_group.text.strip() add_item(items, (vendor, device), text) - with open('20-sdio-vendor-model.hwdb', 'wt') as out: + with open('20-sdio-vendor-model.hwdb', 'w') as out: header(out, 'hwdb.d/sdio.ids') for key in sorted(items): @@ -289,11 +322,11 @@ def sdio_vendor_model(p): p, n = 'sdio:c*v{}*', 'VENDOR' else: p, n = 'sdio:c*v{}d{}*', 'MODEL' - print('', p.format(*key), - f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) + print('', p.format(*key), f' ID_{n}_FROM_DATABASE={items[key]}', sep='\n', file=out) print(f'Wrote {out.name}') + def sdio_classes(p): items = {} @@ -302,16 +335,21 @@ def sdio_classes(p): text = klass_group.text.strip() add_item(items, klass, text) - with open('20-sdio-classes.hwdb', 'wt') as out: + with open('20-sdio-classes.hwdb', 'w') as out: header(out, 'hwdb.d/sdio.ids') for klass in sorted(items): - print(f'', - f'sdio:c{klass}v*d*', - f' ID_SDIO_CLASS_FROM_DATABASE={items[klass]}', sep='\n', file=out) + print( + '', + f'sdio:c{klass}v*d*', + f' ID_SDIO_CLASS_FROM_DATABASE={items[klass]}', + sep='\n', + file=out, + ) print(f'Wrote {out.name}') + # MAC Address Block Large/Medium/Small # Large MA-L 24/24 bit (OUI) # Medium MA-M 28/20 bit (OUI prefix owned by IEEE) @@ -338,19 +376,20 @@ def oui(p1, p2, p3): key = prefix + start if end else prefix add_item(items, key, text) - with open('20-OUI.hwdb', 'wt') as out: - header(out, - 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-L&format=txt', - 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-M&format=txt', - 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-S&format=txt') + with open('20-OUI.hwdb', 'w') as out: + header( + out, + 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-L&format=txt', + 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-M&format=txt', + 'https://services13.ieee.org/RST/standards-ra-web/rest/assignments/download/?registry=MA-S&format=txt', + ) for pattern in sorted(items): - print(f'', - f'OUI:{pattern}*', - f' ID_OUI_FROM_DATABASE={items[pattern]}', sep='\n', file=out) + print('', f'OUI:{pattern}*', f' ID_OUI_FROM_DATABASE={items[pattern]}', sep='\n', file=out) print(f'Wrote {out.name}') + if __name__ == '__main__': args = sys.argv[1:] diff --git a/hwdb.d/parse_hwdb.py b/hwdb.d/parse_hwdb.py index 9e39911ae09a9..d3990ddfb5359 100755 --- a/hwdb.d/parse_hwdb.py +++ b/hwdb.d/parse_hwdb.py @@ -26,16 +26,31 @@ # SOFTWARE. import glob +import os import string import sys -import os try: - from pyparsing import (Word, White, Literal, ParserElement, Regex, LineEnd, - OneOrMore, Combine, Or, Optional, Suppress, Group, - nums, alphanums, printables, - stringEnd, pythonStyleComment, - ParseBaseException) + from pyparsing import ( + Combine, + Group, + LineEnd, + Literal, + OneOrMore, + Optional, + Or, + ParseBaseException, + ParserElement, + Regex, + Suppress, + White, + Word, + alphanums, + nums, + printables, + pythonStyleComment, + stringEnd, + ) except ImportError: print('pyparsing is not available') sys.exit(77) @@ -57,11 +72,7 @@ ecodes = None print('WARNING: evdev is not available') -try: - from functools import lru_cache -except ImportError: - # don't do caching on old python - lru_cache = lambda: (lambda f: f) +from functools import lru_cache EOL = LineEnd().suppress() EMPTYLINE = LineEnd() @@ -72,77 +83,89 @@ UDEV_TAG = Word(string.ascii_uppercase, alphanums + '_') # Those patterns are used in type-specific matches -TYPES = {'mouse': ('usb', 'bluetooth', 'ps2', '*'), - 'evdev': ('name', 'atkbd', 'input'), - 'fb': ('pci', 'vmbus'), - 'id-input': ('modalias', 'bluetooth', 'i2c', 'usb'), - 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'), - 'joystick': ('i8042', 'rmi', 'bluetooth', 'usb'), - 'keyboard': ('name', ), - 'sensor': ('modalias', - 'accel-base', - 'accel-display', - 'accel-camera', - 'proximity-palmrest', - 'proximity-palmrest-left', - 'proximity-palmrest-right', - 'proximity-lap', - 'proximity-wifi', - 'proximity-lte', - 'proximity-wifi-lte', - 'proximity-wifi-left', - 'proximity-wifi-right', - ), - 'ieee1394-unit-function' : ('node', ), - 'camera': ('usb'), - } +TYPES = { + 'mouse': ('usb', 'bluetooth', 'ps2', '*'), + 'evdev': ('name', 'atkbd', 'input'), + 'fb': ('pci', 'vmbus'), + 'id-input': ('modalias', 'bluetooth', 'i2c', 'usb'), + 'touchpad': ('i8042', 'rmi', 'bluetooth', 'usb'), + 'joystick': ('i8042', 'rmi', 'bluetooth', 'usb'), + 'keyboard': ('name',), + 'sensor': ( + 'modalias', + 'accel-base', + 'accel-display', + 'accel-camera', + 'proximity-palmrest', + 'proximity-palmrest-left', + 'proximity-palmrest-right', + 'proximity-lap', + 'proximity-wifi', + 'proximity-lte', + 'proximity-wifi-lte', + 'proximity-wifi-left', + 'proximity-wifi-right', + ), + 'ieee1394-unit-function': ('node',), + 'camera': ('usb'), +} # Patterns that are used to set general properties on a device -GENERAL_MATCHES = {'acpi', - 'bluetooth', - 'usb', - 'pci', - 'sdio', - 'vmbus', - 'OUI', - 'ieee1394', - 'dmi', - } +GENERAL_MATCHES = { + 'acpi', + 'bluetooth', + 'usb', + 'pci', + 'sdio', + 'vmbus', + 'OUI', + 'ieee1394', + 'dmi', +} + def upperhex_word(length): return Word(nums + 'ABCDEF', exact=length) -@lru_cache() + +@lru_cache def hwdb_grammar(): ParserElement.setDefaultWhitespaceChars('') - prefix = Or(category + ':' + Or(conn) + ':' - for category, conn in TYPES.items()) + prefix = Or(category + ':' + Or(conn) + ':' for category, conn in TYPES.items()) matchline_typed = Combine(prefix + Word(printables + ' ' + '®')) matchline_general = Combine(Or(GENERAL_MATCHES) + ':' + Word(printables + ' ' + '®')) matchline = (matchline_typed | matchline_general) + EOL - propertyline = (White(' ', exact=1).suppress() + - Combine(UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/')) - - Optional(pythonStyleComment)) + - EOL) + propertyline = ( + White(' ', exact=1).suppress() + + Combine( + UDEV_TAG - '=' - Optional(Word(alphanums + '_=:@*.!-;, "/')) - Optional(pythonStyleComment) + ) + + EOL + ) propertycomment = White(' ', exact=1) + pythonStyleComment + EOL - group = (OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) - - OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) - - (EMPTYLINE ^ stringEnd()).suppress()) + group = ( + OneOrMore(matchline('MATCHES*') ^ COMMENTLINE.suppress()) + - OneOrMore(propertyline('PROPERTIES*') ^ propertycomment.suppress()) + - (EMPTYLINE ^ stringEnd()).suppress() + ) commentgroup = OneOrMore(COMMENTLINE).suppress() - EMPTYLINE.suppress() grammar = OneOrMore(Group(group)('GROUPS*') ^ commentgroup) + stringEnd() return grammar -@lru_cache() + +@lru_cache def property_grammar(): ParserElement.setDefaultWhitespaceChars(' ') - dpi_setting = Group(Optional('*')('DEFAULT') + INTEGER('DPI') + Optional(Suppress('@') + INTEGER('HZ')))('SETTINGS*') + dpi_setting = Group(Optional('*')('DEFAULT') + INTEGER('DPI') + Optional(Suppress('@') + INTEGER('HZ')))( + 'SETTINGS*' + ) mount_matrix_row = SIGNED_REAL + ',' + SIGNED_REAL + ',' + SIGNED_REAL mount_matrix = Group(mount_matrix_row + ';' + mount_matrix_row + ';' + mount_matrix_row)('MOUNT_MATRIX') xkb_setting = Optional(Word(alphanums + '+-/@._')) @@ -151,113 +174,117 @@ def property_grammar(): # Although this set doesn't cover all of characters in database entries, it's enough for test targets. name_literal = Word(printables + ' ') - props = (('MOUSE_DPI', Group(OneOrMore(dpi_setting))), - ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER), - ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER), - ('MOUSE_WHEEL_CLICK_COUNT', INTEGER), - ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER), - ('ID_INPUT_3D_MOUSE', Or((Literal('0'), Literal('1')))), - ('ID_AUTOSUSPEND', Or((Literal('0'), Literal('1')))), - ('ID_AUTOSUSPEND_DELAY_MS', INTEGER), - ('ID_AV_PRODUCTION_CONTROLLER', Or((Literal('0'), Literal('1')))), - ('ID_AV_LIGHTS', Or((Literal('0'), Literal('1')))), - ('ID_PERSIST', Or((Literal('0'), Literal('1')))), - ('ID_PDA', Or((Literal('0'), Literal('1')))), - ('ID_INPUT', id_input_setting), - ('ID_INPUT_ACCELEROMETER', id_input_setting), - ('ID_INPUT_JOYSTICK', id_input_setting), - ('ID_INPUT_KEY', id_input_setting), - ('ID_INPUT_KEYBOARD', id_input_setting), - ('ID_INPUT_MOUSE', id_input_setting), - ('ID_INPUT_POINTINGSTICK', id_input_setting), - ('ID_INPUT_SWITCH', id_input_setting), - ('ID_INPUT_TABLET', id_input_setting), - ('ID_INPUT_TABLET_PAD', id_input_setting), - ('ID_INPUT_TOUCHPAD', id_input_setting), - ('ID_INPUT_TOUCHSCREEN', id_input_setting), - ('ID_INPUT_TRACKBALL', id_input_setting), - ('ID_SIGNAL_ANALYZER', Or((Literal('0'), Literal('1')))), - ('ID_MAKER_TOOL', Or((Literal('0'), Literal('1')))), - ('ID_HARDWARE_WALLET', Or((Literal('0'), Literal('1')))), - ('ID_SOFTWARE_RADIO', Or((Literal('0'), Literal('1')))), - ('ID_MM_DEVICE_IGNORE', Or((Literal('0'), Literal('1')))), - ('ID_NET_AUTO_LINK_LOCAL_ONLY', Or((Literal('0'), Literal('1')))), - ('POINTINGSTICK_SENSITIVITY', INTEGER), - ('ID_INPUT_JOYSTICK_INTEGRATION', Or(('internal', 'external'))), - ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))), - ('XKB_FIXED_LAYOUT', xkb_setting), - ('XKB_FIXED_VARIANT', xkb_setting), - ('XKB_FIXED_MODEL', xkb_setting), - ('KEYBOARD_LED_NUMLOCK', Literal('0')), - ('KEYBOARD_LED_CAPSLOCK', Literal('0')), - ('ACCEL_MOUNT_MATRIX', mount_matrix), - ('ACCEL_LOCATION', Or(('display', 'base'))), - ('PROXIMITY_NEAR_LEVEL', INTEGER), - ('IEEE1394_UNIT_FUNCTION_MIDI', Or((Literal('0'), Literal('1')))), - ('IEEE1394_UNIT_FUNCTION_AUDIO', Or((Literal('0'), Literal('1')))), - ('IEEE1394_UNIT_FUNCTION_VIDEO', Or((Literal('0'), Literal('1')))), - ('ID_VENDOR_FROM_DATABASE', name_literal), - ('ID_MODEL_FROM_DATABASE', name_literal), - ('ID_TAG_MASTER_OF_SEAT', Literal('1')), - ('ID_INFRARED_CAMERA', Or((Literal('0'), Literal('1')))), - ('ID_CAMERA_DIRECTION', Or(('front', 'rear'))), - ('SOUND_FORM_FACTOR', Or(('internal', 'webcam', 'speaker', 'headphone', 'headset', 'handset', 'microphone'))), - ('ID_SYS_VENDOR_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_PRODUCT_NAME_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_PRODUCT_VERSION_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_BOARD_VERSION_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_PRODUCT_SKU_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_CHASSIS_ASSET_TAG_IS_RUBBISH', Or((Literal('0'), Literal('1')))), - ('ID_CHASSIS', name_literal), - ('ID_SYSFS_ATTRIBUTE_MODEL', name_literal), - ('ID_NET_NAME_FROM_DATABASE', name_literal), - ('ID_NET_NAME_INCLUDE_DOMAIN', Or((Literal('0'), Literal('1')))), - ) - fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE') - for name, val in props] - kbd_props = [Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME') - - Suppress('=') - - Group('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE') - ] - abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME') - - Suppress('=') - - Word('-' + nums + ':')('VALUE') - ] + props = ( + ('MOUSE_DPI', Group(OneOrMore(dpi_setting))), + ('MOUSE_WHEEL_CLICK_ANGLE', INTEGER), + ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', INTEGER), + ('MOUSE_WHEEL_CLICK_COUNT', INTEGER), + ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', INTEGER), + ('ID_INPUT_3D_MOUSE', Or((Literal('0'), Literal('1')))), + ('ID_AUTOSUSPEND', Or((Literal('0'), Literal('1')))), + ('ID_AUTOSUSPEND_DELAY_MS', INTEGER), + ('ID_AV_PRODUCTION_CONTROLLER', Or((Literal('0'), Literal('1')))), + ('ID_AV_LIGHTS', Or((Literal('0'), Literal('1')))), + ('ID_PERSIST', Or((Literal('0'), Literal('1')))), + ('ID_PDA', Or((Literal('0'), Literal('1')))), + ('ID_INPUT', id_input_setting), + ('ID_INPUT_ACCELEROMETER', id_input_setting), + ('ID_INPUT_JOYSTICK', id_input_setting), + ('ID_INPUT_KEY', id_input_setting), + ('ID_INPUT_KEYBOARD', id_input_setting), + ('ID_INPUT_MOUSE', id_input_setting), + ('ID_INPUT_POINTINGSTICK', id_input_setting), + ('ID_INPUT_SWITCH', id_input_setting), + ('ID_INPUT_TABLET', id_input_setting), + ('ID_INPUT_TABLET_PAD', id_input_setting), + ('ID_INPUT_TOUCHPAD', id_input_setting), + ('ID_INPUT_TOUCHSCREEN', id_input_setting), + ('ID_INPUT_TRACKBALL', id_input_setting), + ('ID_SIGNAL_ANALYZER', Or((Literal('0'), Literal('1')))), + ('ID_MAKER_TOOL', Or((Literal('0'), Literal('1')))), + ('ID_HARDWARE_WALLET', Or((Literal('0'), Literal('1')))), + ('ID_SOFTWARE_RADIO', Or((Literal('0'), Literal('1')))), + ('ID_MM_DEVICE_IGNORE', Or((Literal('0'), Literal('1')))), + ('ID_NET_AUTO_LINK_LOCAL_ONLY', Or((Literal('0'), Literal('1')))), + ('POINTINGSTICK_SENSITIVITY', INTEGER), + ('ID_INPUT_JOYSTICK_INTEGRATION', Or(('internal', 'external'))), + ('ID_INPUT_TOUCHPAD_INTEGRATION', Or(('internal', 'external'))), + ('XKB_FIXED_LAYOUT', xkb_setting), + ('XKB_FIXED_VARIANT', xkb_setting), + ('XKB_FIXED_MODEL', xkb_setting), + ('KEYBOARD_LED_NUMLOCK', Literal('0')), + ('KEYBOARD_LED_CAPSLOCK', Literal('0')), + ('ACCEL_MOUNT_MATRIX', mount_matrix), + ('ACCEL_LOCATION', Or(('display', 'base'))), + ('PROXIMITY_NEAR_LEVEL', INTEGER), + ('IEEE1394_UNIT_FUNCTION_MIDI', Or((Literal('0'), Literal('1')))), + ('IEEE1394_UNIT_FUNCTION_AUDIO', Or((Literal('0'), Literal('1')))), + ('IEEE1394_UNIT_FUNCTION_VIDEO', Or((Literal('0'), Literal('1')))), + ('ID_VENDOR_FROM_DATABASE', name_literal), + ('ID_MODEL_FROM_DATABASE', name_literal), + ('ID_TAG_MASTER_OF_SEAT', Literal('1')), + ('ID_INFRARED_CAMERA', Or((Literal('0'), Literal('1')))), + ('ID_CAMERA_DIRECTION', Or(('front', 'rear'))), + ('SOUND_FORM_FACTOR', Or(('internal', 'webcam', 'speaker', 'headphone', 'headset', 'handset', 'microphone'))), + ('ID_SYS_VENDOR_IS_RUBBISH', Or((Literal('0'), Literal('1')))), + ('ID_PRODUCT_NAME_IS_RUBBISH', Or((Literal('0'), Literal('1')))), + ('ID_PRODUCT_VERSION_IS_RUBBISH', Or((Literal('0'), Literal('1')))), + ('ID_BOARD_VERSION_IS_RUBBISH', Or((Literal('0'), Literal('1')))), + ('ID_PRODUCT_SKU_IS_RUBBISH', Or((Literal('0'), Literal('1')))), + ('ID_CHASSIS_ASSET_TAG_IS_RUBBISH', Or((Literal('0'), Literal('1')))), + ('ID_CHASSIS', name_literal), + ('ID_SYSFS_ATTRIBUTE_MODEL', name_literal), + ('ID_NET_NAME_FROM_DATABASE', name_literal), + ('ID_NET_NAME_INCLUDE_DOMAIN', Or((Literal('0'), Literal('1')))), + ) # fmt: skip + fixed_props = [Literal(name)('NAME') - Suppress('=') - val('VALUE') for name, val in props] + kbd_props = [ + Regex(r'KEYBOARD_KEY_[0-9a-f]+')('NAME') + - Suppress('=') + - Group('!' ^ (Optional('!') - Word(alphanums + '_')))('VALUE') + ] + abs_props = [Regex(r'EVDEV_ABS_[0-9a-f]{2}')('NAME') - Suppress('=') - Word('-' + nums + ':')('VALUE')] grammar = Or(fixed_props + kbd_props + abs_props) + EOL return grammar + ERROR = False + + def error(fmt, *args, **kwargs): global ERROR ERROR = True print(fmt.format(*args, **kwargs)) + def convert_properties(group): matches = [m[0] for m in group.MATCHES] props = [p[0] for p in group.PROPERTIES] return matches, props + def parse(fname): grammar = hwdb_grammar() try: - with open(fname, 'r', encoding='UTF-8') as f: + with open(fname, encoding='UTF-8') as f: parsed = grammar.parseFile(f) except ParseBaseException as e: error('Cannot parse {}: {}', fname, e) return [] return [convert_properties(g) for g in parsed.GROUPS] + def check_matches(groups): matches = sum((group[0] for group in groups), []) # This is a partial check. The other cases could be also done, but those # two are most commonly wrong. grammars = { - 'bluetooth' : 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', - 'usb' : 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', - 'pci' : 'v' + upperhex_word(8) + Optional('d' + upperhex_word(8) + Optional(':')) + '*', + 'bluetooth': 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', + 'usb': 'v' + upperhex_word(4) + Optional('p' + upperhex_word(4) + Optional(':')) + '*', + 'pci': 'v' + upperhex_word(8) + Optional('d' + upperhex_word(8) + Optional(':')) + '*', } for match in matches: @@ -281,11 +308,13 @@ def check_matches(groups): error('Match {!r} is duplicated', match) prev = match + def check_one_default(prop, settings): defaults = [s for s in settings if s.DEFAULT] if len(defaults) > 1: error('More than one star entry: {!r}', prop) + def check_one_mount_matrix(prop, value): numbers = [s for s in value if s not in {';', ','}] if len(numbers) != 9: @@ -296,28 +325,33 @@ def check_one_mount_matrix(prop, value): error('Wrong accel matrix: {!r}', prop) bad_x, bad_y, bad_z = max(numbers[0:3]) == 0, max(numbers[3:6]) == 0, max(numbers[6:9]) == 0 if bad_x or bad_y or bad_z: - error('Mount matrix is all zero in {} row: {!r}', - 'x' if bad_x else ('y' if bad_y else 'z'), - prop) + error('Mount matrix is all zero in {} row: {!r}', 'x' if bad_x else ('y' if bad_y else 'z'), prop) + def check_one_keycode(value): if value != '!' and ecodes is not None: key = 'KEY_' + value.upper() - if not (key in ecodes or - value.upper() in ecodes or - # new keys added in kernel 5.5 - 'KBD_LCD_MENU' in key): + if not ( + key in ecodes + or value.upper() in ecodes + # new keys added in kernel 5.5 + or 'KBD_LCD_MENU' in key + ): # fmt: skip error('Keycode {} unknown', key) + def check_wheel_clicks(properties): - pairs = (('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_COUNT'), - ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE'), - ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL'), - ('MOUSE_WHEEL_CLICK_COUNT', 'MOUSE_WHEEL_CLICK_ANGLE')) + pairs = ( + ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_COUNT'), + ('MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE'), + ('MOUSE_WHEEL_CLICK_COUNT_HORIZONTAL', 'MOUSE_WHEEL_CLICK_ANGLE_HORIZONTAL'), + ('MOUSE_WHEEL_CLICK_COUNT', 'MOUSE_WHEEL_CLICK_ANGLE'), + ) for pair in pairs: if pair[0] in properties and pair[1] not in properties: error('{} requires {} to be specified', *pair) + def check_properties(groups): grammar = property_grammar() for _, props in groups: @@ -344,6 +378,7 @@ def check_properties(groups): check_wheel_clicks(seen_props) + def print_summary(fname, groups): n_matches = sum(len(matches) for matches, props in groups) n_props = sum(len(props) for matches, props in groups) @@ -352,12 +387,15 @@ def print_summary(fname, groups): if n_matches == 0 or n_props == 0: print(f'{fname}: no matches or props') + if __name__ == '__main__': - args = sys.argv[1:] or sorted([ - os.path.dirname(sys.argv[0]) + '/20-dmi-id.hwdb', - os.path.dirname(sys.argv[0]) + '/20-net-ifname.hwdb', - *glob.glob(os.path.dirname(sys.argv[0]) + '/[678][0-9]-*.hwdb'), - ]) + args = sys.argv[1:] or sorted( + [ + os.path.dirname(sys.argv[0]) + '/20-dmi-id.hwdb', + os.path.dirname(sys.argv[0]) + '/20-net-ifname.hwdb', + *glob.glob(os.path.dirname(sys.argv[0]) + '/[678][0-9]-*.hwdb'), + ] + ) for fname in args: groups = parse(fname) diff --git a/man/90-rearrange-path.py b/man/90-rearrange-path.py index b5b6294754cae..278311f0ab523 100755 --- a/man/90-rearrange-path.py +++ b/man/90-rearrange-path.py @@ -17,6 +17,7 @@ import os import pathlib + def rearrange_bin_sbin(path): """Make sure any pair of …/bin, …/sbin directories is in this order @@ -27,15 +28,16 @@ def rearrange_bin_sbin(path): for i in range(len(items)): if 'sbin' in items[i].parts: ind = items[i].parts.index('sbin') - bin = pathlib.Path(*items[i].parts[:ind], 'bin', *items[i].parts[ind+1:]) - if bin in items[i+1:]: - j = i + 1 + items[i+1:].index(bin) + bin = pathlib.Path(*items[i].parts[:ind], 'bin', *items[i].parts[ind + 1 :]) + if bin in items[i + 1 :]: + j = i + 1 + items[i + 1 :].index(bin) items[i], items[j] = items[j], items[i] return ':'.join(p.as_posix() for p in items) + if __name__ == '__main__': - path = os.environ['PATH'] # This should be always set. - # If it is not, we will just crash, which is OK too. + # This should be always set. If it is not, we will just crash, which is OK too. + path = os.environ['PATH'] new = rearrange_bin_sbin(path) if new != path: - print('PATH={}'.format(new)) + print(f'PATH={new}') diff --git a/man/check-os-release-simple.py b/man/check-os-release-simple.py index ce73c77b14a36..8c64982790237 100755 --- a/man/check-os-release-simple.py +++ b/man/check-os-release-simple.py @@ -2,11 +2,11 @@ # SPDX-License-Identifier: MIT-0 import platform + os_release = platform.freedesktop_os_release() pretty_name = os_release.get('PRETTY_NAME', 'Linux') print(f'Running on {pretty_name!r}') -if 'fedora' in [os_release.get('ID', 'linux'), - *os_release.get('ID_LIKE', '').split()]: +if 'fedora' in [os_release.get('ID', 'linux'), *os_release.get('ID_LIKE', '').split()]: print('Looks like Fedora!') diff --git a/man/check-os-release.py b/man/check-os-release.py index f0a64f349662a..ec71e57a31a54 100755 --- a/man/check-os-release.py +++ b/man/check-os-release.py @@ -5,6 +5,7 @@ import re import sys + def read_os_release(): try: filename = '/etc/os-release' @@ -23,14 +24,13 @@ def read_os_release(): val = ast.literal_eval(val) yield name, val else: - print(f'{filename}:{line_number}: bad line {line!r}', - file=sys.stderr) + print(f'{filename}:{line_number}: bad line {line!r}', file=sys.stderr) + os_release = dict(read_os_release()) pretty_name = os_release.get('PRETTY_NAME', 'Linux') print(f'Running on {pretty_name!r}') -if 'debian' in [os_release.get('ID', 'linux'), - *os_release.get('ID_LIKE', '').split()]: +if 'debian' in [os_release.get('ID', 'linux'), *os_release.get('ID_LIKE', '').split()]: print('Looks like Debian!') diff --git a/man/notify-selfcontained-example.py b/man/notify-selfcontained-example.py index 6a1e25b99b2ff..1302116221146 100755 --- a/man/notify-selfcontained-example.py +++ b/man/notify-selfcontained-example.py @@ -18,49 +18,56 @@ reloading = False terminating = False + def notify(message): if not message: - raise ValueError("notify() requires a message") + raise ValueError('notify() requires a message') - socket_path = os.environ.get("NOTIFY_SOCKET") + socket_path = os.environ.get('NOTIFY_SOCKET') if not socket_path: return - if socket_path[0] not in ("/", "@"): - raise OSError(errno.EAFNOSUPPORT, "Unsupported socket type") + if socket_path[0] not in ('/', '@'): + raise OSError(errno.EAFNOSUPPORT, 'Unsupported socket type') # Handle abstract socket. - if socket_path[0] == "@": - socket_path = "\0" + socket_path[1:] + if socket_path[0] == '@': + socket_path = '\0' + socket_path[1:] with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM | socket.SOCK_CLOEXEC) as sock: sock.connect(socket_path) sock.sendall(message) + def notify_ready(): - notify(b"READY=1") + notify(b'READY=1') + def notify_reloading(): microsecs = time.clock_gettime_ns(time.CLOCK_MONOTONIC) // 1000 - notify(f"RELOADING=1\nMONOTONIC_USEC={microsecs}".encode()) + notify(f'RELOADING=1\nMONOTONIC_USEC={microsecs}'.encode()) + def notify_stopping(): - notify(b"STOPPING=1") + notify(b'STOPPING=1') + def reload(signum, frame): global reloading reloading = True + def terminate(signum, frame): global terminating terminating = True + def main(): - print("Doing initial setup") + print('Doing initial setup') global reloading, terminating # Set up signal handlers. - print("Setting up signal handlers") + print('Setting up signal handlers') signal.signal(signal.SIGHUP, reload) signal.signal(signal.SIGINT, terminate) signal.signal(signal.SIGTERM, terminate) @@ -68,13 +75,13 @@ def main(): # Do any other setup work here. # Once all setup is done, signal readiness. - print("Done setting up") + print('Done setting up') notify_ready() - print("Starting loop") + print('Starting loop') while not terminating: if reloading: - print("Reloading") + print('Reloading') reloading = False # Support notifying the manager when reloading configuration. @@ -86,19 +93,20 @@ def main(): # Do some reconfiguration work here. - print("Done reloading") + print('Done reloading') notify_ready() # Do the real work here ... - print("Sleeping for five seconds") + print('Sleeping for five seconds') time.sleep(5) - print("Terminating") + print('Terminating') notify_stopping() -if __name__ == "__main__": + +if __name__ == '__main__': sys.stdout.reconfigure(line_buffering=True) - print("Starting app") + print('Starting app') main() - print("Stopped app") + print('Stopped app') diff --git a/ruff.toml b/ruff.toml index 6c0ec6ceb84bb..d4b77fb1e724b 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,6 +1,12 @@ target-version = "py39" line-length = 109 -lint.select = ["E", "F", "I", "UP"] +lint.select = ["E", "F", "I", "Q", "UP"] [format] -quote-style = "single" +# The formatter prefers double quotes for multiline quotes, +# Hence, let's make the formatter not change quotations. +quote-style = "preserve" + +[lint.flake8-quotes] +inline-quotes = "single" +multiline-quotes = "single" diff --git a/src/basic/filesystem-sets.py b/src/basic/filesystem-sets.py index c622b53978843..f8e3dd705fb4f 100755 --- a/src/basic/filesystem-sets.py +++ b/src/basic/filesystem-sets.py @@ -123,12 +123,12 @@ 'z3fold': ['Z3FOLD_MAGIC'], 'zonefs': ['ZONEFS_MAGIC'], 'zsmalloc': ['ZSMALLOC_MAGIC'], -} +} # fmt: skip # System magics are sometimes not unique, because file systems got new # revisions or got renamed. Let's prefer newer over older here, and thus ignore # the old names. -OBSOLETE_NAMES = { +OBSOLETE_NAMES = { 'cpuset', # magic taken over by cgroupfs 'devtmpfs', # not a file system of its own, but just a "named superblock" of tmpfs 'ext2', # ext4 is the newest revision of ext2 + ext3 @@ -141,115 +141,116 @@ 'nfs', # nfs4 is the newest revision of nfs 'pvfs2', # orangefs is the new name of pvfs2 'smb3', # smb3 is an alias for cifs -} +} # fmt: skip FILESYSTEM_SETS = [ ( - "@basic-api", - "Basic filesystem API", - "cgroup", - "cgroup2", - "devpts", - "devtmpfs", - "mqueue", - "proc", - "sysfs", + '@basic-api', + 'Basic filesystem API', + 'cgroup', + 'cgroup2', + 'devpts', + 'devtmpfs', + 'mqueue', + 'proc', + 'sysfs', ), ( - "@anonymous", - "Anonymous inodes", - "anon_inodefs", - "pipefs", - "sockfs", + '@anonymous', + 'Anonymous inodes', + 'anon_inodefs', + 'pipefs', + 'sockfs', ), ( - "@application", - "Application virtual filesystems", - "autofs", - "fuse", - "overlay", + '@application', + 'Application virtual filesystems', + 'autofs', + 'fuse', + 'overlay', ), ( - "@auxiliary-api", - "Auxiliary filesystem API", - "binfmt_misc", - "configfs", - "efivarfs", - "fusectl", - "hugetlbfs", - "rpc_pipefs", - "securityfs", + '@auxiliary-api', + 'Auxiliary filesystem API', + 'binfmt_misc', + 'configfs', + 'efivarfs', + 'fusectl', + 'hugetlbfs', + 'rpc_pipefs', + 'securityfs', ), ( - "@common-block", - "Common block device filesystems", - "btrfs", - "erofs", - "exfat", - "ext4", - "f2fs", - "iso9660", - "ntfs3", - "squashfs", - "udf", - "vfat", - "xfs", + '@common-block', + 'Common block device filesystems', + 'btrfs', + 'erofs', + 'exfat', + 'ext4', + 'f2fs', + 'iso9660', + 'ntfs3', + 'squashfs', + 'udf', + 'vfat', + 'xfs', ), ( - "@historical-block", - "Historical block device filesystems", - "ext2", - "ext3", - "minix", + '@historical-block', + 'Historical block device filesystems', + 'ext2', + 'ext3', + 'minix', ), ( - "@network", - "Well-known network filesystems", - "afs", - "ceph", - "cifs", - "gfs", - "gfs2", - "ncp", - "ncpfs", - "nfs", - "nfs4", - "ocfs2", - "orangefs", - "pvfs2", - "smb3", - "smbfs", + '@network', + 'Well-known network filesystems', + 'afs', + 'ceph', + 'cifs', + 'gfs', + 'gfs2', + 'ncp', + 'ncpfs', + 'nfs', + 'nfs4', + 'ocfs2', + 'orangefs', + 'pvfs2', + 'smb3', + 'smbfs', ), ( - "@privileged-api", - "Privileged filesystem API", - "bpf", - "debugfs", - "pstore", - "tracefs", + '@privileged-api', + 'Privileged filesystem API', + 'bpf', + 'debugfs', + 'pstore', + 'tracefs', ), ( - "@security", - "Security/MAC API VFS", - "apparmorfs", - "selinuxfs", - "smackfs", + '@security', + 'Security/MAC API VFS', + 'apparmorfs', + 'selinuxfs', + 'smackfs', ), ( - "@temporary", - "Temporary filesystems", - "ramfs", - "tmpfs", + '@temporary', + 'Temporary filesystems', + 'ramfs', + 'tmpfs', ), ( - "@known", - "All known filesystems declared in the kernel", + '@known', + 'All known filesystems declared in the kernel', *NAME_TO_MAGIC.keys(), ), ] + def generate_gperf(): - print("""\ + print('''\ /* SPDX-License-Identifier: LGPL-2.1-or-later */ %{ #if __GNUC__ >= 15 @@ -274,12 +275,13 @@ def generate_gperf(): %omit-struct-type %struct-type %includes -%%""") +%%''') for name, magics in NAME_TO_MAGIC.items(): - print(f"{name + ',':16} {{{', '.join(magics)}}}") + print(f'{name + ",":16} {{{", ".join(magics)}}}') + def generate_fs_type_to_string(): - print("""\ + print('''\ #include #include "filesystems.h" @@ -289,7 +291,7 @@ def generate_fs_type_to_string(): #define PROJECT_FILE __FILE__ const char* fs_type_to_string(statfs_f_type_t magic) { - switch (magic) {""") + switch (magic) {''') for name, magics in NAME_TO_MAGIC.items(): if name in OBSOLETE_NAMES: @@ -298,10 +300,11 @@ def generate_fs_type_to_string(): print(f' case (statfs_f_type_t) {magic}:') print(f' return "{name}";') - print("""\ + print('''\ } return NULL; -}""") +}''') + def generate_fs_in_group(): print('bool fs_in_group(const struct statfs *st, FilesystemGroups fs_group) {') @@ -311,14 +314,14 @@ def generate_fs_in_group(): magics = sorted(set(sum((NAME_TO_MAGIC[fs] for fs in filesystems), start=[]))) enum = 'FILESYSTEM_SET_' + name[1:].upper().replace('-', '_') print(f' case {enum}:') - opts = '\n || '.join(f'F_TYPE_EQUAL(st->f_type, {magic})' - for magic in magics) + opts = '\n || '.join(f'F_TYPE_EQUAL(st->f_type, {magic})' for magic in magics) print(f' return {opts};') print(' default: assert_not_reached();') print(' }') print('}') + def generate_filesystem_sets(): print('const FilesystemSet filesystem_sets[_FILESYSTEM_SET_MAX] = {') @@ -328,37 +331,45 @@ def generate_filesystem_sets(): print(f' [{enum}] = {{') print(f' .name = "{name}",') print(f' .help = "{desc}",') - print(f' .value =') + print(' .value =') for filesystem in filesystems: print(f' "{filesystem}\\0"') print(' },') print('};') + def magic_defines(): cpp = os.environ['CPP'].split() out = subprocess.check_output( [*cpp, '-dM', '-include', 'linux/magic.h', '-'], stdin=subprocess.DEVNULL, - text=True) + text=True, + ) for line in out.splitlines(): _, name, *rest = line.split() - if ('_MAGIC' in name - and rest and rest[0].startswith('0x') - and name not in { + if ( + '_MAGIC' in name + and rest + and rest[0].startswith('0x') + and name + not in { 'STACK_END_MAGIC', 'MTD_INODE_FS_MAGIC', 'FUTEXFS_SUPER_MAGIC', 'CRAMFS_MAGIC_WEND', - }): + } + ): yield name + def check(): kernel_magics = set(magic_defines()) our_magics = set(sum(NAME_TO_MAGIC.values(), start=[])) extra = kernel_magics - our_magics if extra: - sys.exit(f"kernel knows additional filesystem magics: {', '.join(sorted(extra))}") + sys.exit(f'kernel knows additional filesystem magics: {", ".join(sorted(extra))}') + if __name__ == '__main__': for arg in sys.argv[1:]: diff --git a/src/boot/generate-hwids-section.py b/src/boot/generate-hwids-section.py index 621183c20fba0..bb740832adbce 100755 --- a/src/boot/generate-hwids-section.py +++ b/src/boot/generate-hwids-section.py @@ -16,12 +16,12 @@ hwids = ukify.parse_hwid_dir(Path(sys.argv[1])) print( - """/* SPDX-License-Identifier: LGPL-2.1-or-later */ + '''/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include const uint8_t hwids_section_data[] = { - """, + ''', end='', ) @@ -33,7 +33,7 @@ print('') print( - """}; -const size_t hwids_section_len =""", + '''}; +const size_t hwids_section_len =''', f'{len(hwids)};', ) diff --git a/src/core/generate-bpf-delegate-configs.py b/src/core/generate-bpf-delegate-configs.py index 200c913b8a826..4e4a54322a480 100755 --- a/src/core/generate-bpf-delegate-configs.py +++ b/src/core/generate-bpf-delegate-configs.py @@ -30,12 +30,12 @@ def print_usage_and_exit() -> None: enumName = '' if output == 'doc': - print("""\ + print('''\ -""") +''') for line in file: line = line.strip() diff --git a/src/fuzz/fuzz-bootspec-gen.py b/src/fuzz/fuzz-bootspec-gen.py index a73e59203bbeb..927c5ed529842 100755 --- a/src/fuzz/fuzz-bootspec-gen.py +++ b/src/fuzz/fuzz-bootspec-gen.py @@ -4,14 +4,12 @@ """Generate sample input for fuzz-bootspec""" import json -import os import sys +from pathlib import Path -config = open(sys.argv[1]).read() -loader = [entry for entry in open(sys.argv[2], encoding='utf-16-le').read().split('\0') - if len(entry) > 2] # filter out fluff from bad decoding -entries = [(os.path.basename(name), open(name).read()) - for name in sys.argv[3:]] +config = Path(sys.argv[1]).read_text() +loader = [entry for entry in Path(sys.argv[2]).read_text(encoding='utf-16-le').split('\0') if len(entry) > 2] +entries = [(Path(name).name, Path(name).read_text()) for name in sys.argv[3:]] data = { 'config': config, diff --git a/src/include/override/sys/generate-syscall.py b/src/include/override/sys/generate-syscall.py index 6f449f9dc1330..470a7badb209c 100755 --- a/src/include/override/sys/generate-syscall.py +++ b/src/include/override/sys/generate-syscall.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -import sys import functools +import sys # We only generate numbers for a dozen or so syscalls SYSCALLS = [ @@ -11,24 +11,29 @@ 'quotactl_fd', # defined in glibc header since glibc-2.35 'removexattrat', 'setxattrat', -] +] # fmt: skip + def dictify(f): def wrap(*args, **kwargs): return dict(f(*args, **kwargs)) + return functools.update_wrapper(wrap, f) + @dictify def parse_syscall_table(filename): print(f'Reading {filename}…') - for line in open(filename): - items = line.split() - if len(items) >= 2: - yield items[0], int(items[1]) + with open(filename) as f: + for line in f: + items = line.split() + if len(items) >= 2: + yield items[0], int(items[1]) + def parse_syscall_tables(filenames): - return {filename.split('-')[-1][:-4]: parse_syscall_table(filename) - for filename in filenames} + return {filename.split('-')[-1][:-4]: parse_syscall_table(filename) for filename in filenames} + HEADER = '''\ /* SPDX-License-Identifier: LGPL-2.1-or-later @@ -56,6 +61,11 @@ def parse_syscall_tables(filenames): #ifndef __IGNORE_{syscall} ''' +ARCH_CHECK_A = '''\ +/* Note: if this code looks strange, this is because it is derived from the same + * template as the per-syscall blocks below. */ +''' + DEF_TEMPLATE_B = '''\ # if defined(__aarch64__) # define systemd_NR_{syscall} {nr_arm64} @@ -107,12 +117,22 @@ def parse_syscall_tables(filenames): # else # define systemd_NR_{syscall} {nr_x86_64} # endif +''' + +DEF_TEMPLATE_C = '''\ # elif !defined(missing_arch_template) -%s +# warning "{syscall}() syscall number is unknown for your architecture" # endif ''' -DEF_TEMPLATE_C = '''\ +ARCH_CHECK_C = '''\ + +# else +# warning "Current architecture is missing from the template" +# define missing_arch_template 1 +# endif''' + +DEF_TEMPLATE_D = '''\ /* may be an (invalid) negative number due to libseccomp, see PR 13319 */ # if defined __NR_{syscall} && __NR_{syscall} >= 0 @@ -129,23 +149,25 @@ def parse_syscall_tables(filenames): # endif #endif''' -DEF_TEMPLATE = (DEF_TEMPLATE_A + - DEF_TEMPLATE_B % '# warning "{syscall}() syscall number is unknown for your architecture"' + - DEF_TEMPLATE_C) +DEF_TEMPLATE = ( + DEF_TEMPLATE_A + + DEF_TEMPLATE_B + + DEF_TEMPLATE_C + + DEF_TEMPLATE_D +) # fmt: skip + +ARCH_CHECK = ( + ARCH_CHECK_A + + '\n'.join(line for line in DEF_TEMPLATE_B.splitlines() if ' define ' not in line) + + ARCH_CHECK_C +) -ARCH_CHECK = '''\ -/* Note: if this code looks strange, this is because it is derived from the same - * template as the per-syscall blocks below. */ -''' + '\n'.join(line for line in DEF_TEMPLATE_B.splitlines() - if ' define ' not in line) % '''\ -# warning "Current architecture is missing from the template" -# define missing_arch_template 1''' def print_syscall_def(syscall, tables, out): - mappings = {f'nr_{arch}':t.get(syscall, -1) - for arch, t in tables.items()} - print(DEF_TEMPLATE.format(syscall=syscall, **mappings), - file=out) + mappings = {f'nr_{arch}': t.get(syscall, -1) + for arch, t in tables.items()} # fmt: skip + print(DEF_TEMPLATE.format(syscall=syscall, **mappings), file=out) + def print_syscall_defs(syscalls, tables, out): print(HEADER, file=out) @@ -153,12 +175,13 @@ def print_syscall_defs(syscalls, tables, out): for syscall in syscalls: print_syscall_def(syscall, tables, out) + if __name__ == '__main__': output_file = sys.argv[1] arch_files = sys.argv[2:] - out = open(output_file, 'wt') tables = parse_syscall_tables(arch_files) - print_syscall_defs(SYSCALLS, tables, out) + with open(output_file, 'w') as out: + print_syscall_defs(SYSCALLS, tables, out) print(f'Wrote {output_file}') diff --git a/src/include/override/sys/syscall.h b/src/include/override/sys/syscall.h index da2f780bed39c..c48e1db0dd87d 100644 --- a/src/include/override/sys/syscall.h +++ b/src/include/override/sys/syscall.h @@ -49,7 +49,7 @@ # if defined(__ILP32__) # else # endif -# elif !defined(missing_arch_template) +# else # warning "Current architecture is missing from the template" # define missing_arch_template 1 # endif diff --git a/src/journal-remote/log-generator.py b/src/journal-remote/log-generator.py index 2843afb4c93c7..1e2c0e5c3e96b 100755 --- a/src/journal-remote/log-generator.py +++ b/src/journal-remote/log-generator.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -import sys import argparse +import sys PARSER = argparse.ArgumentParser() PARSER.add_argument('n', type=int) @@ -12,7 +12,7 @@ PARSER.add_argument('--data-type', choices={'random', 'simple'}) OPTIONS = PARSER.parse_args() -template = """\ +template = '''\ __CURSOR=s=6863c726210b4560b7048889d8ada5c5;i=3e931;b=f446871715504074bf7049ef0718fa93;m={m:x};t=4fd05c __REALTIME_TIMESTAMP={realtime_ts} __MONOTONIC_TIMESTAMP={monotonic_ts} @@ -30,7 +30,7 @@ _PID=25721 _SOURCE_REALTIME_TIMESTAMP={source_realtime_ts} DATA={data} -""" +''' priority = 3 facility = 6 @@ -51,14 +51,16 @@ data = '{:0{}}'.format(counter, OPTIONS.data_size) counter += 1 - entry = template.format(m=0x198603b12d7 + i, - realtime_ts=1404101101501873 + i, - monotonic_ts=1753961140951 + i, - source_realtime_ts=1404101101483516 + i, - priority=priority, - facility=facility, - message=message, - data=data) + entry = template.format( + m=0x198603B12D7 + i, + realtime_ts=1404101101501873 + i, + monotonic_ts=1753961140951 + i, + source_realtime_ts=1404101101483516 + i, + priority=priority, + facility=facility, + message=message, + data=data, + ) bytes += len(entry) @@ -69,4 +71,4 @@ if OPTIONS.dots: print(file=sys.stderr) -print('Wrote {} bytes'.format(bytes), file=sys.stderr) +print(f'Wrote {bytes} bytes', file=sys.stderr) diff --git a/src/shared/ethtool-link-mode.py b/src/shared/ethtool-link-mode.py index 6d23f3c43aee9..c64fa6c456381 100755 --- a/src/shared/ethtool-link-mode.py +++ b/src/shared/ethtool-link-mode.py @@ -7,7 +7,7 @@ import sys OVERRIDES = { - 'autoneg' : 'autonegotiation', + 'autoneg': 'autonegotiation', } mode = sys.argv[1] diff --git a/src/shared/generate-dns_type-gperf.py b/src/shared/generate-dns_type-gperf.py index 0fd003a8023ae..14a42a30f4913 100755 --- a/src/shared/generate-dns_type-gperf.py +++ b/src/shared/generate-dns_type-gperf.py @@ -1,26 +1,27 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -"""Generate %-from-name.gperf from %-list.txt -""" +"""Generate %-from-name.gperf from %-list.txt""" import sys name, prefix, input = sys.argv[1:] -print("""\ +print('''\ %{ _Pragma("GCC diagnostic ignored \\"-Wimplicit-fallthrough\\"") #if __GNUC__ >= 15 _Pragma("GCC diagnostic ignored \\"-Wzero-as-null-pointer-constant\\"") #endif -%}""") -print("""\ -struct {}_name {{ const char* name; int id; }}; +%}''') +print( + f'''\ +struct {name}_name {{ const char* name; int id; }}; %null-strings -%%""".format(name)) +%%''' +) for line in open(input): line = line.rstrip() s = line.replace('_', '-') - print("{}, {}{}".format(s, prefix, line)) + print(f'{s}, {prefix}{line}') diff --git a/src/shared/generate-syscall-list.py b/src/shared/generate-syscall-list.py index c0975a06da812..111e8b7367e04 100755 --- a/src/shared/generate-syscall-list.py +++ b/src/shared/generate-syscall-list.py @@ -4,4 +4,4 @@ import sys for line in open(sys.argv[1]): - print('"{}\\0"'.format(line.strip())) + print(f'"{line.strip()}\\0"') diff --git a/src/test/generate-sym-test.py b/src/test/generate-sym-test.py index b8d64d623f04f..aa6c916fc7c92 100755 --- a/src/test/generate-sym-test.py +++ b/src/test/generate-sym-test.py @@ -120,43 +120,43 @@ def process_source_file(file: IO[str]) -> None: continue -print("""/* SPDX-License-Identifier: LGPL-2.1-or-later */ +print('''/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include -""") +''') for header in sys.argv[3:]: with open(header, 'r') as f: if process_header_file(f): print('#include "{}"'.format(header.split('/')[-1])) -print(""" +print(''' /* We want to check deprecated symbols too, without complaining */ #pragma GCC diagnostic ignored "-Wdeprecated-declarations" -""") +''') -print(""" +print(''' struct symbol { const char *name; const void *symbol; }; -static struct symbol symbols_from_sym[] = {""") +static struct symbol symbols_from_sym[] = {''') with open(sys.argv[1], 'r') as f: process_sym_file(f) -print(""" {} -}, symbols_from_header[] = {""") +print(''' {} +}, symbols_from_header[] = {''') for header in sys.argv[3:]: with open(header, 'r') as f: print(process_header_file(f), end='') -print(""" {} -}, symbols_from_source[] = {""") +print(''' {} +}, symbols_from_source[] = {''') for dirpath, _, filenames in sorted(os.walk(sys.argv[2])): for filename in sorted(filenames): @@ -168,7 +168,7 @@ def process_source_file(file: IO[str]) -> None: with p.open('rt') as f: process_source_file(f) -print(""" {} +print(''' {} }; static int sort_callback(const void *a, const void *b) { @@ -239,4 +239,4 @@ def process_source_file(file: IO[str]) -> None: } return n_error == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -}""") +}''') diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 224a38569f280..0fe27601f15b4 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -33,8 +33,7 @@ sys.exit(77) try: - # pyflakes: noqa - import pefile # noqa + import pefile except ImportError as e: print(str(e), file=sys.stderr) sys.exit(77) @@ -59,42 +58,51 @@ slow_tests = True arg_tools = ['--tools', build_root] if build_root else [] -if build_root and ( - p := pathlib.Path(f"{build_root}/linux{ukify.guess_efi_arch()}.efi.stub") -).exists(): +if ( + build_root + and (p := pathlib.Path(f'{build_root}/linux{ukify.guess_efi_arch()}.efi.stub')).exists() +): # fmt: skip arg_tools += ['--stub', p] + def systemd_measure(): opts = ukify.create_parser().parse_args(arg_tools) return ukify.find_tool('systemd-measure', opts=opts) + def test_guess_efi_arch(): arch = ukify.guess_efi_arch() assert arch in ukify.EFI_ARCHES + def test_shell_join(): assert ukify.shell_join(['a', 'b', ' ']) == "a b ' '" + def test_round_up(): assert ukify.round_up(0) == 0 assert ukify.round_up(4095) == 4096 assert ukify.round_up(4096) == 4096 assert ukify.round_up(4097) == 8192 + def test_namespace_creation(): ns = ukify.create_parser().parse_args(()) assert ns.linux is None assert ns.initrd is None + def test_config_example(): ex = ukify.config_example() assert '[UKI]' in ex assert 'Splash = BMP' in ex + def test_apply_config(tmp_path): config = tmp_path / 'config1.conf' - config.write_text(textwrap.dedent( - f''' + config.write_text( + textwrap.dedent( + f''' [UKI] Linux = LINUX Initrd = initrd1 initrd2 @@ -117,7 +125,9 @@ def test_apply_config(tmp_path): PCRPrivateKey = some/path7 PCRPublicKey = some/path8 Phases = {':'.join(ukify.KNOWN_PHASES)} - ''')) + ''' + ) + ) ns = ukify.create_parser().parse_args(['build']) ns.linux = None @@ -125,9 +135,11 @@ def test_apply_config(tmp_path): ukify.apply_config(ns, config) assert ns.linux == pathlib.Path('LINUX') - assert ns.initrd == [pathlib.Path('initrd1'), - pathlib.Path('initrd2'), - pathlib.Path('initrd3')] + assert ns.initrd == [ + pathlib.Path('initrd1'), + pathlib.Path('initrd2'), + pathlib.Path('initrd3'), + ] assert ns.cmdline == '1 2 3 4 5\n6 7 8' assert ns.os_release == '@some/path1' assert ns.devicetree == pathlib.Path('some/path2') @@ -148,9 +160,11 @@ def test_apply_config(tmp_path): ukify.finalize_options(ns) assert ns.linux == pathlib.Path('LINUX') - assert ns.initrd == [pathlib.Path('initrd1'), - pathlib.Path('initrd2'), - pathlib.Path('initrd3')] + assert ns.initrd == [ + pathlib.Path('initrd1'), + pathlib.Path('initrd2'), + pathlib.Path('initrd3'), + ] assert ns.cmdline == '1 2 3 4 5 6 7 8' assert ns.os_release == pathlib.Path('some/path1') assert ns.devicetree == pathlib.Path('some/path2') @@ -168,6 +182,7 @@ def test_apply_config(tmp_path): assert ns.pcr_public_keys == ['some/path8'] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] + def test_parse_args_minimal(): with pytest.raises(ValueError): ukify.parse_args([]) @@ -175,32 +190,39 @@ def test_parse_args_minimal(): opts = ukify.parse_args('arg1 arg2'.split()) assert opts.linux == pathlib.Path('arg1') assert opts.initrd == [pathlib.Path('arg2')] - assert opts.os_release in (pathlib.Path('/etc/os-release'), - pathlib.Path('/usr/lib/os-release')) + assert opts.os_release in ( + pathlib.Path('/etc/os-release'), + pathlib.Path('/usr/lib/os-release'), + ) + def test_parse_args_many_deprecated(): opts = ukify.parse_args( - ['/ARG1', '///ARG2', '/ARG3 WITH SPACE', - '--cmdline=a b c', - '--os-release=K1=V1\nK2=V2', - '--devicetree=DDDDTTTT', - '--splash=splash', - '--pcrpkey=PATH', - '--uname=1.2.3', - '--stub=STUBPATH', - '--pcr-private-key=PKEY1', - '--pcr-public-key=PKEY2', - '--pcr-banks=SHA1,SHA256', - '--signing-engine=ENGINE', - '--secureboot-private-key=SBKEY', - '--secureboot-certificate=SBCERT', - '--sign-kernel', - '--no-sign-kernel', - '--tools=TOOLZ///', - '--output=OUTPUT', - '--measure', - '--no-measure', - ]) + [ + '/ARG1', + '///ARG2', + '/ARG3 WITH SPACE', + '--cmdline=a b c', + '--os-release=K1=V1\nK2=V2', + '--devicetree=DDDDTTTT', + '--splash=splash', + '--pcrpkey=PATH', + '--uname=1.2.3', + '--stub=STUBPATH', + '--pcr-private-key=PKEY1', + '--pcr-public-key=PKEY2', + '--pcr-banks=SHA1,SHA256', + '--signing-engine=ENGINE', + '--secureboot-private-key=SBKEY', + '--secureboot-certificate=SBCERT', + '--sign-kernel', + '--no-sign-kernel', + '--tools=TOOLZ///', + '--output=OUTPUT', + '--measure', + '--no-measure', + ] + ) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] assert opts.cmdline == 'a b c' @@ -221,34 +243,37 @@ def test_parse_args_many_deprecated(): assert opts.output == pathlib.Path('OUTPUT') assert opts.measure is False + def test_parse_args_many(): opts = ukify.parse_args( - ['build', - '--linux=/ARG1', - '--initrd=///ARG2', - '--initrd=/ARG3 WITH SPACE', - '--cmdline=a b c', - '--os-release=K1=V1\nK2=V2', - '--devicetree=DDDDTTTT', - '--splash=splash', - '--pcrpkey=PATH', - '--uname=1.2.3', - '--stub=STUBPATH', - '--pcr-private-key=PKEY1', - '--pcr-public-key=PKEY2', - '--pcr-banks=SHA1,SHA256', - '--signing-engine=ENGINE', - '--secureboot-private-key=SBKEY', - '--secureboot-certificate=SBCERT', - '--sign-kernel', - '--no-sign-kernel', - '--tools=TOOLZ///', - '--output=OUTPUT', - '--measure', - '--no-measure', - '--policy-digest', - '--no-policy-digest', - ]) + [ + 'build', + '--linux=/ARG1', + '--initrd=///ARG2', + '--initrd=/ARG3 WITH SPACE', + '--cmdline=a b c', + '--os-release=K1=V1\nK2=V2', + '--devicetree=DDDDTTTT', + '--splash=splash', + '--pcrpkey=PATH', + '--uname=1.2.3', + '--stub=STUBPATH', + '--pcr-private-key=PKEY1', + '--pcr-public-key=PKEY2', + '--pcr-banks=SHA1,SHA256', + '--signing-engine=ENGINE', + '--secureboot-private-key=SBKEY', + '--secureboot-certificate=SBCERT', + '--sign-kernel', + '--no-sign-kernel', + '--tools=TOOLZ///', + '--output=OUTPUT', + '--measure', + '--no-measure', + '--policy-digest', + '--no-policy-digest', + ] + ) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] assert opts.cmdline == 'a b c' @@ -270,14 +295,17 @@ def test_parse_args_many(): assert opts.measure is False assert opts.policy_digest is False + def test_parse_sections(): opts = ukify.parse_args( - ['build', - '--linux=/ARG1', - '--initrd=/ARG2', - '--section=test:TESTTESTTEST', - '--section=test2:@FILE', - ]) + [ + 'build', + '--linux=/ARG1', + '--initrd=/ARG2', + '--section=test:TESTTESTTEST', + '--section=test2:@FILE', + ] + ) assert opts.linux == pathlib.Path('/ARG1') assert opts.initrd == [pathlib.Path('/ARG2')] @@ -293,11 +321,13 @@ def test_parse_sections(): assert opts.sections[1].tmpfile is None assert opts.sections[1].measure is False + def test_config_priority(tmp_path): config = tmp_path / 'config1.conf' # config: use pesign and give certdir + certname - config.write_text(textwrap.dedent( - f''' + config.write_text( + textwrap.dedent( + f''' [UKI] Linux = LINUX Initrd = initrd1 initrd2 @@ -321,44 +351,50 @@ def test_config_priority(tmp_path): PCRPrivateKey = some/path7 PCRPublicKey = some/path8 Phases = {':'.join(ukify.KNOWN_PHASES)} - ''')) + ''' + ) + ) # args: use sbsign and give key + cert, should override pesign opts = ukify.parse_args( - ['build', - '--linux=/ARG1', - '--initrd=///ARG2', - '--initrd=/ARG3 WITH SPACE', - '--cmdline= a b c ', - '--os-release=K1=V1\nK2=V2', - '--devicetree=DDDDTTTT', - '--splash=splash', - '--pcrpkey=PATH', - '--uname=1.2.3', - '--stub=STUBPATH', - '--pcr-private-key=PKEY1', - '--pcr-public-key=PKEY2', - '--pcr-banks=SHA1,SHA256', - '--signing-engine=ENGINE', - '--signtool=sbsign', - '--secureboot-private-key=SBKEY', - '--secureboot-certificate=SBCERT', - '--sign-kernel', - '--no-sign-kernel', - '--tools=TOOLZ///', - '--output=OUTPUT', - '--measure', - ]) + [ + 'build', + '--linux=/ARG1', + '--initrd=///ARG2', + '--initrd=/ARG3 WITH SPACE', + '--cmdline= a b c ', + '--os-release=K1=V1\nK2=V2', + '--devicetree=DDDDTTTT', + '--splash=splash', + '--pcrpkey=PATH', + '--uname=1.2.3', + '--stub=STUBPATH', + '--pcr-private-key=PKEY1', + '--pcr-public-key=PKEY2', + '--pcr-banks=SHA1,SHA256', + '--signing-engine=ENGINE', + '--signtool=sbsign', + '--secureboot-private-key=SBKEY', + '--secureboot-certificate=SBCERT', + '--sign-kernel', + '--no-sign-kernel', + '--tools=TOOLZ///', + '--output=OUTPUT', + '--measure', + ] + ) ukify.apply_config(opts, config) ukify.finalize_options(opts) assert opts.linux == pathlib.Path('/ARG1') - assert opts.initrd == [pathlib.Path('initrd1'), - pathlib.Path('initrd2'), - pathlib.Path('initrd3'), - pathlib.Path('/ARG2'), - pathlib.Path('/ARG3 WITH SPACE')] + assert opts.initrd == [ + pathlib.Path('initrd1'), + pathlib.Path('initrd2'), + pathlib.Path('initrd3'), + pathlib.Path('/ARG2'), + pathlib.Path('/ARG3 WITH SPACE'), + ] assert opts.cmdline == 'a b c' assert opts.os_release == 'K1=V1\nK2=V2' assert opts.devicetree == pathlib.Path('DDDDTTTT') @@ -370,16 +406,17 @@ def test_config_priority(tmp_path): assert opts.pcr_public_keys == ['PKEY2', 'some/path8'] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' - assert opts.signtool == 'sbsign' # from args - assert opts.sb_key == 'SBKEY' # from args - assert opts.sb_cert == pathlib.Path('SBCERT') # from args - assert opts.sb_certdir == 'some/path5' # from config - assert opts.sb_cert_name == 'some/name1' # from config + assert opts.signtool == 'sbsign' # from args + assert opts.sb_key == 'SBKEY' # from args + assert opts.sb_cert == pathlib.Path('SBCERT') # from args + assert opts.sb_certdir == 'some/path5' # from config + assert opts.sb_cert_name == 'some/name1' # from config assert opts.sign_kernel is False assert opts.tools == [pathlib.Path('TOOLZ/')] assert opts.output == pathlib.Path('OUTPUT') assert opts.measure is True + def test_help(capsys): with pytest.raises(SystemExit): ukify.parse_args(['--help']) @@ -387,6 +424,7 @@ def test_help(capsys): assert '--section' in out.out assert not out.err + def test_help_display(capsys): with pytest.raises(SystemExit): ukify.parse_args(['inspect', '--help']) @@ -394,6 +432,7 @@ def test_help_display(capsys): assert '--section' in out.out assert not out.err + def test_help_error_deprecated(capsys): with pytest.raises(SystemExit): ukify.parse_args(['a', 'b', '--no-such-option']) @@ -402,6 +441,7 @@ def test_help_error_deprecated(capsys): assert '--no-such-option' in out.err assert len(out.err.splitlines()) == 1 + def test_help_error(capsys): with pytest.raises(SystemExit): ukify.parse_args(['build', '--no-such-option']) @@ -410,6 +450,7 @@ def test_help_error(capsys): assert '--no-such-option' in out.err assert len(out.err.splitlines()) == 1 + @pytest.fixture(scope='session') def kernel_initrd(): items = sorted(glob.glob('/lib/modules/*/vmlinuz')) @@ -426,6 +467,7 @@ def kernel_initrd(): # We don't look _into_ the initrd. Any file is OK. return ['--linux', linux, '--initrd', ukify.__file__] + def test_check_splash(): try: # pyflakes: noqa @@ -436,16 +478,19 @@ def test_check_splash(): with pytest.raises(OSError): ukify.check_splash(os.devnull) + def test_basic_operation(kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') output = f'{tmp_path}/basic.efi' - opts = ukify.parse_args([ - 'build', - *kernel_initrd, - f'--output={output}', - ]) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd, + f'--output={output}', + ] + ) try: ukify.check_inputs(opts) except OSError as e: @@ -458,20 +503,23 @@ def test_basic_operation(kernel_initrd, tmp_path): shutil.rmtree(tmp_path) + def test_sections(kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') output = f'{tmp_path}/basic.efi' - opts = ukify.parse_args([ - 'build', - *kernel_initrd, - f'--output={output}', - '--uname=1.2.3', - '--cmdline=ARG1 ARG2 ARG3', - '--os-release=K1=V1\nK2=V2\n', - '--section=.test:CONTENTZ', - ]) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd, + f'--output={output}', + '--uname=1.2.3', + '--cmdline=ARG1 ARG2 ARG3', + '--os-release=K1=V1\nK2=V2\n', + '--section=.test:CONTENTZ', + ] + ) try: ukify.check_inputs(opts) @@ -484,24 +532,25 @@ def test_sections(kernel_initrd, tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text osrel cmdline linux initrd uname test'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) shutil.rmtree(tmp_path) + def test_addon(tmp_path): output = f'{tmp_path}/addon.efi' args = [ 'build', f'--output={output}', '--cmdline=ARG1 ARG2 ARG3', - """--sbat=sbat,1,foo + '''--sbat=sbat,1,foo foo,1 bar,2 -""", +''', '--section=.test:CONTENTZ', - """--sbat=sbat,1,foo + '''--sbat=sbat,1,foo baz,3 -""" +''', ] if stub := os.getenv('EFI_ADDON'): args += [f'--stub={stub}'] @@ -521,26 +570,33 @@ def test_addon(tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text cmdline test sbat'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) pe = pefile.PE(output, fast_load=True) found = False for section in pe.sections: - if section.Name.rstrip(b"\x00").decode() == ".sbat": + if section.Name.rstrip(b'\x00').decode() == '.sbat': assert found is False - split = section.get_data().rstrip(b"\x00").decode().splitlines() - assert split == ["sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md", "foo,1", "bar,2", "baz,3"] + split = section.get_data().rstrip(b'\x00').decode().splitlines() + assert split == [ + 'sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md', + 'foo,1', + 'bar,2', + 'baz,3', + ] found = True assert found is True + def unbase64(filename): tmp = tempfile.NamedTemporaryFile() base64.decode(filename.open('rb'), tmp) tmp.flush() return tmp + def test_uname_scraping(kernel_initrd): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -549,8 +605,9 @@ def test_uname_scraping(kernel_initrd): uname = ukify.Uname.scrape(kernel_initrd[1]) assert re.match(r'\d+\.\d+\.\d+', uname) + @pytest.mark.skipif(not slow_tests, reason='slow') -@pytest.mark.parametrize("days", [365*10, None]) +@pytest.mark.parametrize('days', [365 * 10, None]) def test_efi_signing_sbsign(days, kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -585,16 +642,20 @@ def test_efi_signing_sbsign(days, kernel_initrd, tmp_path): if shutil.which('sbverify'): # let's check that sbverify likes the resulting file - dump = subprocess.check_output([ - 'sbverify', - '--cert', cert.name, - output, - ], text=True) + dump = subprocess.check_output( + [ + 'sbverify', + '--cert', cert.name, + output, + ], + text=True, + ) # fmt: skip assert 'Signature verification OK' in dump shutil.rmtree(tmp_path) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_efi_signing_pesign(kernel_initrd, tmp_path): if kernel_initrd is None: @@ -613,16 +674,18 @@ def test_efi_signing_pesign(kernel_initrd, tmp_path): subprocess.check_call(cmd) output = f'{tmp_path}/signed.efi' - opts = ukify.parse_args([ - 'build', - *kernel_initrd, - f'--output={output}', - '--uname=1.2.3', - '--signtool=pesign', - '--cmdline=ARG1 ARG2 ARG3', - f'--secureboot-certificate-name={name}', - f'--secureboot-certificate-dir={nss_db}', - ]) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd, + f'--output={output}', + '--uname=1.2.3', + '--signtool=pesign', + '--cmdline=ARG1 ARG2 ARG3', + f'--secureboot-certificate-name={name}', + f'--secureboot-certificate-dir={nss_db}', + ] + ) try: ukify.check_inputs(opts) @@ -632,15 +695,20 @@ def test_efi_signing_pesign(kernel_initrd, tmp_path): ukify.make_uki(opts) # let's check that pesign likes the resulting file - dump = subprocess.check_output([ - 'pesign', '-S', - '-i', output, - ], text=True) + dump = subprocess.check_output( + [ + 'pesign', + '-S', + '-i', output, + ], + text=True, + ) # fmt: skip assert f"The signer's common name is {author}" in dump shutil.rmtree(tmp_path) + def test_inspect(kernel_initrd, tmp_path, capsys, osrel=True): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -652,9 +720,9 @@ def test_inspect(kernel_initrd, tmp_path, capsys, osrel=True): key = unbase64(ourdir / 'example.signing.key.base64') output = f'{tmp_path}/signed2.efi' - uname_arg='1.2.3' - osrel_arg='Linux' if osrel else '' - cmdline_arg='ARG1 ARG2 ARG3' + uname_arg = '1.2.3' + osrel_arg = 'Linux' if osrel else '' + cmdline_arg = 'ARG1 ARG2 ARG3' args = [ 'build', @@ -698,9 +766,11 @@ def test_inspect(kernel_initrd, tmp_path, capsys, osrel=True): shutil.rmtree(tmp_path) + def test_inspect_no_osrel(kernel_initrd, tmp_path, capsys): test_inspect(kernel_initrd, tmp_path, capsys, osrel=False) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_pcr_signing(kernel_initrd, tmp_path): if kernel_initrd is None: @@ -722,7 +792,7 @@ def test_pcr_signing(kernel_initrd, tmp_path): '--uname=1.2.3', '--cmdline=ARG1 ARG2 ARG3', '--os-release=ID=foobar\n', - '--pcr-banks=sha384', # sha1 might not be allowed, use something else + '--pcr-banks=sha384', # sha1 might not be allowed, use something else f'--pcr-private-key={priv.name}', ] + arg_tools @@ -743,21 +813,25 @@ def test_pcr_signing(kernel_initrd, tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text osrel cmdline linux initrd uname pcrsig'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) # objcopy fails when called without an output argument (EPERM). # It also fails when called with /dev/null (file truncated). # It also fails when called with /dev/zero (because it reads the # output file, infinitely in this case.) # So let's just call it with a dummy output argument. - subprocess.check_call([ - 'objcopy', - *(f'--dump-section=.{n}={tmp_path}/out.{n}' for n in ( - 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline')), - output, - tmp_path / 'dummy', - ], - text=True) + subprocess.check_call( + [ + 'objcopy', + *( + f'--dump-section=.{n}={tmp_path}/out.{n}' + for n in ('pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline') + ), + output, + tmp_path / 'dummy', + ], + text=True, + ) assert open(tmp_path / 'out.pcrpkey').read() == open(pub.name).read() assert open(tmp_path / 'out.osrel').read() == 'ID=foobar\n' @@ -766,10 +840,11 @@ def test_pcr_signing(kernel_initrd, tmp_path): sig = open(tmp_path / 'out.pcrsig').read() sig = json.loads(sig) assert list(sig.keys()) == ['sha384'] - assert len(sig['sha384']) == 4 # four items for four phases + assert len(sig['sha384']) == 4 # four items for four phases shutil.rmtree(tmp_path) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_pcr_signing2(kernel_initrd, tmp_path): if kernel_initrd is None: @@ -791,24 +866,27 @@ def test_pcr_signing2(kernel_initrd, tmp_path): output = f'{tmp_path}/signed.efi' assert kernel_initrd[0] == '--linux' - opts = ukify.parse_args([ - 'build', - *kernel_initrd[:2], - f'--initrd={microcode.name}', - *kernel_initrd[2:], - f'--output={output}', - '--uname=1.2.3', - '--cmdline=ARG1 ARG2 ARG3', - '--os-release=ID=foobar\n', - '--pcr-banks=sha384', - f'--pcrpkey={pub2.name}', - f'--pcr-public-key={pub.name}', - f'--pcr-private-key={priv.name}', - '--phases=enter-initrd enter-initrd:leave-initrd', - f'--pcr-public-key={pub2.name}', - f'--pcr-private-key={priv2.name}', - '--phases=sysinit ready shutdown final', # yes, those phase paths are not reachable - ] + arg_tools) + opts = ukify.parse_args( + [ + 'build', + *kernel_initrd[:2], + f'--initrd={microcode.name}', + *kernel_initrd[2:], + f'--output={output}', + '--uname=1.2.3', + '--cmdline=ARG1 ARG2 ARG3', + '--os-release=ID=foobar\n', + '--pcr-banks=sha384', + f'--pcrpkey={pub2.name}', + f'--pcr-public-key={pub.name}', + f'--pcr-private-key={priv.name}', + '--phases=enter-initrd enter-initrd:leave-initrd', + f'--pcr-public-key={pub2.name}', + f'--pcr-private-key={priv2.name}', + '--phases=sysinit ready shutdown final', # yes, those phase paths are not reachable + ] + + arg_tools + ) try: ukify.check_inputs(opts) @@ -821,16 +899,20 @@ def test_pcr_signing2(kernel_initrd, tmp_path): dump = subprocess.check_output(['objdump', '-h', output], text=True) for sect in 'text osrel cmdline linux initrd uname pcrsig'.split(): - assert re.search(fr'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) + assert re.search(rf'^\s*\d+\s+\.{sect}\s+[0-9a-f]+', dump, re.MULTILINE) - subprocess.check_call([ - 'objcopy', - *(f'--dump-section=.{n}={tmp_path}/out.{n}' for n in ( - 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd')), - output, - tmp_path / 'dummy', - ], - text=True) + subprocess.check_call( + [ + 'objcopy', + *( + f'--dump-section=.{n}={tmp_path}/out.{n}' + for n in ('pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd') + ), + output, + tmp_path / 'dummy', + ], + text=True, + ) assert open(tmp_path / 'out.pcrpkey').read() == open(pub2.name).read() assert open(tmp_path / 'out.osrel').read() == 'ID=foobar\n' @@ -841,22 +923,25 @@ def test_pcr_signing2(kernel_initrd, tmp_path): sig = open(tmp_path / 'out.pcrsig').read() sig = json.loads(sig) assert list(sig.keys()) == ['sha384'] - assert len(sig['sha384']) == 6 # six items for six phases paths + assert len(sig['sha384']) == 6 # six items for six phases paths shutil.rmtree(tmp_path) + def test_key_cert_generation(tmp_path): - opts = ukify.parse_args([ - 'genkey', - f"--pcr-public-key={tmp_path / 'pcr1.pub.pem'}", - f"--pcr-private-key={tmp_path / 'pcr1.priv.pem'}", - '--phases=enter-initrd enter-initrd:leave-initrd', - f"--pcr-public-key={tmp_path / 'pcr2.pub.pem'}", - f"--pcr-private-key={tmp_path / 'pcr2.priv.pem'}", - '--phases=sysinit ready', - f"--secureboot-private-key={tmp_path / 'sb.priv.pem'}", - f"--secureboot-certificate={tmp_path / 'sb.cert.pem'}", - ]) + opts = ukify.parse_args( + [ + 'genkey', + f'--pcr-public-key={tmp_path / "pcr1.pub.pem"}', + f'--pcr-private-key={tmp_path / "pcr1.priv.pem"}', + '--phases=enter-initrd enter-initrd:leave-initrd', + f'--pcr-public-key={tmp_path / "pcr2.pub.pem"}', + f'--pcr-private-key={tmp_path / "pcr2.priv.pem"}', + '--phases=sysinit ready', + f'--secureboot-private-key={tmp_path / "sb.priv.pem"}', + f'--secureboot-certificate={tmp_path / "sb.cert.pem"}', + ] + ) assert opts.verb == 'genkey' ukify.check_cert_and_keys_nonexistent(opts) @@ -867,39 +952,46 @@ def test_key_cert_generation(tmp_path): if not shutil.which('openssl'): return - for key in (tmp_path / 'pcr1.priv.pem', - tmp_path / 'pcr2.priv.pem', - tmp_path / 'sb.priv.pem'): - out = subprocess.check_output([ - 'openssl', 'rsa', - '-in', key, - '-text', - '-noout', - ], text = True) + for key in (tmp_path / 'pcr1.priv.pem', tmp_path / 'pcr2.priv.pem', tmp_path / 'sb.priv.pem'): + out = subprocess.check_output( + [ + 'openssl', 'rsa', + '-in', key, + '-text', + '-noout', + ], + text=True, + ) # fmt: skip assert 'Private-Key' in out assert '2048 bit' in out - for pub in (tmp_path / 'pcr1.pub.pem', - tmp_path / 'pcr2.pub.pem'): - out = subprocess.check_output([ - 'openssl', 'rsa', - '-pubin', - '-in', pub, - '-text', - '-noout', - ], text = True) + for pub in (tmp_path / 'pcr1.pub.pem', tmp_path / 'pcr2.pub.pem'): + out = subprocess.check_output( + [ + 'openssl', 'rsa', + '-pubin', + '-in', pub, + '-text', + '-noout', + ], + text=True, + ) # fmt: skip assert 'Public-Key' in out assert '2048 bit' in out - out = subprocess.check_output([ - 'openssl', 'x509', - '-in', tmp_path / 'sb.cert.pem', - '-text', - '-noout', - ], text = True) + out = subprocess.check_output( + [ + 'openssl', 'x509', + '-in', tmp_path / 'sb.cert.pem', + '-text', + '-noout', + ], + text=True, + ) # fmt: skip assert 'Certificate' in out assert re.search(r'Issuer: CN\s?=\s?SecureBoot signing key on host', out) + @pytest.mark.skipif(not slow_tests, reason='slow') def test_join_pcrsig(capsys, kernel_initrd, tmp_path): if kernel_initrd is None: @@ -965,5 +1057,6 @@ def test_join_pcrsig(capsys, kernel_initrd, tmp_path): shutil.rmtree(tmp_path) + if __name__ == '__main__': sys.exit(pytest.main(sys.argv)) diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index b7542c7eca305..99256da083a5d 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -599,7 +599,7 @@ def sign(input_f: str, output_f: str, opts: UkifyConfig) -> None: ) cmd = [ tool, - "sign", + 'sign', '--private-key', opts.sb_key, '--certificate', opts.sb_cert, *( @@ -1305,15 +1305,15 @@ def parse_efifw_dir(path: Path) -> bytes: return efifw_blob -STUB_SBAT = """\ +STUB_SBAT = '''\ sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md uki,1,UKI,uki,1,https://uapi-group.org/specifications/specs/unified_kernel_image/ -""" +''' -ADDON_SBAT = """\ +ADDON_SBAT = '''\ sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md uki-addon,1,UKI Addon,addon,1,https://www.freedesktop.org/software/systemd/man/latest/systemd-stub.html -""" +''' def make_uki(opts: UkifyConfig) -> None: @@ -2321,11 +2321,11 @@ def create_parser() -> argparse.ArgumentParser: p = argparse.ArgumentParser( description='Build and sign Unified Kernel Images', usage='\n ' - + textwrap.dedent("""\ + + textwrap.dedent('''\ ukify {b}build{e} [--linux=LINUX] [--initrd=INITRD] [options…] ukify {b}genkey{e} [options…] ukify {b}inspect{e} FILE… [options…] - """).format(b=Style.bold, e=Style.reset), + ''').format(b=Style.bold, e=Style.reset), allow_abbrev=False, add_help=False, epilog='\n '.join(('config file:', *config_example())), diff --git a/test/create-sys-script.py b/test/create-sys-script.py index 11ed185de071b..f2995bcfd6a27 100755 --- a/test/create-sys-script.py +++ b/test/create-sys-script.py @@ -1,28 +1,29 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later -OUTFILE_HEADER = """#!/usr/bin/env python3 +import filecmp +import os +import stat +import subprocess +import sys +import tempfile + +OUTFILE_HEADER = '''#!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later # # create-sys-script.py # # © 2017 Canonical Ltd. # Author: Dan Streetman -""" +''' # Use this only to (re-)create the test/sys-script.py script, # after adding or modifying anything in the test/sys/ directory -import os, sys -import stat -import tempfile -import filecmp -import subprocess - OUTFILE_MODE = 0o775 -OUTFILE_FUNCS = r""" +OUTFILE_FUNCS = r''' import os, sys import shutil @@ -36,9 +37,9 @@ def f(path, mode, contents): with open(path, "wb") as f: f.write(contents) os.chmod(path, mode) -""" +''' -OUTFILE_MAIN = """ +OUTFILE_MAIN = ''' if len(sys.argv) < 2: exit("Usage: {} ".format(sys.argv[0])) @@ -49,7 +50,7 @@ def f(path, mode, contents): if os.path.exists('sys'): shutil.rmtree('sys') -""" +''' def handle_dir(outfile, path): @@ -67,17 +68,17 @@ def escape_single_quotes(b): r = repr(b)[2:-1] # python escapes all ' only if there are ' and " in the string if '"' not in r: - r = r.replace("'", r"\'") + r = r.replace("'", r'\'') # return line with all ' escaped return r def handle_file(outfile, path): m = os.lstat(path).st_mode & 0o777 - with open(path, "rb") as f: + with open(path, 'rb') as f: b = f.read() - if b.count(b"\n") > 1: - r = "\n".join( escape_single_quotes(l) for l in b.split(b"\n") ) + if b.count(b'\n') > 1: + r = '\n'.join(escape_single_quotes(line) for line in b.split(b'\n')) r = f"b'''{r}'''" else: r = repr(b) @@ -85,7 +86,7 @@ def handle_file(outfile, path): def process_sysdir(outfile): - for (dirpath, dirnames, filenames) in os.walk('sys'): + for dirpath, dirnames, filenames in os.walk('sys'): handle_dir(outfile, dirpath) for d in dirnames: path = os.path.join(dirpath, d) @@ -105,17 +106,17 @@ def verify_dir(tmpd, path_a): mode_a = os.lstat(path_a).st_mode mode_b = os.lstat(path_b).st_mode if not stat.S_ISDIR(mode_b): - raise Exception("Not directory") + raise Exception('Not directory') if (mode_a & 0o777) != (mode_b & 0o777): - raise Exception("Permissions mismatch") + raise Exception('Permissions mismatch') def verify_link(tmpd, path_a): path_b = os.path.join(tmpd, path_a) if not stat.S_ISLNK(os.lstat(path_b).st_mode): - raise Exception("Not symlink") + raise Exception('Not symlink') if os.readlink(path_a) != os.readlink(path_b): - raise Exception("Symlink dest mismatch") + raise Exception('Symlink dest mismatch') def verify_file(tmpd, path_a): @@ -123,16 +124,16 @@ def verify_file(tmpd, path_a): mode_a = os.lstat(path_a).st_mode mode_b = os.lstat(path_b).st_mode if not stat.S_ISREG(mode_b): - raise Exception("Not file") + raise Exception('Not file') if (mode_a & 0o777) != (mode_b & 0o777): - raise Exception("Permissions mismatch") + raise Exception('Permissions mismatch') if not filecmp.cmp(path_a, path_b, shallow=False): - raise Exception("File contents mismatch") + raise Exception('File contents mismatch') def verify_script(tmpd): any = False - for (dirpath, dirnames, filenames) in os.walk("sys"): + for dirpath, dirnames, filenames in os.walk('sys'): any = True try: path = dirpath @@ -154,7 +155,8 @@ def verify_script(tmpd): if not any: exit('Nothing found!') -if __name__ == "__main__": + +if __name__ == '__main__': if len(sys.argv) < 2: exit('Usage: create-sys-script.py /path/to/test/') @@ -163,10 +165,9 @@ def verify_script(tmpd): os.chdir(sys.argv[1]) - with open(outfile, "w") as f: + with open(outfile, 'w') as f: os.chmod(outfile, OUTFILE_MODE) - f.write(OUTFILE_HEADER.replace(os.path.basename(sys.argv[0]), - os.path.basename(outfile))) + f.write(OUTFILE_HEADER.replace(os.path.basename(sys.argv[0]), os.path.basename(outfile))) f.write(OUTFILE_FUNCS) f.write(OUTFILE_MAIN) process_sysdir(f) diff --git a/test/fuzz/generate-directives.py b/test/fuzz/generate-directives.py index d05108962f7b8..0f6a744e34421 100755 --- a/test/fuzz/generate-directives.py +++ b/test/fuzz/generate-directives.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later +import collections +import re import sys -import collections, re d = collections.defaultdict(list) for line in open(sys.argv[1]): diff --git a/test/integration-tests/TEST-04-JOURNAL/TEST-04-JOURNAL.units/logs-filtering-syslog.py b/test/integration-tests/TEST-04-JOURNAL/TEST-04-JOURNAL.units/logs-filtering-syslog.py index e1d3ad7c116e6..e56348a7a1ddd 100755 --- a/test/integration-tests/TEST-04-JOURNAL/TEST-04-JOURNAL.units/logs-filtering-syslog.py +++ b/test/integration-tests/TEST-04-JOURNAL/TEST-04-JOURNAL.units/logs-filtering-syslog.py @@ -5,10 +5,7 @@ import syslog if __name__ == '__main__': - syslog.openlog(ident="logs-filtering", logoption=syslog.LOG_PID) - syslog.syslog(syslog.LOG_NOTICE, "Logging from the service, and ~more~ foo bar") + syslog.openlog(ident='logs-filtering', logoption=syslog.LOG_PID) + syslog.syslog(syslog.LOG_NOTICE, 'Logging from the service, and ~more~ foo bar') - subprocess.check_output( - ['journalctl', '--sync'], - stdin=subprocess.DEVNULL, - text=True) + subprocess.check_output(['journalctl', '--sync'], stdin=subprocess.DEVNULL, text=True) diff --git a/test/integration-tests/integration-test-wrapper.py b/test/integration-tests/integration-test-wrapper.py index 0bbfb6044d434..0dd589fb7c224 100755 --- a/test/integration-tests/integration-test-wrapper.py +++ b/test/integration-tests/integration-test-wrapper.py @@ -21,13 +21,13 @@ from types import FrameType from typing import Optional -EMERGENCY_EXIT_DROPIN = """\ +EMERGENCY_EXIT_DROPIN = '''\ [Unit] Wants=emergency-exit.service -""" +''' -EMERGENCY_EXIT_SERVICE = """\ +EMERGENCY_EXIT_SERVICE = '''\ [Unit] DefaultDependencies=no Conflicts=shutdown.target @@ -38,7 +38,7 @@ [Service] ExecStart=false -""" +''' @dataclasses.dataclass(frozen=True) @@ -459,43 +459,43 @@ def main() -> None: name = args.name + (f'-{i}' if (i := os.getenv('MESON_TEST_ITERATION')) else '') dropin = textwrap.dedent( - """\ + '''\ [Service] StandardOutput=journal+console - """ + ''' ) if not shell: dropin += textwrap.dedent( - """ + ''' [Unit] SuccessAction=exit SuccessActionExitStatus=123 - """ + ''' ) if os.getenv('TEST_MATCH_SUBTEST'): dropin += textwrap.dedent( - f""" + f''' [Service] Environment=TEST_MATCH_SUBTEST={os.environ['TEST_MATCH_SUBTEST']} - """ + ''' ) if os.getenv('TEST_MATCH_TESTCASE'): dropin += textwrap.dedent( - f""" + f''' [Service] Environment=TEST_MATCH_TESTCASE={os.environ['TEST_MATCH_TESTCASE']} - """ + ''' ) if os.getenv('TEST_RUN_DFUZZER'): dropin += textwrap.dedent( - f""" + f''' [Service] Environment=TEST_RUN_DFUZZER={os.environ['TEST_RUN_DFUZZER']} - """ + ''' ) if os.getenv('TEST_JOURNAL_USE_TMP', '0') == '1': @@ -512,14 +512,14 @@ def main() -> None: if not sys.stdin.isatty(): dropin += textwrap.dedent( - """ + ''' [Unit] FailureAction=exit - """ + ''' ) elif not shell: dropin += textwrap.dedent( - """ + ''' [Unit] Wants=multi-user.target getty-pre.target Before=getty-pre.target @@ -533,17 +533,17 @@ def main() -> None: IgnoreSIGPIPE=no # bash ignores SIGTERM KillSignal=SIGHUP - """ + ''' ) if sys.stdin.isatty(): dropin += textwrap.dedent( - """ + ''' [Service] ExecStartPre=/usr/lib/systemd/tests/testdata/integration-test-setup.sh setup ExecStopPost=/usr/lib/systemd/tests/testdata/integration-test-setup.sh finalize StateDirectory=%N - """ + ''' ) if args.rtc: diff --git a/test/networkd-test.py b/test/networkd-test.py index d9f7af3678260..27619da6ff1ab 100755 --- a/test/networkd-test.py +++ b/test/networkd-test.py @@ -33,8 +33,7 @@ NETWORK_UNITDIR = '/run/systemd/network' -NETWORKD_WAIT_ONLINE = shutil.which('systemd-networkd-wait-online', - path='/usr/lib/systemd:/lib/systemd') +NETWORKD_WAIT_ONLINE = shutil.which('systemd-networkd-wait-online', path='/usr/lib/systemd:/lib/systemd') RESOLV_CONF = '/run/systemd/resolve/resolv.conf' @@ -44,10 +43,10 @@ def setUpModule(): - global tmpmounts - """Initialize the environment, and perform sanity checks on it.""" + global tmpmounts + if shutil.which('networkctl') is None: raise unittest.SkipTest('networkd not installed') if shutil.which('resolvectl') is None: @@ -57,8 +56,10 @@ def setUpModule(): raise OSError(errno.ENOENT, 'systemd-networkd-wait-online not found') # Do not run any tests if the system is using networkd already and it's not virtualized - if (subprocess.call(['systemctl', 'is-active', '--quiet', 'systemd-networkd.service']) == 0 and - subprocess.call(['systemd-detect-virt', '--quiet']) != 0): + if ( + subprocess.call(['systemctl', 'is-active', '--quiet', 'systemd-networkd.service']) == 0 + and subprocess.call(['systemd-detect-virt', '--quiet']) != 0 + ): raise unittest.SkipTest('not virtualized and networkd is already active') # Ensure we don't mess with an existing networkd config @@ -80,7 +81,9 @@ def setUpModule(): # Generate debugging logs. os.makedirs('/run/systemd/system/systemd-networkd.service.d', exist_ok=True) - with open(f'/run/systemd/system/systemd-networkd.service.d/00-debug.conf', mode='w', encoding='utf-8') as f: + with open( + '/run/systemd/system/systemd-networkd.service.d/00-debug.conf', mode='w', encoding='utf-8' + ) as f: f.write('[Service]\nEnvironment=SYSTEMD_LOG_LEVEL=debug\n') subprocess.call(['systemctl', 'daemon-reload']) @@ -91,10 +94,15 @@ def setUpModule(): if subprocess.call(['getent', 'passwd', 'systemd-network']) != 0: subprocess.call(['useradd', '--system', '--no-create-home', 'systemd-network']) - for d in ['/etc/systemd/network', '/run/systemd/network', - '/run/systemd/netif', '/run/systemd/report', - '/run/systemd/resolve', '/run/systemd/resolve.hook']: - subprocess.check_call(["mount", "-m", "-t", "tmpfs", "none", d]) + for d in [ + '/etc/systemd/network', + '/run/systemd/network', + '/run/systemd/netif', + '/run/systemd/report', + '/run/systemd/resolve', + '/run/systemd/resolve.hook', + ]: + subprocess.check_call(['mount', '-m', '-t', 'tmpfs', 'none', d]) tmpmounts.append(d) if os.path.isdir('/run/systemd/resolve'): os.chmod('/run/systemd/resolve', 0o755) @@ -119,11 +127,11 @@ def setUpModule(): def tearDownModule(): global tmpmounts for d in tmpmounts: - subprocess.check_call(["umount", "--lazy", d]) + subprocess.check_call(['umount', '--lazy', d]) for u in stopped_units: - subprocess.call(["systemctl", "stop", u]) + subprocess.call(['systemctl', 'stop', u]) for u in running_units: - subprocess.call(["systemctl", "restart", u]) + subprocess.call(['systemctl', 'restart', u]) class NetworkdTestingUtilities: @@ -135,14 +143,12 @@ class NetworkdTestingUtilities: def add_veth_pair(self, veth, peer, veth_options=(), peer_options=()): """Add a veth interface pair, and queue them to be removed.""" - subprocess.check_call(['ip', 'link', 'add', 'name', veth] + - list(veth_options) + - ['type', 'veth', 'peer', 'name', peer] + - list(peer_options)) + subprocess.check_call(['ip', 'link', 'add', 'name', veth, *veth_options, + 'type', 'veth', 'peer', 'name', peer, *peer_options]) # fmt: skip self.addCleanup(subprocess.call, ['ip', 'link', 'del', 'dev', peer]) def write_config(self, path, contents): - """"Write a configuration file, and queue it to be removed.""" + """ "Write a configuration file, and queue it to be removed.""" with open(path, 'w') as f: f.write(contents) @@ -155,8 +161,8 @@ def write_network(self, unit_name, contents): def write_network_dropin(self, unit_name, dropin_name, contents): """Write a network unit drop-in, and queue it to be removed.""" - dropin_dir = os.path.join(NETWORK_UNITDIR, "{}.d".format(unit_name)) - dropin_path = os.path.join(dropin_dir, "{}.conf".format(dropin_name)) + dropin_dir = os.path.join(NETWORK_UNITDIR, f'{unit_name}.d') + dropin_path = os.path.join(dropin_dir, f'{dropin_name}.conf') os.makedirs(dropin_dir, exist_ok=True) self.addCleanup(os.rmdir, dropin_dir) @@ -169,7 +175,7 @@ def read_attr(self, link, attribute): # Note we don't want to check if interface `link' is managed, we # want to evaluate link variable and pass the value of the link to # assert_link_states e.g. eth0=managed. - self.assert_link_states(**{link:'managed'}) + self.assert_link_states(**{link: 'managed'}) with open(os.path.join('/sys/class/net', link, attribute)) as f: return f.readline().strip() @@ -191,8 +197,7 @@ def assert_link_states(self, **kwargs): interfaces = set(kwargs) # Wait for the requested interfaces, but don't fail for them. - subprocess.call([NETWORKD_WAIT_ONLINE, '--timeout=5'] + - ['--interface={}'.format(iface) for iface in kwargs]) + subprocess.call([NETWORKD_WAIT_ONLINE, '--timeout=5'] + [f'--interface={iface}' for iface in kwargs]) # Validate each link state found in the networkctl output. out = subprocess.check_output(['networkctl', '--no-legend']).rstrip() @@ -202,14 +207,13 @@ def assert_link_states(self, **kwargs): iface = fields[1] expected = kwargs[iface] actual = fields[-1] - if (actual != expected and - not (expected == 'managed' and actual != 'unmanaged')): - self.fail("Link {} expects state {}, found {}".format(iface, expected, actual)) + if actual != expected and not (expected == 'managed' and actual != 'unmanaged'): + self.fail(f'Link {iface} expects state {expected}, found {actual}') interfaces.remove(iface) # Ensure that all requested interfaces have been covered. if interfaces: - self.fail("Missing links in status output: {}".format(interfaces)) + self.fail(f'Missing links in status output: {interfaces}') class BridgeTest(NetworkdTestingUtilities, unittest.TestCase): @@ -217,7 +221,9 @@ class BridgeTest(NetworkdTestingUtilities, unittest.TestCase): def wait_online(self): try: - subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', 'port1', '--interface', 'port2', '--timeout=10']) + subprocess.check_call( + [NETWORKD_WAIT_ONLINE, '--interface', 'port1', '--interface', 'port2', '--timeout=10'] + ) except (AssertionError, subprocess.CalledProcessError): # show networkd status, journal, and DHCP server log on failure print('---- interface status ----') @@ -233,40 +239,59 @@ def wait_online(self): print('---- journal ----') subprocess.check_output(['journalctl', '--sync']) sys.stdout.flush() - subprocess.call(['journalctl', '-b', '--no-pager', '--quiet', '-I', '-u', 'systemd-networkd.service']) + subprocess.call( + ['journalctl', '-b', '--no-pager', '--quiet', '-I', '-u', 'systemd-networkd.service'] + ) raise def setUp(self): - self.write_network('50-port1.netdev', '''\ + self.write_network( + '50-port1.netdev', + '''\ [NetDev] Name=port1 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-port2.netdev', '''\ +''', + ) + self.write_network( + '50-port2.netdev', + '''\ [NetDev] Name=port2 Kind=dummy MACAddress=12:34:56:78:9a:bd -''') - self.write_network('50-mybridge.netdev', '''\ +''', + ) + self.write_network( + '50-mybridge.netdev', + '''\ [NetDev] Name=mybridge Kind=bridge -''') - self.write_network('50-port1.network', '''\ +''', + ) + self.write_network( + '50-port1.network', + '''\ [Match] Name=port1 [Network] Bridge=mybridge -''') - self.write_network('50-port2.network', '''\ +''', + ) + self.write_network( + '50-port2.network', + '''\ [Match] Name=port2 [Network] Bridge=mybridge -''') - self.write_network('50-mybridge.network', '''\ +''', + ) + self.write_network( + '50-mybridge.network', + '''\ [Match] Name=mybridge [Network] @@ -274,7 +299,8 @@ def setUp(self): DNS=192.168.250.1 Address=192.168.250.33/24 Gateway=192.168.250.1 -''') +''', + ) subprocess.call(['systemctl', 'reset-failed', 'systemd-networkd', 'systemd-resolved']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -290,17 +316,18 @@ def tearDown(self): subprocess.check_call(['ip', 'link', 'del', 'port2']) def test_bridge_init(self): - self.assert_link_states( - port1='managed', - port2='managed', - mybridge='managed') + self.assert_link_states(port1='managed', port2='managed', mybridge='managed') def test_bridge_port_priority(self): self.assertEqual(self.read_attr('port1', 'brport/priority'), '32') - self.write_network_dropin('50-port1.network', 'priority', '''\ + self.write_network_dropin( + '50-port1.network', + 'priority', + '''\ [Bridge] Priority=28 -''') +''', + ) subprocess.check_call(['ip', 'link', 'set', 'dev', 'port1', 'down']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -309,10 +336,14 @@ def test_bridge_port_priority(self): def test_bridge_port_priority_set_zero(self): """It should be possible to set the bridge port priority to 0""" self.assertEqual(self.read_attr('port2', 'brport/priority'), '32') - self.write_network_dropin('50-port2.network', 'priority', '''\ + self.write_network_dropin( + '50-port2.network', + 'priority', + '''\ [Bridge] Priority=0 -''') +''', + ) subprocess.check_call(['ip', 'link', 'set', 'dev', 'port2', 'down']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -321,7 +352,10 @@ def test_bridge_port_priority_set_zero(self): def test_bridge_port_property(self): """Test the "[Bridge]" section keys""" self.assertEqual(self.read_attr('port2', 'brport/priority'), '32') - self.write_network_dropin('50-port2.network', 'property', '''\ + self.write_network_dropin( + '50-port2.network', + 'property', + '''\ [Bridge] UnicastFlood=true HairPin=true @@ -331,7 +365,8 @@ def test_bridge_port_property(self): AllowPortToBeRoot=true Cost=555 Priority=23 -''') +''', + ) subprocess.check_call(['ip', 'link', 'set', 'dev', 'port2', 'down']) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.wait_online() @@ -345,14 +380,15 @@ def test_bridge_port_property(self): self.assertEqual(self.read_attr('port2', 'brport/bpdu_guard'), '0') self.assertEqual(self.read_attr('port2', 'brport/root_block'), '0') + class ClientTestBase(NetworkdTestingUtilities): """Provide common methods for testing networkd against servers.""" @classmethod def setUpClass(klass): klass.orig_log_level = subprocess.check_output( - ['systemctl', 'show', '--value', '--property', 'LogLevel'], - universal_newlines=True).strip() + ['systemctl', 'show', '--value', '--property', 'LogLevel'], universal_newlines=True + ).strip() subprocess.check_call(['systemd-analyze', 'log-level', 'debug']) @classmethod @@ -368,9 +404,9 @@ def setUp(self): # get current journal cursor subprocess.check_output(['journalctl', '--sync']) - out = subprocess.check_output(['journalctl', '-b', '--quiet', - '--no-pager', '-n0', '--show-cursor'], - universal_newlines=True) + out = subprocess.check_output( + ['journalctl', '-b', '--quiet', '--no-pager', '-n0', '--show-cursor'], universal_newlines=True + ) self.assertTrue(out.startswith('-- cursor:')) self.journal_cursor = out.split()[-1] @@ -383,44 +419,44 @@ def tearDown(self): subprocess.call(['systemctl', 'stop', 'systemd-networkd-varlink.socket']) subprocess.call(['systemctl', 'stop', 'systemd-networkd-varlink-metrics.socket']) subprocess.call(['systemctl', 'stop', 'systemd-networkd.service']) - subprocess.call(['ip', 'link', 'del', 'dummy0'], - stderr=subprocess.DEVNULL) + subprocess.call(['ip', 'link', 'del', 'dummy0'], stderr=subprocess.DEVNULL) def show_journal(self, unit): - '''Show journal of given unit since start of the test''' + """Show journal of given unit since start of the test""" - print('---- {} ----'.format(unit)) + print(f'---- {unit} ----') subprocess.check_output(['journalctl', '--sync']) sys.stdout.flush() - subprocess.call(['journalctl', '-b', '--no-pager', '--quiet', - '--cursor', self.journal_cursor, '-u', unit]) + subprocess.call( + ['journalctl', '-b', '--no-pager', '--quiet', '--cursor', self.journal_cursor, '-u', unit] + ) def show_ifaces(self): - '''Show network interfaces''' + """Show network interfaces""" print('--- networkctl ---') sys.stdout.flush() subprocess.call(['networkctl', 'status', '-n', '0', '-a']) def show_resolvectl(self): - '''Show resolved settings''' + """Show resolved settings""" print('--- resolvectl ---') sys.stdout.flush() subprocess.call(['resolvectl']) def create_iface(self, ipv6=False): - '''Create test interface with DHCP server behind it''' + """Create test interface with DHCP server behind it""" raise NotImplementedError('must be implemented by a subclass') def shutdown_iface(self): - '''Remove test interface and stop DHCP server''' + """Remove test interface and stop DHCP server""" raise NotImplementedError('must be implemented by a subclass') def print_server_log(self): - '''Print DHCP server log for debugging failures''' + """Print DHCP server log for debugging failures""" raise NotImplementedError('must be implemented by a subclass') @@ -432,16 +468,18 @@ def start_unit(self, unit): self.show_journal(unit) raise - def do_test(self, coldplug=True, ipv6=False, extra_opts='', - online_timeout=10, dhcp_mode='yes'): + def do_test(self, coldplug=True, ipv6=False, extra_opts='', online_timeout=10, dhcp_mode='yes'): self.start_unit('systemd-resolved') - self.write_network(self.config, '''\ + self.write_network( + self.config, + f'''\ [Match] -Name={iface} +Name={self.iface} [Network] DHCP={dhcp_mode} {extra_opts} -'''.format(iface=self.iface, dhcp_mode=dhcp_mode, extra_opts=extra_opts)) +''', + ) if coldplug: # create interface first, then start networkd @@ -456,8 +494,9 @@ def do_test(self, coldplug=True, ipv6=False, extra_opts='', self.start_unit('systemd-networkd') try: - subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', - self.iface, '--timeout=%i' % online_timeout]) + subprocess.check_call( + [NETWORKD_WAIT_ONLINE, '--interface', self.iface, f'--timeout={online_timeout:d}'] + ) if ipv6: # check iface state and IP 6 address; FIXME: we need to wait a bit @@ -465,7 +504,12 @@ def do_test(self, coldplug=True, ipv6=False, extra_opts='', # IPv6, but we want to wait for both for _ in range(10): out = subprocess.check_output(['ip', 'a', 'show', 'dev', self.iface]) - if b'state UP' in out and b'inet6 2600' in out and b'inet 192.168' in out and b'tentative' not in out: + if ( + b'state UP' in out + and b'inet6 2600' in out + and b'inet 192.168' in out + and b'tentative' not in out + ): break time.sleep(1) else: @@ -476,43 +520,43 @@ def do_test(self, coldplug=True, ipv6=False, extra_opts='', else: # should have link-local address on IPv6 only out = subprocess.check_output(['ip', '-6', 'a', 'show', 'dev', self.iface]) - self.assertRegex(out, br'inet6 fe80::.* scope link') + self.assertRegex(out, rb'inet6 fe80::.* scope link') self.assertNotIn(b'scope global', out) # should have IPv4 address out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface]) self.assertIn(b'state UP', out) - self.assertRegex(out, br'inet 192.168.5.\d+/.* scope global dynamic') + self.assertRegex(out, rb'inet 192.168.5.\d+/.* scope global dynamic') # check networkctl state out = subprocess.check_output(['networkctl']) - self.assertRegex(out, (r'{}\s+ether\s+[a-z-]+\s+unmanaged'.format(self.if_router)).encode()) - self.assertRegex(out, (r'{}\s+ether\s+routable\s+configured'.format(self.iface)).encode()) + self.assertRegex(out, (rf'{self.if_router}\s+ether\s+[a-z-]+\s+unmanaged').encode()) + self.assertRegex(out, (rf'{self.iface}\s+ether\s+routable\s+configured').encode()) out = subprocess.check_output(['networkctl', '-n', '0', 'status', self.iface]) - self.assertRegex(out, br'Type:\s+ether') - self.assertRegex(out, br'State:\s+routable.*configured') - self.assertRegex(out, br'Online state:\s+online') - self.assertRegex(out, br'Address:\s+192.168.5.\d+') + self.assertRegex(out, rb'Type:\s+ether') + self.assertRegex(out, rb'State:\s+routable.*configured') + self.assertRegex(out, rb'Online state:\s+online') + self.assertRegex(out, rb'Address:\s+192.168.5.\d+') if ipv6: - self.assertRegex(out, br'2600::') + self.assertRegex(out, rb'2600::') else: - self.assertNotIn(br'2600::', out) - self.assertRegex(out, br'fe80::') - self.assertRegex(out, br'Gateway:\s+192.168.5.1') - self.assertRegex(out, br'DNS:\s+192.168.5.1') + self.assertNotIn(rb'2600::', out) + self.assertRegex(out, rb'fe80::') + self.assertRegex(out, rb'Gateway:\s+192.168.5.1') + self.assertRegex(out, rb'DNS:\s+192.168.5.1') except (AssertionError, subprocess.CalledProcessError): # show networkd status, journal, and DHCP server log on failure with open(os.path.join(NETWORK_UNITDIR, self.config)) as f: - print('\n---- {} ----\n{}'.format(self.config, f.read())) + print(f'\n---- {self.config} ----\n{f.read()}') print('---- interface status ----') sys.stdout.flush() subprocess.call(['ip', 'a', 'show', 'dev', self.iface]) - print('---- networkctl status {} ----'.format(self.iface)) + print(f'---- networkctl status {self.iface} ----') sys.stdout.flush() rc = subprocess.call(['networkctl', '-n', '0', 'status', self.iface]) if rc != 0: - print("'networkctl status' exited with an unexpected code {}".format(rc)) + print(f"'networkctl status' exited with an unexpected code {rc}") self.show_journal('systemd-networkd.service') self.print_server_log() raise @@ -536,18 +580,15 @@ def test_coldplug_dhcp_yes_ip4(self): def test_coldplug_dhcp_yes_ip4_no_ra(self): # with disabling RA explicitly things should be fast - self.do_test(coldplug=True, ipv6=False, - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, extra_opts='IPv6AcceptRA=no') def test_coldplug_dhcp_ip4_only(self): # we have a 12s timeout on RA, so we need to wait longer - self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', - online_timeout=15) + self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', online_timeout=15) def test_coldplug_dhcp_ip4_only_no_ra(self): # with disabling RA explicitly things should be fast - self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, dhcp_mode='ipv4', extra_opts='IPv6AcceptRA=no') def test_coldplug_dhcp_ip6(self): self.do_test(coldplug=True, ipv6=True) @@ -560,13 +601,18 @@ def test_hotplug_dhcp_ip6(self): self.do_test(coldplug=False, ipv6=True) def test_route_only_dns(self): - self.write_network('50-myvpn.netdev', '''\ + self.write_network( + '50-myvpn.netdev', + '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-myvpn.network', '''\ +''', + ) + self.write_network( + '50-myvpn.network', + '''\ [Match] Name=dummy0 [Network] @@ -574,11 +620,11 @@ def test_route_only_dns(self): Address=192.168.42.100/24 DNS=192.168.42.1 Domains= ~company -''') +''', + ) try: - self.do_test(coldplug=True, ipv6=False, - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, extra_opts='IPv6AcceptRA=no') except subprocess.CalledProcessError as e: # networkd often fails to start in LXC: https://github.com/systemd/systemd/issues/11848 if IS_CONTAINER and e.cmd == ['systemctl', 'restart', 'systemd-networkd']: @@ -596,23 +642,28 @@ def test_route_only_dns(self): self.assertNotIn('nameserver 192.168.42.1\n', contents) def test_route_only_dns_all_domains(self): - self.write_network('50-myvpn.netdev', '''[NetDev] + self.write_network( + '50-myvpn.netdev', + '''[NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-myvpn.network', '''[Match] +''', + ) + self.write_network( + '50-myvpn.network', + '''[Match] Name=dummy0 [Network] IPv6AcceptRA=no Address=192.168.42.100/24 DNS=192.168.42.1 Domains= ~company ~. -''') +''', + ) try: - self.do_test(coldplug=True, ipv6=False, - extra_opts='IPv6AcceptRA=no') + self.do_test(coldplug=True, ipv6=False, extra_opts='IPv6AcceptRA=no') except subprocess.CalledProcessError as e: # networkd often fails to start in LXC: https://github.com/systemd/systemd/issues/11848 if IS_CONTAINER and e.cmd == ['systemctl', 'restart', 'systemd-networkd']: @@ -634,7 +685,7 @@ def test_route_only_dns_all_domains(self): @unittest.skipUnless(HAVE_DNSMASQ, 'dnsmasq not installed') class DnsmasqClientTest(ClientTestBase, unittest.TestCase): - '''Test networkd client against dnsmasq''' + """Test networkd client against dnsmasq""" def setUp(self): super().setUp() @@ -642,12 +693,25 @@ def setUp(self): self.iface_mac = 'de:ad:be:ef:47:11' def create_iface(self, ipv6=False, dnsmasq_opts=None): - '''Create test interface with DHCP server behind it''' + """Create test interface with DHCP server behind it""" # add veth pair - subprocess.check_call(['ip', 'link', 'add', 'name', self.iface, - 'address', self.iface_mac, - 'type', 'veth', 'peer', 'name', self.if_router]) + subprocess.check_call( + [ + 'ip', + 'link', + 'add', + 'name', + self.iface, + 'address', + self.iface_mac, + 'type', + 'veth', + 'peer', + 'name', + self.if_router, + ] + ) # give our router an IP subprocess.check_call(['ip', 'a', 'flush', 'dev', self.if_router]) @@ -666,14 +730,24 @@ def create_iface(self, ipv6=False, dnsmasq_opts=None): if dnsmasq_opts: extra_opts += dnsmasq_opts self.dnsmasq = subprocess.Popen( - ['dnsmasq', '--keep-in-foreground', '--log-queries=extra', '--log-dhcp', - '--log-facility=' + self.dnsmasq_log, '--conf-file=/dev/null', - '--dhcp-leasefile=' + lease_file, '--bind-interfaces', - '--interface=' + self.if_router, '--except-interface=lo', - '--dhcp-range=192.168.5.10,192.168.5.200'] + extra_opts) + [ + 'dnsmasq', + '--keep-in-foreground', + '--log-queries=extra', + '--log-dhcp', + '--log-facility=' + self.dnsmasq_log, + '--conf-file=/dev/null', + '--dhcp-leasefile=' + lease_file, + '--bind-interfaces', + '--interface=' + self.if_router, + '--except-interface=lo', + '--dhcp-range=192.168.5.10,192.168.5.200', + ] + + extra_opts + ) def shutdown_iface(self): - '''Remove test interface and stop DHCP server''' + """Remove test interface and stop DHCP server""" if self.if_router: subprocess.check_call(['ip', 'link', 'del', 'dev', self.if_router]) @@ -684,16 +758,17 @@ def shutdown_iface(self): self.dnsmasq = None def print_server_log(self, log_file=None): - '''Print DHCP server log for debugging failures''' + """Print DHCP server log for debugging failures""" path = log_file if log_file else self.dnsmasq_log with open(path) as f: - sys.stdout.write('\n\n---- {} ----\n{}\n------\n\n'.format(os.path.basename(path), f.read())) + sys.stdout.write(f'\n\n---- {os.path.basename(path)} ----\n{f.read()}\n------\n\n') def test_resolved_domain_restricted_dns(self): - '''resolved: domain-restricted DNS servers''' + """resolved: domain-restricted DNS servers""" - # enable DNSSEC in allow downgrade mode, and turn off stuff we don't want to test to make looking at logs easier + # enable DNSSEC in allow downgrade mode, and turn off stuff we don't want to test to make looking at + # logs easier conf = '/run/systemd/resolved.conf.d/test-enable-dnssec.conf' os.makedirs(os.path.dirname(conf), exist_ok=True) with open(conf, 'w') as f: @@ -703,14 +778,17 @@ def test_resolved_domain_restricted_dns(self): # create interface for generic connections; this will map all DNS names # to 192.168.42.1 self.create_iface(dnsmasq_opts=['--address=/#/192.168.42.1']) - self.write_network('50-general.network', '''\ + self.write_network( + '50-general.network', + f'''\ [Match] -Name={} +Name={self.iface} [Network] DHCP=ipv4 IPv6AcceptRA=no DNSSECNegativeTrustAnchors=search.example.com -'''.format(self.iface)) +''', + ) # create second device/dnsmasq for a .company/.lab VPN interface # static IPs for simplicity @@ -721,15 +799,26 @@ def test_resolved_domain_restricted_dns(self): vpn_dnsmasq_log = os.path.join(self.workdir, 'dnsmasq-vpn.log') vpn_dnsmasq = subprocess.Popen( - ['dnsmasq', '--keep-in-foreground', '--log-queries=extra', - '--log-facility=' + vpn_dnsmasq_log, '--conf-file=/dev/null', - '--dhcp-leasefile=/dev/null', '--bind-interfaces', - '--interface=testvpnrouter', '--except-interface=lo', - '--address=/math.lab/10.241.3.3', '--address=/cantina.company/10.241.4.4']) + [ + 'dnsmasq', + '--keep-in-foreground', + '--log-queries=extra', + '--log-facility=' + vpn_dnsmasq_log, + '--conf-file=/dev/null', + '--dhcp-leasefile=/dev/null', + '--bind-interfaces', + '--interface=testvpnrouter', + '--except-interface=lo', + '--address=/math.lab/10.241.3.3', + '--address=/cantina.company/10.241.4.4', + ] + ) self.addCleanup(vpn_dnsmasq.wait) self.addCleanup(vpn_dnsmasq.kill) - self.write_network('50-vpn.network', '''\ + self.write_network( + '50-vpn.network', + '''\ [Match] Name=testvpnclient [Network] @@ -738,11 +827,13 @@ def test_resolved_domain_restricted_dns(self): DNS=10.241.3.1 Domains=~company ~lab DNSSECNegativeTrustAnchors=company lab -''') +''', + ) self.start_unit('systemd-networkd') - subprocess.check_call([NETWORKD_WAIT_ONLINE, '--interface', self.iface, - '--interface=testvpnclient', '--timeout=20']) + subprocess.check_call( + [NETWORKD_WAIT_ONLINE, '--interface', self.iface, '--interface=testvpnclient', '--timeout=20'] + ) # ensure we start fresh with every test subprocess.check_call(['systemctl', 'restart', 'systemd-resolved']) @@ -784,7 +875,7 @@ def test_resolved_domain_restricted_dns(self): raise def test_resolved_etc_hosts(self): - '''resolved queries to /etc/hosts''' + """resolved queries to /etc/hosts""" # enabled DNSSEC in allow-downgrade mode conf = '/run/systemd/resolved.conf.d/test-enable-dnssec.conf' @@ -811,10 +902,14 @@ def test_resolved_etc_hosts(self): # note: different IPv4 address here, so that it's easy to tell apart # what resolved the query - self.create_iface(dnsmasq_opts=['--host-record=my.example.com,172.16.99.1,2600::99:99', - '--host-record=other.example.com,172.16.0.42,2600::42', - '--mx-host=example.com,mail.example.com'], - ipv6=True) + self.create_iface( + dnsmasq_opts=[ + '--host-record=my.example.com,172.16.99.1,2600::99:99', + '--host-record=other.example.com,172.16.0.42,2600::42', + '--mx-host=example.com,mail.example.com', + ], + ipv6=True, + ) self.do_test(coldplug=None, ipv6=True) try: @@ -848,7 +943,7 @@ def test_resolved_etc_hosts(self): raise def test_transient_hostname(self): - '''networkd sets transient hostname from DHCP''' + """networkd sets transient hostname from DHCP""" orig_hostname = socket.gethostname() self.addCleanup(socket.sethostname, orig_hostname) @@ -859,7 +954,7 @@ def test_transient_hostname(self): subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service']) self.addCleanup(subprocess.call, ['systemctl', 'stop', 'systemd-hostnamed.service']) - self.create_iface(dnsmasq_opts=['--dhcp-host={},192.168.5.210,testgreen'.format(self.iface_mac)]) + self.create_iface(dnsmasq_opts=[f'--dhcp-host={self.iface_mac},192.168.5.210,testgreen']) self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=no', dhcp_mode='ipv4') try: @@ -873,10 +968,10 @@ def test_transient_hostname(self): if b'testgreen' in out: break time.sleep(5) - sys.stdout.write('[retry %i] ' % retry) + sys.stdout.write(f'[retry {retry}] ') sys.stdout.flush() else: - self.fail('Transient hostname not found in hostnamectl:\n{}'.format(out.decode())) + self.fail(f'Transient hostname not found in hostnamectl:\n{out.decode()}') # and also applied to the system self.assertEqual(socket.gethostname(), 'testgreen') except AssertionError: @@ -886,24 +981,23 @@ def test_transient_hostname(self): raise def test_transient_hostname_with_static(self): - '''transient hostname is not applied if static hostname exists''' + """transient hostname is not applied if static hostname exists""" orig_hostname = socket.gethostname() self.addCleanup(socket.sethostname, orig_hostname) if not os.path.exists('/etc/hostname'): - self.write_config('/etc/hostname', "foobarqux") + self.write_config('/etc/hostname', 'foobarqux') else: - self.write_config('/run/hostname.tmp', "foobarqux") + self.write_config('/run/hostname.tmp', 'foobarqux') subprocess.check_call(['mount', '--bind', '/run/hostname.tmp', '/etc/hostname']) self.addCleanup(subprocess.call, ['umount', '/etc/hostname']) - socket.sethostname("foobarqux"); - + socket.sethostname('foobarqux') subprocess.check_call(['systemctl', 'stop', 'systemd-hostnamed.service']) self.addCleanup(subprocess.call, ['systemctl', 'stop', 'systemd-hostnamed.service']) - self.create_iface(dnsmasq_opts=['--dhcp-host={},192.168.5.210,testgreen'.format(self.iface_mac)]) + self.create_iface(dnsmasq_opts=[f'--dhcp-host={self.iface_mac},192.168.5.210,testgreen']) self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=no', dhcp_mode='ipv4') try: @@ -911,7 +1005,7 @@ def test_transient_hostname_with_static(self): out = subprocess.check_output(['ip', '-4', 'a', 'show', 'dev', self.iface]) self.assertRegex(out, b'inet 192.168.5.210/24 .* scope global dynamic') # static hostname wins over transient one, thus *not* applied - self.assertEqual(socket.gethostname(), "foobarqux") + self.assertEqual(socket.gethostname(), 'foobarqux') except AssertionError: self.show_journal('systemd-networkd.service') self.show_journal('systemd-hostnamed.service') @@ -920,21 +1014,22 @@ def test_transient_hostname_with_static(self): class NetworkdClientTest(ClientTestBase, unittest.TestCase): - '''Test networkd client against networkd server''' + """Test networkd client against networkd server""" def setUp(self): super().setUp() self.dnsmasq = None def create_iface(self, ipv6=False, dhcpserver_opts=None): - '''Create test interface with DHCP server behind it''' + """Create test interface with DHCP server behind it""" # run "router-side" networkd in own mount namespace to shield it from # "client-side" configuration and networkd (fd, script) = tempfile.mkstemp(prefix='networkd-router.sh') self.addCleanup(os.remove, script) with os.fdopen(fd, 'w+') as f: - f.write('''\ + f.write( + '''\ #!/bin/sh set -eu mkdir -p /run/systemd/network @@ -984,34 +1079,46 @@ def create_iface(self, ipv6=False, dhcpserver_opts=None): # run networkd as in systemd-networkd.service exec $(systemctl cat systemd-networkd.service | sed -n '/^ExecStart=/ {{ s/^.*=//; s/^[@+-]//; s/^!*//; p}}') -'''.format(ifr=self.if_router, - ifc=self.iface, - addr6=('Address=2600::1/64' if ipv6 else ''), - dhopts=(dhcpserver_opts or ''))) +'''.format( + ifr=self.if_router, + ifc=self.iface, + addr6=('Address=2600::1/64' if ipv6 else ''), + dhopts=(dhcpserver_opts or ''), + ) + ) os.fchmod(fd, 0o755) - subprocess.check_call(['systemd-run', '--unit=networkd-test-router.service', - '-p', 'InaccessibleDirectories=-/etc/systemd/network', - '-p', 'InaccessibleDirectories=-/run/systemd/network', - '-p', 'InaccessibleDirectories=-/run/systemd/netif', - '-p', 'InaccessibleDirectories=-/run/systemd/report', - '-p', 'InaccessibleDirectories=-/run/systemd/resolve.hook', - '-p', 'InaccessibleDirectories=-/var/lib/systemd/network', - '--service-type=notify', script]) + subprocess.check_call( + [ + 'systemd-run', + '--unit=networkd-test-router.service', + '-p', 'InaccessibleDirectories=-/etc/systemd/network', + '-p', 'InaccessibleDirectories=-/run/systemd/network', + '-p', 'InaccessibleDirectories=-/run/systemd/netif', + '-p', 'InaccessibleDirectories=-/run/systemd/report', + '-p', 'InaccessibleDirectories=-/run/systemd/resolve.hook', + '-p', 'InaccessibleDirectories=-/var/lib/systemd/network', + '--service-type=notify', + script, + ] + ) # fmt: skip # wait until devices got created for _ in range(50): - if subprocess.run(['ip', 'link', 'show', 'dev', self.if_router], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0: + if subprocess.run( + ['ip', 'link', 'show', 'dev', self.if_router], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ).returncode == 0: # fmt: skip break time.sleep(0.1) else: subprocess.call(['ip', 'link', 'show', 'dev', self.if_router]) - self.fail('Timed out waiting for {ifr} created.'.format(ifr=self.if_router)) + self.fail(f'Timed out waiting for {self.if_router} created.') def shutdown_iface(self): - '''Remove test interface and stop DHCP server''' + """Remove test interface and stop DHCP server""" if self.if_router: subprocess.check_call(['systemctl', 'stop', 'networkd-test-router.service']) @@ -1021,7 +1128,7 @@ def shutdown_iface(self): self.if_router = None def print_server_log(self): - '''Print DHCP server log for debugging failures''' + """Print DHCP server log for debugging failures""" self.show_journal('networkd-test-router.service') @@ -1038,13 +1145,18 @@ def test_search_domains(self): # we don't use this interface for this test self.if_router = None - self.write_network('50-test.netdev', '''\ + self.write_network( + '50-test.netdev', + '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-test.network', '''\ +''', + ) + self.write_network( + '50-test.network', + '''\ [Match] Name=dummy0 [Network] @@ -1052,7 +1164,8 @@ def test_search_domains(self): Address=192.168.42.100/24 DNS=192.168.42.1 Domains= one two three four five six seven eight nine ten -''') +''', + ) self.start_unit('systemd-networkd') @@ -1068,24 +1181,34 @@ def test_dropin(self): # we don't use this interface for this test self.if_router = None - self.write_network('50-test.netdev', '''\ + self.write_network( + '50-test.netdev', + '''\ [NetDev] Name=dummy0 Kind=dummy MACAddress=12:34:56:78:9a:bc -''') - self.write_network('50-test.network', '''\ +''', + ) + self.write_network( + '50-test.network', + '''\ [Match] Name=dummy0 [Network] IPv6AcceptRA=no Address=192.168.42.100/24 DNS=192.168.42.1 -''') - self.write_network_dropin('50-test.network', 'dns', '''\ +''', + ) + self.write_network_dropin( + '50-test.network', + 'dns', + '''\ [Network] DNS=127.0.0.1 -''') +''', + ) self.start_unit('systemd-resolved') self.start_unit('systemd-networkd') @@ -1106,11 +1229,19 @@ def test_dropin(self): self.fail(f'Expected DNS servers not found in resolv.conf: {contents}') def test_dhcp_timezone(self): - '''networkd sets time zone from DHCP''' + """networkd sets time zone from DHCP""" def get_tz(): - out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.timedate1', - '/org/freedesktop/timedate1', 'org.freedesktop.timedate1', 'Timezone']) + out = subprocess.check_output( + [ + 'busctl', + 'get-property', + 'org.freedesktop.timedate1', + '/org/freedesktop/timedate1', + 'org.freedesktop.timedate1', + 'Timezone', + ] + ) assert out.startswith(b's "') out = out.strip() assert out.endswith(b'"') @@ -1120,7 +1251,9 @@ def get_tz(): self.addCleanup(subprocess.call, ['timedatectl', 'set-timezone', orig_timezone]) self.create_iface(dhcpserver_opts='EmitTimezone=yes\nTimezone=Pacific/Honolulu') - self.do_test(coldplug=None, extra_opts='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode='ipv4') + self.do_test( + coldplug=None, extra_opts='IPv6AcceptRA=false\n[DHCP]\nUseTimezone=true', dhcp_mode='ipv4' + ) # Should have applied the received timezone. This is asynchronous, so we need to wait for a while: for _ in range(20): @@ -1151,12 +1284,15 @@ def tearDown(self): def test_basic_matching(self): """Verify the Name= line works throughout this class.""" self.add_veth_pair('test_if1', 'fake_if2') - self.write_network('50-test.network', '''\ + self.write_network( + '50-test.network', + '''\ [Match] Name=test_* [Network] IPv6AcceptRA=no -''') +''', + ) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.assert_link_states(test_if1='managed', fake_if2='unmanaged') @@ -1165,15 +1301,17 @@ def test_inverted_matching(self): # Use a MAC address as the interfaces' common matching attribute # to avoid depending on udev, to support testing in containers. mac = '00:01:02:03:98:99' - self.add_veth_pair('test_veth', 'test_peer', - ['addr', mac], ['addr', mac]) - self.write_network('50-no-veth.network', '''\ + self.add_veth_pair('test_veth', 'test_peer', ['addr', mac], ['addr', mac]) + self.write_network( + '50-no-veth.network', + f'''\ [Match] -MACAddress={} +MACAddress={mac} Name=!nonexistent *peer* [Network] IPv6AcceptRA=no -'''.format(mac)) +''', + ) subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.assert_link_states(test_veth='managed', test_peer='unmanaged') @@ -1192,14 +1330,14 @@ def setUp(self): # Define the contents of .network files to be read in order. self.configs = ( - "[Match]\nName=m1def\n", - "[Match]\nName=m1unm\n[Link]\nUnmanaged=yes\n", - "[Match]\nName=m1*\n[Link]\nUnmanaged=no\n", + '[Match]\nName=m1def\n', + '[Match]\nName=m1unm\n[Link]\nUnmanaged=yes\n', + '[Match]\nName=m1*\n[Link]\nUnmanaged=no\n', ) # Write out the .network files to be cleaned up automatically. for i, config in enumerate(self.configs): - self.write_network("%02d-test.network" % i, config) + self.write_network(f'{i:02d}-test.network', config) def tearDown(self): """Stop networkd.""" @@ -1215,43 +1353,30 @@ def test_unmanaged_setting(self): """Verify link states with Unmanaged= settings, hot-plug.""" subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.create_iface() - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='unmanaged') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='unmanaged') def test_unmanaged_setting_coldplug(self): """Verify link states with Unmanaged= settings, cold-plug.""" self.create_iface() subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='unmanaged') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='unmanaged') def test_catchall_config(self): """Verify link states with a catch-all config, hot-plug.""" # Don't actually catch ALL interfaces. It messes up the host. - self.write_network('50-all.network', "[Match]\nName=m[01]???\n") + self.write_network('50-all.network', '[Match]\nName=m[01]???\n') subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) self.create_iface() - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='managed') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='managed') def test_catchall_config_coldplug(self): """Verify link states with a catch-all config, cold-plug.""" # Don't actually catch ALL interfaces. It messes up the host. - self.write_network('50-all.network', "[Match]\nName=m[01]???\n") + self.write_network('50-all.network', '[Match]\nName=m[01]???\n') self.create_iface() subprocess.check_call(['systemctl', 'restart', 'systemd-networkd']) - self.assert_link_states(m1def='managed', - m1man='managed', - m1unm='unmanaged', - m0unm='managed') + self.assert_link_states(m1def='managed', m1man='managed', m1unm='unmanaged', m0unm='managed') if __name__ == '__main__': - unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, - verbosity=2)) + unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout, verbosity=2)) diff --git a/test/rule-syntax-check.py b/test/rule-syntax-check.py index ec1c75a854cda..777012a2ac98d 100755 --- a/test/rule-syntax-check.py +++ b/test/rule-syntax-check.py @@ -14,21 +14,21 @@ sys.exit('Specify files to test as arguments') 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 + '$') +no_args_tests = re.compile(rf'(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|PROGRAM|RESULT|TEST)\s*(?:=|!)=\s*{quoted_string_re}$') # fmt: skip # PROGRAM can also be specified as an assignment. program_assign = re.compile(r'PROGRAM\s*=\s*' + quoted_string_re + '$') -args_tests = re.compile(r'(ATTRS?|ENV|CONST|TEST){([a-zA-Z0-9/_.*%-]+)}\s*(?:=|!)=\s*' + quoted_string_re + '$') -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 + '$') +args_tests = re.compile(rf'(ATTRS?|ENV|CONST|TEST){{([a-zA-Z0-9/_.*%-]+)}}\s*(?:=|!)=\s*{quoted_string_re}$') +no_args_assign = re.compile(rf'(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|RUN|LABEL|GOTO|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*{quoted_string_re}$') # fmt: skip +args_assign = re.compile(rf'(ATTR|ENV|IMPORT|RUN){{([a-zA-Z0-9/_.*%-]+)}}\s*(=|\+=)\s*{quoted_string_re}$') # Find comma-separated groups, but allow commas that are inside quoted strings. # 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 + '?)+') +comma_separated_group_re = re.compile(rf'(?:[^,"]|{quoted_string_re}?)+') result = 0 buffer = '' for path in rules_files: - print('# looking at {}'.format(path)) + print(f'# looking at {path}') lineno = 0 for line in open(path): lineno += 1 @@ -50,11 +50,14 @@ # it generally improves the readability of the rules. for clause_match in comma_separated_group_re.finditer(line): clause = clause_match.group().strip() - if not (no_args_tests.match(clause) or args_tests.match(clause) or - no_args_assign.match(clause) or args_assign.match(clause) or - program_assign.match(clause)): - - print('Invalid line {}:{}: {}'.format(path, lineno, line)) + if not ( + no_args_tests.match(clause) + or args_tests.match(clause) + or no_args_assign.match(clause) + or args_assign.match(clause) + or program_assign.match(clause) + ): + print(f'Invalid line {path}:{lineno}: {line}') print(' clause:', clause) print() result = 1 diff --git a/test/run-unit-tests.py b/test/run-unit-tests.py index de8ac5c26cb43..1b790d7a5cf47 100755 --- a/test/run-unit-tests.py +++ b/test/run-unit-tests.py @@ -6,8 +6,10 @@ import pathlib import subprocess import sys + try: import colorama as c + GREEN = c.Fore.GREEN YELLOW = c.Fore.YELLOW RED = c.Fore.RED @@ -16,23 +18,38 @@ except ImportError: GREEN = YELLOW = RED = RESET_ALL = BRIGHT = '' + class total: total = None good = 0 skip = 0 fail = 0 + def argument_parser(): p = argparse.ArgumentParser() - p.add_argument('-u', '--unsafe', action='store_true', - help='run "unsafe" tests too') - p.add_argument('-A', '--artifact_directory', - help='store output from failed tests in this dir') - p.add_argument('-s', '--skip', action='append', default=[], - help='skip the named test') + p.add_argument( + '-u', + '--unsafe', + action='store_true', + help='run "unsafe" tests too', + ) + p.add_argument( + '-A', + '--artifact_directory', + help='store output from failed tests in this dir', + ) + p.add_argument( + '-s', + '--skip', + action='append', + default=[], + help='skip the named test', + ) return p + opts = argument_parser().parse_args() unittestdir = pathlib.Path(__file__).parent.absolute() / 'unit-tests' diff --git a/test/sd-script.py b/test/sd-script.py index 51ebf70c39eca..432136e692e6b 100755 --- a/test/sd-script.py +++ b/test/sd-script.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-or-later # +# ruff: noqa: F821 +# # sd-script.py: create LOTS of sd device entries in fake sysfs # # (C) 2018 Martin Wilck, SUSE Linux GmbH @@ -15,63 +17,67 @@ # Note that sys-script.py already creates 10 sd device nodes # (sda+sdb and partitions). This script starts with sdc. -import re -import os import errno +import os +import re import sys + def d(path, mode): os.mkdir(path, mode) -def l(path, src): + +def l(path, src): # noqa: E743 os.symlink(src, path) + def f(path, mode, contents): - with open(path, "wb") as f: + with open(path, 'wb') as f: f.write(contents) os.chmod(path, mode) -class SD(object): +class SD: sd_major = [8] + list(range(65, 72)) + list(range(128, 136)) _name_re = re.compile(r'sd(?P[a-z]*)$') def _init_from_name(self, name): mt = self._name_re.match(name) if mt is None: - raise RuntimeError("invalid name %s" % name) - nm = mt.group("f") + raise RuntimeError(f'invalid name {name}') + nm = mt.group('f') base = 1 ls = nm[-1] nm = nm[:-1] - n = base * (ord(ls)-ord('a')) + n = base * (ord(ls) - ord('a')) while len(nm) > 0: ls = nm[-1] nm = nm[:-1] base *= 26 - n += base * (1 + ord(ls)-ord('a')) + n += base * (1 + ord(ls) - ord('a')) self._num = n def _init_from_dev(self, dev): - maj, min = dev.split(":") + maj, min = dev.split(':') maj = self.sd_major.index(int(maj, 10)) min = int(min, 10) num = int(min / 16) - self._num = 16*maj + num%16 + 256*int(num/16) + self._num = 16 * maj + num % 16 + 256 * int(num / 16) @staticmethod def _disk_num(a, b): - n = ord(a)-ord('a') + n = ord(a) - ord('a') if b != '': - n = 26 * (n+1) + ord(b)-ord('a') + n = 26 * (n + 1) + ord(b) - ord('a') return n @staticmethod def _get_major(n): - return SD.sd_major[(n%256)//16] + return SD.sd_major[(n % 256) // 16] + @staticmethod def _get_minor(n): - return 16 * (n % 16 + 16 * n//256) + return 16 * (n % 16 + 16 * n // 256) @staticmethod def _get_name(n): @@ -81,7 +87,7 @@ def _get_name(n): while n >= 0: s = chr(n % 26 + ord('a')) + s n = n // 26 - 1 - return "sd" + s + return 'sd' + s @staticmethod def _get_dev_t(n): @@ -90,9 +96,9 @@ def _get_dev_t(n): return (maj << 20) + min def __init__(self, arg): - if type(arg) is type(0): + if type(arg) is int: self._num = arg - elif arg.startswith("sd"): + elif arg.startswith('sd'): self._init_from_name(arg) else: self._init_from_dev(arg) @@ -110,9 +116,7 @@ def __hash__(self): return hash(self._num) def __str__(self): - return "%s/%s" % ( - self.devstr(), - self._get_name(self._num)) + return f'{self.devstr()}/{self._get_name(self._num)}' def major(self): return self._get_major(self._num) @@ -121,29 +125,27 @@ def minor(self): return self._get_minor(self._num) def devstr(self): - return "%d:%d" % (self._get_major(self._num), - self._get_minor(self._num)) + return f'{self._get_major(self._num)}:{self._get_minor(self._num)}' def namestr(self): return self._get_name(self._num) def longstr(self): - return "%d\t%s\t%s\t%08x" % (self._num, - self.devstr(), - self.namestr(), - self._get_dev_t(self._num)) + return f'{self._num}\t{self.devstr()}\t{self.namestr()}\t{self._get_dev_t(self._num):08x}' + class MySD(SD): def subst(self, first_sg): disk = { - "lun": self._num, - "major": self.major(), - "devnode": self.namestr(), - "disk_minor": self.minor(), - "sg_minor": first_sg + self._num, + 'lun': self._num, + 'major': self.major(), + 'devnode': self.namestr(), + 'disk_minor': self.minor(), + 'sg_minor': first_sg + self._num, } return disk + disk_template = r"""\ l('sys/bus/scsi/drivers/sd/7:0:0:{lun}', '../../../../devices/pci0000:00/0000:00:1d.7/usb5/5-1/5-1:1.0/host7/target7:0:0/7:0:0:{lun}') l('sys/bus/scsi/devices/7:0:0:{lun}', '../../../devices/pci0000:00/0000:00:1d.7/usb5/5-1/5-1:1.0/host7/target7:0:0/7:0:0:{lun}') @@ -282,29 +284,33 @@ def subst(self, first_sg): """ if len(sys.argv) != 3: - exit("Usage: {} ".format(sys.argv[0])) + exit(f'Usage: {sys.argv[0]} ') if not os.path.isdir(sys.argv[1]): - exit("Target dir {} not found".format(sys.argv[1])) + exit(f'Target dir {sys.argv[1]} not found') + def create_part_sysfs(disk, sd, prt): part = disk - part.update ({ - "part_num": prt, - "part_minor": disk["disk_minor"] + prt, - }) + part.update( + { + 'part_num': prt, + 'part_minor': disk['disk_minor'] + prt, + } + ) try: exec(part_template.format(**part)) except OSError: si = sys.exc_info()[1] - if (si.errno == errno.EEXIST): - print("sysfs structures for %s%d exist" % (sd.namestr(), prt)) + if si.errno == errno.EEXIST: + print(f'sysfs structures for {sd.namestr()}{prt} exist') else: - print("error for %s%d: %s" % (sd.namestr(), prt, si[1])) + print(f'error for {sd.namestr()}{prt}: {si[1]}') raise else: - print("sysfs structures for %s%d created" % (sd.namestr(), prt)) + print(f'sysfs structures for {sd.namestr()}{prt} created') + def create_disk_sysfs(dsk, first_sg, n): sd = MySD(dsk) @@ -314,17 +320,16 @@ def create_disk_sysfs(dsk, first_sg, n): exec(disk_template.format(**disk)) except OSError: si = sys.exc_info()[1] - if (si.errno == errno.EEXIST): - print("sysfs structures for %s exist" % sd.namestr()) - elif (si.errno == errno.ENOENT): - print("error for %s: %s - have you run sys-script py first?" % - (sd.namestr(), si.strerror)) + if si.errno == errno.EEXIST: + print(f'sysfs structures for {sd.namestr()} exist') + elif si.errno == errno.ENOENT: + print(f'error for {sd.namestr()}: {si.strerror} - have you run sys-script py first?') return -1 else: - print("error for %s: %s" % (sd.namestr(), si.strerror)) + print(f'error for {sd.namestr()}: {si.strerror}') raise else: - print("sysfs structures for %s created" % sd.namestr()) + print(f'sysfs structures for {sd.namestr()} created') n += 1 if n >= last: @@ -338,6 +343,7 @@ def create_disk_sysfs(dsk, first_sg, n): return n + os.chdir(sys.argv[1]) n = 0 last = int(sys.argv[2]) diff --git a/test/sys-script.py b/test/sys-script.py index e9ce665313525..3a772eabd6d4b 100755 --- a/test/sys-script.py +++ b/test/sys-script.py @@ -6,30 +6,36 @@ # © 2017 Canonical Ltd. # Author: Dan Streetman -import os, sys +import os import shutil +import sys + def d(path, mode): os.mkdir(path, mode) -def l(path, src): + +def l(path, src): # noqa: E743 os.symlink(src, path) + def f(path, mode, contents): - with open(path, "wb") as f: + with open(path, 'wb') as f: f.write(contents) os.chmod(path, mode) + if len(sys.argv) < 2: - exit("Usage: {} ".format(sys.argv[0])) + exit(f'Usage: {sys.argv[0]} ') if not os.path.isdir(sys.argv[1]): - exit("Target dir {} not found".format(sys.argv[1])) + exit(f'Target dir {sys.argv[1]} not found') os.chdir(sys.argv[1]) if os.path.exists('sys'): shutil.rmtree('sys') +# fmt: off d('sys', 0o755) d('sys/kernel', 0o775) f('sys/kernel/kexec_crash_loaded', 0o664, b'0\n') @@ -4516,14 +4522,14 @@ def f(path, mode, contents): XU1A\x03\x00\x01XU2A\x03\x00\x01XPA_\x02\x00\x02XFA_\x01\x00\x03XU1E\x01XU2E\x01XPE_\x01XFE_\x01\x00\x0cXG1E\x01\x00\x06XG1A\t\x00\x10XG2E\x01\x00\x03XG2A\x0c\x00@\x0b\x00\x02CLKR\x01GYEN\x01\x00\x03C4C3\x01\x00\x02EXPE\x01\x00\x05[\x80LPIO\x01\x0b\x80\x11 @[\x81O LPIO\x03GU00\x08GU01\x08GU02\x08GU03\x08GI00\x08GI01\x08GI02\x08GI03\x08\x00 GL00\x08GL01\x08GL02\x08GL03\x08\x00@\x04GB00\x08GB01\x08GB02\x08GB03\x08\x00@\x08GV00\x08GV01\x08GV02\x08GV03\x08GU04\x08GU05\x08GU06\x08GU07\x08GI04\x08GI05\x08GI06\x08GI07\x08GL04\x08GL05\x08GL06\x08GL07\x08[\x80PMIO\x01\x0b\x00\x10 -\x80[\x81\x10PMIO\x00\x00@!\x00\x01SWGE\x01[\x823PIC_\x08_HID\x0bA\xd0\x08_CRS\x11 +\x80[\x81\x10PMIO\x00\x00@!\x00\x01SWGE\x01[\x823PIC_\x08_HID\x0bA\xd0\x08_CRS\x11 \x1dG\x01 \x00 \x00\x01\x02G\x01\xa0\x00\xa0\x00\x01\x02G\x01\xd0\x04\xd0\x04\x01\x02"\x04\x00y\x00[\x82%TIMR\x08_HID\x0cA\xd0\x01\x00\x08_CRS\x11\x10 \rG\x01@\x00@\x00\x01\x04"\x01\x00y\x00[\x82B\x05HPET\x08_HID\x0cA\xd0\x01\x03\x14*_STA\x00\xa0\x08W98F\xa4 \x00\xa1\x17\xa0\x10\x90\\WNTF\x92\\WXPF\xa4 \x00\xa1\x04\xa4 \x0f\xa4 \x00\x08_CRS\x11\x11 -\x0e\x86\t\x00\x00\x00\x00\xd0\xfe\x00\x04\x00\x00y\x00[\x825DMAC\x08_HID\x0cA\xd0\x02\x00\x08_CRS\x11 +\x0e\x86\t\x00\x00\x00\x00\xd0\xfe\x00\x04\x00\x00y\x00[\x825DMAC\x08_HID\x0cA\xd0\x02\x00\x08_CRS\x11 \x1dG\x01\x00\x00\x00\x00\x01\x10G\x01\x80\x00\x80\x00\x01\x10G\x01\xc0\x00\xc0\x00\x01 *\x10\x05y\x00[\x82"SPKR\x08_HID\x0cA\xd0\x08\x00\x08_CRS\x11\r G\x01a\x00a\x00\x01\x01y\x00[\x82%FPU_\x08_HID\x0cA\xd0\x0c\x04\x08_CRS\x11\x10 @@ -7165,9 +7171,9 @@ def f(path, mode, contents): \x01\x10C\x1f\\_TZ_[\x85L\x0cTHM0\x14\r_CRT\x00\xa4C2K_ \x7f\x14G\x0b_TMP\x00\xa0B\x05\\H8DRp\\/\x05_SB_PCI0LPC_EC__TMP0`p\\/\x05_SB_PCI0LPC_EC__HT12ap\\/\x05_SB_PCI0LPC_EC__HT13b\xa1$p\\RBEC x`p{\\RBEC - + @\x00ap{\\RBEC - + \x80\x00b\xa0\tb\xa4C2K_ \x80\xa0\'\x92\\/\x06_SB_PCI0LPC_EC__HKEYDHKC\xa0\ta\xa4C2K_ \x80\xa4C2K_`[\x85@\x0fTHM1\x143_PSL\x00\xa0\x1e\\MPEN\xa4\x12\x16\x02\\._PR_CPU0\\._PR_CPU1\xa4\x12\x0c\x01\\._PR_CPU0\x14\x0c_CRT\x00\xa4\\TCRT\x14\x0c_PSV\x00\xa4\\TPSV\x14\x0c_TC1\x00\xa4\\TTC1\x14\x0c_TC2\x00\xa4\\TTC2\x14\x0c_TSP\x00\xa4\\TTSP\x14D\x07_TMP\x00\xa0J\x05\\DTSETHRM @@ -7178,9 +7184,9 @@ def f(path, mode, contents): `\xa0\x0c\x92\x94`\x0b\xac p\x0b\xb8\x0b`\xa0\x0b\x94`\x0b\xac\x0fp\x0b\xb8\x0b`\xa4`\x10O\x13\\/\x04_SB_PCI0LPC_EC__\x14I\x12_Q40\x00\x86\\._TZ_THM0 \x80\xa08\\H8DRp\\/\x05_SB_PCI0LPC_EC__HT11`p\\/\x05_SB_PCI0LPC_EC__HT12a\xa1\x1bp{\\RBEC - + \x00`p{\\RBEC - + @\x00a\xa0=\\/\x06_SB_PCI0LPC_EC__HKEYDHKC\xa0 a\\/\x06_SB_PCI0LPC_EC__HKEYMHKQ\x0b"`\xa0\x06VIGD\xa3\xa1\x06\\VTHR\xa0C\x07\\SPEN\xa0\'\\OSPX\x86\\._PR_CPU0 \x80\xa0\x13\\MPEN\x86\\._PR_CPU1 \x80\xa1C\x04\xa07\x91\\/\x05_SB_PCI0LPC_EC__HT00\\/\x05_SB_PCI0LPC_EC__HT10\\STEP diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py index bbbab8e3fe636..a8cc3efde953b 100755 --- a/test/test-network/systemd-networkd-tests.py +++ b/test/test-network/systemd-networkd-tests.py @@ -3,7 +3,8 @@ # systemd-networkd tests # These tests can be executed in the systemd mkosi image when booted in QEMU. After booting the QEMU VM, -# simply run this file which can be found in the VM at /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py. +# simply run this file which can be found in the VM at +# /usr/lib/systemd/tests/testdata/test-network/systemd-networkd-tests.py. # # To run an individual test, specify it as a command line argument in the form # of .. E.g. the NetworkdMTUTests class has a test @@ -27,8 +28,8 @@ import datetime import enum import errno -import itertools import ipaddress +import itertools import json import os import pathlib @@ -84,8 +85,8 @@ asan_options = os.getenv('ASAN_OPTIONS') lsan_options = os.getenv('LSAN_OPTIONS') ubsan_options = os.getenv('UBSAN_OPTIONS') -with_coverage = os.getenv('COVERAGE_BUILD_DIR') != None -show_journal = True # When true, show journal on stopping networkd. +with_coverage = os.getenv('COVERAGE_BUILD_DIR') is not None +show_journal = True # When true, show journal on stopping networkd. active_units = [] protected_links = { @@ -107,54 +108,98 @@ saved_ipv6_rules = None saved_timezone = None + def rm_f(path): if os.path.exists(path): os.remove(path) + def rm_rf(path): shutil.rmtree(path, ignore_errors=True) + def cp(src, dst): shutil.copy(src, dst) + def cp_r(src, dst): shutil.copytree(src, dst, copy_function=shutil.copy, dirs_exist_ok=True) + def mkdir_p(path): os.makedirs(path, exist_ok=True) + def touch(path): pathlib.Path(path).touch() + # pylint: disable=R1710 def check_output(*command, **kwargs): # This checks the result and returns stdout (and stderr) on success. command = command[0].split() + list(command[1:]) - ret = subprocess.run(command, check=False, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, **kwargs) + ret = subprocess.run( + command, + check=False, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + **kwargs, + ) if ret.returncode == 0: return ret.stdout.rstrip() # When returncode != 0, print stdout and stderr, then trigger CalledProcessError. print(ret.stdout) ret.check_returncode() + def call(*command, **kwargs): # This returns returncode. stdout and stderr are merged and shown in console command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stderr=subprocess.STDOUT, **kwargs).returncode + return subprocess.run( + command, + check=False, + text=True, + stderr=subprocess.STDOUT, + **kwargs, + ).returncode + def call_check(*command, **kwargs): # Same as call() above, but it triggers CalledProcessError if rc != 0 command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stderr=subprocess.STDOUT, **kwargs).check_returncode() + return subprocess.run( + command, + check=False, + text=True, + stderr=subprocess.STDOUT, + **kwargs, + ).check_returncode() + def call_quiet(*command, **kwargs): command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, **kwargs).returncode + return subprocess.run( + command, + check=False, + text=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + **kwargs, + ).returncode + def run(*command, **kwargs): # This returns CompletedProcess instance. command = command[0].split() + list(command[1:]) - return subprocess.run(command, check=False, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs) + return subprocess.run( + command, + check=False, + text=True, + capture_output=True, + **kwargs, + ) + def check_json(string): try: @@ -163,6 +208,7 @@ def check_json(string): print(f"String is not a valid JSON: '{string}'") raise + def is_module_available(*module_names): for module_name in module_names: lsmod_output = check_output('lsmod') @@ -171,124 +217,19 @@ def is_module_available(*module_names): return False return True + def expectedFailureIfModuleIsNotAvailable(*module_names): def f(func): return func if is_module_available(*module_names) else unittest.expectedFailure(func) return f -def expectedFailureIfERSPANv0IsNotSupported(): - # erspan version 0 is supported since f989d546a2d5a9f001f6f8be49d98c10ab9b1897 (v5.8) - def f(func): - rc = call_quiet('ip link add dev erspan99 type erspan seq key 30 local 192.168.1.4 remote 192.168.1.1 erspan_ver 0') - remove_link('erspan99') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfERSPANv2IsNotSupported(): - # erspan version 2 is supported since f551c91de262ba36b20c3ac19538afb4f4507441 (v4.16) - def f(func): - rc = call_quiet('ip link add dev erspan99 type erspan seq key 30 local 192.168.1.4 remote 192.168.1.1 erspan_ver 2') - remove_link('erspan99') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyPortRangeIsNotAvailable(): - def f(func): - rc = call_quiet('ip rule add from 192.168.100.19 sport 1123-1150 dport 3224-3290 table 7') - call_quiet('ip rule del from 192.168.100.19 sport 1123-1150 dport 3224-3290 table 7') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable(): - def f(func): - # IP protocol name is parsed by getprotobyname(), and it requires /etc/protocols. - # Hence. here we use explicit number: 6 == tcp. - rc = call_quiet('ip rule add not from 192.168.100.19 ipproto 6 table 7') - call_quiet('ip rule del not from 192.168.100.19 ipproto 6 table 7') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable(): - def f(func): - supported = False - if call_quiet('ip rule add from 192.168.100.19 table 7 uidrange 200-300') == 0: - ret = run('ip rule list from 192.168.100.19 table 7') - supported = ret.returncode == 0 and 'uidrange 200-300' in ret.stdout - call_quiet('ip rule del from 192.168.100.19 table 7 uidrange 200-300') - return func if supported else unittest.expectedFailure(func) - - return f - -def expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable(): - def f(func): - rc = call_quiet('ip rule add not from 192.168.100.19 l3mdev') - call_quiet('ip rule del not from 192.168.100.19 l3mdev') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfNexthopIsNotAvailable(): - def f(func): - rc = call_quiet('ip nexthop list') - return func if rc == 0 else unittest.expectedFailure(func) - - return f - -def expectedFailureIfAlternativeNameIsNotAvailable(): - def f(func): - call_quiet('ip link add dummy98 type dummy') - supported = \ - call_quiet('ip link prop add dev dummy98 altname hogehogehogehogehoge') == 0 and \ - call_quiet('ip link show dev hogehogehogehogehoge') == 0 - remove_link('dummy98') - return func if supported else unittest.expectedFailure(func) - - return f - -def expectedFailureIfNetdevsimWithSRIOVIsNotAvailable(): - def f(func): - def finalize(func, supported): - call_quiet('rmmod netdevsim') - return func if supported else unittest.expectedFailure(func) - - call_quiet('rmmod netdevsim') - if call_quiet('modprobe netdevsim') != 0: - return finalize(func, False) - - try: - with open('/sys/bus/netdevsim/new_device', mode='w', encoding='utf-8') as f: - f.write('99 1') - except OSError: - return finalize(func, False) - - return finalize(func, os.path.exists('/sys/bus/netdevsim/devices/netdevsim99/sriov_numvfs')) - - return f - -def expectedFailureIfKernelReturnsInvalidFlags(): - ''' - This checks the kernel bug caused by 3ddc2231c8108302a8229d3c5849ee792a63230d (v6.9), - fixed by 1af7f88af269c4e06a4dc3bc920ff6cdf7471124 (v6.10, backported to 6.9.3). - ''' - def f(func): - call_quiet('ip link add dummy98 type dummy') - call_quiet('ip link set up dev dummy98') - call_quiet('ip address add 192.0.2.1/24 dev dummy98 noprefixroute') - output = check_output('ip address show dev dummy98') - remove_link('dummy98') - return func if 'noprefixroute' in output else unittest.expectedFailure(func) - - return f # pylint: disable=C0415 def compare_kernel_version(min_kernel_version): try: import platform + from packaging import version except ImportError: print('Failed to import either platform or packaging module, assuming the comparison failed') @@ -302,6 +243,7 @@ def compare_kernel_version(min_kernel_version): return version.parse(kver) >= version.parse(min_kernel_version) + def copy_network_unit(*units, copy_dropins=True): """ Copy networkd unit files into the testbed. @@ -317,7 +259,10 @@ def copy_network_unit(*units, copy_dropins=True): mkdir_p(network_unit_dir) for unit in units: if copy_dropins and os.path.exists(os.path.join(networkd_ci_temp_dir, unit + '.d')): - cp_r(os.path.join(networkd_ci_temp_dir, unit + '.d'), os.path.join(network_unit_dir, unit + '.d')) + cp_r( + os.path.join(networkd_ci_temp_dir, unit + '.d'), + os.path.join(network_unit_dir, unit + '.d'), + ) if unit.endswith('.conf'): dropin = unit @@ -334,10 +279,11 @@ def copy_network_unit(*units, copy_dropins=True): if has_link: udevadm_reload() + def copy_credential(src, target): - mkdir_p(credstore_dir) - cp(os.path.join(networkd_ci_temp_dir, src), - os.path.join(credstore_dir, target)) + mkdir_p(credstore_dir) + cp(os.path.join(networkd_ci_temp_dir, src), os.path.join(credstore_dir, target)) + def remove_network_unit(*units): """ @@ -356,10 +302,12 @@ def remove_network_unit(*units): if has_link: udevadm_reload() + def touch_network_unit(*units): for unit in units: touch(os.path.join(network_unit_dir, unit)) + def clear_network_units(): has_link = False if os.path.exists(network_unit_dir): @@ -373,20 +321,24 @@ def clear_network_units(): if has_link: udevadm_reload() + def copy_networkd_conf_dropin(*dropins): """Copy networkd.conf dropin files into the testbed.""" mkdir_p(networkd_conf_dropin_dir) for dropin in dropins: cp(os.path.join(networkd_ci_temp_dir, dropin), networkd_conf_dropin_dir) + def remove_networkd_conf_dropin(*dropins): """Remove previously copied networkd.conf dropin files from the testbed.""" for dropin in dropins: rm_f(os.path.join(networkd_conf_dropin_dir, dropin)) + def clear_networkd_conf_dropins(): rm_rf(networkd_conf_dropin_dir) + def setup_systemd_udev_rules(): if not build_dir and not source_dir: return @@ -397,49 +349,55 @@ def setup_systemd_udev_rules(): if not path: continue - path = os.path.join(path, "rules.d") - print(f"Copying udev rules from {path} to {udev_rules_dir}") + path = os.path.join(path, 'rules.d') + print(f'Copying udev rules from {path} to {udev_rules_dir}') for rule in os.listdir(path): - if not rule.endswith(".rules"): + if not rule.endswith('.rules'): continue cp(os.path.join(path, rule), udev_rules_dir) + def clear_networkd_state_files(): rm_rf('/var/lib/systemd/network/') + def copy_udev_rule(*rules): """Copy udev rules""" mkdir_p(udev_rules_dir) for rule in rules: cp(os.path.join(networkd_ci_temp_dir, rule), udev_rules_dir) + def remove_udev_rule(*rules): """Remove previously copied udev rules""" for rule in rules: rm_f(os.path.join(udev_rules_dir, rule)) + def clear_udev_rules(): rm_rf(udev_rules_dir) + def save_active_units(): for u in [ - 'systemd-networkd.socket', - 'systemd-networkd-resolve-hook.socket', - 'systemd-networkd-varlink.socket', - 'systemd-networkd-varlink-metrics.socket', - 'systemd-networkd.service', - 'systemd-resolved-monitor.socket', - 'systemd-resolved-varlink.socket', - 'systemd-resolved.service', - 'systemd-timesyncd.service', - 'firewalld.service', - 'nftables.service', + 'systemd-networkd.socket', + 'systemd-networkd-resolve-hook.socket', + 'systemd-networkd-varlink.socket', + 'systemd-networkd-varlink-metrics.socket', + 'systemd-networkd.service', + 'systemd-resolved-monitor.socket', + 'systemd-resolved-varlink.socket', + 'systemd-resolved.service', + 'systemd-timesyncd.service', + 'firewalld.service', + 'nftables.service', ]: if call(f'systemctl is-active --quiet {u}') == 0: call(f'systemctl stop {u}') active_units.append(u) + def restore_active_units(): has_network_socket = False has_resolve_socket = False @@ -477,11 +435,13 @@ def restore_active_units(): for u in active_units: call(f'systemctl restart {u}') + def create_unit_dropin(unit, contents): mkdir_p(f'/run/systemd/system/{unit}.d') with open(f'/run/systemd/system/{unit}.d/00-override.conf', mode='w', encoding='utf-8') as f: f.write('\n'.join(contents)) + def create_service_dropin(service, command, additional_settings=None): drop_in = ['[Service]'] if command: @@ -519,37 +479,43 @@ def create_service_dropin(service, command, additional_settings=None): create_unit_dropin(f'{service}.service', drop_in) + def setup_system_units(): if build_dir or source_dir: mkdir_p('/run/systemd/system/') for unit in [ - 'systemd-networkd.service', - 'systemd-networkd.socket', - 'systemd-networkd-persistent-storage.service', - 'systemd-networkd-resolve-hook.socket', - 'systemd-networkd-varlink.socket', - 'systemd-networkd-varlink-metrics.socket', - 'systemd-resolved.service', - 'systemd-timesyncd.service', - 'systemd-udevd.service', + 'systemd-networkd.service', + 'systemd-networkd.socket', + 'systemd-networkd-persistent-storage.service', + 'systemd-networkd-resolve-hook.socket', + 'systemd-networkd-varlink.socket', + 'systemd-networkd-varlink-metrics.socket', + 'systemd-resolved.service', + 'systemd-timesyncd.service', + 'systemd-udevd.service', ]: for path in [build_dir, source_dir]: if not path: continue - fullpath = os.path.join(os.path.join(path, "units"), unit) + fullpath = os.path.join(os.path.join(path, 'units'), unit) if os.path.exists(fullpath): - print(f"Copying unit file from {fullpath} to /run/systemd/system/") + print(f'Copying unit file from {fullpath} to /run/systemd/system/') cp(fullpath, '/run/systemd/system/') break - create_service_dropin('systemd-networkd', networkd_bin, - ['[Service]', - 'Restart=no', - 'Environment=SYSTEMD_NETWORK_TEST_MODE=yes', - '[Unit]', - 'StartLimitIntervalSec=0']) + create_service_dropin( + 'systemd-networkd', + networkd_bin, + [ + '[Service]', + 'Restart=no', + 'Environment=SYSTEMD_NETWORK_TEST_MODE=yes', + '[Unit]', + 'StartLimitIntervalSec=0', + ], + ) create_service_dropin('systemd-resolved', resolved_bin) create_service_dropin('systemd-timesyncd', timesyncd_bin) @@ -559,7 +525,7 @@ def setup_system_units(): [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-networkd-persistent-storage.service', @@ -572,28 +538,28 @@ def setup_system_units(): 'ExecStop=', f'ExecStop={networkctl_bin} persistent-storage no', 'Environment=SYSTEMD_LOG_LEVEL=debug' if enable_debug else '', - ] + ], ) create_unit_dropin( 'systemd-networkd-resolve-hook.socket', [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-networkd-varlink.socket', [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-networkd-varlink-metrics.socket', [ '[Unit]', 'StartLimitIntervalSec=0', - ] + ], ) create_unit_dropin( 'systemd-udevd.service', @@ -601,7 +567,7 @@ def setup_system_units(): '[Service]', 'ExecStart=', f'ExecStart=@{udevadm_bin} systemd-udevd', - ] + ], ) check_output('systemctl daemon-reload') @@ -614,6 +580,7 @@ def setup_system_units(): check_output('systemctl restart systemd-timesyncd.service') check_output('systemctl restart systemd-udevd.service') + def clear_system_units(): def rm_unit(name): rm_f(f'/run/systemd/system/{name}') @@ -631,15 +598,18 @@ def rm_unit(name): check_output('systemctl daemon-reload') check_output('systemctl restart systemd-udevd.service') + def link_exists(*links): for link in links: if call_quiet(f'ip link show {link}') != 0: return False return True + def link_resolve(link): return check_output(f'ip link show {link}').split(':')[1].strip().split('@')[0] + def remove_link(*links, protect=False): for link in links: if protect and link in protected_links: @@ -647,6 +617,7 @@ def remove_link(*links, protect=False): if link_exists(link): call(f'ip link del dev {link}') + def save_existing_links(): links = os.listdir('/sys/class/net') for link in links: @@ -656,6 +627,7 @@ def save_existing_links(): print('### The following links will be protected:') print(', '.join(sorted(list(protected_links)))) + def unmanage_existing_links(): mkdir_p(network_unit_dir) @@ -665,16 +637,19 @@ def unmanage_existing_links(): f.write(f'Name={link}\n') f.write('\n[Link]\nUnmanaged=yes\n') + def flush_links(): links = os.listdir('/sys/class/net') remove_link(*links, protect=True) + def flush_nexthops(): # Currently, the 'ip nexthop' command does not have 'save' and 'restore'. # Hence, we cannot restore nexthops in a simple way. # Let's assume there is no nexthop used in the system call_quiet('ip nexthop flush') + def save_routes(): # pylint: disable=global-statement global saved_routes @@ -682,6 +657,7 @@ def save_routes(): print('### The following routes will be protected:') print(saved_routes) + def flush_routes(): have = False output = check_output('ip route show table all') @@ -690,7 +666,7 @@ def flush_routes(): continue if 'proto kernel' in line: continue - if ' dev ' in line and not ' dev lo ' in line: + if ' dev ' in line and ' dev lo ' not in line: continue if not have: have = True @@ -698,9 +674,11 @@ def flush_routes(): print(f'# {line}') call(f'ip route del {line}') + def save_routing_policy_rules(): # pylint: disable=global-statement global saved_ipv4_rules, saved_ipv6_rules + def save(ipv): output = check_output(f'ip -{ipv} rule show') print(f'### The following IPv{ipv} routing policy rules will be protected:') @@ -710,6 +688,7 @@ def save(ipv): saved_ipv4_rules = save(4) saved_ipv6_rules = save(6) + def flush_routing_policy_rules(): def flush(ipv, saved_rules): have = False @@ -719,7 +698,7 @@ def flush(ipv, saved_rules): continue if not have: have = True - print(f'### Removing IPv{ipv} routing policy rules that did not exist when the test started.') + print(f'### Removing IPv{ipv} routing policy rules that did not exist when the test started.') # fmt: skip print(f'# {line}') words = line.replace('lookup [l3mdev-table]', 'l3mdev').replace('[detached]', '').split() priority = words[0].rstrip(':') @@ -728,19 +707,21 @@ def flush(ipv, saved_rules): flush(4, saved_ipv4_rules) flush(6, saved_ipv6_rules) + def flush_fou_ports(): ret = run('ip fou show') if ret.returncode != 0: - return # fou may not be supported + return # fou may not be supported for line in ret.stdout.splitlines(): port = line.split()[1] call(f'ip fou del port {port}') + def flush_l2tp_tunnels(): tids = [] ret = run('ip l2tp show tunnel') if ret.returncode != 0: - return # l2tp may not be supported + return # l2tp may not be supported for line in ret.stdout.splitlines(): words = line.split() if words[0] == 'Tunnel': @@ -754,10 +735,11 @@ def flush_l2tp_tunnels(): r = run(f'ip l2tp show tunnel tunnel_id {tid}') if r.returncode != 0 or len(r.stdout.rstrip()) == 0: break - time.sleep(.2) + time.sleep(0.2) else: print(f'Cannot remove L2TP tunnel {tid}, ignoring.') + def save_timezone(): # pylint: disable=global-statement global saved_timezone @@ -766,66 +748,79 @@ def save_timezone(): saved_timezone = r.stdout.rstrip() print(f'### Saved timezone: {saved_timezone}') + def restore_timezone(): if saved_timezone: call(*timedatectl_cmd, 'set-timezone', f'{saved_timezone}', env=env) + def read_link_attr(*args): with open(os.path.join('/sys/class/net', *args), encoding='utf-8') as f: return f.readline().strip() + def read_manager_state_file(): with open('/run/systemd/netif/state', encoding='utf-8') as f: return f.read() + def read_link_state_file(link): ifindex = read_link_attr(link, 'ifindex') path = os.path.join('/run/systemd/netif/links', ifindex) with open(path, encoding='utf-8') as f: return f.read() + def read_ip_sysctl_attr(link, attribute, ipv): with open(os.path.join('/proc/sys/net', ipv, 'conf', link, attribute), encoding='utf-8') as f: return f.readline().strip() + def read_ip_neigh_sysctl_attr(link, attribute, ipv): with open(os.path.join('/proc/sys/net', ipv, 'neigh', link, attribute), encoding='utf-8') as f: return f.readline().strip() + def read_ipv6_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'ipv6') + def read_ipv6_neigh_sysctl_attr(link, attribute): return read_ip_neigh_sysctl_attr(link, attribute, 'ipv6') + def read_ipv4_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'ipv4') + def read_mpls_sysctl_attr(link, attribute): return read_ip_sysctl_attr(link, attribute, 'mpls') + def stop_by_pid_file(pid_file): if not os.path.exists(pid_file): return - with open(pid_file, 'r', encoding='utf-8') as f: + with open(pid_file, encoding='utf-8') as f: pid = f.read().rstrip(' \t\r\n\0') os.kill(int(pid), signal.SIGTERM) for _ in range(25): try: os.kill(int(pid), 0) - print(f"PID {pid} is still alive, waiting...") - time.sleep(.2) + print(f'PID {pid} is still alive, waiting...') + time.sleep(0.2) except OSError as e: if e.errno == errno.ESRCH: break - print(f"Unexpected exception when waiting for {pid} to die: {e.errno}") + print(f'Unexpected exception when waiting for {pid} to die: {e.errno}') rm_f(pid_file) -def dnr_v4_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None): - b = bytes() - pack = lambda c, w=1: struct.pack('>' + "_BH_I"[w], len(c)) + c - pyton = lambda n, w=2: struct.pack('>' + "_BH_I"[w], n) + +def dnr_v4_instance_data(adn, addrs=None, prio=1, alpns=('dot',), dohpath=None): + b = b'' + pack = lambda c, w=1: struct.pack('>' + '_BH_I'[w], len(c)) + c + pyton = lambda n, w=2: struct.pack('>' + '_BH_I'[w], n) ipv4 = ipaddress.IPv4Address + class SvcParam(enum.Enum): ALPN = 1 DOHPATH = 7 @@ -835,7 +830,7 @@ class SvcParam(enum.Enum): adn = adn.rstrip('.') + '.' data += pack(b.join(pack(label.encode('ascii')) for label in adn.split('.'))) - if not addrs: # adn-only mode + if not addrs: # adn-only mode return pack(data, 2) data += pack(b.join(ipv4(addr).packed for addr in addrs)) @@ -845,11 +840,13 @@ class SvcParam(enum.Enum): return pack(data, 2) -def dnr_v6_instance_data(adn, addrs=None, prio=1, alpns=("dot",), dohpath=None): - b = bytes() - pack = lambda c, w=1: struct.pack('>' + "_BH_I"[w], len(c)) + c - pyton = lambda n, w=2: struct.pack('>' + "_BH_I"[w], n) + +def dnr_v6_instance_data(adn, addrs=None, prio=1, alpns=('dot',), dohpath=None): + b = b'' + pack = lambda c, w=1: struct.pack('>' + '_BH_I'[w], len(c)) + c + pyton = lambda n, w=2: struct.pack('>' + '_BH_I'[w], n) ipv6 = ipaddress.IPv6Address + class SvcParam(enum.Enum): ALPN = 1 DOHPATH = 7 @@ -859,7 +856,7 @@ class SvcParam(enum.Enum): adn = adn.rstrip('.') + '.' data += pack(b.join(pack(label.encode('ascii')) for label in adn.split('.')), 2) - if not addrs: # adn-only mode + if not addrs: # adn-only mode return data data += pack(b.join(ipv6(addr).packed for addr in addrs), 2) @@ -869,7 +866,15 @@ class SvcParam(enum.Enum): return data -def start_dnsmasq(*additional_options, interface='veth-peer', ra_mode=None, ipv4_range='192.168.5.10,192.168.5.200', ipv4_router='192.168.5.1', ipv6_range='2600::10,2600::20'): + +def start_dnsmasq( + *additional_options, + interface='veth-peer', + ra_mode=None, + ipv4_range='192.168.5.10,192.168.5.200', + ipv4_router='192.168.5.1', + ipv6_range='2600::10,2600::20', +): if ra_mode: ra_mode = f',{ra_mode}' else: @@ -895,29 +900,43 @@ def start_dnsmasq(*additional_options, interface='veth-peer', ra_mode=None, ipv4 ) + additional_options check_output(*command) + def stop_dnsmasq(): stop_by_pid_file(dnsmasq_pid_file) rm_f(dnsmasq_lease_file) rm_f(dnsmasq_log_file) + def read_dnsmasq_log_file(): with open(dnsmasq_log_file, encoding='utf-8') as f: return f.read() + def start_isc_dhcpd(conf_file, ipv, interface='veth-peer'): conf_file_path = os.path.join(networkd_ci_temp_dir, conf_file) - isc_dhcpd_command = f'dhcpd {ipv} -cf {conf_file_path} -lf {isc_dhcpd_lease_file} -pf {isc_dhcpd_pid_file} {interface}' + isc_dhcpd_command = f'dhcpd {ipv} -cf {conf_file_path} -lf {isc_dhcpd_lease_file} -pf {isc_dhcpd_pid_file} {interface}' # fmt: skip touch(isc_dhcpd_lease_file) check_output(isc_dhcpd_command) + def stop_isc_dhcpd(): stop_by_pid_file(isc_dhcpd_pid_file) rm_f(isc_dhcpd_lease_file) + def get_dbus_link_path(link): - out = subprocess.check_output(['busctl', 'call', 'org.freedesktop.network1', - '/org/freedesktop/network1', 'org.freedesktop.network1.Manager', - 'GetLinkByName', 's', link]) + out = subprocess.check_output( + [ + 'busctl', + 'call', + 'org.freedesktop.network1', + '/org/freedesktop/network1', + 'org.freedesktop.network1.Manager', + 'GetLinkByName', + 's', + link, + ] + ) assert out.startswith(b'io ') out = out.strip() @@ -925,35 +944,56 @@ def get_dbus_link_path(link): out = out.decode() return out[:-1].split('"')[1] + def get_dhcp_client_state(link, family): link_path = get_dbus_link_path(link) - out = subprocess.check_output(['busctl', 'get-property', 'org.freedesktop.network1', - link_path, f'org.freedesktop.network1.DHCPv{family}Client', 'State']) + out = subprocess.check_output( + [ + 'busctl', + 'get-property', + 'org.freedesktop.network1', + link_path, + f'org.freedesktop.network1.DHCPv{family}Client', + 'State', + ] + ) assert out.startswith(b's "') out = out.strip() assert out.endswith(b'"') return out[3:-1].decode() + def get_dhcp4_client_state(link): return get_dhcp_client_state(link, '4') + def get_dhcp6_client_state(link): return get_dhcp_client_state(link, '6') + def get_link_description(link): link_path = get_dbus_link_path(link) - out = subprocess.check_output(['busctl', 'call', 'org.freedesktop.network1', - link_path, 'org.freedesktop.network1.Link', 'Describe']) + out = subprocess.check_output( + [ + 'busctl', + 'call', + 'org.freedesktop.network1', + link_path, + 'org.freedesktop.network1.Link', + 'Describe', + ] + ) assert out.startswith(b's "') out = out.strip() assert out.endswith(b'"') json_raw = out[2:].decode() check_json(json_raw) - description = json.loads(json_raw) # Convert from escaped sequences to json + description = json.loads(json_raw) # Convert from escaped sequences to json check_json(description) - return json.loads(description) # Now parse the json + return json.loads(description) # Now parse the json + def start_radvd(*additional_options, config_file): config_file_path = os.path.join(networkd_ci_temp_dir, 'radvd', config_file) @@ -965,9 +1005,11 @@ def start_radvd(*additional_options, config_file): ) + additional_options check_output(*command) + def stop_radvd(): stop_by_pid_file(radvd_pid_file) + def radvd_check_config(config_file): if not shutil.which('radvd'): print('radvd is not installed, assuming the config check failed') @@ -978,11 +1020,14 @@ def radvd_check_config(config_file): config_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf/radvd', config_file) return call(f'radvd --config={config_file_path} --configtest') == 0 + def networkd_invocation_id(): return check_output('systemctl show --value -p InvocationID systemd-networkd.service') + def networkd_pid(): - return check_output('systemctl show --value -p MainPID systemd-networkd.service') + return int(check_output('systemctl show --value -p MainPID systemd-networkd.service')) + def read_networkd_log(invocation_id=None, since=None): if not invocation_id: @@ -998,9 +1043,11 @@ def read_networkd_log(invocation_id=None, since=None): check_output('journalctl --sync') return check_output(*command) + def networkd_is_failed(): return call_quiet('systemctl is-failed -q systemd-networkd.service') != 1 + def stop_networkd(show_logs=True, check_failed=True): global show_journal show_logs = show_logs and show_journal @@ -1027,12 +1074,14 @@ def stop_networkd(show_logs=True, check_failed=True): if check_failed: assert not networkd_is_failed() + def start_networkd(): check_output('systemctl start systemd-networkd') invocation_id = networkd_invocation_id() pid = networkd_pid() print(f'Started systemd-networkd.service: PID={pid}, Invocation ID={invocation_id}') + def restart_networkd(show_logs=True): global show_journal show_logs = show_logs and show_journal @@ -1046,8 +1095,6 @@ def restart_networkd(show_logs=True): pid = networkd_pid() print(f'Restarted systemd-networkd.service: PID={pid}, Invocation ID={invocation_id}') -def networkd_pid(): - return int(check_output('systemctl show --value -p MainPID systemd-networkd.service')) def networkctl(*args): # Do not call networkctl if networkd is in failed state. @@ -1055,33 +1102,43 @@ def networkctl(*args): assert not networkd_is_failed() return check_output(*(networkctl_cmd + list(args)), env=env) + def networkctl_status(*args): return networkctl('-n', '0', 'status', *args) + def networkctl_json(*args): return networkctl('--json=short', 'status', *args) + def networkctl_reconfigure(*links): networkctl('reconfigure', *links) + def networkctl_reload(): networkctl('reload') + def resolvectl(*args): return check_output(*(resolvectl_cmd + list(args)), env=env) + def timedatectl(*args): return check_output(*(timedatectl_cmd + list(args)), env=env) + def udevadm(*args): return check_output(*(udevadm_cmd + list(args))) + def udevadm_reload(): udevadm('control', '--reload') + def udevadm_trigger(*args, action='add'): udevadm('trigger', '--settle', f'--action={action}', *args) + def setup_common(): # Protect existing links unmanage_existing_links() @@ -1092,6 +1149,7 @@ def setup_common(): sys.stdout.flush() check_output('journalctl --sync') + def tear_down_common(): # 1. stop DHCP/RA servers stop_dnsmasq() @@ -1130,6 +1188,7 @@ def tear_down_common(): # 9. check the status of networkd assert not networkd_is_failed() + def setUpModule(): rm_rf(networkd_ci_temp_dir) cp_r(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'conf'), networkd_ci_temp_dir) @@ -1151,6 +1210,7 @@ def setUpModule(): setup_system_units() + def tearDownModule(): rm_rf(networkd_ci_temp_dir) clear_udev_rules() @@ -1167,7 +1227,8 @@ def tearDownModule(): sys.stdout.flush() check_output('journalctl --sync') -class Utilities(): + +class Utilities: # pylint: disable=no-member def check_link_exists(self, *link, expected=True): @@ -1219,7 +1280,9 @@ def wait_activated(self, link, state='down', trial=40): else: self.fail(f'Timed out waiting for {link} activated.') - def wait_operstate(self, link, operstate='degraded', setup_state='configured', setup_timeout=5, fail_assert=True): + def wait_operstate( + self, link, operstate='degraded', setup_state='configured', setup_timeout=5, fail_assert=True + ): """Wait for the link to reach the specified operstate and/or setup state. Specify None or '' for either operstate or setup_state to ignore that state. @@ -1251,7 +1314,17 @@ def wait_operstate(self, link, operstate='degraded', setup_state='configured', s self.fail(f'Timed out waiting for {link} to reach state {operstate}/{setup_state}') return False - def wait_online(self, *links_with_operstate, timeout='20s', bool_any=False, ipv4=False, ipv6=False, setup_state='configured', setup_timeout=5, bool_dns=False): + def wait_online( + self, + *links_with_operstate, + timeout='20s', + bool_any=False, + ipv4=False, + ipv6=False, + setup_state='configured', + setup_timeout=5, + bool_dns=False, + ): """Wait for the links to reach the specified operstate and/or setup state. This is similar to wait_operstate() but can be used for multiple links, @@ -1267,8 +1340,8 @@ def wait_online(self, *links_with_operstate, timeout='20s', bool_any=False, ipv4 Set 'bool_dns' to True to wait for DNS servers to be accessible. - Set 'ipv4' or 'ipv6' to True to wait for IPv4 address or IPv6 address, respectively, of each of the given links. - This is applied only for the operational state 'degraded' or above. + Set 'ipv4' or 'ipv6' to True to wait for IPv4 address or IPv6 address, respectively, of each of the + given links. This is applied only for the operational state 'degraded' or above. Note that this function waits for the links to reach *or exceed* the given operstate. However, the setup_state, if specified, must be matched *exactly*. @@ -1276,7 +1349,12 @@ def wait_online(self, *links_with_operstate, timeout='20s', bool_any=False, ipv4 This returns if the links reached the requested operstate/setup_state; otherwise it raises CalledProcessError or fails test assertion. """ - args = wait_online_cmd + [f'--timeout={timeout}'] + [f'--interface={link}' for link in links_with_operstate] + [f'--ignore={link}' for link in protected_links] + args = ( + wait_online_cmd + + [f'--timeout={timeout}'] + + [f'--interface={link}' for link in links_with_operstate] + + [f'--ignore={link}' for link in protected_links] + ) if bool_any: args += ['--any'] if bool_dns: @@ -1345,7 +1423,7 @@ def check_netlabel(self, interface, address, label='system_u:object_r:root_t:s0' print('## Checking NetLabel skipped: selinuxenabled command not found.') elif call_quiet('selinuxenabled') != 0: print('## Checking NetLabel skipped: SELinux disabled.') - elif not shutil.which('netlabelctl'): # not packaged by all distros + elif not shutil.which('netlabelctl'): # not packaged by all distros print('## Checking NetLabel skipped: netlabelctl command not found.') else: output = check_output('netlabelctl unlbl list') @@ -1356,7 +1434,7 @@ def setup_nftset(self, filter_name, filter_type, flags=''): if not shutil.which('nft'): print('## Setting up NFT sets skipped: nft command not found.') else: - if call(f'nft add table inet sd_test') != 0: + if call('nft add table inet sd_test') != 0: print('## Setting up NFT table failed.') self.fail() if call(f'nft add set inet sd_test {filter_name} {{ type {filter_type}; {flags} }}') != 0: @@ -1371,7 +1449,7 @@ def teardown_nftset(self, *filters): if call(f'nft delete set inet sd_test {filter_name}') != 0: print('## Tearing down NFT sets failed.') self.fail() - if call(f'nft delete table inet sd_test') != 0: + if call('nft delete table inet sd_test') != 0: print('## Tearing down NFT table failed.') self.fail() @@ -1405,15 +1483,14 @@ def networkctl_check_unit(self, ifname, netdev_file=None, network_file=None, lin if link_file: self.assertRegex(output, rf'Link File: .*/{link_file}\.link') -class NetworkctlTests(unittest.TestCase, Utilities): +class NetworkctlTests(unittest.TestCase, Utilities): def setUp(self): setup_common() def tearDown(self): tear_down_common() - @expectedFailureIfAlternativeNameIsNotAvailable() def test_altname(self): copy_network_unit('26-netdev-link-local-addressing-yes.network', '12-dummy.netdev', '12-dummy.link') start_networkd() @@ -1422,10 +1499,12 @@ def test_altname(self): output = networkctl_status('dummy98') self.assertRegex(output, 'hogehogehogehogehogehoge') - @expectedFailureIfAlternativeNameIsNotAvailable() def test_rename_to_altname(self): - copy_network_unit('26-netdev-link-local-addressing-yes.network', - '12-dummy.netdev', '12-dummy-rename-to-altname.link') + copy_network_unit( + '26-netdev-link-local-addressing-yes.network', + '12-dummy.netdev', + '12-dummy-rename-to-altname.link', + ) start_networkd() self.wait_online('dummyalt:degraded') @@ -1596,12 +1675,11 @@ def test_unit_file(self): self.assertIn('Network File: /run/systemd/network/11-test-unit-file.network', output) self.assertIn('/run/systemd/network/11-test-unit-file.network.d/dropin.conf', output) - self.check_networkd_log('test1: Configuring with /run/systemd/network/11-test-unit-file.network (dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).') + self.check_networkd_log( + 'test1: Configuring with /run/systemd/network/11-test-unit-file.network ' + '(dropins: /run/systemd/network/11-test-unit-file.network.d/dropin.conf).' + ) - # This test may be run on the system that has older udevd than 70f32a260b5ebb68c19ecadf5d69b3844896ba55 (v249). - # In that case, the udev DB for the loopback network interface may already have ID_NET_LINK_FILE property. - # Let's reprocess the interface and drop the property. - udevadm_trigger('/sys/class/net/lo') output = networkctl_status('lo') print(output) self.assertIn('Link File: n/a', output) @@ -1617,22 +1695,23 @@ def test_delete_links(self): def test_label(self): networkctl('label') -class NetworkdMatchTests(unittest.TestCase, Utilities): +class NetworkdMatchTests(unittest.TestCase, Utilities): def setUp(self): setup_common() def tearDown(self): tear_down_common() - @expectedFailureIfAlternativeNameIsNotAvailable() def test_match(self): - copy_network_unit('12-dummy-mac.netdev', - '12-dummy-match-mac-01.network', - '12-dummy-match-mac-02.network', - '12-dummy-match-renamed.network', - '12-dummy-match-altname.network', - '12-dummy-altname.link') + copy_network_unit( + '12-dummy-mac.netdev', + '12-dummy-match-mac-01.network', + '12-dummy-match-mac-02.network', + '12-dummy-match-renamed.network', + '12-dummy-match-altname.network', + '12-dummy-altname.link', + ) start_networkd() self.wait_online('dummy98:routable') @@ -1667,7 +1746,11 @@ def test_match(self): self.assertIn('Network File: /run/systemd/network/12-dummy-match-altname.network', output) def test_match_udev_property(self): - copy_network_unit('12-dummy.netdev', '13-not-match-udev-property.network', '14-match-udev-property.network') + copy_network_unit( + '12-dummy.netdev', + '13-not-match-udev-property.network', + '14-match-udev-property.network', + ) start_networkd() self.wait_online('dummy98:routable') @@ -1675,8 +1758,8 @@ def test_match_udev_property(self): print(output) self.assertRegex(output, 'Network File: /run/systemd/network/14-match-udev-property') -class WaitOnlineTests(unittest.TestCase, Utilities): +class WaitOnlineTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -1704,7 +1787,7 @@ def do_test_wait_online_dns( if network_dropin is not None: network_dropin_path = os.path.join( network_unit_dir, - '25-dhcp-client-use-dns-ipv4.network.d/test.conf' + '25-dhcp-client-use-dns-ipv4.network.d/test.conf', ) mkdir_p(os.path.dirname(network_dropin_path)) with open(network_dropin_path, 'w') as f: @@ -1713,7 +1796,7 @@ def do_test_wait_online_dns( copy_network_unit( '25-veth.netdev', '25-dhcp-client-use-dns-ipv4.network', - '25-dhcp-server.network' + '25-dhcp-server.network', ) start_networkd() self.wait_online('veth-peer:routable') @@ -1722,11 +1805,11 @@ def do_test_wait_online_dns( resolved_dropin = '/run/systemd/resolved.conf.d/global-dns.conf' mkdir_p(os.path.dirname(resolved_dropin)) with open(resolved_dropin, 'w') as f: - f.write(( - '[Resolve]\n' - f'DNS={global_dns}\n' - f'FallbackDNS={fallback_dns}\n' - )) + f.write(f''' +[Resolve] +DNS={global_dns} +FallbackDNS={fallback_dns} +''') self.addCleanup(os.remove, resolved_dropin) check_output('systemctl reload systemd-resolved') @@ -1740,61 +1823,61 @@ def do_test_wait_online_dns( if expect_timeout: # The above should have thrown an exception. - self.fail( - 'Expected systemd-networkd-wait-online to time out' - ) + self.fail('Expected systemd-networkd-wait-online to time out') except subprocess.CalledProcessError as e: if expect_timeout: self.assertRegex( e.output, - f'veth99: No link-specific DNS server is accessible', - f'Missing expected log message:\n{e.output}' + 'veth99: No link-specific DNS server is accessible', + f'Missing expected log message:\n{e.output}', ) else: - self.fail( - f'Command timed out:\n{e.output}' - ) + self.fail(f'Command timed out:\n{e.output}') finally: wait_online_env = wait_online_env_copy def test_wait_online_dns(self): - ''' test systemd-networkd-wait-online with --dns ''' + """test systemd-networkd-wait-online with --dns""" self.do_test_wait_online_dns() def test_wait_online_dns_global(self): - ''' + """ test systemd-networkd-wait-online with --dns, expect pass due to global DNS - ''' + """ # Set UseDNS=no, and allow global DNS to be used. self.do_test_wait_online_dns( global_dns='192.168.5.1', network_dropin=( - '[DHCPv4]\n' - 'UseDNS=no\n' - ) + ''' +[DHCPv4] +UseDNS=no +''' + ), ) def test_wait_online_dns_expect_timeout(self): - ''' test systemd-networkd-wait-online with --dns, and expect timeout ''' + """test systemd-networkd-wait-online with --dns, and expect timeout""" # Explicitly set DNSDefaultRoute=yes, and require link-specific DNS to be used. self.do_test_wait_online_dns( expect_timeout=True, network_dropin=( - '[Network]\n' - 'DNSDefaultRoute=yes\n' - '[DHCPv4]\n' - 'UseDNS=no\n' - ) + ''' +[Network] +DNSDefaultRoute=yes +[DHCPv4] +UseDNS=no +''' + ), ) def test_wait_online_dns_expect_timeout_global(self): - ''' + """ test systemd-networkd-wait-online with --dns, and expect timeout despite global DNS - ''' + """ # Configure Domains=~., and expect timeout despite global DNS servers # being available. @@ -1802,16 +1885,17 @@ def test_wait_online_dns_expect_timeout_global(self): expect_timeout=True, global_dns='192.168.5.1', network_dropin=( - '[Network]\n' - 'Domains=~.\n' - '[DHCPv4]\n' - 'UseDNS=no\n' - ) + ''' +[Network] +Domains=~. +[DHCPv4] +UseDNS=no +''' + ), ) class NetworkdNetDevTests(unittest.TestCase, Utilities): - def setUp(self): setup_common() @@ -1874,12 +1958,12 @@ def test_bridge(self): self.assertEqual(9, round(float(read_link_attr('bridge99', 'bridge', 'max_age')) / tick)) self.assertEqual(9, round(float(read_link_attr('bridge99', 'bridge', 'forward_delay')) / tick)) self.assertEqual(9, round(float(read_link_attr('bridge99', 'bridge', 'ageing_time')) / tick)) - self.assertEqual(9, int(read_link_attr('bridge99', 'bridge', 'priority'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_querier'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_snooping'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'stp_state'))) - self.assertEqual(3, int(read_link_attr('bridge99', 'bridge', 'multicast_igmp_version'))) - self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'no_linklocal_learn'))) + self.assertEqual(9, int(read_link_attr('bridge99', 'bridge', 'priority'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_querier'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'multicast_snooping'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'stp_state'))) + self.assertEqual(3, int(read_link_attr('bridge99', 'bridge', 'multicast_igmp_version'))) + self.assertEqual(1, int(read_link_attr('bridge99', 'bridge', 'no_linklocal_learn'))) output = networkctl_status('bridge99') print(output) @@ -1906,20 +1990,20 @@ def test_bond(self): self.networkctl_check_unit('bond98', '25-bond-balanced-tlb') self.networkctl_check_unit('bond97', '25-bond-property') - self.check_link_attr('bond99', 'bonding', 'mode', '802.3ad 4') - self.check_link_attr('bond99', 'bonding', 'xmit_hash_policy', 'layer3+4 1') - self.check_link_attr('bond99', 'bonding', 'miimon', '1000') - self.check_link_attr('bond99', 'bonding', 'lacp_rate', 'fast 1') - self.check_link_attr('bond99', 'bonding', 'updelay', '2000') - self.check_link_attr('bond99', 'bonding', 'downdelay', '2000') - self.check_link_attr('bond99', 'bonding', 'resend_igmp', '4') - self.check_link_attr('bond99', 'bonding', 'min_links', '1') + self.check_link_attr('bond99', 'bonding', 'mode', '802.3ad 4') + self.check_link_attr('bond99', 'bonding', 'xmit_hash_policy', 'layer3+4 1') + self.check_link_attr('bond99', 'bonding', 'miimon', '1000') + self.check_link_attr('bond99', 'bonding', 'lacp_rate', 'fast 1') + self.check_link_attr('bond99', 'bonding', 'updelay', '2000') + self.check_link_attr('bond99', 'bonding', 'downdelay', '2000') + self.check_link_attr('bond99', 'bonding', 'resend_igmp', '4') + self.check_link_attr('bond99', 'bonding', 'min_links', '1') self.check_link_attr('bond99', 'bonding', 'ad_actor_sys_prio', '1218') - self.check_link_attr('bond99', 'bonding', 'ad_user_port_key', '811') - self.check_link_attr('bond99', 'bonding', 'ad_actor_system', '00:11:22:33:44:55') + self.check_link_attr('bond99', 'bonding', 'ad_user_port_key', '811') + self.check_link_attr('bond99', 'bonding', 'ad_actor_system', '00:11:22:33:44:55') - self.check_link_attr('bond98', 'bonding', 'mode', 'balance-tlb 5') - self.check_link_attr('bond98', 'bonding', 'tlb_dynamic_lb', '1') + self.check_link_attr('bond98', 'bonding', 'mode', 'balance-tlb 5') + self.check_link_attr('bond98', 'bonding', 'tlb_dynamic_lb', '1') output = networkctl_status('bond99') print(output) @@ -1935,7 +2019,7 @@ def test_bond(self): output = networkctl_status('bond97') print(output) - self.check_link_attr('bond97', 'bonding', 'arp_missed_max', '10') + self.check_link_attr('bond97', 'bonding', 'arp_missed_max', '10') self.check_link_attr('bond97', 'bonding', 'peer_notif_delay', '300000') def check_vlan(self, id, flags): @@ -1974,26 +2058,36 @@ def check_vlan(self, id, flags): self.assertRegex(output, 'inet 192.168.23.5/24 brd 192.168.23.255 scope global vlan99') def test_vlan(self): - copy_network_unit('21-vlan.netdev', '11-dummy.netdev', - '21-vlan.network', '21-vlan-test1.network') + copy_network_unit( + '21-vlan.netdev', + '11-dummy.netdev', + '21-vlan.network', + '21-vlan-test1.network', + ) start_networkd() self.check_vlan(id=99, flags=True) # Test for reloading .netdev file. See issue #34907. - with open(os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8' + ) as f: f.write('[VLAN]\nId=42\n') # VLAN ID cannot be changed, so we need to remove the existing netdev. - check_output("ip link del vlan99") + check_output('ip link del vlan99') networkctl_reload() self.check_vlan(id=42, flags=True) - with open(os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8') as f: - f.write('[VLAN]\n' - 'GVRP=no\n' - 'MVRP=no\n' - 'LooseBinding=no\n' - 'ReorderHeader=no\n') + with open( + os.path.join(network_unit_dir, '21-vlan.netdev.d/override.conf'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[VLAN] +GVRP=no +MVRP=no +LooseBinding=no +ReorderHeader=no +''') # flags can be changed, hence it is not necessary to remove the existing netdev. networkctl_reload() @@ -2003,8 +2097,12 @@ def test_vlan_on_bond(self): # For issue #24377 (https://github.com/systemd/systemd/issues/24377), # which is fixed by b05e52000b4eee764b383cc3031da0a3739e996e (PR#24020). - copy_network_unit('21-bond-802.3ad.netdev', '21-bond-802.3ad.network', - '21-vlan-on-bond.netdev', '21-vlan-on-bond.network') + copy_network_unit( + '21-bond-802.3ad.netdev', + '21-bond-802.3ad.network', + '21-vlan-on-bond.netdev', + '21-vlan-on-bond.network', + ) start_networkd() self.wait_online('bond99:off') self.wait_operstate('vlan99', operstate='off', setup_state='configuring', setup_timeout=10) @@ -2027,14 +2125,22 @@ def test_macvtap(self): print(f'### test_macvtap(mode={mode})') with self.subTest(mode=mode): - copy_network_unit('21-macvtap.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-macvtap.network') - with open(os.path.join(network_unit_dir, '21-macvtap.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '21-macvtap.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-macvtap.network', + ) + with open( + os.path.join(network_unit_dir, '21-macvtap.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[MACVTAP]\nMode=' + mode) start_networkd() - self.wait_online('macvtap99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvtap99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) self.networkctl_check_unit('macvtap99', '21-macvtap', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('test1', '11-dummy', '25-macvtap') @@ -2044,8 +2150,10 @@ def test_macvtap(self): touch_network_unit('21-macvtap.netdev') networkctl_reload() - self.wait_online('macvtap99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvtap99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) @expectedFailureIfModuleIsNotAvailable('macvlan') def test_macvlan(self): @@ -2058,14 +2166,22 @@ def test_macvlan(self): print(f'### test_macvlan(mode={mode})') with self.subTest(mode=mode): - copy_network_unit('21-macvlan.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-macvlan.network') - with open(os.path.join(network_unit_dir, '21-macvlan.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '21-macvlan.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-macvlan.network', + ) + with open( + os.path.join(network_unit_dir, '21-macvlan.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[MACVLAN]\nMode=' + mode) start_networkd() - self.wait_online('macvlan99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvlan99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) self.networkctl_check_unit('macvlan99', '21-macvlan', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('test1', '11-dummy', '25-macvlan') @@ -2081,9 +2197,11 @@ def test_macvlan(self): remove_link('test1') time.sleep(1) - check_output("ip link add test1 type dummy") - self.wait_online('macvlan99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + check_output('ip link add test1 type dummy') + self.wait_online( + 'macvlan99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) output = check_output('ip -d link show test1') print(output) @@ -2094,13 +2212,15 @@ def test_macvlan(self): self.assertIn(' mtu 2000 ', output) self.assertIn(f' macvlan mode {mode} ', output) self.assertIn(' bcqueuelen 1234 ', output) - if ' bclim ' in output: # This is new in kernel and iproute2 v6.4 + if ' bclim ' in output: # This is new in kernel and iproute2 v6.4 self.assertIn(' bclim 2147483647 ', output) touch_network_unit('21-macvlan.netdev') networkctl_reload() - self.wait_online('macvlan99:degraded', - 'test1:carrier' if mode == 'passthru' else 'test1:degraded') + self.wait_online( + 'macvlan99:degraded', + 'test1:carrier' if mode == 'passthru' else 'test1:degraded', + ) @expectedFailureIfModuleIsNotAvailable('ipvlan') def test_ipvlan(self): @@ -2113,9 +2233,15 @@ def test_ipvlan(self): print(f'### test_ipvlan(mode={mode}, flag={flag})') with self.subTest(mode=mode, flag=flag): - copy_network_unit('25-ipvlan.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-ipvlan.network') - with open(os.path.join(network_unit_dir, '25-ipvlan.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '25-ipvlan.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-ipvlan.network', + ) + with open( + os.path.join(network_unit_dir, '25-ipvlan.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[IPVLAN]\nMode=' + mode + '\nFlags=' + flag) start_networkd() @@ -2142,9 +2268,14 @@ def test_hsr(self): print(f'### test_hsr(proto={proto}, supervision={supervision})') with self.subTest(proto=proto, supervision=supervision): - copy_network_unit('25-hsr.netdev', '25-hsr.network', - '11-dummy.netdev', '11-dummy.network', - '12-dummy.netdev', '12-dummy-no-address.network') + copy_network_unit( + '25-hsr.netdev', + '25-hsr.network', + '11-dummy.netdev', + '11-dummy.network', + '12-dummy.netdev', + '12-dummy-no-address.network', + ) with open(os.path.join(network_unit_dir, '25-hsr.netdev'), mode='a', encoding='utf-8') as f: f.write('Protocol=' + proto + '\nSupervision=' + str(supervision)) @@ -2175,9 +2306,15 @@ def test_ipvtap(self): print(f'### test_ipvtap(mode={mode}, flag={flag})') with self.subTest(mode=mode, flag=flag): - copy_network_unit('25-ipvtap.netdev', '26-netdev-link-local-addressing-yes.network', - '11-dummy.netdev', '25-ipvtap.network') - with open(os.path.join(network_unit_dir, '25-ipvtap.netdev'), mode='a', encoding='utf-8') as f: + copy_network_unit( + '25-ipvtap.netdev', + '26-netdev-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-ipvtap.network', + ) + with open( + os.path.join(network_unit_dir, '25-ipvtap.netdev'), mode='a', encoding='utf-8' + ) as f: f.write('[IPVTAP]\nMode=' + mode + '\nFlags=' + flag) start_networkd() @@ -2194,11 +2331,19 @@ def test_ipvtap(self): self.wait_online('ipvtap99:degraded', 'test1:degraded') def test_veth(self): - copy_network_unit('25-veth.netdev', '26-netdev-link-local-addressing-yes.network', - '25-veth-mtu.netdev') + copy_network_unit( + '25-veth.netdev', + '26-netdev-link-local-addressing-yes.network', + '25-veth-mtu.netdev', + ) start_networkd() - self.wait_online('veth99:degraded', 'veth-peer:degraded', 'veth-mtu:degraded', 'veth-mtu-peer:degraded') + self.wait_online( + 'veth99:degraded', + 'veth-peer:degraded', + 'veth-mtu:degraded', + 'veth-mtu-peer:degraded', + ) self.networkctl_check_unit('veth99', '25-veth', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('veth-peer', '25-veth', '26-netdev-link-local-addressing-yes') self.networkctl_check_unit('veth-mtu', '25-veth-mtu', '26-netdev-link-local-addressing-yes') @@ -2223,13 +2368,15 @@ def test_veth(self): touch_network_unit( '25-veth.netdev', '26-netdev-link-local-addressing-yes.network', - '25-veth-mtu.netdev') + '25-veth-mtu.netdev', + ) networkctl_reload() self.wait_online( 'veth99:degraded', 'veth-peer:degraded', 'veth-mtu:degraded', - 'veth-mtu-peer:degraded') + 'veth-mtu-peer:degraded', + ) def check_tuntap(self, attached): pid = networkd_pid() @@ -2237,24 +2384,25 @@ def check_tuntap(self, attached): output = check_output('ip -d -oneline tuntap show') print(output) + # fmt: off self.assertRegex(output, r'testtap99: tap pi (multi_queue |)vnet_hdr persist filter.*\tAttached to processes:') self.assertRegex(output, r'testtun99: tun pi (multi_queue |)vnet_hdr persist filter.*\tAttached to processes:') + # fmt: on if attached: - self.assertRegex(output, fr'testtap99: .*{name}\({pid}\)') - self.assertRegex(output, fr'testtun99: .*{name}\({pid}\)') + self.assertRegex(output, rf'testtap99: .*{name}\({pid}\)') + self.assertRegex(output, rf'testtun99: .*{name}\({pid}\)') self.assertRegex(output, r'testtap99: .*systemd\(1\)') self.assertRegex(output, r'testtun99: .*systemd\(1\)') output = check_output('ip -d link show testtun99') print(output) - # Old ip command does not support IFF_ flags - self.assertRegex(output, 'tun (type tun pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tun pi on vnet_hdr on multi_queue', output) self.assertIn('UP,LOWER_UP', output) output = check_output('ip -d link show testtap99') print(output) - self.assertRegex(output, 'tun (type tap pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tap pi on vnet_hdr on multi_queue', output) self.assertIn('UP,LOWER_UP', output) else: @@ -2264,7 +2412,7 @@ def check_tuntap(self, attached): for _ in range(20): output = check_output('ip -d link show testtun99') print(output) - self.assertRegex(output, 'tun (type tun pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tun pi on vnet_hdr on multi_queue', output) if 'NO-CARRIER' in output: break time.sleep(0.5) @@ -2274,7 +2422,7 @@ def check_tuntap(self, attached): for _ in range(20): output = check_output('ip -d link show testtap99') print(output) - self.assertRegex(output, 'tun (type tap pi on vnet_hdr on multi_queue|addrgenmode) ') + self.assertIn('tun type tap pi on vnet_hdr on multi_queue', output) if 'NO-CARRIER' in output: break time.sleep(0.5) @@ -2327,8 +2475,12 @@ def test_vrf(self): @expectedFailureIfModuleIsNotAvailable('vcan') def test_vcan(self): - copy_network_unit('25-vcan.netdev', '26-netdev-link-local-addressing-yes.network', - '25-vcan98.netdev', '25-vcan98.network') + copy_network_unit( + '25-vcan.netdev', + '26-netdev-link-local-addressing-yes.network', + '25-vcan98.netdev', + '25-vcan98.network', + ) start_networkd() self.wait_online('vcan99:carrier', 'vcan98:carrier') @@ -2350,7 +2502,8 @@ def test_vcan(self): '25-vcan.netdev', '26-netdev-link-local-addressing-yes.network', '25-vcan98.netdev', - '25-vcan98.network') + '25-vcan98.network', + ) networkctl_reload() self.wait_online('vcan99:carrier', 'vcan98:carrier') @@ -2371,14 +2524,30 @@ def test_vxcan(self): @expectedFailureIfModuleIsNotAvailable('wireguard') def test_wireguard(self): - copy_credential('25-wireguard-endpoint-peer0-cred.txt', 'network.wireguard.peer0.endpoint') - copy_credential('25-wireguard-preshared-key-peer2-cred.txt', 'network.wireguard.peer2.psk') - copy_credential('25-wireguard-no-peer-private-key-cred.txt', 'network.wireguard.private.25-wireguard-no-peer') + copy_credential( + '25-wireguard-endpoint-peer0-cred.txt', + 'network.wireguard.peer0.endpoint', + ) + copy_credential( + '25-wireguard-preshared-key-peer2-cred.txt', + 'network.wireguard.peer2.psk', + ) + copy_credential( + '25-wireguard-no-peer-private-key-cred.txt', + 'network.wireguard.private.25-wireguard-no-peer', + ) - copy_network_unit('25-wireguard.netdev', '25-wireguard.network', - '25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network', - '25-wireguard-public-key.txt', '25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt', - '25-wireguard-no-peer.netdev', '25-wireguard-no-peer.network') + copy_network_unit( + '25-wireguard.netdev', + '25-wireguard.network', + '25-wireguard-23-peers.netdev', + '25-wireguard-23-peers.network', + '25-wireguard-public-key.txt', + '25-wireguard-preshared-key.txt', + '25-wireguard-private-key.txt', + '25-wireguard-no-peer.netdev', + '25-wireguard-no-peer.network', + ) start_networkd() self.wait_online('wg99:routable', 'wg98:routable', 'wg97:carrier') self.networkctl_check_unit('wg99', '25-wireguard', '25-wireguard') @@ -2472,10 +2641,12 @@ def test_wireguard(self): output = check_output('wg show wg99 private-key') self.assertEqual(output, 'EEGlnEPYJV//kbvvIqxKkQwOiS+UENyPncC4bF46ong=') output = check_output('wg show wg99 allowed-ips') + # fmt: off self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\t192.168.124.3/32', output) self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\t192.168.124.2/32', output) self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\tfdbc:bae2:7871:e1fe:793:8636::/96 fdbc:bae2:7871:500:e1fe:793:8636:dad1/128', output) self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\t192.168.26.0/24 fd31:bf08:57cb::/48', output) + # fmt: on output = check_output('wg show wg99 persistent-keepalive') self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\toff', output) self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\toff', output) @@ -2487,10 +2658,12 @@ def test_wireguard(self): self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\t(none)', output) self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\t192.168.27.3:51820', output) output = check_output('wg show wg99 preshared-keys') + # fmt: off self.assertIn('9uioxkGzjvGjkse3V35I9AhorWfIjBcrf3UPMS0bw2c=\t6Fsg8XN0DE6aPQgAX4r2oazEYJOGqyHUz3QRH/jCB+I=', output) self.assertIn('TTiCUpCxS7zDn/ax4p5W6Evg41r8hOrnWQw2Sq6Nh10=\tit7nd33chCT/tKT2ZZWfYyp43Zs+6oif72hexnSNMqA=', output) self.assertIn('lsDtM3AbjxNlauRKzHEPfgS1Zp7cp/VX5Use/P4PQSc=\tcPLOy1YUrEI0EMMIycPJmOo0aTu3RZnw8bL5meVD6m0=', output) self.assertIn('RDf+LSpeEre7YEIKaxg+wbpsNV7du+ktR99uBEtIiCA=\tIIWIV17wutHv7t4cR6pOT91z6NSz/T8Arh0yaywhw3M=', output) + # fmt: on output = check_output('wg show wg98 private-key') self.assertEqual(output, 'CJQUtcS9emY2fLYqDlpSZiE/QJyHkPWr+WHtZLZ90FU=') @@ -2501,10 +2674,16 @@ def test_wireguard(self): self.assertEqual(output, '0x4d3') touch_network_unit( - '25-wireguard.netdev', '25-wireguard.network', - '25-wireguard-23-peers.netdev', '25-wireguard-23-peers.network', - '25-wireguard-public-key.txt', '25-wireguard-preshared-key.txt', '25-wireguard-private-key.txt', - '25-wireguard-no-peer.netdev', '25-wireguard-no-peer.network') + '25-wireguard.netdev', + '25-wireguard.network', + '25-wireguard-23-peers.netdev', + '25-wireguard-23-peers.network', + '25-wireguard-public-key.txt', + '25-wireguard-preshared-key.txt', + '25-wireguard-private-key.txt', + '25-wireguard-no-peer.netdev', + '25-wireguard-no-peer.network', + ) networkctl_reload() self.wait_online('wg99:routable', 'wg98:routable', 'wg97:carrier') @@ -2527,24 +2706,39 @@ def test_geneve(self): self.wait_online('geneve99:degraded') def _test_ipip_tunnel(self, mode): - copy_network_unit('12-dummy.netdev', '25-ipip.network', - '25-ipip-tunnel.netdev', '25-tunnel.network', - '25-ipip-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-ipip-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-ipip-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-ipip.network', + '25-ipip-tunnel.netdev', + '25-tunnel.network', + '25-ipip-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-ipip-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-ipip-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) if mode: - for netdev in ['25-ipip-tunnel.netdev', - '25-ipip-tunnel-local-any.netdev', - '25-ipip-tunnel-remote-any.netdev', - '25-ipip-tunnel-any-any.netdev']: + for netdev in [ + '25-ipip-tunnel.netdev', + '25-ipip-tunnel-local-any.netdev', + '25-ipip-tunnel-remote-any.netdev', + '25-ipip-tunnel-any-any.netdev', + ]: with open(os.path.join(network_unit_dir, netdev), mode='a', encoding='utf-8') as f: f.write(f'[Tunnel]\nMode={mode}\n') else: - mode = 'ipip' # kernel default + mode = 'ipip' # kernel default start_networkd() - self.wait_online('ipiptun99:routable', 'ipiptun98:routable', 'ipiptun97:routable', 'ipiptun96:routable', 'dummy98:degraded') + self.wait_online( + 'ipiptun99:routable', + 'ipiptun98:routable', + 'ipiptun97:routable', + 'ipiptun96:routable', + 'dummy98:degraded', + ) output = check_output('ip -d link show ipiptun99') print(output) @@ -2563,14 +2757,16 @@ def _test_ipip_tunnel(self, mode): '25-ipip-tunnel.netdev', '25-ipip-tunnel-local-any.netdev', '25-ipip-tunnel-remote-any.netdev', - '25-ipip-tunnel-any-any.netdev') + '25-ipip-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'ipiptun99:routable', 'ipiptun98:routable', 'ipiptun97:routable', 'ipiptun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_ipip_tunnel(self): first = True @@ -2585,13 +2781,26 @@ def test_ipip_tunnel(self): self._test_ipip_tunnel(mode) def test_gre_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-gretun.network', - '25-gre-tunnel.netdev', '25-tunnel.network', - '25-gre-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-gre-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-gretun.network', + '25-gre-tunnel.netdev', + '25-tunnel.network', + '25-gre-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-gre-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-gre-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) start_networkd() - self.wait_online('gretun99:routable', 'gretun98:routable', 'gretun97:routable', 'gretun96:routable', 'dummy98:degraded') + self.wait_online( + 'gretun99:routable', + 'gretun98:routable', + 'gretun97:routable', + 'gretun96:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('gretun99', '25-gre-tunnel', '25-tunnel') self.networkctl_check_unit('gretun98', '25-gre-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('gretun97', '25-gre-tunnel-remote-any', '25-tunnel-remote-any') @@ -2631,30 +2840,43 @@ def test_gre_tunnel(self): '25-gre-tunnel.netdev', '25-gre-tunnel-local-any.netdev', '25-gre-tunnel-remote-any.netdev', - '25-gre-tunnel-any-any.netdev') + '25-gre-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'gretun99:routable', 'gretun98:routable', 'gretun97:routable', 'gretun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_ip6gre_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-ip6gretun.network', - '25-ip6gre-tunnel.netdev', '25-tunnel.network', - '25-ip6gre-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-ip6gre-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-ip6gre-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-ip6gretun.network', + '25-ip6gre-tunnel.netdev', + '25-tunnel.network', + '25-ip6gre-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-ip6gre-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-ip6gre-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) start_networkd() - # Old kernels seem not to support IPv6LL address on ip6gre tunnel, So please do not use wait_online() here. - - self.wait_links('dummy98', 'ip6gretun99', 'ip6gretun98', 'ip6gretun97', 'ip6gretun96') + self.wait_online( + 'ip6gretun99:routable', + 'ip6gretun98:routable', + 'ip6gretun97:routable', + 'ip6gretun96:routable', + 'dummy98:degraded', + ) output = check_output('ip -d link show ip6gretun99') print(output) - self.assertRegex(output, 'ip6gre remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') + self.assertRegex(output, 'ip6gre remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') # fmt: skip output = check_output('ip -d link show ip6gretun98') print(output) self.assertRegex(output, 'ip6gre remote 2001:473:fece:cafe::5179 local any dev dummy98') @@ -2669,19 +2891,26 @@ def test_ip6gre_tunnel(self): '25-ip6gre-tunnel.netdev', '25-ip6gre-tunnel-local-any.netdev', '25-ip6gre-tunnel-remote-any.netdev', - '25-ip6gre-tunnel-any-any.netdev') + '25-ip6gre-tunnel-any-any.netdev', + ) networkctl_reload() - self.wait_links( - 'dummy98', - 'ip6gretun99', - 'ip6gretun98', - 'ip6gretun97', - 'ip6gretun96') + self.wait_online( + 'ip6gretun99:routable', + 'ip6gretun98:routable', + 'ip6gretun97:routable', + 'ip6gretun96:routable', + 'dummy98:degraded', + ) def test_gretap_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-gretap.network', - '25-gretap-tunnel.netdev', '25-tunnel.network', - '25-gretap-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-gretap.network', + '25-gretap-tunnel.netdev', + '25-tunnel.network', + '25-gretap-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('gretap99:routable', 'gretap98:routable', 'dummy98:degraded') self.networkctl_check_unit('gretap99', '25-gretap-tunnel', '25-tunnel') @@ -2707,17 +2936,24 @@ def test_gretap_tunnel(self): touch_network_unit( '25-gretap-tunnel.netdev', - '25-gretap-tunnel-local-any.netdev') + '25-gretap-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'gretap99:routable', 'gretap98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_ip6gretap_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-ip6gretap.network', - '25-ip6gretap-tunnel.netdev', '25-tunnel.network', - '25-ip6gretap-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-ip6gretap.network', + '25-ip6gretap-tunnel.netdev', + '25-tunnel.network', + '25-ip6gretap-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('ip6gretap99:routable', 'ip6gretap98:routable', 'dummy98:degraded') self.networkctl_check_unit('ip6gretap99', '25-ip6gretap-tunnel', '25-tunnel') @@ -2726,28 +2962,43 @@ def test_ip6gretap_tunnel(self): output = check_output('ip -d link show ip6gretap99') print(output) - self.assertRegex(output, 'ip6gretap remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') + self.assertRegex(output, 'ip6gretap remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') # fmt: skip output = check_output('ip -d link show ip6gretap98') print(output) self.assertRegex(output, 'ip6gretap remote 2001:473:fece:cafe::5179 local any dev dummy98') touch_network_unit( '25-ip6gretap-tunnel.netdev', - '25-ip6gretap-tunnel-local-any.netdev') + '25-ip6gretap-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'ip6gretap99:routable', 'ip6gretap98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_vti_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-vti.network', - '25-vti-tunnel.netdev', '25-tunnel.network', - '25-vti-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-vti-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-vti-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-vti.network', + '25-vti-tunnel.netdev', + '25-tunnel.network', + '25-vti-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-vti-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-vti-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) start_networkd() - self.wait_online('vtitun99:routable', 'vtitun98:routable', 'vtitun97:routable', 'vtitun96:routable', 'dummy98:degraded') + self.wait_online( + 'vtitun99:routable', + 'vtitun98:routable', + 'vtitun97:routable', + 'vtitun96:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('vtitun99', '25-vti-tunnel', '25-tunnel') self.networkctl_check_unit('vtitun98', '25-vti-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('vtitun97', '25-vti-tunnel-remote-any', '25-tunnel-remote-any') @@ -2771,22 +3022,35 @@ def test_vti_tunnel(self): '25-vti-tunnel.netdev', '25-vti-tunnel-local-any.netdev', '25-vti-tunnel-remote-any.netdev', - '25-vti-tunnel-any-any.netdev') + '25-vti-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'vtitun99:routable', 'vtitun98:routable', 'vtitun97:routable', 'vtitun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_vti6_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-vti6.network', - '25-vti6-tunnel.netdev', '25-tunnel.network', - '25-vti6-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-vti6-tunnel-remote-any.netdev', '25-tunnel-remote-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-vti6.network', + '25-vti6-tunnel.netdev', + '25-tunnel.network', + '25-vti6-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-vti6-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + ) start_networkd() - self.wait_online('vti6tun99:routable', 'vti6tun98:routable', 'vti6tun97:routable', 'dummy98:degraded') + self.wait_online( + 'vti6tun99:routable', + 'vti6tun98:routable', + 'vti6tun97:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('vti6tun99', '25-vti6-tunnel', '25-tunnel') self.networkctl_check_unit('vti6tun98', '25-vti6-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('vti6tun97', '25-vti6-tunnel-remote-any', '25-tunnel-remote-any') @@ -2794,7 +3058,7 @@ def test_vti6_tunnel(self): output = check_output('ip -d link show vti6tun99') print(output) - self.assertRegex(output, 'vti6 remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') + self.assertRegex(output, 'vti6 remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98') # fmt: skip output = check_output('ip -d link show vti6tun98') print(output) self.assertRegex(output, 'vti6 remote 2001:473:fece:cafe::5179 local (any|::) dev dummy98') @@ -2805,50 +3069,103 @@ def test_vti6_tunnel(self): touch_network_unit( '25-vti6-tunnel.netdev', '25-vti6-tunnel-local-any.netdev', - '25-vti6-tunnel-remote-any.netdev') + '25-vti6-tunnel-remote-any.netdev', + ) networkctl_reload() self.wait_online( 'vti6tun99:routable', 'vti6tun98:routable', 'vti6tun97:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def _test_ip6tnl_tunnel(self, mode): - copy_network_unit('12-dummy.netdev', '25-ip6tnl.network', - '25-ip6tnl-tunnel.netdev', '25-tunnel.network', - '25-ip6tnl-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-ip6tnl-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-veth.netdev', '25-ip6tnl-slaac.network', '25-ipv6-prefix.network', - '25-ip6tnl-tunnel-local-slaac.netdev', '25-ip6tnl-tunnel-local-slaac.network', - '25-ip6tnl-tunnel-external.netdev', '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '12-dummy.netdev', + '25-ip6tnl.network', + '25-ip6tnl-tunnel.netdev', + '25-tunnel.network', + '25-ip6tnl-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-ip6tnl-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-veth.netdev', + '25-ip6tnl-slaac.network', + '25-ipv6-prefix.network', + '25-ip6tnl-tunnel-local-slaac.netdev', + '25-ip6tnl-tunnel-local-slaac.network', + '25-ip6tnl-tunnel-external.netdev', + '26-netdev-link-local-addressing-yes.network', + ) if mode: - for netdev in ['25-ip6tnl-tunnel.netdev', - '25-ip6tnl-tunnel-local-any.netdev', - '25-ip6tnl-tunnel-remote-any.netdev', - '25-ip6tnl-tunnel-local-slaac.netdev', - '25-ip6tnl-tunnel-external.netdev']: + for netdev in [ + '25-ip6tnl-tunnel.netdev', + '25-ip6tnl-tunnel-local-any.netdev', + '25-ip6tnl-tunnel-remote-any.netdev', + '25-ip6tnl-tunnel-local-slaac.netdev', + '25-ip6tnl-tunnel-external.netdev', + ]: with open(os.path.join(network_unit_dir, netdev), mode='a', encoding='utf-8') as f: f.write(f'[Tunnel]\nMode={mode}\n') else: - mode = 'any' # kernel default - - start_networkd() - self.wait_online('ip6tnl99:routable', 'ip6tnl98:routable', 'ip6tnl97:routable', - 'ip6tnl-slaac:degraded', 'ip6tnl-external:degraded', - 'dummy98:degraded', 'veth99:routable', 'veth-peer:degraded') - self.networkctl_check_unit('ip6tnl99', '25-ip6tnl-tunnel', '25-tunnel') - self.networkctl_check_unit('ip6tnl98', '25-ip6tnl-tunnel-local-any', '25-tunnel-local-any') - self.networkctl_check_unit('ip6tnl97', '25-ip6tnl-tunnel-remote-any', '25-tunnel-remote-any') - self.networkctl_check_unit('ip6tnl-slaac', '25-ip6tnl-tunnel-local-slaac', '25-ip6tnl-tunnel-local-slaac') - self.networkctl_check_unit('ip6tnl-external', '25-ip6tnl-tunnel-external', '26-netdev-link-local-addressing-yes') - self.networkctl_check_unit('dummy98', '12-dummy', '25-ip6tnl') - self.networkctl_check_unit('veth99', '25-veth', '25-ip6tnl-slaac') - self.networkctl_check_unit('veth-peer', '25-veth', '25-ipv6-prefix') + mode = 'any' # kernel default + + start_networkd() + self.wait_online( + 'ip6tnl99:routable', + 'ip6tnl98:routable', + 'ip6tnl97:routable', + 'ip6tnl-slaac:degraded', + 'ip6tnl-external:degraded', + 'dummy98:degraded', + 'veth99:routable', + 'veth-peer:degraded', + ) + self.networkctl_check_unit( + 'ip6tnl99', + '25-ip6tnl-tunnel', + '25-tunnel', + ) + self.networkctl_check_unit( + 'ip6tnl98', + '25-ip6tnl-tunnel-local-any', + '25-tunnel-local-any', + ) + self.networkctl_check_unit( + 'ip6tnl97', + '25-ip6tnl-tunnel-remote-any', + '25-tunnel-remote-any', + ) + self.networkctl_check_unit( + 'ip6tnl-slaac', + '25-ip6tnl-tunnel-local-slaac', + '25-ip6tnl-tunnel-local-slaac', + ) + self.networkctl_check_unit( + 'ip6tnl-external', + '25-ip6tnl-tunnel-external', + '26-netdev-link-local-addressing-yes', + ) + self.networkctl_check_unit( + 'dummy98', + '12-dummy', + '25-ip6tnl', + ) + self.networkctl_check_unit( + 'veth99', + '25-veth', + '25-ip6tnl-slaac', + ) + self.networkctl_check_unit( + 'veth-peer', + '25-veth', + '25-ipv6-prefix', + ) output = check_output('ip -d link show ip6tnl99') print(output) - self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98', output) + self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2a00:ffde:4567:edde::4987 dev dummy98', output) # fmt: skip output = check_output('ip -d link show ip6tnl98') print(output) self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local any dev dummy98', output) @@ -2861,7 +3178,7 @@ def _test_ip6tnl_tunnel(self, mode): self.assertIn('ip6tnl external ', output) output = check_output('ip -d link show ip6tnl-slaac') print(output) - self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2002:da8:1:0:1034:56ff:fe78:9abc dev veth99', output) + self.assertIn(f'ip6tnl {mode} remote 2001:473:fece:cafe::5179 local 2002:da8:1:0:1034:56ff:fe78:9abc dev veth99', output) # fmt: skip output = check_output('ip -6 address show veth99') print(output) @@ -2876,7 +3193,8 @@ def _test_ip6tnl_tunnel(self, mode): '25-ip6tnl-tunnel-local-any.netdev', '25-ip6tnl-tunnel-remote-any.netdev', '25-ip6tnl-tunnel-local-slaac.netdev', - '25-ip6tnl-tunnel-external.netdev') + '25-ip6tnl-tunnel-external.netdev', + ) networkctl_reload() self.wait_online( 'ip6tnl99:routable', @@ -2886,7 +3204,8 @@ def _test_ip6tnl_tunnel(self, mode): 'ip6tnl-external:degraded', 'dummy98:degraded', 'veth99:routable', - 'veth-peer:degraded') + 'veth-peer:degraded', + ) def test_ip6tnl_tunnel(self): first = True @@ -2901,24 +3220,39 @@ def test_ip6tnl_tunnel(self): self._test_ip6tnl_tunnel(mode) def _test_sit_tunnel(self, mode): - copy_network_unit('12-dummy.netdev', '25-sit.network', - '25-sit-tunnel.netdev', '25-tunnel.network', - '25-sit-tunnel-local-any.netdev', '25-tunnel-local-any.network', - '25-sit-tunnel-remote-any.netdev', '25-tunnel-remote-any.network', - '25-sit-tunnel-any-any.netdev', '25-tunnel-any-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-sit.network', + '25-sit-tunnel.netdev', + '25-tunnel.network', + '25-sit-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + '25-sit-tunnel-remote-any.netdev', + '25-tunnel-remote-any.network', + '25-sit-tunnel-any-any.netdev', + '25-tunnel-any-any.network', + ) if mode: - for netdev in ['25-sit-tunnel.netdev', - '25-sit-tunnel-local-any.netdev', - '25-sit-tunnel-remote-any.netdev', - '25-sit-tunnel-any-any.netdev']: + for netdev in [ + '25-sit-tunnel.netdev', + '25-sit-tunnel-local-any.netdev', + '25-sit-tunnel-remote-any.netdev', + '25-sit-tunnel-any-any.netdev', + ]: with open(os.path.join(network_unit_dir, netdev), mode='a', encoding='utf-8') as f: f.write(f'[Tunnel]\nMode={mode}\n') else: - mode = 'ip6ip' # kernel default + mode = 'ip6ip' # kernel default start_networkd() - self.wait_online('sittun99:routable', 'sittun98:routable', 'sittun97:routable', 'sittun96:routable', 'dummy98:degraded') + self.wait_online( + 'sittun99:routable', + 'sittun98:routable', + 'sittun97:routable', + 'sittun96:routable', + 'dummy98:degraded', + ) self.networkctl_check_unit('sittun99', '25-sit-tunnel', '25-tunnel') self.networkctl_check_unit('sittun98', '25-sit-tunnel-local-any', '25-tunnel-local-any') self.networkctl_check_unit('sittun97', '25-sit-tunnel-remote-any', '25-tunnel-remote-any') @@ -2942,14 +3276,16 @@ def _test_sit_tunnel(self, mode): '25-sit-tunnel.netdev', '25-sit-tunnel-local-any.netdev', '25-sit-tunnel-remote-any.netdev', - '25-sit-tunnel-any-any.netdev') + '25-sit-tunnel-any-any.netdev', + ) networkctl_reload() self.wait_online( 'sittun99:routable', 'sittun98:routable', 'sittun97:routable', 'sittun96:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_sit_tunnel(self): first = True @@ -2964,8 +3300,12 @@ def test_sit_tunnel(self): self._test_sit_tunnel(mode) def test_isatap_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-isatap.network', - '25-isatap-tunnel.netdev', '25-tunnel.network') + copy_network_unit( + '12-dummy.netdev', + '25-isatap.network', + '25-isatap-tunnel.netdev', + '25-tunnel.network', + ) start_networkd() self.wait_online('isataptun99:routable', 'dummy98:degraded') self.networkctl_check_unit('isataptun99', '25-isatap-tunnel', '25-tunnel') @@ -2973,15 +3313,19 @@ def test_isatap_tunnel(self): output = check_output('ip -d link show isataptun99') print(output) - self.assertRegex(output, "isatap ") + self.assertRegex(output, 'isatap ') touch_network_unit('25-isatap-tunnel.netdev') networkctl_reload() self.wait_online('isataptun99:routable', 'dummy98:degraded') def test_6rd_tunnel(self): - copy_network_unit('12-dummy.netdev', '25-6rd.network', - '25-6rd-tunnel.netdev', '25-tunnel.network') + copy_network_unit( + '12-dummy.netdev', + '25-6rd.network', + '25-6rd-tunnel.netdev', + '25-tunnel.network', + ) start_networkd() self.wait_online('sittun99:routable', 'dummy98:degraded') self.networkctl_check_unit('sittun99', '25-6rd-tunnel', '25-tunnel') @@ -2995,11 +3339,15 @@ def test_6rd_tunnel(self): networkctl_reload() self.wait_online('sittun99:routable', 'dummy98:degraded') - @expectedFailureIfERSPANv0IsNotSupported() def test_erspan_tunnel_v0(self): - copy_network_unit('12-dummy.netdev', '25-erspan.network', - '25-erspan0-tunnel.netdev', '25-tunnel.network', - '25-erspan0-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-erspan.network', + '25-erspan0-tunnel.netdev', + '25-tunnel.network', + '25-erspan0-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('erspan99:routable', 'erspan98:routable', 'dummy98:degraded') self.networkctl_check_unit('erspan99', '25-erspan0-tunnel', '25-tunnel') @@ -3029,17 +3377,24 @@ def test_erspan_tunnel_v0(self): touch_network_unit( '25-erspan0-tunnel.netdev', - '25-erspan0-tunnel-local-any.netdev') + '25-erspan0-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'erspan99:routable', 'erspan98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_erspan_tunnel_v1(self): - copy_network_unit('12-dummy.netdev', '25-erspan.network', - '25-erspan1-tunnel.netdev', '25-tunnel.network', - '25-erspan1-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-erspan.network', + '25-erspan1-tunnel.netdev', + '25-tunnel.network', + '25-erspan1-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('erspan99:routable', 'erspan98:routable', 'dummy98:degraded') self.networkctl_check_unit('erspan99', '25-erspan1-tunnel', '25-tunnel') @@ -3071,18 +3426,24 @@ def test_erspan_tunnel_v1(self): touch_network_unit( '25-erspan1-tunnel.netdev', - '25-erspan1-tunnel-local-any.netdev') + '25-erspan1-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'erspan99:routable', 'erspan98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) - @expectedFailureIfERSPANv2IsNotSupported() def test_erspan_tunnel_v2(self): - copy_network_unit('12-dummy.netdev', '25-erspan.network', - '25-erspan2-tunnel.netdev', '25-tunnel.network', - '25-erspan2-tunnel-local-any.netdev', '25-tunnel-local-any.network') + copy_network_unit( + '12-dummy.netdev', + '25-erspan.network', + '25-erspan2-tunnel.netdev', + '25-tunnel.network', + '25-erspan2-tunnel-local-any.netdev', + '25-tunnel-local-any.network', + ) start_networkd() self.wait_online('erspan99:routable', 'erspan98:routable', 'dummy98:degraded') self.networkctl_check_unit('erspan99', '25-erspan2-tunnel', '25-tunnel') @@ -3114,32 +3475,52 @@ def test_erspan_tunnel_v2(self): touch_network_unit( '25-erspan2-tunnel.netdev', - '25-erspan2-tunnel-local-any.netdev') + '25-erspan2-tunnel-local-any.netdev', + ) networkctl_reload() self.wait_online( 'erspan99:routable', 'erspan98:routable', - 'dummy98:degraded') + 'dummy98:degraded', + ) def test_tunnel_independent(self): - copy_network_unit('25-ipip-tunnel-independent.netdev', '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '25-ipip-tunnel-independent.netdev', + '26-netdev-link-local-addressing-yes.network', + ) start_networkd() self.wait_online('ipiptun99:carrier') - self.networkctl_check_unit('ipiptun99', '25-ipip-tunnel-independent', '26-netdev-link-local-addressing-yes') + self.networkctl_check_unit( + 'ipiptun99', + '25-ipip-tunnel-independent', + '26-netdev-link-local-addressing-yes', + ) def test_tunnel_independent_loopback(self): - copy_network_unit('25-ipip-tunnel-independent-loopback.netdev', '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '25-ipip-tunnel-independent-loopback.netdev', + '26-netdev-link-local-addressing-yes.network', + ) start_networkd() self.wait_online('ipiptun99:carrier') - self.networkctl_check_unit('ipiptun99', '25-ipip-tunnel-independent-loopback', '26-netdev-link-local-addressing-yes') + self.networkctl_check_unit( + 'ipiptun99', + '25-ipip-tunnel-independent-loopback', + '26-netdev-link-local-addressing-yes', + ) @expectedFailureIfModuleIsNotAvailable('xfrm_interface') def test_xfrm(self): - copy_network_unit('12-dummy.netdev', '25-xfrm.network', - '25-xfrm.netdev', '25-xfrm-independent.netdev', - '26-netdev-link-local-addressing-yes.network') + copy_network_unit( + '12-dummy.netdev', + '25-xfrm.network', + '25-xfrm.netdev', + '25-xfrm-independent.netdev', + '26-netdev-link-local-addressing-yes.network', + ) start_networkd() self.wait_online('dummy98:degraded', 'xfrm98:degraded', 'xfrm99:degraded') @@ -3163,12 +3544,23 @@ def test_xfrm(self): @expectedFailureIfModuleIsNotAvailable('fou') def test_fou(self): - copy_network_unit('25-fou-ipproto-ipip.netdev', '25-fou-ipproto-gre.netdev', - '25-fou-ipip.netdev', '25-fou-sit.netdev', - '25-fou-gre.netdev', '25-fou-gretap.netdev') + copy_network_unit( + '25-fou-ipproto-ipip.netdev', + '25-fou-ipproto-gre.netdev', + '25-fou-ipip.netdev', + '25-fou-sit.netdev', + '25-fou-gre.netdev', + '25-fou-gretap.netdev', + ) start_networkd() - self.wait_online('ipiptun96:off', 'sittun96:off', 'gretun96:off', 'gretap96:off', setup_state='unmanaged') + self.wait_online( + 'ipiptun96:off', + 'sittun96:off', + 'gretun96:off', + 'gretap96:off', + setup_state='unmanaged', + ) self.networkctl_check_unit('ipiptun96', '25-fou-ipip') self.networkctl_check_unit('sittun96', '25-fou-sit') self.networkctl_check_unit('gretun96', '25-fou-gre') @@ -3193,25 +3585,52 @@ def test_fou(self): self.assertRegex(output, 'encap fou encap-sport auto encap-dport 55556') touch_network_unit( - '25-fou-ipproto-ipip.netdev', '25-fou-ipproto-gre.netdev', - '25-fou-ipip.netdev', '25-fou-sit.netdev', - '25-fou-gre.netdev', '25-fou-gretap.netdev') + '25-fou-ipproto-ipip.netdev', + '25-fou-ipproto-gre.netdev', + '25-fou-ipip.netdev', + '25-fou-sit.netdev', + '25-fou-gre.netdev', + '25-fou-gretap.netdev', + ) networkctl_reload() - self.wait_online('ipiptun96:off', 'sittun96:off', 'gretun96:off', 'gretap96:off', setup_state='unmanaged') + self.wait_online( + 'ipiptun96:off', + 'sittun96:off', + 'gretun96:off', + 'gretap96:off', + setup_state='unmanaged', + ) def test_vxlan(self): - copy_network_unit('11-dummy.netdev', '25-vxlan-test1.network', - '25-vxlan.netdev', '25-vxlan.network', - '25-vxlan-ipv6.netdev', '25-vxlan-ipv6.network', - '25-vxlan-independent.netdev', '26-netdev-link-local-addressing-yes.network', - '25-veth.netdev', '25-vxlan-veth99.network', '25-ipv6-prefix.network', - '25-vxlan-local-slaac.netdev', '25-vxlan-local-slaac.network', - '25-vxlan-external.netdev', '25-vxlan-external.network') - start_networkd() - - self.wait_online('test1:degraded', 'veth99:routable', 'veth-peer:degraded', - 'vxlan99:degraded', 'vxlan98:degraded', 'vxlan97:degraded', 'vxlan-slaac:degraded', - 'vxlan-external:degraded') + copy_network_unit( + '11-dummy.netdev', + '25-vxlan-test1.network', + '25-vxlan.netdev', + '25-vxlan.network', + '25-vxlan-ipv6.netdev', + '25-vxlan-ipv6.network', + '25-vxlan-independent.netdev', + '26-netdev-link-local-addressing-yes.network', + '25-veth.netdev', + '25-vxlan-veth99.network', + '25-ipv6-prefix.network', + '25-vxlan-local-slaac.netdev', + '25-vxlan-local-slaac.network', + '25-vxlan-external.netdev', + '25-vxlan-external.network', + ) + start_networkd() + + self.wait_online( + 'test1:degraded', + 'veth99:routable', + 'veth-peer:degraded', + 'vxlan99:degraded', + 'vxlan98:degraded', + 'vxlan97:degraded', + 'vxlan-slaac:degraded', + 'vxlan-external:degraded', + ) self.networkctl_check_unit('test1', '11-dummy', '25-vxlan-test1') self.networkctl_check_unit('veth99', '25-veth', '25-vxlan-veth99') self.networkctl_check_unit('veth-peer', '25-veth', '25-ipv6-prefix') @@ -3270,10 +3689,18 @@ def test_vxlan(self): self.assertIn('external', output) self.assertIn('vnifilter', output) - @unittest.skipUnless(compare_kernel_version("6"), reason="Causes kernel panic on unpatched kernels: https://bugzilla.kernel.org/show_bug.cgi?id=208315") + @unittest.skipUnless( + compare_kernel_version('6'), + reason='Causes kernel panic on unpatched kernels: https://bugzilla.kernel.org/show_bug.cgi?id=208315', + ) def test_macsec(self): - copy_network_unit('25-macsec.netdev', '25-macsec.network', '25-macsec.key', - '26-macsec.network', '12-dummy.netdev') + copy_network_unit( + '25-macsec.netdev', + '25-macsec.network', + '25-macsec.key', + '26-macsec.network', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:degraded', 'macsec99:routable') @@ -3328,7 +3755,7 @@ def test_ifb(self): networkctl_reload() self.wait_online('ifb99:degraded') - @unittest.skipUnless(os.cpu_count() >= 2, reason="CPU count should be >= 2 to pass this test") + @unittest.skipUnless(os.cpu_count() >= 2, reason='CPU count should be >= 2 to pass this test') def test_rps_cpu_1(self): copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-1.link') start_networkd() @@ -3340,7 +3767,7 @@ def test_rps_cpu_1(self): print(output) self.assertEqual(int(output.replace(',', ''), base=16), 2) - @unittest.skipUnless(os.cpu_count() >= 2, reason="CPU count should be >= 2 to pass this test") + @unittest.skipUnless(os.cpu_count() >= 2, reason='CPU count should be >= 2 to pass this test') def test_rps_cpu_0_1(self): copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-0-1.link') start_networkd() @@ -3352,7 +3779,7 @@ def test_rps_cpu_0_1(self): print(output) self.assertEqual(int(output.replace(',', ''), base=16), 3) - @unittest.skipUnless(os.cpu_count() >= 4, reason="CPU count should be >= 4 to pass this test") + @unittest.skipUnless(os.cpu_count() >= 4, reason='CPU count should be >= 4 to pass this test') def test_rps_cpu_multi(self): copy_network_unit('12-dummy.netdev', '12-dummy.network', '25-rps-cpu-multi.link') start_networkd() @@ -3388,7 +3815,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '25-rps-cpu-all') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('25-rps-cpu-all.link') # disable @@ -3406,7 +3833,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '25-rps-cpu-all') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('25-rps-cpu-all.link') # empty -> unchanged @@ -3415,7 +3842,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '24-rps-cpu-empty') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('24-rps-cpu-empty.link') # 0, then empty -> unchanged @@ -3424,7 +3851,7 @@ def test_rps_cpu(self): self.networkctl_check_unit('dummy98', '12-dummy', '12-dummy', '25-rps-cpu-0-empty') output = check_output('cat /sys/class/net/dummy98/queues/rx-0/rps_cpus') print(output) - self.assertEqual(f"{int(output.replace(',', ''), base=16):x}", f'{(1 << cpu_count) - 1:x}') + self.assertEqual(f'{int(output.replace(",", ""), base=16):x}', f'{(1 << cpu_count) - 1:x}') remove_network_unit('25-rps-cpu-0-empty.link') # 0, then invalid -> 0 @@ -3445,8 +3872,8 @@ def test_rps_cpu(self): self.assertEqual(int(output.replace(',', ''), base=16), 1) remove_network_unit('24-rps-cpu-invalid.link') -class NetworkdL2TPTests(unittest.TestCase, Utilities): +class NetworkdL2TPTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -3455,8 +3882,12 @@ def tearDown(self): @expectedFailureIfModuleIsNotAvailable('l2tp_eth', 'l2tp_netlink') def test_l2tp_udp(self): - copy_network_unit('11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-udp.netdev', '25-l2tp.network') + copy_network_unit( + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-udp.netdev', + '25-l2tp.network', + ) start_networkd() self.wait_online('test1:routable', 'l2tp-ses1:degraded', 'l2tp-ses2:degraded') @@ -3466,34 +3897,41 @@ def test_l2tp_udp(self): output = check_output('ip l2tp show tunnel tunnel_id 10') print(output) - self.assertRegex(output, "Tunnel 10, encap UDP") - self.assertRegex(output, "From 192.168.30.100 to 192.168.30.101") - self.assertRegex(output, "Peer tunnel 11") - self.assertRegex(output, "UDP source / dest ports: 3000/4000") - self.assertRegex(output, "UDP checksum: enabled") + self.assertRegex(output, 'Tunnel 10, encap UDP') + self.assertRegex(output, 'From 192.168.30.100 to 192.168.30.101') + self.assertRegex(output, 'Peer tunnel 11') + self.assertRegex(output, 'UDP source / dest ports: 3000/4000') + self.assertRegex(output, 'UDP checksum: enabled') output = check_output('ip l2tp show session tid 10 session_id 15') print(output) - self.assertRegex(output, "Session 15 in tunnel 10") - self.assertRegex(output, "Peer session 16, tunnel 11") - self.assertRegex(output, "interface name: l2tp-ses1") + self.assertRegex(output, 'Session 15 in tunnel 10') + self.assertRegex(output, 'Peer session 16, tunnel 11') + self.assertRegex(output, 'interface name: l2tp-ses1') output = check_output('ip l2tp show session tid 10 session_id 17') print(output) - self.assertRegex(output, "Session 17 in tunnel 10") - self.assertRegex(output, "Peer session 18, tunnel 11") - self.assertRegex(output, "interface name: l2tp-ses2") + self.assertRegex(output, 'Session 17 in tunnel 10') + self.assertRegex(output, 'Peer session 18, tunnel 11') + self.assertRegex(output, 'interface name: l2tp-ses2') touch_network_unit( - '11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-udp.netdev', '25-l2tp.network') + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-udp.netdev', + '25-l2tp.network', + ) networkctl_reload() self.wait_online('test1:routable', 'l2tp-ses1:degraded', 'l2tp-ses2:degraded') @expectedFailureIfModuleIsNotAvailable('l2tp_eth', 'l2tp_ip', 'l2tp_netlink') def test_l2tp_ip(self): - copy_network_unit('11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-ip.netdev', '25-l2tp.network') + copy_network_unit( + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-ip.netdev', + '25-l2tp.network', + ) start_networkd() self.wait_online('test1:routable', 'l2tp-ses3:degraded', 'l2tp-ses4:degraded') @@ -3503,30 +3941,33 @@ def test_l2tp_ip(self): output = check_output('ip l2tp show tunnel tunnel_id 10') print(output) - self.assertRegex(output, "Tunnel 10, encap IP") - self.assertRegex(output, "From 192.168.30.100 to 192.168.30.101") - self.assertRegex(output, "Peer tunnel 12") + self.assertRegex(output, 'Tunnel 10, encap IP') + self.assertRegex(output, 'From 192.168.30.100 to 192.168.30.101') + self.assertRegex(output, 'Peer tunnel 12') output = check_output('ip l2tp show session tid 10 session_id 25') print(output) - self.assertRegex(output, "Session 25 in tunnel 10") - self.assertRegex(output, "Peer session 26, tunnel 12") - self.assertRegex(output, "interface name: l2tp-ses3") + self.assertRegex(output, 'Session 25 in tunnel 10') + self.assertRegex(output, 'Peer session 26, tunnel 12') + self.assertRegex(output, 'interface name: l2tp-ses3') output = check_output('ip l2tp show session tid 10 session_id 27') print(output) - self.assertRegex(output, "Session 27 in tunnel 10") - self.assertRegex(output, "Peer session 28, tunnel 12") - self.assertRegex(output, "interface name: l2tp-ses4") + self.assertRegex(output, 'Session 27 in tunnel 10') + self.assertRegex(output, 'Peer session 28, tunnel 12') + self.assertRegex(output, 'interface name: l2tp-ses4') touch_network_unit( - '11-dummy.netdev', '25-l2tp-dummy.network', - '25-l2tp-ip.netdev', '25-l2tp.network') + '11-dummy.netdev', + '25-l2tp-dummy.network', + '25-l2tp-ip.netdev', + '25-l2tp.network', + ) networkctl_reload() self.wait_online('test1:routable', 'l2tp-ses3:degraded', 'l2tp-ses4:degraded') -class NetworkdNetworkTests(unittest.TestCase, Utilities): +class NetworkdNetworkTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -3545,34 +3986,34 @@ def test_ID_NET_MANAGED_BY(self): self.wait_online('test1:off', setup_state='unmanaged') def verify_address_static( - self, - label1: str, - label2: str, - label3: str, - broadcast1: str, - broadcast2: str, - broadcast3: str, - peer1: str, - peer2: str, - peer3: str, - peer4: str, - peer5: str, - peer6: str, - scope1: str, - scope2: str, - deprecated1: str, - deprecated2: str, - deprecated3: str, - deprecated4: str, - route_metric: int, - flag1: str, - flag2: str, - flag3: str, - flag4: str, - ip4_null_16: str, - ip4_null_24: str, - ip6_null_73: str, - ip6_null_74: str, + self, + label1: str, + label2: str, + label3: str, + broadcast1: str, + broadcast2: str, + broadcast3: str, + peer1: str, + peer2: str, + peer3: str, + peer4: str, + peer5: str, + peer6: str, + scope1: str, + scope2: str, + deprecated1: str, + deprecated2: str, + deprecated3: str, + deprecated4: str, + route_metric: int, + flag1: str, + flag2: str, + flag3: str, + flag4: str, + ip4_null_16: str, + ip4_null_24: str, + ip6_null_73: str, + ip6_null_74: str, ): output = check_output('ip address show dev dummy98') print(output) @@ -3614,12 +4055,12 @@ def verify_address_static( self.assertIn(f'inet6 2001:db8:0:f104::2/64 scope global{deprecated4}', output) # route metric - self.assertRegex(output, rf'inet 10.8.1.1/24 (metric {route_metric} |)brd 10.8.1.255 scope global dummy98') + self.assertRegex(output, rf'inet 10.8.1.1/24 (metric {route_metric} |)brd 10.8.1.255 scope global dummy98') # fmt: skip self.assertRegex(output, rf'inet6 2001:db8:0:f105::1/64 (metric {route_metric} |)scope global') output_route = check_output('ip -4 route show dev dummy98 10.8.1.0/24') print(output_route) - self.assertIn(f'10.8.1.0/24 proto kernel scope link src 10.8.1.1 metric {route_metric}', output_route) + self.assertIn(f'10.8.1.0/24 proto kernel scope link src 10.8.1.1 metric {route_metric}', output_route) # fmt: skip output_route = check_output('ip -6 route show dev dummy98 2001:db8:0:f105::/64') print(output_route) @@ -3633,9 +4074,9 @@ def verify_address_static( # null address self.assertTrue(ip4_null_16.endswith('.0.1')) - prefix16 = ip4_null_16[:-len('.0.1')] + prefix16 = ip4_null_16[: -len('.0.1')] self.assertTrue(ip4_null_24.endswith('.1')) - prefix24 = ip4_null_24[:-len('.1')] + prefix24 = ip4_null_24[: -len('.1')] self.assertIn(f'inet {ip4_null_16}/16 brd {prefix16}.255.255 scope global subnet16', output) self.assertIn(f'inet {ip4_null_24}/24 brd {prefix24}.255 scope global subnet24', output) self.assertIn(f'inet6 {ip6_null_73}/73 scope global', output) @@ -3655,7 +4096,6 @@ def verify_address_static( check_json(networkctl_json()) - @expectedFailureIfKernelReturnsInvalidFlags() def test_address_static(self): copy_network_unit('25-address-static.network', '12-dummy.netdev', copy_dropins=False) self.setup_nftset('addr4', 'ipv4_addr') @@ -3897,9 +4337,14 @@ def test_address_peer_ipv4(self): @expectedFailureIfModuleIsNotAvailable('vrf') def test_prefix_route(self): - copy_network_unit('25-prefix-route-with-vrf.network', '12-dummy.netdev', - '25-prefix-route-without-vrf.network', '11-dummy.netdev', - '25-vrf.netdev', '25-vrf.network') + copy_network_unit( + '25-prefix-route-with-vrf.network', + '12-dummy.netdev', + '25-prefix-route-without-vrf.network', + '11-dummy.netdev', + '25-vrf.netdev', + '25-vrf.network', + ) for trial in range(2): if trial == 0: start_networkd() @@ -3924,7 +4369,7 @@ def test_prefix_route(self): if trial == 0: # Kernel's bug? self.assertRegex(output, 'local fdde:11:22::1 proto kernel metric 0 pref medium') - #self.assertRegex(output, 'fdde:11:22::1 proto kernel metric 256 pref medium') + # self.assertRegex(output, 'fdde:11:22::1 proto kernel metric 256 pref medium') self.assertRegex(output, 'local fdde:11:33::1 proto kernel metric 0 pref medium') self.assertRegex(output, 'fdde:11:33::/64 proto kernel metric 256 pref medium') self.assertRegex(output, 'local fdde:11:44::1 proto kernel metric 0 pref medium') @@ -3998,7 +4443,7 @@ def test_configure_without_carrier_yes_ignore_carrier_loss_no(self): carrier_map = {'on': '1', 'off': '0'} routable_map = {'on': 'routable', 'off': 'no-carrier'} - for (carrier, have_config) in [('off', True), ('on', True), ('off', False)]: + for carrier, have_config in [('off', True), ('on', True), ('off', False)]: with self.subTest(carrier=carrier, have_config=have_config): if carrier_map[carrier] != read_link_attr('test1', 'carrier'): check_output(f'ip link set dev test1 carrier {carrier}') @@ -4019,7 +4464,7 @@ def check_routing_policy_rule_test1(self): output = check_output('ip rule list iif test1 priority 111') print(output) - self.assertRegex(output, r'111: from 192.168.100.18 tos (0x08|throughput) iif test1 oif test1 lookup 7') + self.assertRegex(output, r'111: from 192.168.100.18 tos (0x08|throughput) iif test1 oif test1 lookup 7') # fmt: skip output = check_output('ip -6 rule list iif test1 priority 100') print(output) @@ -4078,7 +4523,7 @@ def check_routing_policy_rule_dummy98(self): output = check_output('ip rule list priority 112') print(output) - self.assertRegex(output, r'112: from 192.168.101.18 tos (0x08|throughput) iif dummy98 oif dummy98 lookup 8') + self.assertRegex(output, r'112: from 192.168.101.18 tos (0x08|throughput) iif dummy98 oif dummy98 lookup 8') # fmt: skip def _test_routing_policy_rule(self, manage_foreign_routes): if not manage_foreign_routes: @@ -4125,8 +4570,12 @@ def test_routing_policy_rule(self): self._test_routing_policy_rule(manage_foreign_routes) def test_routing_policy_rule_restart_and_reconfigure(self): - copy_network_unit('25-routing-policy-rule-test1.network', '11-dummy.netdev', - '25-routing-policy-rule-dummy98.network', '12-dummy.netdev') + copy_network_unit( + '25-routing-policy-rule-test1.network', + '11-dummy.netdev', + '25-routing-policy-rule-dummy98.network', + '12-dummy.netdev', + ) # For #11280 and #34068. @@ -4213,7 +4662,8 @@ def test_routing_policy_rule_manual(self): # For issue #36244. copy_network_unit( '11-dummy.netdev', - '25-routing-policy-rule-manual.network') + '25-routing-policy-rule-manual.network', + ) start_networkd() self.wait_operstate('test1', operstate='off', setup_state='configuring', setup_timeout=20) @@ -4248,7 +4698,6 @@ def test_routing_policy_rule_manual(self): else: self.assertFalse(True) - @expectedFailureIfRoutingPolicyPortRangeIsNotAvailable() def test_routing_policy_rule_port_range(self): copy_network_unit('25-fibrule-port-range.network', '11-dummy.netdev') start_networkd() @@ -4263,7 +4712,6 @@ def test_routing_policy_rule_port_range(self): self.assertRegex(output, 'ipproto (tcp|ipproto-6) ') self.assertIn('lookup 7 ', output) - @expectedFailureIfRoutingPolicyIPProtoIsNotAvailable() def test_routing_policy_rule_invert(self): copy_network_unit('25-fibrule-invert.network', '11-dummy.netdev') start_networkd() @@ -4277,7 +4725,6 @@ def test_routing_policy_rule_invert(self): self.assertRegex(output, 'ipproto (tcp|ipproto-6) ') self.assertIn('lookup 7 ', output) - @expectedFailureIfRoutingPolicyL3MasterDeviceIsNotAvailable() def test_routing_policy_rule_l3mdev(self): copy_network_unit('25-fibrule-l3mdev.network', '11-dummy.netdev') start_networkd() @@ -4288,7 +4735,6 @@ def test_routing_policy_rule_l3mdev(self): self.assertIn('1500: from all lookup [l3mdev-table]', output) self.assertIn('2000: from all lookup [l3mdev-table] unreachable', output) - @expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable() def test_routing_policy_rule_uidrange(self): copy_network_unit('25-fibrule-uidrange.network', '11-dummy.netdev') start_networkd() @@ -4389,24 +4835,21 @@ def _check_route_static(self, test1_is_managed: bool): print('### ip route show 192.168.10.2') output = check_output('ip route show 192.168.10.2') print(output) - # old ip command does not show IPv6 gateways... self.assertIn('192.168.10.2 proto static', output) - self.assertIn('nexthop', output) - self.assertIn('dev test1 weight 20', output) - self.assertIn('dev test1 weight 30', output) - self.assertIn('dev dummy98 weight 10', output) - self.assertIn('dev dummy98 weight 5', output) + self.assertIn('nexthop via inet6 2001:1234:5:6fff:ff:ff:ff:ff dev test1 weight 20', output) + self.assertIn('nexthop via inet6 2001:1234:5:7fff:ff:ff:ff:ff dev test1 weight 30', output) + self.assertIn('nexthop via inet6 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98 weight 10', output) + self.assertIn('nexthop via inet6 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98 weight 5', output) print('### ip -6 route show 2001:1234:5:bfff:ff:ff:ff:ff') output = check_output('ip -6 route show 2001:1234:5:bfff:ff:ff:ff:ff') print(output) - # old ip command does not show 'nexthop' keyword and weight... self.assertIn('2001:1234:5:bfff:ff:ff:ff:ff', output) if test1_is_managed: - self.assertIn('via 2001:1234:5:6fff:ff:ff:ff:ff dev test1', output) - self.assertIn('via 2001:1234:5:7fff:ff:ff:ff:ff dev test1', output) - self.assertIn('via 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98', output) - self.assertIn('via 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98', output) + self.assertIn('nexthop via 2001:1234:5:6fff:ff:ff:ff:ff dev test1 weight 20', output) + self.assertIn('nexthop via 2001:1234:5:7fff:ff:ff:ff:ff dev test1 weight 30', output) + self.assertIn('nexthop via 2001:1234:5:8fff:ff:ff:ff:ff dev dummy98 weight 10', output) + self.assertIn('nexthop via 2001:1234:5:9fff:ff:ff:ff:ff dev dummy98 weight 5', output) print('### ip route show 192.168.20.0/24') output = check_output('ip route show 192.168.20.0/24') @@ -4454,8 +4897,12 @@ def _test_route_static(self, manage_foreign_routes): if not manage_foreign_routes: copy_networkd_conf_dropin('networkd-manage-foreign-routes-no.conf') - copy_network_unit('25-route-static.network', '12-dummy.netdev', - '25-route-static-test1.network', '11-dummy.netdev') + copy_network_unit( + '25-route-static.network', + '12-dummy.netdev', + '25-route-static-test1.network', + '11-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:routable', 'test1:routable') self._check_route_static(test1_is_managed=True) @@ -4512,7 +4959,8 @@ def test_route_static_issue_35047(self): '25-route-static-issue-35047.network', '25-route-static-issue-35047.network.d/step1.conf', '12-dummy.netdev', - copy_dropins=False) + copy_dropins=False, + ) start_networkd() self.wait_online('dummy98:routable') @@ -4524,9 +4972,27 @@ def test_route_static_issue_35047(self): self.assertIn('198.51.100.0/24 via 192.0.2.2 proto static', output) check_output('ip link set dev dummy98 down') - self.wait_route_dropped('dummy98', '192.0.2.2 proto kernel scope link src 192.0.2.1', ipv='-4', table='all', timeout_sec=10) - self.wait_route_dropped('dummy98', 'local 192.0.2.1 table local proto kernel scope host src 192.0.2.1', ipv='-4', table='all', timeout_sec=10) - self.wait_route_dropped('dummy98', '198.51.100.0/24 via 192.0.2.2 proto static', ipv='-4', table='all', timeout_sec=10) + self.wait_route_dropped( + 'dummy98', + '192.0.2.2 proto kernel scope link src 192.0.2.1', + ipv='-4', + table='all', + timeout_sec=10, + ) + self.wait_route_dropped( + 'dummy98', + 'local 192.0.2.1 table local proto kernel scope host src 192.0.2.1', + ipv='-4', + table='all', + timeout_sec=10, + ) + self.wait_route_dropped( + 'dummy98', + '198.51.100.0/24 via 192.0.2.2 proto static', + ipv='-4', + table='all', + timeout_sec=10, + ) print('### ip -4 route show table all dev dummy98') output = check_output('ip -4 route show table all dev dummy98') @@ -4565,6 +5031,7 @@ def test_route_static_issue_37714(self): print('### ip -4 route show table all dev dummy98') output = check_output('ip -4 route show table all dev dummy98') print(output) + # fmt: off self.assertIn('default via 192.168.0.193 table 249 proto static src 192.168.0.227 metric 128 onlink', output) self.assertIn('192.168.0.192/26 table 249 proto static scope link src 192.168.0.227 metric 128', output) self.assertIn('10.1.2.2 via 192.168.0.193 proto static src 192.168.0.227 metric 128 onlink', output) @@ -4572,16 +5039,19 @@ def test_route_static_issue_37714(self): self.assertIn('192.168.0.193 proto static scope link src 192.168.0.227 metric 128', output) self.assertIn('local 192.168.0.227 table local proto kernel scope host src 192.168.0.227', output) self.assertIn('broadcast 192.168.0.255 table local proto kernel scope link src 192.168.0.227', output) + # fmt: on print('### ip -6 route show table all dev dummy98') output = check_output('ip -6 route show table all dev dummy98') print(output) + # fmt: off self.assertIn('2000:f00::/64 table 249 proto static src 2000:f00::227 metric 128 pref medium', output) self.assertIn('default via 2000:f00::1 table 249 proto static src 2000:f00::227 metric 128 onlink pref medium', output) self.assertIn('fe80::/64 proto kernel metric 256 pref medium', output) self.assertIn('local 2000:f00::227 table local proto kernel metric 0 pref medium', output) - self.assertRegex(output, 'local fe80:[a-f0-9:]* table local proto kernel metric 0 pref medium', output) + self.assertRegex(output, 'local fe80:[a-f0-9:]* table local proto kernel metric 0 pref medium') self.assertIn('multicast ff00::/8 table local proto kernel metric 256 pref medium', output) + # fmt: on def test_route_via_ipv6(self): copy_network_unit('25-route-via-ipv6.network', '12-dummy.netdev') @@ -4624,8 +5094,12 @@ def test_route_congctl(self): @expectedFailureIfModuleIsNotAvailable('vrf') def test_route_vrf(self): - copy_network_unit('25-route-vrf.network', '12-dummy.netdev', - '25-vrf.netdev', '25-vrf.network') + copy_network_unit( + '25-route-vrf.network', + '12-dummy.netdev', + '25-vrf.netdev', + '25-vrf.network', + ) start_networkd() self.wait_online('dummy98:routable', 'vrf99:carrier') @@ -4676,7 +5150,12 @@ def test_ip_route_ipv6_src_route(self): # a dummy device does not make the addresses go through tentative state, so we # reuse a bond from an earlier test, which does make the addresses go through # tentative state, and do our test on that - copy_network_unit('23-active-slave.network', '25-route-ipv6-src.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev') + copy_network_unit( + '23-active-slave.network', + '25-route-ipv6-src.network', + '25-bond-active-backup-slave.netdev', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bond199:routable') @@ -4697,7 +5176,7 @@ def test_route_preferred_source_with_existing_address(self): output = check_output('ip -6 route list dev dummy98') print(output) - self.assertIn('abcd::/16 via 2001:1234:56:8f63::1:1 proto static src 2001:1234:56:8f63::1', output) + self.assertIn('abcd::/16 via 2001:1234:56:8f63::1:1 proto static src 2001:1234:56:8f63::1', output) # fmt: skip output = check_output('ip -4 route list dev dummy98') print(output) @@ -4741,7 +5220,7 @@ def test_ipv6_proxy_ndp(self): self.assertRegex(output, f'2607:5300:203:5215:{i}::1 *proxy') def test_ipv6_neigh_retrans_time(self): - link='test25' + link = 'test25' copy_network_unit('25-dummy.netdev', '25-dummy.network') start_networkd() @@ -4791,10 +5270,16 @@ def test_ipv6_neigh_retrans_time(self): remove_network_unit('25-ipv6-neigh-retrans-time-4s.network') def test_neighbor(self): - copy_network_unit('12-dummy.netdev', '25-neighbor-dummy.network', '25-neighbor-dummy.network.d/10-step1.conf', - '25-gre-tunnel-remote-any.netdev', '25-neighbor-ip.network', - '25-ip6gre-tunnel-remote-any.netdev', '25-neighbor-ipv6.network', - copy_dropins=False) + copy_network_unit( + '12-dummy.netdev', + '25-neighbor-dummy.network', + '25-neighbor-dummy.network.d/10-step1.conf', + '25-gre-tunnel-remote-any.netdev', + '25-neighbor-ip.network', + '25-ip6gre-tunnel-remote-any.netdev', + '25-neighbor-ipv6.network', + copy_dropins=False, + ) start_networkd() self.wait_online('dummy98:degraded', 'gretun97:routable', 'ip6gretun97:routable') @@ -4807,7 +5292,7 @@ def test_neighbor(self): print('### ip neigh list dev ip6gretun97') output = check_output('ip neigh list dev ip6gretun97') print(output) - self.assertRegex(output, '2001:db8:0:f102::17 lladdr 2a:?00:ff:?de:45:?67:ed:?de:[0:]*:49:?88 PERMANENT') + self.assertRegex(output, '2001:db8:0:f102::17 lladdr 2a:?00:ff:?de:45:?67:ed:?de:[0:]*:49:?88 PERMANENT') # fmt: skip self.assertNotIn('2001:db8:0:f102::18', output) print('### ip neigh list dev dummy98') @@ -4838,8 +5323,10 @@ def test_neighbor(self): check_json(networkctl_json()) - remove_network_unit('25-neighbor-dummy.network.d/10-step1.conf', - '25-neighbor-dummy.network.d/10-step2.conf') + remove_network_unit( + '25-neighbor-dummy.network.d/10-step1.conf', + '25-neighbor-dummy.network.d/10-step2.conf', + ) copy_network_unit('25-neighbor-dummy.network.d/10-step3.conf') networkctl_reload() self.wait_online('dummy98:degraded') @@ -4854,8 +5341,12 @@ def test_neighbor(self): self.assertNotIn('2004:da8:1::1', output) def test_link_local_addressing(self): - copy_network_unit('25-link-local-addressing-yes.network', '11-dummy.netdev', - '25-link-local-addressing-no.network', '12-dummy.netdev') + copy_network_unit( + '25-link-local-addressing-yes.network', + '11-dummy.netdev', + '25-link-local-addressing-no.network', + '12-dummy.netdev', + ) start_networkd() self.wait_online('test1:degraded', 'dummy98:carrier') @@ -5077,7 +5568,7 @@ def _test_activation_policy(self, interface, test): start_networkd() always = test.startswith('always') - initial_up = test != 'manual' and not test.endswith('down') # note: default is up + initial_up = test != 'manual' and not test.endswith('down') # note: default is up expect_up = initial_up next_up = not expect_up @@ -5166,7 +5657,7 @@ def test_activation_policy_required_for_online(self): else: self.tearDown() - print(f'### test_activation_policy_required_for_online(policy={policy}, required={required})') + print(f'### test_activation_policy_required_for_online(policy={policy}, required={required})') # fmt: skip with self.subTest(policy=policy, required=required): self._test_activation_policy_required_for_online(policy, required) @@ -5249,20 +5740,28 @@ def test_keep_configuration_on_restart(self): call('ip -6 addr add 2001:db8:9999:f101::15/64 dev unmanaged0') # Wait for all addresses + # fmt: off self.wait_address('unmanaged0', 'inet 10.20.30.40/32', scope='global', ipv='-4', timeout_sec=10) self.wait_address('unmanaged0', 'inet6 2001:db8:9999:f101::15/64', scope='global', ipv='-6', timeout_sec=10) self.wait_address('unmanaged0', 'inet6 fe80::[0-9a-f:]*/64', scope='link', ipv='-6', timeout_sec=10) + # fmt: on # Wait for all routes + # fmt: off self.wait_route('unmanaged0', 'local 10.20.30.40 proto kernel', table='local', ipv='-4', timeout_sec=10) self.wait_route('unmanaged0', 'local fe80::[0-9a-f:]* proto kernel', table='local', ipv='-6', timeout_sec=10) self.wait_route('unmanaged0', 'multicast ff00::/8 proto kernel', table='local', ipv='-6', timeout_sec=10) self.wait_route('unmanaged0', '2001:db8:9999:f101::/64 proto kernel', table='main', ipv='-6', timeout_sec=10) self.wait_route('unmanaged0', 'fe80::/64 proto kernel', table='main', ipv='-6', timeout_sec=10) + # fmt: on # Start `ip monitor` with output to a temporary file with tempfile.TemporaryFile(mode='r+', prefix='ip_monitor_u') as logfile_unmanaged: - process_u = subprocess.Popen(['ip', 'monitor', 'dev', 'unmanaged0'], stdout=logfile_unmanaged, text=True) + process_u = subprocess.Popen( + ['ip', 'monitor', 'dev', 'unmanaged0'], + stdout=logfile_unmanaged, + text=True, + ) start_networkd() self.check_keep_configuration_on_restart() @@ -5406,10 +5905,12 @@ def check_nexthop(self, manage_foreign_nexthops, first): output = check_output('ip -6 route show dev veth99 2001:1234:5:8f62::1') print(output) + # fmt: off if first: self.assertEqual('2001:1234:5:8f62::1 nhid 2 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output) else: self.assertEqual('2001:1234:5:8f62::1 nhid 7 via 2001:1234:5:8f63::2 proto static metric 1024 pref medium', output) + # fmt: on output = check_output('ip route show 10.10.10.13') print(output) @@ -5420,10 +5921,12 @@ def check_nexthop(self, manage_foreign_nexthops, first): output = check_output('ip -6 route show 2001:1234:5:8f62::2') print(output) + # fmt: off if first: self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 7 dev lo proto static metric 1024 pref medium', output) else: self.assertEqual('blackhole 2001:1234:5:8f62::2 nhid 2 dev lo proto static metric 1024 pref medium', output) + # fmt: on output = check_output('ip route show 10.10.10.14') print(output) @@ -5448,8 +5951,13 @@ def _test_nexthop(self, manage_foreign_nexthops): check_output('ip address add 192.168.20.20/24 dev dummy98') check_output('ip nexthop add id 42 via 192.168.20.2 dev dummy98') - copy_network_unit('25-nexthop-1.network', '25-veth.netdev', '25-veth-peer.network', - '12-dummy.netdev', '25-nexthop-dummy-1.network') + copy_network_unit( + '25-nexthop-1.network', + '25-veth.netdev', + '25-veth-peer.network', + '12-dummy.netdev', + '25-nexthop-dummy-1.network', + ) start_networkd() self.check_nexthop(manage_foreign_nexthops, first=True) @@ -5476,8 +5984,10 @@ def _test_nexthop(self, manage_foreign_nexthops): # Of course, networkctl_reconfigure() below is unnecessary in normal operation, but it is intentional # here to test reconfiguring with different .network files does not trigger race. # See also comments in link_drop_requests(). - networkctl_reconfigure('dummy98') # reconfigured with 25-nexthop-dummy-2.network - networkctl_reload() # reconfigured with 25-nexthop-dummy-1.network + # fmt: off + networkctl_reconfigure('dummy98') # reconfigured with 25-nexthop-dummy-2.network + networkctl_reload() # reconfigured with 25-nexthop-dummy-1.network + # fmt: on self.check_nexthop(manage_foreign_nexthops, first=True) @@ -5527,7 +6037,6 @@ def _test_nexthop(self, manage_foreign_nexthops): print(output) self.assertEqual(output, '') - @expectedFailureIfNexthopIsNotAvailable() def test_nexthop(self): first = True for manage_foreign_nexthops in [True, False]: @@ -5540,8 +6049,8 @@ def test_nexthop(self): with self.subTest(manage_foreign_nexthops=manage_foreign_nexthops): self._test_nexthop(manage_foreign_nexthops) -class NetworkdTCTests(unittest.TestCase, Utilities): +class NetworkdTCTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -5592,7 +6101,7 @@ def test_qdisc_codel(self): output = check_output('tc qdisc show dev dummy98') print(output) self.assertRegex(output, 'qdisc codel 33: root') - self.assertRegex(output, 'limit 2000p target 10(.0)?ms ce_threshold 100(.0)?ms interval 50(.0)?ms ecn') + self.assertRegex(output, 'limit 2000p target 10(.0)?ms ce_threshold 100(.0)?ms interval 50(.0)?ms ecn') # fmt: skip @expectedFailureIfModuleIsNotAvailable('sch_drr') def test_qdisc_drr(self): @@ -5644,7 +6153,7 @@ def test_qdisc_fq_codel(self): output = check_output('tc qdisc show dev dummy98') print(output) self.assertRegex(output, 'qdisc fq_codel 34: root') - self.assertRegex(output, 'limit 20480p flows 2048 quantum 1400 target 10(.0)?ms ce_threshold 100(.0)?ms interval 200(.0)?ms memory_limit 64Mb ecn') + self.assertRegex(output, 'limit 20480p flows 2048 quantum 1400 target 10(.0)?ms ce_threshold 100(.0)?ms interval 200(.0)?ms memory_limit 64Mb ecn') # fmt: skip @expectedFailureIfModuleIsNotAvailable('sch_fq_pie') def test_qdisc_fq_pie(self): @@ -5718,8 +6227,12 @@ def test_qdisc_htb_fifo(self): @expectedFailureIfModuleIsNotAvailable('sch_ingress') def test_qdisc_ingress(self): - copy_network_unit('25-qdisc-clsact.network', '12-dummy.netdev', - '25-qdisc-ingress.network', '11-dummy.netdev') + copy_network_unit( + '25-qdisc-clsact.network', + '12-dummy.netdev', + '25-qdisc-ingress.network', + '11-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:routable', 'test1:routable') @@ -5752,8 +6265,12 @@ def test_qdisc_multiq(self): @expectedFailureIfModuleIsNotAvailable('sch_netem') def test_qdisc_netem(self): - copy_network_unit('25-qdisc-netem.network', '12-dummy.netdev', - '25-qdisc-netem-compat.network', '11-dummy.netdev') + copy_network_unit( + '25-qdisc-netem.network', + '12-dummy.netdev', + '25-qdisc-netem-compat.network', + '11-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:routable', 'test1:routable') @@ -5823,7 +6340,7 @@ def test_qdisc_tbf(self): output = check_output('tc qdisc show dev dummy98') print(output) self.assertRegex(output, 'qdisc tbf 35: root') - self.assertRegex(output, 'rate 1Gbit burst 5000b peakrate 100Gbit minburst (987500b|999200b) lat 70(.0)?ms') + self.assertRegex(output, 'rate 1Gbit burst 5000b peakrate 100Gbit minburst (987500b|999200b) lat 70(.0)?ms') # fmt: skip @expectedFailureIfModuleIsNotAvailable('sch_teql') def test_qdisc_teql(self): @@ -5851,13 +6368,13 @@ def test_qdisc_drop(self): self.assertFalse(networkd_is_failed()) check_output('tc qdisc replace dev dummy98 root fq pacing') self.assertFalse(networkd_is_failed()) - check_output('tc qdisc replace dev dummy98 handle 10: root tbf rate 0.5mbit burst 5kb latency 70ms peakrate 1mbit minburst 1540') + check_output('tc qdisc replace dev dummy98 handle 10: root tbf rate 0.5mbit burst 5kb latency 70ms peakrate 1mbit minburst 1540') # fmt: skip self.assertFalse(networkd_is_failed()) check_output('tc qdisc add dev dummy98 parent 10:1 handle 100: sfq') self.assertFalse(networkd_is_failed()) -class NetworkdStateFileTests(unittest.TestCase, Utilities): +class NetworkdStateFileTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -5885,7 +6402,7 @@ def test_state_file(self): self.assertIn('REQUIRED_FAMILY_FOR_ONLINE=both', output) self.assertIn('ACTIVATION_POLICY=up', output) self.assertIn('NETWORK_FILE=/run/systemd/network/25-state-file-tests.network', output) - self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) + self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) # fmt: skip self.assertIn('NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org', output) self.assertIn('DOMAINS=hogehoge', output) self.assertIn('ROUTE_DOMAINS=foofoo', output) @@ -5932,7 +6449,7 @@ def test_state_file(self): output = read_link_state_file('dummy98') print(output) - self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) + self.assertIn('DNS=10.10.10.10#aaa.com 10.10.10.11:1111#bbb.com [1111:2222::3333]:1234#ccc.com', output) # fmt: skip self.assertIn('NTP=0.fedora.pool.ntp.org 1.fedora.pool.ntp.org', output) self.assertIn('DOMAINS=hogehoge', output) self.assertIn('ROUTE_DOMAINS=foofoo', output) @@ -5970,8 +6487,8 @@ def test_address_state(self): self.assertIn('IPV4_ADDRESS_STATE=off', output) self.assertIn('IPV6_ADDRESS_STATE=routable', output) -class NetworkdBondTests(unittest.TestCase, Utilities): +class NetworkdBondTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6011,7 +6528,12 @@ def test_bond_keep_master(self): self.wait_online('dummy98:enslaved') def test_bond_active_slave(self): - copy_network_unit('23-active-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev') + copy_network_unit( + '23-active-slave.network', + '23-bond199.network', + '25-bond-active-backup-slave.netdev', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bond199:degraded') @@ -6030,12 +6552,18 @@ def test_bond_active_slave(self): '23-active-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', - '12-dummy.netdev') + '12-dummy.netdev', + ) networkctl_reload() self.wait_online('dummy98:enslaved', 'bond199:degraded') def test_bond_primary_slave(self): - copy_network_unit('23-primary-slave.network', '23-bond199.network', '25-bond-active-backup-slave.netdev', '12-dummy.netdev') + copy_network_unit( + '23-primary-slave.network', + '23-bond199.network', + '25-bond-active-backup-slave.netdev', + '12-dummy.netdev', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bond199:degraded') @@ -6046,7 +6574,9 @@ def test_bond_primary_slave(self): # for issue #25627 mkdir_p(os.path.join(network_unit_dir, '23-bond199.network.d')) for mac in ['00:11:22:33:44:55', '00:11:22:33:44:56']: - with open(os.path.join(network_unit_dir, '23-bond199.network.d/mac.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '23-bond199.network.d/mac.conf'), mode='w', encoding='utf-8' + ) as f: f.write(f'[Link]\nMACAddress={mac}\n') networkctl_reload() @@ -6057,8 +6587,13 @@ def test_bond_primary_slave(self): self.assertIn(f'link/ether {mac}', output) def test_bond_operstate(self): - copy_network_unit('25-bond.netdev', '11-dummy.netdev', '12-dummy.netdev', - '25-bond99.network', '25-bond-slave.network') + copy_network_unit( + '25-bond.netdev', + '11-dummy.netdev', + '12-dummy.netdev', + '25-bond99.network', + '25-bond-slave.network', + ) start_networkd() self.wait_online('dummy98:enslaved', 'test1:enslaved', 'bond99:routable') @@ -6107,8 +6642,8 @@ def test_bond_operstate(self): print(output) self.assertNotRegex(output, 'NO-CARRIER') -class NetworkdBridgeTests(unittest.TestCase, Utilities): +class NetworkdBridgeTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6116,8 +6651,13 @@ def tearDown(self): tear_down_common() def test_bridge_mac_none(self): - copy_network_unit('12-dummy-mac.netdev', '26-bridge-mac-slave.network', - '26-bridge-mac.netdev', '26-bridge-mac-master.network', '26-bridge-mac.link') + copy_network_unit( + '12-dummy-mac.netdev', + '26-bridge-mac-slave.network', + '26-bridge-mac.netdev', + '26-bridge-mac-master.network', + '26-bridge-mac.link', + ) start_networkd() self.wait_online('dummy98:enslaved', 'bridge99:degraded') @@ -6130,9 +6670,13 @@ def test_bridge_mac_none(self): self.assertIn('link/ether 12:34:56:78:9a:01', output) def test_bridge_vlan(self): - copy_network_unit('11-dummy.netdev', '26-bridge-vlan-slave.network', - '26-bridge.netdev', '26-bridge-vlan-master.network', - copy_dropins=False) + copy_network_unit( + '11-dummy.netdev', + '26-bridge-vlan-slave.network', + '26-bridge.netdev', + '26-bridge-vlan-master.network', + copy_dropins=False, + ) start_networkd() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6165,8 +6709,10 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) # Change vlan IDs - copy_network_unit('26-bridge-vlan-slave.network.d/10-override.conf', - '26-bridge-vlan-master.network.d/10-override.conf') + copy_network_unit( + '26-bridge-vlan-slave.network.d/10-override.conf', + '26-bridge-vlan-master.network.d/10-override.conf', + ) networkctl_reload() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6195,8 +6741,10 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) # Remove several vlan IDs - copy_network_unit('26-bridge-vlan-slave.network.d/20-override.conf', - '26-bridge-vlan-master.network.d/20-override.conf') + copy_network_unit( + '26-bridge-vlan-slave.network.d/20-override.conf', + '26-bridge-vlan-master.network.d/20-override.conf', + ) networkctl_reload() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6225,8 +6773,10 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) # Remove all vlan IDs - copy_network_unit('26-bridge-vlan-slave.network.d/30-override.conf', - '26-bridge-vlan-master.network.d/30-override.conf') + copy_network_unit( + '26-bridge-vlan-slave.network.d/30-override.conf', + '26-bridge-vlan-master.network.d/30-override.conf', + ) networkctl_reload() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6243,9 +6793,14 @@ def test_bridge_vlan(self): self.assertNotIn(f'{i}', output) def test_bridge_vlan_issue_20373(self): - copy_network_unit('11-dummy.netdev', '26-bridge-vlan-slave-issue-20373.network', - '26-bridge-issue-20373.netdev', '26-bridge-vlan-master-issue-20373.network', - '21-vlan.netdev', '21-vlan.network') + copy_network_unit( + '11-dummy.netdev', + '26-bridge-vlan-slave-issue-20373.network', + '26-bridge-issue-20373.netdev', + '26-bridge-vlan-master-issue-20373.network', + '21-vlan.netdev', + '21-vlan.network', + ) start_networkd() self.wait_online('test1:enslaved', 'bridge99:degraded', 'vlan99:routable') @@ -6262,8 +6817,12 @@ def test_bridge_vlan_issue_20373(self): self.assertIn('600', output) def test_bridge_mdb(self): - copy_network_unit('11-dummy.netdev', '26-bridge-mdb-slave.network', - '26-bridge.netdev', '26-bridge-mdb-master.network') + copy_network_unit( + '11-dummy.netdev', + '26-bridge-mdb-slave.network', + '26-bridge.netdev', + '26-bridge-mdb-master.network', + ) start_networkd() self.wait_online('test1:enslaved', 'bridge99:degraded') @@ -6271,14 +6830,11 @@ def test_bridge_mdb(self): print(output) self.assertRegex(output, 'dev bridge99 port test1 grp ff02:aaaa:fee5::1:3 permanent *vid 4064') self.assertRegex(output, 'dev bridge99 port test1 grp 224.0.1.1 permanent *vid 4065') + self.assertRegex(output, 'dev bridge99 port bridge99 grp ff02:aaaa:fee5::1:4 temp *vid 4066') + self.assertRegex(output, 'dev bridge99 port bridge99 grp 224.0.1.2 temp *vid 4067') - # Old kernel may not support bridge MDB entries on bridge master - if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 224.0.1.3 temp vid 4068') == 0: - self.assertRegex(output, 'dev bridge99 port bridge99 grp ff02:aaaa:fee5::1:4 temp *vid 4066') - self.assertRegex(output, 'dev bridge99 port bridge99 grp 224.0.1.2 temp *vid 4067') - - # Old kernel may not support L2 bridge MDB entries - if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 01:80:c2:00:00:0f permanent vid 4070') == 0: + # The kernels older than 955062b03fa62b802a1ee34fbb04e39f7a70ae73 (v5.11) do not support L2 bridge MDB entries + if call_quiet('bridge mdb add dev bridge99 port bridge99 grp 01:80:c2:00:00:0f permanent vid 4070') == 0: # fmt: skip self.assertRegex(output, 'dev bridge99 port bridge99 grp 01:80:c2:00:00:0e permanent *vid 4069') def test_bridge_keep_master(self): @@ -6298,18 +6854,18 @@ def test_bridge_keep_master(self): output = check_output('bridge -d link show dummy98') print(output) - self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') - self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') + self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') # CONFIG_BRIDGE_IGMP_SNOOPING=y self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') - self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) + self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') + self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') def check_bridge_property(self): self.wait_online('dummy98:enslaved', 'test1:enslaved', 'bridge99:routable') @@ -6336,31 +6892,38 @@ def check_bridge_property(self): output = check_output('bridge -d link show dummy98') print(output) - self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') - self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'isolated', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'path_cost', '400') + self.check_bridge_port_attr('bridge99', 'dummy98', 'hairpin_mode', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'isolated', '1') self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_fast_leave', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') - self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'unicast_flood', '1') + self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_flood', '0') # CONFIG_BRIDGE_IGMP_SNOOPING=y self.check_bridge_port_attr('bridge99', 'dummy98', 'multicast_to_unicast', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) - self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') - self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') - self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'neigh_suppress', '1', allow_enoent=True) + self.check_bridge_port_attr('bridge99', 'dummy98', 'learning', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'priority', '23') + self.check_bridge_port_attr('bridge99', 'dummy98', 'bpdu_guard', '0') + self.check_bridge_port_attr('bridge99', 'dummy98', 'root_block', '0') output = check_output('bridge -d link show test1') print(output) - self.check_bridge_port_attr('bridge99', 'test1', 'priority', '0') + self.check_bridge_port_attr('bridge99', 'test1', 'priority', '0') self.assertIn('locked on', output) - if ' mab ' in output: # This is new in kernel and iproute2 v6.2 + if ' mab ' in output: # This is new in kernel and iproute2 v6.2 self.assertIn('mab on', output) def test_bridge_property(self): - copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev', - '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network', - '25-bridge99.network', '14-dummy.netdev', '26-bridge-vlan-tunnel.network') + copy_network_unit( + '11-dummy.netdev', + '12-dummy.netdev', + '26-bridge.netdev', + '26-bridge-slave-interface-1.network', + '26-bridge-slave-interface-2.network', + '25-bridge99.network', + '14-dummy.netdev', + '26-bridge-vlan-tunnel.network', + ) start_networkd() self.check_bridge_property() @@ -6372,7 +6935,8 @@ def test_bridge_property(self): '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network', '26-bridge-vlan-tunnel.network', - '25-bridge99.network') + '25-bridge99.network', + ) networkctl_reload() self.check_bridge_property() @@ -6415,7 +6979,7 @@ def test_bridge_property(self): output = check_output('ip address show bridge99') print(output) self.assertNotIn('192.168.0.15/24', output) - self.assertIn('192.168.0.16/24', output) # foreign address is kept + self.assertIn('192.168.0.16/24', output) # foreign address is kept print('### ip -6 route list table all dev bridge99') output = check_output('ip -6 route list table all dev bridge99') @@ -6436,8 +7000,11 @@ def test_bridge_property(self): self.assertIn('mtu 9000 ', output) def test_bridge_configure_without_carrier(self): - copy_network_unit('26-bridge.netdev', '26-bridge-configure-without-carrier.network', - '11-dummy.netdev') + copy_network_unit( + '26-bridge.netdev', + '26-bridge-configure-without-carrier.network', + '11-dummy.netdev', + ) start_networkd() # With ConfigureWithoutCarrier=yes, the bridge should remain configured for all these situations @@ -6445,13 +7012,18 @@ def test_bridge_configure_without_carrier(self): with self.subTest(test=test): if test == 'no-slave': # bridge has no slaves; it's up but *might* not have carrier - self.wait_operstate('bridge99', operstate=r'(no-carrier|routable)', setup_state=None, setup_timeout=30) + self.wait_operstate( + 'bridge99', + operstate=r'(no-carrier|routable)', + setup_state=None, + setup_timeout=30, + ) # due to a bug in the kernel, newly-created bridges are brought up # *with* carrier, unless they have had any setting changed; e.g. # their mac set, priority set, etc. Then, they will lose carrier # as soon as a (down) slave interface is added, and regain carrier # again once the slave interface is brought up. - #self.check_link_attr('bridge99', 'carrier', '0') + # self.check_link_attr('bridge99', 'carrier', '0') elif test == 'add-slave': # add slave to bridge, but leave it down; bridge is definitely no-carrier self.check_link_attr('test1', 'operstate', 'down') @@ -6484,9 +7056,14 @@ def test_bridge_configure_without_carrier(self): self.assertRegex(output, '10.1.2.1') def test_bridge_ignore_carrier_loss(self): - copy_network_unit('11-dummy.netdev', '12-dummy.netdev', '26-bridge.netdev', - '26-bridge-slave-interface-1.network', '26-bridge-slave-interface-2.network', - '25-bridge99-ignore-carrier-loss.network') + copy_network_unit( + '11-dummy.netdev', + '12-dummy.netdev', + '26-bridge.netdev', + '26-bridge-slave-interface-1.network', + '26-bridge-slave-interface-2.network', + '25-bridge99-ignore-carrier-loss.network', + ) start_networkd() self.wait_online('dummy98:enslaved', 'test1:enslaved', 'bridge99:routable') @@ -6501,8 +7078,11 @@ def test_bridge_ignore_carrier_loss(self): self.assertRegex(output, 'inet 192.168.0.16/24 scope global secondary bridge99') def test_bridge_ignore_carrier_loss_frequent_loss_and_gain(self): - copy_network_unit('26-bridge.netdev', '26-bridge-slave-interface-1.network', - '25-bridge99-ignore-carrier-loss.network') + copy_network_unit( + '26-bridge.netdev', + '26-bridge-slave-interface-1.network', + '25-bridge99-ignore-carrier-loss.network', + ) start_networkd() self.wait_online('bridge99:no-carrier') @@ -6522,8 +7102,8 @@ def test_bridge_ignore_carrier_loss_frequent_loss_and_gain(self): print(output) self.assertIn('from all to 8.8.8.8 lookup 100', output) -class NetworkdSRIOVTests(unittest.TestCase, Utilities): +class NetworkdSRIOVTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6539,10 +7119,12 @@ def setup_netdevsim(self, id=99, num_ports=1, num_vfs=0): # Create VF. if num_vfs > 0: - with open(f'/sys/bus/netdevsim/devices/netdevsim{id}/sriov_numvfs', mode='w', encoding='utf-8') as f: + with open( + f'/sys/bus/netdevsim/devices/netdevsim{id}/sriov_numvfs', mode='w', encoding='utf-8' + ) as f: f.write(f'{num_vfs}') - @expectedFailureIfNetdevsimWithSRIOVIsNotAvailable() + @expectedFailureIfModuleIsNotAvailable('netdevsim') def test_sriov(self): copy_network_unit('25-netdevsim.link', '25-sriov.network') @@ -6553,13 +7135,14 @@ def test_sriov(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off', + ) - @expectedFailureIfNetdevsimWithSRIOVIsNotAvailable() + @expectedFailureIfModuleIsNotAvailable('netdevsim') def test_sriov_udev(self): copy_network_unit('25-sriov.link', '25-sriov-udev.network') @@ -6573,11 +7156,12 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off', + ) self.assertNotIn('vf 3', output) self.assertNotIn('vf 4', output) @@ -6589,12 +7173,13 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' - 'vf 3' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' + 'vf 3', + ) self.assertNotIn('vf 4', output) with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f: @@ -6605,12 +7190,13 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' - 'vf 3' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off\n *' + 'vf 3', + ) self.assertNotIn('vf 4', output) with open(os.path.join(network_unit_dir, '25-sriov.link'), mode='a', encoding='utf-8') as f: @@ -6621,10 +7207,11 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off', + ) self.assertNotIn('vf 2', output) self.assertNotIn('vf 3', output) self.assertNotIn('vf 4', output) @@ -6637,16 +7224,17 @@ def test_sriov_udev(self): output = check_output('ip link show dev sim99') print(output) - self.assertRegex(output, - 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' - 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' - 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off' - ) + self.assertRegex( + output, + 'vf 0 .*00:11:22:33:44:55.*vlan 5, qos 1, vlan protocol 802.1ad, spoof checking on, link-state enable, trust on, query_rss on\n *' + 'vf 1 .*00:11:22:33:44:56.*vlan 6, qos 2, spoof checking off, link-state disable, trust off, query_rss off\n *' + 'vf 2 .*00:11:22:33:44:57.*vlan 7, qos 3, spoof checking off, link-state auto, trust off, query_rss off', + ) self.assertNotIn('vf 3', output) self.assertNotIn('vf 4', output) -class NetworkdLLDPTests(unittest.TestCase, Utilities): +class NetworkdLLDPTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6668,12 +7256,12 @@ def test_lldp(self): self.fail() # With interface name - output = networkctl('lldp', 'veth99'); + output = networkctl('lldp', 'veth99') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* .......a...') # With interface name pattern - output = networkctl('lldp', 've*9'); + output = networkctl('lldp', 've*9') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* .......a...') @@ -6720,12 +7308,12 @@ def test_lldp(self): self.fail() # With interface name - output = networkctl('lldp', 'veth99'); + output = networkctl('lldp', 'veth99') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* ....r......') # With interface name pattern - output = networkctl('lldp', 've*9'); + output = networkctl('lldp', 've*9') print(output) self.assertRegex(output, r'veth99 .* veth-peer .* ....r......') @@ -6757,13 +7345,13 @@ def test_lldp(self): # Compare the json output from sender and receiver sender_json = get_link_description('veth-peer')['LLDP'] - receiver_json = json.loads(networkctl('--json=short', 'lldp', 'veth99'))['Neighbors'][0]['Neighbors'][0] + receiver_json = json.loads(networkctl('--json=short', 'lldp', 'veth99'))['Neighbors'][0]['Neighbors'][0] # fmt: skip print(sender_json) print(receiver_json) self.assertEqual(sender_json, receiver_json) -class NetworkdRATests(unittest.TestCase, Utilities): +class NetworkdRATests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -6819,7 +7407,11 @@ def check_ipv6_token_static(self): self.assertRegex(output, '2002:da8:2:0:fa:de:ca:fe') def test_ipv6_token_static(self): - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-static.network', + ) start_networkd() self.check_ipv6_token_static() @@ -6840,35 +7432,63 @@ def test_ipv6_token_static(self): def test_ndisc_redirect(self): if not os.path.exists(test_ndisc_send): - self.skipTest(f"{test_ndisc_send} does not exist.") + self.skipTest(f'{test_ndisc_send} does not exist.') - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-static.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-static.network', + ) start_networkd() self.check_ipv6_token_static() # Introduce three redirect routes. + # fmt: off check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:1:1a:2b:3c:4d --redirect-destination 2002:da8:1:1:1a:2b:3c:4d') check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:2:1a:2b:3c:4d --redirect-destination 2002:da8:1:2:1a:2b:3c:4d') check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address 2002:da8:1:3:1a:2b:3c:4d --redirect-destination 2002:da8:1:3:1a:2b:3c:4d') + # fmt: on self.wait_route('veth99', '2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) self.wait_route('veth99', '2002:da8:1:2:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) self.wait_route('veth99', '2002:da8:1:3:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) # Change the target address of the redirects. + # fmt: off check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address fe80::1 --redirect-destination 2002:da8:1:1:1a:2b:3c:4d') check_output(f'{test_ndisc_send} --interface veth-peer --type redirect --target-address fe80::2 --redirect-destination 2002:da8:1:2:1a:2b:3c:4d') - self.wait_route_dropped('veth99', '2002:da8:1:1:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) - self.wait_route_dropped('veth99', '2002:da8:1:2:1a:2b:3c:4d proto redirect', ipv='-6', timeout_sec=10) - self.wait_route('veth99', r'2002:da8:1:1:1a:2b:3c:4d nhid [0-9]* via fe80::1 proto redirect', ipv='-6', timeout_sec=10) - self.wait_route('veth99', r'2002:da8:1:2:1a:2b:3c:4d nhid [0-9]* via fe80::2 proto redirect', ipv='-6', timeout_sec=10) + # fmt: on + self.wait_route_dropped( + 'veth99', + '2002:da8:1:1:1a:2b:3c:4d proto redirect', + ipv='-6', + timeout_sec=10, + ) + self.wait_route_dropped( + 'veth99', + '2002:da8:1:2:1a:2b:3c:4d proto redirect', + ipv='-6', + timeout_sec=10, + ) + self.wait_route( + 'veth99', + r'2002:da8:1:1:1a:2b:3c:4d nhid [0-9]* via fe80::1 proto redirect', + ipv='-6', + timeout_sec=10, + ) + self.wait_route( + 'veth99', + r'2002:da8:1:2:1a:2b:3c:4d nhid [0-9]* via fe80::2 proto redirect', + ipv='-6', + timeout_sec=10, + ) - # Send Neighbor Advertisement without the router flag to announce the default router is not available anymore. - # Then, verify that all redirect routes and the default route are dropped. + # Send Neighbor Advertisement without the router flag to announce the default router is not available + # anymore. Then, verify that all redirect routes and the default route are dropped. output = check_output('ip -6 address show dev veth-peer scope link') veth_peer_ipv6ll = re.search('fe80:[:0-9a-f]*', output).group() print(f'veth-peer IPv6LL address: {veth_peer_ipv6ll}') - check_output(f'{test_ndisc_send} --interface veth-peer --type neighbor-advertisement --target-address {veth_peer_ipv6ll} --is-router no') + check_output(f'{test_ndisc_send} --interface veth-peer --type neighbor-advertisement --target-address {veth_peer_ipv6ll} --is-router no') # fmt: skip self.wait_route_dropped('veth99', 'proto ra', ipv='-6', timeout_sec=10) self.wait_route_dropped('veth99', 'proto redirect', ipv='-6', timeout_sec=10) @@ -6876,7 +7496,10 @@ def test_ndisc_redirect(self): # See https://github.com/systemd/systemd/pull/32267#discussion_r1566721306 since = datetime.datetime.now() check_output(f'{test_ndisc_send} --interface veth-peer --type rs --dest {veth_peer_ipv6ll}') - self.check_networkd_log('veth-peer: RADV: Received RS from the same interface, ignoring.', since=since) + self.check_networkd_log( + 'veth-peer: RADV: Received RS from the same interface, ignoring.', + since=since, + ) def check_ndisc_mtu(self, mtu): for _ in range(20): @@ -6889,11 +7512,13 @@ def check_ndisc_mtu(self, mtu): def test_ndisc_mtu(self): if not os.path.exists(test_ndisc_send): - self.skipTest(f"{test_ndisc_send} does not exist.") + self.skipTest(f'{test_ndisc_send} does not exist.') - copy_network_unit('25-veth.netdev', - '25-veth-peer-no-address.network', - '25-ipv6-prefix-veth-token-static.network') + copy_network_unit( + '25-veth.netdev', + '25-veth-peer-no-address.network', + '25-ipv6-prefix-veth-token-static.network', + ) start_networkd() self.wait_online('veth-peer:degraded') @@ -6917,16 +7542,24 @@ def test_ndisc_mtu(self): self.check_ndisc_mtu(1700) def test_ipv6_token_prefixstable(self): - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-prefixstable.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') output = check_output('ip -6 address show dev veth99') print(output) - self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable - self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 + self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable + self.assertIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 - with open(os.path.join(network_unit_dir, '25-ipv6-prefix-veth-token-prefixstable.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-ipv6-prefix-veth-token-prefixstable.network'), + mode='a', + encoding='utf-8', + ) as f: f.write('\n[IPv6AcceptRA]\nPrefixAllowList=2002:da8:1:0::/64\n') networkctl_reload() @@ -6934,28 +7567,57 @@ def test_ipv6_token_prefixstable(self): output = check_output('ip -6 address show dev veth99') print(output) - self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable - self.assertNotIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 + self.assertIn('2002:da8:1:0:b47e:7975:fc7a:7d6e/64', output) # the 1st prefixstable + self.assertNotIn('2002:da8:2:0:1034:56ff:fe78:9abc/64', output) # EUI64 check_output('ip address del 2002:da8:1:0:b47e:7975:fc7a:7d6e/64 dev veth99') check_output('ip address add 2002:da8:1:0:b47e:7975:fc7a:7d6e/64 dev veth-peer nodad') networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - self.wait_address('veth99', '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', ipv='-6', timeout_sec=10) # the 2nd prefixstable - self.wait_address_dropped('veth99', '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', ipv='-6', timeout_sec=10) # the 1st prefixstable + self.wait_address( + 'veth99', + '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', + ipv='-6', + timeout_sec=10, + ) # the 2nd prefixstable + self.wait_address_dropped( + 'veth99', + '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', + ipv='-6', + timeout_sec=10, + ) # the 1st prefixstable check_output('ip address del 2002:da8:1:0:da5d:e50a:43fd:5d0f/64 dev veth99') check_output('ip address add 2002:da8:1:0:da5d:e50a:43fd:5d0f/64 dev veth-peer nodad') networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - self.wait_address('veth99', '2002:da8:1:0:c7e4:77ec:eb31:1b0d/64', ipv='-6', timeout_sec=10) # the 3rd prefixstable - self.wait_address_dropped('veth99', '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', ipv='-6', timeout_sec=10) # the 2nd prefixstable - self.wait_address_dropped('veth99', '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', ipv='-6', timeout_sec=10) # the 1st prefixstable + self.wait_address( + 'veth99', + '2002:da8:1:0:c7e4:77ec:eb31:1b0d/64', + ipv='-6', + timeout_sec=10, + ) # the 3rd prefixstable + self.wait_address_dropped( + 'veth99', + '2002:da8:1:0:da5d:e50a:43fd:5d0f/64', + ipv='-6', + timeout_sec=10, + ) # the 2nd prefixstable + self.wait_address_dropped( + 'veth99', + '2002:da8:1:0:b47e:7975:fc7a:7d6e/64', + ipv='-6', + timeout_sec=10, + ) # the 1st prefixstable def test_ipv6_token_prefixstable_without_address(self): - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-token-prefixstable-without-address.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-token-prefixstable-without-address.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') @@ -6965,21 +7627,29 @@ def test_ipv6_token_prefixstable_without_address(self): self.assertIn('2002:da8:2:0:f689:561a:8eda:7443', output) def test_router_hop_limit(self): - copy_network_unit('25-veth-client.netdev', - '25-veth-router.netdev', - '26-bridge.netdev', - '25-veth-bridge.network', - '25-veth-client.network', - '25-veth-router-hop-limit.network', - '25-bridge99.network') - start_networkd() - self.wait_online('client:routable', 'client-p:enslaved', - 'router:degraded', 'router-p:enslaved', - 'bridge99:routable') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router.netdev', + '26-bridge.netdev', + '25-veth-bridge.network', + '25-veth-client.network', + '25-veth-router-hop-limit.network', + '25-bridge99.network', + ) + start_networkd() + self.wait_online( + 'client:routable', + 'client-p:enslaved', + 'router:degraded', + 'router-p:enslaved', + 'bridge99:routable', + ) self.check_ipv6_sysctl_attr('client', 'hop_limit', '42') - with open(os.path.join(network_unit_dir, '25-veth-router-hop-limit.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-veth-router-hop-limit.network'), mode='a', encoding='utf-8' + ) as f: f.write('\n[IPv6SendRA]\nHopLimit=43\n') networkctl_reload() @@ -6996,14 +7666,26 @@ def check_router_preference(self, suffix, metric_1, preference_1, metric_2, pref self.wait_online('client:routable') self.wait_address('client', f'2002:da8:1:99:1034:56ff:fe78:9a{suffix}/64', ipv='-6', timeout_sec=10) self.wait_address('client', f'2002:da8:1:98:1034:56ff:fe78:9a{suffix}/64', ipv='-6', timeout_sec=10) - self.wait_route('client', rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric {metric_1}', ipv='-6', timeout_sec=10) - self.wait_route('client', rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric {metric_2}', ipv='-6', timeout_sec=10) + self.wait_route( + 'client', + rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric {metric_1}', + ipv='-6', + timeout_sec=10, + ) + self.wait_route( + 'client', + rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric {metric_2}', + ipv='-6', + timeout_sec=10, + ) print('### ip -6 route show dev client default') output = check_output('ip -6 route show dev client default') print(output) + # fmt: off self.assertRegex(output, rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a99 proto ra metric {metric_1} expires [0-9]*sec pref {preference_1}') self.assertRegex(output, rf'default nhid [0-9]* via fe80::1034:56ff:fe78:9a98 proto ra metric {metric_2} expires [0-9]*sec pref {preference_2}') + # fmt: on for i in [100, 200, 300, 512, 1024, 2048]: if i not in [metric_1, metric_2]: @@ -7014,20 +7696,26 @@ def check_router_preference(self, suffix, metric_1, preference_1, metric_2, pref self.assertNotIn(f'pref {i}', output) def test_router_preference(self): - copy_network_unit('25-veth-client.netdev', - '25-veth-router-high.netdev', - '25-veth-router-low.netdev', - '26-bridge.netdev', - '25-veth-bridge.network', - '25-veth-client.network', - '25-veth-router-high.network', - '25-veth-router-low.network', - '25-bridge99.network') - start_networkd() - self.wait_online('client-p:enslaved', - 'router-high:degraded', 'router-high-p:enslaved', - 'router-low:degraded', 'router-low-p:enslaved', - 'bridge99:routable') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router-high.netdev', + '25-veth-router-low.netdev', + '26-bridge.netdev', + '25-veth-bridge.network', + '25-veth-client.network', + '25-veth-router-high.network', + '25-veth-router-low.network', + '25-bridge99.network', + ) + start_networkd() + self.wait_online( + 'client-p:enslaved', + 'router-high:degraded', + 'router-high-p:enslaved', + 'router-low:degraded', + 'router-low-p:enslaved', + 'bridge99:routable', + ) networkctl_reconfigure('client') self.wait_online('client:routable') @@ -7035,64 +7723,135 @@ def test_router_preference(self): # change the map from preference to metric. with open(os.path.join(network_unit_dir, '25-veth-client.network'), mode='a', encoding='utf-8') as f: - f.write('\n[Link]\nMACAddress=12:34:56:78:9a:01\n[IPv6AcceptRA]\nRouteMetric=100:200:300\n') + f.write(''' +[Link] +MACAddress=12:34:56:78:9a:01 +[IPv6AcceptRA] +RouteMetric=100:200:300 +''') networkctl_reload() self.check_router_preference('01', 100, 'high', 300, 'low') # swap the preference (for issue #28439) - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=low\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=high\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=low +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=high +''') networkctl_reload() self.check_router_preference('01', 300, 'low', 100, 'high') # Use the same preference, and check if the two routes are not coalesced. See issue #33470. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=medium\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=medium\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=medium +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=medium +''') networkctl_reload() self.check_router_preference('01', 200, 'medium', 200, 'medium') # Use route options to configure default routes. # The preference specified in the RA header should be ignored. See issue #33468. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=high\n[IPv6RoutePrefix]\nRoute=::/0\nLifetimeSec=1200\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('\n[IPv6SendRA]\nRouterPreference=low\n[IPv6RoutePrefix]\nRoute=::/0\nLifetimeSec=1200\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=high +[IPv6RoutePrefix] +Route=::/0 +LifetimeSec=1200 +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +[IPv6SendRA] +RouterPreference=low +[IPv6RoutePrefix] +Route=::/0 +LifetimeSec=1200 +''') networkctl_reload() self.check_router_preference('01', 200, 'medium', 200, 'medium') # Set zero lifetime to the route options. # The preference specified in the RA header should be used. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') networkctl_reload() self.check_router_preference('01', 100, 'high', 300, 'low') # Use route options with preference to configure default routes. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=1200\nPreference=low\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=1200\nPreference=high\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=1200 +Preference=low +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=1200 +Preference=high +''') networkctl_reload() self.check_router_preference('01', 300, 'low', 100, 'high') # Set zero lifetime again to the route options. - with open(os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') - with open(os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8') as f: - f.write('LifetimeSec=0\n') + with open( + os.path.join(network_unit_dir, '25-veth-router-high.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') + with open( + os.path.join(network_unit_dir, '25-veth-router-low.network'), mode='a', encoding='utf-8' + ) as f: + f.write(''' +LifetimeSec=0 +''') networkctl_reload() self.check_router_preference('01', 100, 'high', 300, 'low') def _test_ndisc_vs_static_route(self, manage_foreign_nexthops): if not manage_foreign_nexthops: copy_networkd_conf_dropin('networkd-manage-foreign-nexthops-no.conf') - copy_network_unit('25-veth.netdev', '25-ipv6-prefix.network', '25-ipv6-prefix-veth-static-route.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6-prefix.network', + '25-ipv6-prefix-veth-static-route.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:degraded') @@ -7102,7 +7861,7 @@ def _test_ndisc_vs_static_route(self, manage_foreign_nexthops): print(output) self.assertIn('via fe80::1034:56ff:fe78:9abd proto static metric 256 pref medium', output) if manage_foreign_nexthops: - self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9abd proto ra metric 256 expires [0-9]*sec pref medium') + self.assertRegex(output, r'default nhid [0-9]* via fe80::1034:56ff:fe78:9abd proto ra metric 256 expires [0-9]*sec pref medium') # fmt: skip else: self.assertNotIn('proto ra', output) @@ -7110,15 +7869,23 @@ def _test_ndisc_vs_static_route(self, manage_foreign_nexthops): output = check_output('ip -6 nexthop show dev veth99') print(output) if manage_foreign_nexthops: - self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abd dev veth99 scope link proto ra') + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abd dev veth99 scope link proto ra') # fmt: skip else: self.assertEqual(output, '') # Also check if the static route is protected from RA with zero lifetime with open(os.path.join(network_unit_dir, '25-ipv6-prefix.network'), mode='a', encoding='utf-8') as f: - f.write('\n[Network]\nIPv6SendRA=no\n') - networkctl_reload() # This makes veth-peer being reconfigured, and send RA with zero lifetime - self.wait_route_dropped('veth99', r'default (nhid [0-9]* |)via fe80::1034:56ff:fe78:9abd proto ra metric 256', ipv='-6', timeout_sec=10) + f.write(''' +[Network] +IPv6SendRA=no +''') + networkctl_reload() # This makes veth-peer being reconfigured, and send RA with zero lifetime + self.wait_route_dropped( + 'veth99', + r'default (nhid [0-9]* |)via fe80::1034:56ff:fe78:9abd proto ra metric 256', + ipv='-6', + timeout_sec=10, + ) print('### ip -6 route show dev veth99 default') output = check_output('ip -6 route show dev veth99 default') @@ -7146,18 +7913,27 @@ def test_ndisc_vs_static_route(self): # radvd supports captive portal since v2.20. # https://github.com/radvd-project/radvd/commit/791179a7f730decbddb2290ef0e34aa85d71b1bc - @unittest.skipUnless(radvd_check_config('captive-portal.conf'), "Installed radvd doesn't support captive portals") + @unittest.skipUnless( + radvd_check_config('captive-portal.conf'), + "Installed radvd doesn't support captive portals", + ) def test_captive_portal(self): - copy_network_unit('25-veth-client.netdev', - '25-veth-router-captive.netdev', - '26-bridge.netdev', - '25-veth-client-captive.network', - '25-veth-router-captive.network', - '25-veth-bridge-captive.network', - '25-bridge99.network') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router-captive.netdev', + '26-bridge.netdev', + '25-veth-client-captive.network', + '25-veth-router-captive.network', + '25-veth-bridge-captive.network', + '25-bridge99.network', + ) start_networkd() - self.wait_online('bridge99:routable', 'client-p:enslaved', - 'router-captive:degraded', 'router-captivep:enslaved') + self.wait_online( + 'bridge99:routable', + 'client-p:enslaved', + 'router-captive:degraded', + 'router-captivep:enslaved', + ) start_radvd(config_file='captive-portal.conf') networkctl_reconfigure('client') @@ -7168,31 +7944,53 @@ def test_captive_portal(self): print(output) self.assertIn('Captive Portal: http://systemd.io', output) - @unittest.skipUnless(radvd_check_config('captive-portal.conf'), "Installed radvd doesn't support captive portals") + @unittest.skipUnless( + radvd_check_config('captive-portal.conf'), + "Installed radvd doesn't support captive portals", + ) def test_invalid_captive_portal(self): def radvd_write_config(captive_portal_uri): - with open(os.path.join(networkd_ci_temp_dir, 'radvd/bogus-captive-portal.conf'), mode='w', encoding='utf-8') as f: - f.write(f'interface router-captive {{ AdvSendAdvert on; AdvCaptivePortalAPI "{captive_portal_uri}"; prefix 2002:da8:1:99::/64 {{ AdvOnLink on; AdvAutonomous on; }}; }};') + with open( + os.path.join(networkd_ci_temp_dir, 'radvd/bogus-captive-portal.conf'), + mode='w', + encoding='utf-8', + ) as f: + f.write(f''' +interface router-captive {{ + AdvSendAdvert on; + AdvCaptivePortalAPI "{captive_portal_uri}"; + prefix 2002:da8:1:99::/64 {{ + AdvOnLink on; + AdvAutonomous on; + }}; +}}; +''') captive_portal_uris = [ - "42ěščěškd ěšč ě s", - " ", - "🤔", + '42ěščěškd ěšč ě s', + ' ', + '🤔', ] - copy_network_unit('25-veth-client.netdev', - '25-veth-router-captive.netdev', - '26-bridge.netdev', - '25-veth-client-captive.network', - '25-veth-router-captive.network', - '25-veth-bridge-captive.network', - '25-bridge99.network') + copy_network_unit( + '25-veth-client.netdev', + '25-veth-router-captive.netdev', + '26-bridge.netdev', + '25-veth-client-captive.network', + '25-veth-router-captive.network', + '25-veth-bridge-captive.network', + '25-bridge99.network', + ) start_networkd() - self.wait_online('bridge99:routable', 'client-p:enslaved', - 'router-captive:degraded', 'router-captivep:enslaved') + self.wait_online( + 'bridge99:routable', + 'client-p:enslaved', + 'router-captive:degraded', + 'router-captivep:enslaved', + ) for uri in captive_portal_uris: - print(f"Captive portal: {uri}") + print(f'Captive portal: {uri}') radvd_write_config(uri) stop_radvd() start_radvd(config_file='bogus-captive-portal.conf') @@ -7204,8 +8002,8 @@ def radvd_write_config(captive_portal_uri): print(output) self.assertNotIn('Captive Portal:', output) -class NetworkdDHCPServerTests(unittest.TestCase, Utilities): +class NetworkdDHCPServerTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -7222,7 +8020,7 @@ def check_dhcp_server(self, persist_leases='yes'): output = networkctl_status('veth-peer') print(output) - self.assertRegex(output, "Offered DHCP leases: 192.168.5.[0-9]*") + self.assertRegex(output, 'Offered DHCP leases: 192.168.5.[0-9]*') if persist_leases == 'yes': path = '/var/lib/systemd/network/dhcp-server-lease/veth-peer' @@ -7249,7 +8047,7 @@ def test_dhcp_server(self): output = check_output(*networkctl_cmd, '-n', '0', 'status', 'veth-peer', env=env) if 'Offered DHCP leases: 192.168.5.' in output: break - time.sleep(.2) + time.sleep(0.2) else: self.fail() @@ -7286,7 +8084,11 @@ def test_dhcp_server_persist_leases_runtime(self): self.check_dhcp_server(persist_leases='runtime') def test_dhcp_server_null_server_address(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-null-server-address.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client.network', + '25-dhcp-server-null-server-address.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7331,8 +8133,13 @@ def test_dhcp_server_null_server_address(self): self.assertIn(f'Offered DHCP leases: {client_address}', output) def test_dhcp_server_with_uplink(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client.network', '25-dhcp-server-downstream.network', - '12-dummy.netdev', '25-dhcp-server-uplink.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client.network', + '25-dhcp-server-downstream.network', + '12-dummy.netdev', + '25-dhcp-server-uplink.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7344,7 +8151,11 @@ def test_dhcp_server_with_uplink(self): self.assertIn('NTP: 192.168.5.1', output) def test_emit_router_timezone(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client-timezone-router.network', '25-dhcp-server-timezone-router.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-timezone-router.network', + '25-dhcp-server-timezone-router.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7355,7 +8166,11 @@ def test_emit_router_timezone(self): self.assertIn('Time Zone: Europe/Berlin', output) def test_dhcp_server_static_lease_mac_by_network(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client-static-lease.network', '25-dhcp-server-static-lease.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-static-lease.network', + '25-dhcp-server-static-lease.network', + ) copy_networkd_conf_dropin('10-dhcp-client-id-duid.conf') start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7387,9 +8202,11 @@ def test_dhcp_server_static_lease_duid(self): self.assertRegex(output, 'DHCPv4 Client ID: IAID:[0-9a-z]*/DUID') def test_dhcp_server_static_lease_hostname_simple(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-client-simple-hostname.network', - '25-dhcp-server-static-hostname.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-simple-hostname.network', + '25-dhcp-server-static-hostname.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7399,9 +8216,11 @@ def test_dhcp_server_static_lease_hostname_simple(self): self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'simple-host') def test_dhcp_server_static_lease_hostname_fqdn(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-client-fqdn-hostname.network', - '25-dhcp-server-static-hostname.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-fqdn-hostname.network', + '25-dhcp-server-static-hostname.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7411,7 +8230,11 @@ def test_dhcp_server_static_lease_hostname_fqdn(self): self.assertEqual(data['DHCPv4Client']['Lease']['Hostname'], 'fqdn.example.com') def test_dhcp_server_resolve_hook(self): - copy_network_unit('25-veth.netdev', '25-dhcp-client-resolve-hook.network', '25-dhcp-server-resolve-hook.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-client-resolve-hook.network', + '25-dhcp-server-resolve-hook.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7421,7 +8244,6 @@ def test_dhcp_server_resolve_hook(self): class NetworkdDHCPServerRelayAgentTests(unittest.TestCase, Utilities): - def setUp(self): setup_common() @@ -7429,12 +8251,14 @@ def tearDown(self): tear_down_common() def test_relay_agent(self): - copy_network_unit('25-agent-veth-client.netdev', - '25-agent-veth-server.netdev', - '25-agent-client.network', - '25-agent-server.network', - '25-agent-client-peer.network', - '25-agent-server-peer.network') + copy_network_unit( + '25-agent-veth-client.netdev', + '25-agent-veth-server.netdev', + '25-agent-client.network', + '25-agent-server.network', + '25-agent-client-peer.network', + '25-agent-server-peer.network', + ) start_networkd() self.wait_online('client:routable') @@ -7444,19 +8268,21 @@ def test_relay_agent(self): self.assertRegex(output, r'Address: 192.168.5.150 \(DHCPv4 via 192.168.5.1\)') def test_relay_agent_on_bridge(self): - copy_network_unit('25-agent-bridge.netdev', - '25-agent-veth-client.netdev', - '25-agent-bridge.network', - '25-agent-bridge-port.network', - '25-agent-client.network') + copy_network_unit( + '25-agent-bridge.netdev', + '25-agent-veth-client.netdev', + '25-agent-bridge.network', + '25-agent-bridge-port.network', + '25-agent-client.network', + ) start_networkd() self.wait_online('bridge-relay:routable', 'client-peer:enslaved') # For issue #30763. self.check_networkd_log('bridge-relay: DHCPv4 server: STARTED') -class NetworkdDHCPClientTests(unittest.TestCase, Utilities): +class NetworkdDHCPClientTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -7464,7 +8290,11 @@ def tearDown(self): tear_down_common() def test_dhcp_client_ipv6_only(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv6-only.network', + ) start_networkd() self.wait_online('veth-peer:carrier') @@ -7472,10 +8302,12 @@ def test_dhcp_client_ipv6_only(self): # information request mode # The name ipv6-only option may not be supported by older dnsmasq # start_dnsmasq('--dhcp-option=option:ipv6-only,300') - start_dnsmasq('--dhcp-option=108,00:00:02:00', - '--dhcp-option=option6:dns-server,[2600::ee]', - '--dhcp-option=option6:ntp-server,[2600::ff]', - ra_mode='ra-stateless') + start_dnsmasq( + '--dhcp-option=108,00:00:02:00', + '--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]', + ra_mode='ra-stateless', + ) self.wait_online('veth99:routable', 'veth-peer:routable') # DHCPv6 REPLY for INFORMATION-REQUEST may be received after the link entered configured state. @@ -7484,7 +8316,7 @@ def test_dhcp_client_ipv6_only(self): output = read_link_state_file('veth99') if 'DNS=2600::ee' in output: break - time.sleep(.2) + time.sleep(0.2) # Check link state file print('## link state file') @@ -7514,9 +8346,11 @@ def test_dhcp_client_ipv6_only(self): # solicit mode stop_dnsmasq() - start_dnsmasq('--dhcp-option=108,00:00:02:00', - '--dhcp-option=option6:dns-server,[2600::ee]', - '--dhcp-option=option6:ntp-server,[2600::ff]') + start_dnsmasq( + '--dhcp-option=108,00:00:02:00', + '--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]', + ) networkctl_reconfigure('veth99') self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7567,13 +8401,17 @@ def test_dhcp_client_ipv6_only(self): check_json(networkctl_json('veth99')) # Testing without rapid commit support - with open(os.path.join(network_unit_dir, '25-dhcp-client-ipv6-only.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client-ipv6-only.network'), mode='a', encoding='utf-8' + ) as f: f.write('\n[DHCPv6]\nRapidCommit=no\n') stop_dnsmasq() - start_dnsmasq('--dhcp-option=108,00:00:02:00', - '--dhcp-option=option6:dns-server,[2600::ee]', - '--dhcp-option=option6:ntp-server,[2600::ff]') + start_dnsmasq( + '--dhcp-option=108,00:00:02:00', + '--dhcp-option=option6:dns-server,[2600::ee]', + '--dhcp-option=option6:ntp-server,[2600::ff]', + ) networkctl_reload() self.wait_online('veth99:routable', 'veth-peer:routable') @@ -7620,25 +8458,29 @@ def test_dhcp_client_ipv6_only(self): check_json(networkctl_json('veth99')) def test_dhcp_client_ipv6_dbus_status(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv6-only.network', + ) start_networkd() self.wait_online('veth-peer:carrier') # Note that at this point the DHCPv6 client has not been started because no RA (with managed # bit set) has yet been received and the configuration does not include WithoutRA=true state = get_dhcp6_client_state('veth99') - print(f"DHCPv6 client state = {state}") + print(f'DHCPv6 client state = {state}') self.assertEqual(state, 'stopped') state = get_dhcp4_client_state('veth99') - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'selecting') start_dnsmasq('--dhcp-option=108,00:00:02:00') self.wait_online('veth99:routable', 'veth-peer:routable') state = get_dhcp6_client_state('veth99') - print(f"DHCPv6 client state = {state}") + print(f'DHCPv6 client state = {state}') self.assertEqual(state, 'bound') # DHCPv4 client will stop after an DHCPOFFER message received, so we need to wait for a while. @@ -7646,9 +8488,9 @@ def test_dhcp_client_ipv6_dbus_status(self): state = get_dhcp4_client_state('veth99') if state == 'stopped': break - time.sleep(.2) + time.sleep(0.2) - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'stopped') # restart dnsmasq to clear log @@ -7663,9 +8505,9 @@ def test_dhcp_client_ipv6_dbus_status(self): state = get_dhcp4_client_state('veth99') if state == 'stopped': break - time.sleep(.2) + time.sleep(0.2) - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'stopped') print('## dnsmasq log') @@ -7677,7 +8519,11 @@ def test_dhcp_client_ipv6_dbus_status(self): self.assertNotIn('DHCPACK(veth-peer)', output) def test_dhcp_client_ipv6_only_with_custom_client_identifier(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv6-only-custom-client-identifier.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv6-only-custom-client-identifier.network', + ) start_networkd() self.wait_online('veth-peer:carrier') @@ -7699,10 +8545,14 @@ def test_dhcp_client_ipv6_only_with_custom_client_identifier(self): self.assertIn('DHCPREPLY(veth-peer)', output) self.assertIn('sent size: 0 option: 14 rapid-commit', output) - @expectedFailureIfKernelReturnsInvalidFlags() def test_dhcp_client_ipv4_only(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network', - '25-sit-dhcp4.netdev', '25-sit-dhcp4.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv4-only.network', + '25-sit-dhcp4.netdev', + '25-sit-dhcp4.network', + ) self.setup_nftset('addr4', 'ipv4_addr') self.setup_nftset('network4', 'ipv4_addr', 'flags interval;') @@ -7710,11 +8560,13 @@ def test_dhcp_client_ipv4_only(self): start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', - '--dhcp-option=option:sip-server,192.168.5.21,192.168.5.22', - '--dhcp-option=option:domain-search,example.com', - '--dhcp-alternate-port=67,5555', - ipv4_range='192.168.5.110,192.168.5.119') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', + '--dhcp-option=option:sip-server,192.168.5.21,192.168.5.22', + '--dhcp-option=option:domain-search,example.com', + '--dhcp-alternate-port=67,5555', + ipv4_range='192.168.5.110,192.168.5.119', + ) self.wait_online('veth99:routable', 'veth-peer:routable', 'sit-dhcp4:carrier') self.wait_address('veth99', r'inet 192.168.5.11[0-9]*/24', ipv='-4') @@ -7723,7 +8575,7 @@ def test_dhcp_client_ipv4_only(self): print(output) self.assertIn('mtu 1492', output) self.assertIn('inet 192.168.5.250/24 brd 192.168.5.255 scope global veth99', output) - self.assertRegex(output, r'inet 192.168.5.11[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') + self.assertRegex(output, r'inet 192.168.5.11[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') # fmt: skip self.assertNotIn('2600::', output) output = check_output('ip -4 --json address show dev veth99') @@ -7798,7 +8650,7 @@ def test_dhcp_client_ipv4_only(self): print('## tunnel') output = check_output('ip -d link show sit-dhcp4') print(output) - self.assertRegex(output, fr'sit (ip6ip )?remote any local {address1} dev veth99') + self.assertRegex(output, rf'sit (ip6ip )?remote any local {address1} dev veth99') print('## dnsmasq log') output = read_dnsmasq_log_file() @@ -7810,11 +8662,13 @@ def test_dhcp_client_ipv4_only(self): # change address range, DNS servers, and Domains stop_dnsmasq() - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1,192.168.5.7,192.168.5.8', - '--dhcp-option=option:sip-server,192.168.5.23,192.168.5.24', - '--dhcp-option=option:domain-search,foo.example.com', - '--dhcp-alternate-port=67,5555', - ipv4_range='192.168.5.120,192.168.5.129',) + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.1,192.168.5.7,192.168.5.8', + '--dhcp-option=option:sip-server,192.168.5.23,192.168.5.24', + '--dhcp-option=option:domain-search,foo.example.com', + '--dhcp-alternate-port=67,5555', + ipv4_range='192.168.5.120,192.168.5.129', + ) # Sleep for 120 sec as the dnsmasq minimum lease time can only be set to 120 print('Wait for the DHCP lease to be expired') @@ -7829,7 +8683,7 @@ def test_dhcp_client_ipv4_only(self): self.assertIn('mtu 1492', output) self.assertIn('inet 192.168.5.250/24 brd 192.168.5.255 scope global veth99', output) self.assertNotIn(f'{address1}', output) - self.assertRegex(output, r'inet 192.168.5.12[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') + self.assertRegex(output, r'inet 192.168.5.12[0-9]/24 metric 24 brd 192.168.5.255 scope global secondary dynamic noprefixroute test-label') # fmt: skip self.assertNotIn('2600::', output) output = check_output('ip -4 --json address show dev veth99') @@ -7905,7 +8759,7 @@ def test_dhcp_client_ipv4_only(self): print('## tunnel') output = check_output('ip -d link show sit-dhcp4') print(output) - self.assertRegex(output, fr'sit (ip6ip )?remote any local {address2} dev veth99') + self.assertRegex(output, rf'sit (ip6ip )?remote any local {address2} dev veth99') print('## dnsmasq log') output = read_dnsmasq_log_file() @@ -7948,48 +8802,71 @@ def test_dhcp_client_ipv4_only(self): self.teardown_nftset('addr4', 'network4', 'ifindex') def test_dhcp_client_ipv4_dbus_status(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-ipv4-only.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-ipv4-only.network', + ) start_networkd() self.wait_online('veth-peer:carrier') state = get_dhcp4_client_state('veth99') - print(f"State = {state}") + print(f'State = {state}') self.assertEqual(state, 'rebooting') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', - '--dhcp-option=option:domain-search,example.com', - '--dhcp-alternate-port=67,5555', - ipv4_range='192.168.5.110,192.168.5.119') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.6,192.168.5.7', + '--dhcp-option=option:domain-search,example.com', + '--dhcp-alternate-port=67,5555', + ipv4_range='192.168.5.110,192.168.5.119', + ) self.wait_online('veth99:routable', 'veth-peer:routable') self.wait_address('veth99', r'inet 192.168.5.11[0-9]*/24', ipv='-4') state = get_dhcp4_client_state('veth99') - print(f"State = {state}") + print(f'State = {state}') self.assertEqual(state, 'bound') def test_dhcp_client_allow_list(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-allow-list.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-allow-list.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') since = datetime.datetime.now() start_dnsmasq() - self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', since=since) + self.check_networkd_log( + 'veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', + since=since, + ) copy_network_unit('25-dhcp-client-allow-list.network.d/00-allow-list.conf') since = datetime.datetime.now() networkctl_reload() - self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', since=since) + self.check_networkd_log( + 'veth99: DHCPv4 server IP address 192.168.5.1 not found in allow-list, ignoring offer.', + since=since, + ) copy_network_unit('25-dhcp-client-allow-list.network.d/10-deny-list.conf') since = datetime.datetime.now() networkctl_reload() - self.check_networkd_log('veth99: DHCPv4 server IP address 192.168.5.1 found in deny-list, ignoring offer.', since=since) + self.check_networkd_log( + 'veth99: DHCPv4 server IP address 192.168.5.1 found in deny-list, ignoring offer.', + since=since, + ) - @unittest.skipUnless("--dhcp-rapid-commit" in run("dnsmasq --help").stdout, reason="dnsmasq is missing dhcp-rapid-commit support") + @unittest.skipUnless( + '--dhcp-rapid-commit' in run('dnsmasq --help').stdout, + reason='dnsmasq is missing dhcp-rapid-commit support', + ) def test_dhcp_client_rapid_commit(self): copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network') start_networkd() @@ -8000,7 +8877,7 @@ def test_dhcp_client_rapid_commit(self): self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24', ipv='-4') state = get_dhcp4_client_state('veth99') - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'bound') output = read_dnsmasq_log_file() @@ -8016,7 +8893,7 @@ def check_bootp_client(self, check_log): self.assertRegex(output, r'inet 192.168.5.[0-9]*/24') state = get_dhcp4_client_state('veth99') - print(f"DHCPv4 client state = {state}") + print(f'DHCPv4 client state = {state}') self.assertEqual(state, 'bound') if check_log: @@ -8039,7 +8916,9 @@ def test_bootp_client(self): networkctl_reload() self.check_bootp_client(check_log=True) - with open(os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8' + ) as f: f.write('[DHCPv4]\nBOOTP=no\n') networkctl_reload() @@ -8054,7 +8933,9 @@ def test_bootp_client(self): self.assertIn('DHCPREQUEST(veth-peer)', output) self.assertIn('DHCPACK(veth-peer)', output) - with open(os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-bootp-client.network'), mode='a', encoding='utf-8' + ) as f: f.write('[DHCPv4]\nBOOTP=yes\n') since = datetime.datetime.now() @@ -8066,30 +8947,36 @@ def test_bootp_client(self): self.check_networkd_log('veth99: DHCPv4 client: RELEASE', since=since) def test_dhcp_client_ipv6_only_mode_without_ipv6_connectivity(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-server-ipv6-only-mode.network', - '25-dhcp-client-ipv6-only-mode.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-ipv6-only-mode.network', + '25-dhcp-client-ipv6-only-mode.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable', timeout='40s') self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24', ipv='-4') state = get_dhcp4_client_state('veth99') - print(f"State = {state}") + print(f'State = {state}') self.assertEqual(state, 'bound') def test_dhcp_client_ipv4_use_routes_gateway(self): first = True - for (routes, gateway, dns_and_ntp_routes, classless) in itertools.product([True, False], repeat=4): + for routes, gateway, dns_and_ntp_routes, classless in itertools.product([True, False], repeat=4): if first: first = False else: self.tearDown() + # fmt: off print(f'### test_dhcp_client_ipv4_use_routes_gateway(routes={routes}, gateway={gateway}, dns_and_ntp_routes={dns_and_ntp_routes}, classless={classless})') with self.subTest(routes=routes, gateway=gateway, dns_and_ntp_routes=dns_and_ntp_routes, classless=classless): self._test_dhcp_client_ipv4_use_routes_gateway(routes, gateway, dns_and_ntp_routes, classless) + # fmt: on - def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns_and_ntp_routes, classless): + def _test_dhcp_client_ipv4_use_routes_gateway( + self, use_routes, use_gateway, dns_and_ntp_routes, classless + ): testunit = '25-dhcp-client-ipv4-use-routes-use-gateway.network' testunits = ['25-veth.netdev', '25-dhcp-server-veth-peer.network', testunit] testunits.append(f'{testunit}.d/use-routes-{use_routes}.conf') @@ -8102,7 +8989,7 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns additional_options = [ '--dhcp-option=option:dns-server,192.168.5.10,8.8.8.8', '--dhcp-option=option:ntp-server,192.168.5.11,9.9.9.9', - '--dhcp-option=option:static-route,192.168.6.100,192.168.5.2,8.8.8.8,192.168.5.3' + '--dhcp-option=option:static-route,192.168.6.100,192.168.5.2,8.8.8.8,192.168.5.3', ] if classless: additional_options += [ @@ -8115,6 +9002,7 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns print(output) # Check UseRoutes= + # fmt: off if use_routes: if classless: self.assertRegex(output, r'default via 192.168.5.4 proto dhcp src 192.168.5.[0-9]* metric 1024') @@ -8136,20 +9024,22 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns self.assertNotRegex(output, r'8.0.0.0/8 via 192.168.5.3 proto dhcp src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'192.168.5.2 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'192.168.5.3 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + # fmt: on # Check UseGateway= if use_gateway and (not classless or not use_routes): self.assertRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') else: - self.assertNotRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'default via 192.168.5.1 proto dhcp src 192.168.5.[0-9]* metric 1024') # fmt: skip # Check route to gateway if (use_gateway or dns_and_ntp_routes) and (not classless or not use_routes): self.assertRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') else: - self.assertNotRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') + self.assertNotRegex(output, r'192.168.5.1 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') # fmt: skip # Check RoutesToDNS= and RoutesToNTP= + # fmt: off if dns_and_ntp_routes: self.assertRegex(output, r'192.168.5.10 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') self.assertRegex(output, r'192.168.5.11 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') @@ -8168,11 +9058,16 @@ def _test_dhcp_client_ipv4_use_routes_gateway(self, use_routes, use_gateway, dns self.assertNotRegex(output, r'192.168.5.11 proto dhcp scope link src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'8.8.8.8 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024') self.assertNotRegex(output, r'9.9.9.9 via 192.168.5.[0-9]* proto dhcp src 192.168.5.[0-9]* metric 1024') + # fmt: on check_json(networkctl_json()) def test_dhcp_client_settings_anonymize(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-anonymize.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-anonymize.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8186,9 +9081,11 @@ def test_dhcp_client_settings_anonymize(self): self.assertNotIn('26:mtu', output) def test_dhcp_keep_configuration_dynamic(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-server-veth-peer.network', - '25-dhcp-client-keep-configuration-dynamic.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-keep-configuration-dynamic.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8196,8 +9093,7 @@ def test_dhcp_keep_configuration_dynamic(self): output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip # Stopping dnsmasq as networkd won't be allowed to renew the DHCP lease. stop_dnsmasq() @@ -8209,18 +9105,20 @@ def test_dhcp_keep_configuration_dynamic(self): # The lease address should be kept after the lease expired output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip stop_networkd() # The lease address should be kept after networkd stopped output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip - with open(os.path.join(network_unit_dir, '25-dhcp-client-keep-configuration-dynamic.network'), mode='a', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client-keep-configuration-dynamic.network'), + mode='a', + encoding='utf-8', + ) as f: f.write('[Network]\nDHCP=no\n') start_networkd() @@ -8229,13 +9127,14 @@ def test_dhcp_keep_configuration_dynamic(self): # Still the lease address should be kept after networkd restarted output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip def test_dhcp_keep_configuration_dynamic_on_stop(self): - copy_network_unit('25-veth.netdev', - '25-dhcp-server-veth-peer.network', - '25-dhcp-client-keep-configuration-dynamic-on-stop.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-keep-configuration-dynamic-on-stop.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8243,14 +9142,14 @@ def test_dhcp_keep_configuration_dynamic_on_stop(self): output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') # fmt: skip stop_dnsmasq() stop_networkd() output = check_output('ip address show dev veth99 scope global') print(output) - self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') + self.assertRegex(output, r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') # fmt: skip start_networkd() self.wait_online('veth-peer:routable') @@ -8267,13 +9166,28 @@ def test_dhcp_client_reuse_address_as_static(self): self.wait_online('veth99:routable', 'veth-peer:routable') # link become 'routable' when at least one protocol provide an valid address. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = check_output('ip address show dev veth99 scope global') ipv4_address = re.search(r'192.168.5.[0-9]*/24', output).group() ipv6_address = re.search(r'2600::[0-9a-f:]*/128', output).group() - static_network = '\n'.join(['[Match]', 'Name=veth99', '[Network]', 'IPv6AcceptRA=no', 'Address=' + ipv4_address, 'Address=' + ipv6_address]) + static_network = f''' +[Match] +Name=veth99 +[Network] +IPv6AcceptRA=no +Address={ipv4_address} +Address={ipv6_address} +''' print(static_network) remove_network_unit('25-dhcp-client.network') @@ -8286,26 +9200,37 @@ def test_dhcp_client_reuse_address_as_static(self): output = check_output('ip -4 address show dev veth99 scope global') print(output) - self.assertRegex(output, f'inet {ipv4_address} brd 192.168.5.255 scope global veth99\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, f'inet {ipv4_address} brd 192.168.5.255 scope global veth99\n *valid_lft forever preferred_lft forever') # fmt: skip output = check_output('ip -6 address show dev veth99 scope global') print(output) - self.assertRegex(output, f'inet6 {ipv6_address} scope global *\n *' - 'valid_lft forever preferred_lft forever') + self.assertRegex(output, f'inet6 {ipv6_address} scope global *\n *valid_lft forever preferred_lft forever') # fmt: skip @expectedFailureIfModuleIsNotAvailable('vrf') def test_dhcp_client_vrf(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client-vrf.network', - '25-vrf.netdev', '25-vrf.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-vrf.network', + '25-vrf.netdev', + '25-vrf.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() self.wait_online('veth99:routable', 'veth-peer:routable', 'vrf99:carrier') # link become 'routable' when at least one protocol provide an valid address. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) print('## ip -d link show dev vrf99') output = check_output('ip -d link show dev vrf99') @@ -8315,16 +9240,20 @@ def test_dhcp_client_vrf(self): print('## ip address show vrf vrf99') output = check_output('ip address show vrf vrf99') print(output) + # fmt: off self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') self.assertRegex(output, 'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)') self.assertRegex(output, 'inet6 .* scope link') + # fmt: on print('## ip address show dev veth99') output = check_output('ip address show dev veth99') print(output) + # fmt: off self.assertRegex(output, 'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic veth99') self.assertRegex(output, 'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)') self.assertRegex(output, 'inet6 .* scope link') + # fmt: on print('## ip route show vrf vrf99') output = check_output('ip route show vrf vrf99') @@ -8339,8 +9268,11 @@ def test_dhcp_client_vrf(self): self.assertEqual(output, '') def test_dhcp_client_gateway_onlink_implicit(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', - '25-dhcp-client-gateway-onlink-implicit.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-gateway-onlink-implicit.network', + ) start_networkd() self.wait_online('veth-peer:carrier') start_dnsmasq() @@ -8358,8 +9290,11 @@ def test_dhcp_client_gateway_onlink_implicit(self): self.assertRegex(output, 'onlink') def test_dhcp_client_with_ipv4ll(self): - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', - '25-dhcp-client-with-ipv4ll.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client-with-ipv4ll.network', + ) start_networkd() # we need to increase timeout above default, as this will need to wait for # systemd-networkd to get the dhcpv4 transient failure event @@ -8372,20 +9307,39 @@ def test_dhcp_client_with_ipv4ll(self): start_dnsmasq() print('Wait for a DHCP lease to be acquired and the IPv4LL address to be dropped') - self.wait_address('veth99', r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', ipv='-4') - self.wait_address_dropped('veth99', r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link', scope='link', ipv='-4') + self.wait_address( + 'veth99', + r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', + ipv='-4', + ) + self.wait_address_dropped( + 'veth99', + r'inet 169\.254\.\d+\.\d+/16 metric 2048 brd 169\.254\.255\.255 scope link', + scope='link', + ipv='-4', + ) self.wait_online('veth99:routable') output = check_output('ip -4 address show dev veth99') print(output) - self.assertRegex(output, r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic veth99') + self.assertRegex(output, r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic veth99') # fmt: skip self.assertNotIn('169.254.', output) self.assertNotIn('scope link', output) stop_dnsmasq() print('Wait for the DHCP lease to be expired and an IPv4LL address to be acquired') - self.wait_address_dropped('veth99', r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', ipv='-4', timeout_sec=130) - self.wait_address('veth99', r'inet 169\.254\.133\.11/16 metric 2048 brd 169\.254\.255\.255 scope link', scope='link', ipv='-4') + self.wait_address_dropped( + 'veth99', + r'inet 192\.168\.5\.\d+/24 metric 1024 brd 192\.168\.5\.255 scope global dynamic', + ipv='-4', + timeout_sec=130, + ) + self.wait_address( + 'veth99', + r'inet 169\.254\.133\.11/16 metric 2048 brd 169\.254\.255\.255 scope link', + scope='link', + ipv='-4', + ) output = check_output('ip -4 address show dev veth99') print(output) @@ -8395,7 +9349,11 @@ def test_dhcp_client_with_ipv4ll(self): def test_dhcp_client_use_dns(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseDNS=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseDNS=') @@ -8407,9 +9365,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) # make resolved re-read the link state file resolvectl('revert', 'veth99') @@ -8427,12 +9394,19 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1', - '--dhcp-option=option6:dns-server,[2600::1]') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.1', + '--dhcp-option=option6:dns-server,[2600::1]', + ) check(self, True, True) check(self, True, False) @@ -8442,7 +9416,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_default_use_domains(self): def check(self, common, ipv4, ipv6): mkdir_p(networkd_conf_dropin_dir) - with open(os.path.join(networkd_conf_dropin_dir, 'default_use_domains.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(networkd_conf_dropin_dir, 'default_use_domains.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[Network]\nUseDomains=') f.write('yes\n' if common else 'no\n') f.write('[DHCPv4]\nUseDomains=') @@ -8452,16 +9430,27 @@ def check(self, common, ipv4, ipv6): restart_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:dns-server,192.168.5.1', - '--dhcp-option=option6:dns-server,[2600::1]', - '--dhcp-option=option:domain-search,example.com', - '--dhcp-option=option6:domain-search,example.com') + start_dnsmasq( + '--dhcp-option=option:dns-server,192.168.5.1', + '--dhcp-option=option6:dns-server,[2600::1]', + '--dhcp-option=option:domain-search,example.com', + '--dhcp-option=option6:domain-search,example.com', + ) self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) for _ in range(20): output = resolvectl('domain', 'veth99') @@ -8480,7 +9469,12 @@ def check(self, common, ipv4, ipv6): stop_dnsmasq() remove_networkd_conf_dropin('default_use_domains.conf') - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) check(self, True, False, False) check(self, False, True, True) check(self, False, True, False) @@ -8490,7 +9484,11 @@ def check(self, common, ipv4, ipv6): def test_dhcp_client_use_dnr(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseDNS=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseDNS=') @@ -8502,9 +9500,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) # make resolved re-read the link state file resolvectl('revert', 'veth99') @@ -8523,16 +9530,31 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - dnr_v4 = dnr_v4_instance_data(adn = "dns.google", addrs = ["8.8.8.8", "8.8.4.4"]) - dnr_v4 += dnr_v4_instance_data(adn = "homer.simpson", addrs = ["0.7.4.2"], alpns = ("dot","h2","h3"), dohpath = "/springfield{?dns}") - dnr_v6 = dnr_v6_instance_data(adn = "dns.google", addrs = ["2001:4860:4860::8888", "2001:4860:4860::8844"]) - masq = lambda bs: ':'.join(f"{b:02x}" for b in bs) - start_dnsmasq(f'--dhcp-option=162,{masq(dnr_v4)}', - f'--dhcp-option=option6:144,{masq(dnr_v6)}') + dnr_v4 = dnr_v4_instance_data( + adn='dns.google', + addrs=['8.8.8.8', '8.8.4.4'], + ) + dnr_v4 += dnr_v4_instance_data( + adn='homer.simpson', + addrs=['0.7.4.2'], + alpns=('dot', 'h2', 'h3'), + dohpath='/springfield{?dns}', + ) + dnr_v6 = dnr_v6_instance_data( + adn='dns.google', + addrs=['2001:4860:4860::8888', '2001:4860:4860::8844'], + ) + masq = lambda bs: ':'.join(f'{b:02x}' for b in bs) + start_dnsmasq(f'--dhcp-option=162,{masq(dnr_v4)}', f'--dhcp-option=option6:144,{masq(dnr_v6)}') check(self, True, True) check(self, True, False) @@ -8542,7 +9564,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_use_sip(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseSIP=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseSIP=') @@ -8553,9 +9579,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = networkctl_status('veth99') print(output) @@ -8572,13 +9607,20 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=option:sip-server,192.168.5.1', - '--dhcp-option=option6:sip-server,[2600::1]', - '--dhcp-option=option6:sip-server-domain,foo.example.com') + start_dnsmasq( + '--dhcp-option=option:sip-server,192.168.5.1', + '--dhcp-option=option6:sip-server,[2600::1]', + '--dhcp-option=option6:sip-server-domain,foo.example.com', + ) check(self, True, True) check(self, True, False) @@ -8588,7 +9630,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_use_captive_portal(self): def check(self, ipv4, ipv6, needs_reconfigure=False): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseCaptivePortal=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseCaptivePortal=') @@ -8600,9 +9646,18 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): networkctl_reconfigure('veth99') self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = networkctl_status('veth99') print(output) @@ -8613,12 +9668,19 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=114,http://systemd.io', - '--dhcp-option=option6:103,http://systemd.io') + start_dnsmasq( + '--dhcp-option=114,http://systemd.io', + '--dhcp-option=option6:103,http://systemd.io', + ) check(self, True, True) check(self, True, False) @@ -8628,7 +9690,11 @@ def check(self, ipv4, ipv6, needs_reconfigure=False): def test_dhcp_client_reject_captive_portal(self): def check(self, ipv4, ipv6): os.makedirs(os.path.join(network_unit_dir, '25-dhcp-client.network.d'), exist_ok=True) - with open(os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), mode='w', encoding='utf-8') as f: + with open( + os.path.join(network_unit_dir, '25-dhcp-client.network.d/override.conf'), + mode='w', + encoding='utf-8', + ) as f: f.write('[DHCPv4]\nUseCaptivePortal=') f.write('yes' if ipv4 else 'no') f.write('\n[DHCPv6]\nUseCaptivePortal=') @@ -8638,9 +9704,18 @@ def check(self, ipv4, ipv6): networkctl_reload() self.wait_online('veth99:routable') - # link becomes 'routable' when at least one protocol provide an valid address. Hence, we need to explicitly wait for both addresses. - self.wait_address('veth99', r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', ipv='-4') - self.wait_address('veth99', r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', ipv='-6') + # link becomes 'routable' when at least one protocol provide an valid address. + # Hence, we need to explicitly wait for both addresses. + self.wait_address( + 'veth99', + r'inet 192.168.5.[0-9]*/24 metric 1024 brd 192.168.5.255 scope global dynamic', + ipv='-4', + ) + self.wait_address( + 'veth99', + r'inet6 2600::[0-9a-f]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)', + ipv='-6', + ) output = networkctl_status('veth99') print(output) @@ -8649,20 +9724,27 @@ def check(self, ipv4, ipv6): check_json(networkctl_json()) - copy_network_unit('25-veth.netdev', '25-dhcp-server-veth-peer.network', '25-dhcp-client.network', copy_dropins=False) + copy_network_unit( + '25-veth.netdev', + '25-dhcp-server-veth-peer.network', + '25-dhcp-client.network', + copy_dropins=False, + ) start_networkd() self.wait_online('veth-peer:carrier') - start_dnsmasq('--dhcp-option=114,http://|invalid/url', - '--dhcp-option=option6:103,http://|invalid/url') + start_dnsmasq( + '--dhcp-option=114,http://|invalid/url', + '--dhcp-option=option6:103,http://|invalid/url', + ) check(self, True, True) check(self, True, False) check(self, False, True) check(self, False, False) -class NetworkdDHCPPDTests(unittest.TestCase, Utilities): +class NetworkdDHCPPDTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -8689,10 +9771,14 @@ def check_dhcp6_prefix(self, link): self.assertGreater(prefixInfo[0]['PreferredLifetimeUSec'], 0) self.assertGreater(prefixInfo[0]['ValidLifetimeUSec'], 0) - @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available") + @unittest.skipUnless(shutil.which('dhcpd'), reason='dhcpd is not available') def test_dhcp6pd_no_address(self): # For issue #29979. - copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream-no-address.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp6pd-server.network', + '25-dhcp6pd-upstream-no-address.network', + ) start_networkd() self.wait_online('veth-peer:routable') @@ -8711,11 +9797,15 @@ def test_dhcp6pd_no_address(self): self.check_dhcp6_prefix('veth99') - @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available") + @unittest.skipUnless(shutil.which('dhcpd'), reason='dhcpd is not available') def test_dhcp6pd_no_assign(self): # Similar to test_dhcp6pd_no_assign(), but in this case UseAddress=yes (default), # However, the server does not provide IA_NA. For issue #31349. - copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream-no-assign.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp6pd-server.network', + '25-dhcp6pd-upstream-no-assign.network', + ) start_networkd() self.wait_online('veth-peer:routable') @@ -8734,21 +9824,40 @@ def test_dhcp6pd_no_assign(self): self.check_dhcp6_prefix('veth99') - @unittest.skipUnless(shutil.which('dhcpd'), reason="dhcpd is not available") + @unittest.skipUnless(shutil.which('dhcpd'), reason='dhcpd is not available') def test_dhcp6pd(self): - copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream.network', - '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network', - '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network', - '11-dummy.netdev', '25-dhcp-pd-downstream-test1.network', - '25-dhcp-pd-downstream-dummy97.network', - '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network', - '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp6pd-server.network', + '25-dhcp6pd-upstream.network', + '25-veth-downstream-veth97.netdev', + '25-dhcp-pd-downstream-veth97.network', + '25-dhcp-pd-downstream-veth97-peer.network', + '25-veth-downstream-veth98.netdev', + '25-dhcp-pd-downstream-veth98.network', + '25-dhcp-pd-downstream-veth98-peer.network', + '11-dummy.netdev', + '25-dhcp-pd-downstream-test1.network', + '25-dhcp-pd-downstream-dummy97.network', + '12-dummy.netdev', + '25-dhcp-pd-downstream-dummy98.network', + '13-dummy.netdev', + '25-dhcp-pd-downstream-dummy99.network', + ) start_networkd() self.wait_online('veth-peer:routable') start_isc_dhcpd(conf_file='isc-dhcpd-dhcp6pd.conf', ipv='-6') - self.wait_online('veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded', - 'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable') + self.wait_online( + 'veth99:routable', + 'test1:routable', + 'dummy98:routable', + 'dummy99:degraded', + 'veth97:routable', + 'veth97-peer:routable', + 'veth98:routable', + 'veth98-peer:routable', + ) self.setup_nftset('addr6', 'ipv6_addr') self.setup_nftset('network6', 'ipv6_addr', 'flags interval;') @@ -8774,31 +9883,45 @@ def test_dhcp6pd(self): print('### ip -6 address show dev veth99 scope global') output = check_output('ip -6 address show dev veth99 scope global') print(output) + # fmt: off # IA_NA self.assertRegex(output, 'inet6 3ffe:501:ffff:100::[0-9]*/128 scope global (dynamic noprefixroute|noprefixroute dynamic)') # address in IA_PD (Token=static) self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic') # address in IA_PD (Token=eui64) self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic') + # fmt: on # address in IA_PD (temporary) # Note that the temporary addresses may appear after the link enters configured state - self.wait_address('veth99', 'inet6 3ffe:501:ffff:[2-9a-f]10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth99', + 'inet6 3ffe:501:ffff:[2-9a-f]10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev test1 scope global') output = check_output('ip -6 address show dev test1 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('test1', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'test1', + 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy98 scope global') output = check_output('ip -6 address show dev dummy98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy98', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy98', + 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy99 scope global') output = check_output('ip -6 address show dev dummy99 scope global') @@ -8810,41 +9933,57 @@ def test_dhcp6pd(self): output = check_output('ip -6 address show dev veth97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth97', 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97', + 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth97-peer scope global') output = check_output('ip -6 address show dev veth97-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth97-peer', 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97-peer', + 'inet6 3ffe:501:ffff:[2-9a-f]08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98 scope global') output = check_output('ip -6 address show dev veth98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth98', 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98', + 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98-peer scope global') output = check_output('ip -6 address show dev veth98-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth98-peer', 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98-peer', + 'inet6 3ffe:501:ffff:[2-9a-f]09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show type unreachable') output = check_output('ip -6 route show type unreachable') @@ -8899,9 +10038,13 @@ def test_dhcp6pd(self): output = check_output('ip -6 address show dev dummy97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy97', 'inet6 3ffe:501:ffff:[2-9a-f]01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy97', + 'inet6 3ffe:501:ffff:[2-9a-f]01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show dev dummy97') output = check_output('ip -6 route show dev dummy97') @@ -8916,9 +10059,13 @@ def test_dhcp6pd(self): output = check_output('ip -6 address show dev dummy98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 3ffe:501:ffff:[2-9a-f]00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy98', 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy98', + 'inet6 3ffe:501:ffff:[2-9a-f]00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy99 scope global') output = check_output('ip -6 address show dev dummy99 scope global') @@ -8964,34 +10111,46 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde print('### ip -4 address show dev veth99 scope global') output = check_output('ip -4 address show dev veth99 scope global') print(output) - self.assertRegex(output, fr'inet {address_prefix}[0-9]*/8 (metric 1024 |)brd 10.255.255.255 scope global dynamic veth99') + self.assertRegex(output, rf'inet {address_prefix}[0-9]*/8 (metric 1024 |)brd 10.255.255.255 scope global dynamic veth99') # fmt: skip print('### ip -6 address show dev veth99 scope global') output = check_output('ip -6 address show dev veth99 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+10:1034:56ff:fe78:9abc/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) # Note that the temporary addresses may appear after the link enters configured state - self.wait_address('veth99', 'inet6 2001:db8:6464:[0-9a-f]+10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth99', + 'inet6 2001:db8:6464:[0-9a-f]+10:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev test1 scope global') output = check_output('ip -6 address show dev test1 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('test1', 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'test1', + 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy98 scope global') output = check_output('ip -6 address show dev dummy98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+00:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy98', 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy98', + 'inet6 2001:db8:6464:[0-9a-f]+00:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev dummy99 scope global') output = check_output('ip -6 address show dev dummy99 scope global') @@ -9003,41 +10162,57 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde output = check_output('ip -6 address show dev veth97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9ace/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth97', 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97', + 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth97-peer scope global') output = check_output('ip -6 address show dev veth97-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+08:1034:56ff:fe78:9acf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth97-peer', 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth97-peer', + 'inet6 2001:db8:6464:[0-9a-f]+08:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98 scope global') output = check_output('ip -6 address show dev veth98 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abe/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('veth98', 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98', + 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 address show dev veth98-peer scope global') output = check_output('ip -6 address show dev veth98-peer scope global') print(output) # NDisc address (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1a:2b:3c:4e/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (Token=eui64) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+09:1034:56ff:fe78:9abf/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # NDisc address (temporary) - self.wait_address('veth98-peer', 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'veth98-peer', + 'inet6 2001:db8:6464:[0-9a-f]+09:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show type unreachable') output = check_output('ip -6 route show type unreachable') @@ -9088,9 +10263,13 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde output = check_output('ip -6 address show dev dummy97 scope global') print(output) # address in IA_PD (Token=static) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+01:1a:2b:3c:4d/64 (metric 256 |)scope global dynamic mngtmpaddr') # fmt: skip # address in IA_PD (temporary) - self.wait_address('dummy97', 'inet6 2001:db8:6464:[0-9a-f]+01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', ipv='-6') + self.wait_address( + 'dummy97', + 'inet6 2001:db8:6464:[0-9a-f]+01:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global temporary dynamic', + ipv='-6', + ) print('### ip -6 route show dev dummy97') output = check_output('ip -6 route show dev dummy97') @@ -9109,8 +10288,8 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde print(f'### ip -6 address show dev {tunnel_name}') output = check_output(f'ip -6 address show dev {tunnel_name}') print(output) - self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+0[23]:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global dynamic') - self.assertRegex(output, fr'inet6 ::{address_prefix_re}/96 scope global') + self.assertRegex(output, 'inet6 2001:db8:6464:[0-9a-f]+0[23]:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*:[0-9a-f]*/64 (metric 256 |)scope global dynamic') # fmt: skip + self.assertRegex(output, rf'inet6 ::{address_prefix_re}/96 scope global') print(f'### ip -6 route show dev {tunnel_name}') output = check_output(f'ip -6 route show dev {tunnel_name}') @@ -9122,7 +10301,7 @@ def verify_dhcp4_6rd(self, tunnel_name, address_prefix, address_prefix_re, borde output = check_output('ip -6 route show default') print(output) self.assertIn('default', output) - self.assertRegex(output, fr'via ::{border_router} dev {tunnel_name}') + self.assertRegex(output, rf'via ::{border_router} dev {tunnel_name}') def test_dhcp4_6rd(self): def get_dhcp_6rd_prefix(link): @@ -9139,14 +10318,25 @@ def get_dhcp_6rd_prefix(link): return prefixInfo - copy_network_unit('25-veth.netdev', '25-dhcp4-6rd-server.network', '25-dhcp4-6rd-upstream.network', - '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network', - '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network', - '11-dummy.netdev', '25-dhcp-pd-downstream-test1.network', - '25-dhcp-pd-downstream-dummy97.network', - '12-dummy.netdev', '25-dhcp-pd-downstream-dummy98.network', - '13-dummy.netdev', '25-dhcp-pd-downstream-dummy99.network', - '80-6rd-tunnel.network') + copy_network_unit( + '25-veth.netdev', + '25-dhcp4-6rd-server.network', + '25-dhcp4-6rd-upstream.network', + '25-veth-downstream-veth97.netdev', + '25-dhcp-pd-downstream-veth97.network', + '25-dhcp-pd-downstream-veth97-peer.network', + '25-veth-downstream-veth98.netdev', + '25-dhcp-pd-downstream-veth98.network', + '25-dhcp-pd-downstream-veth98-peer.network', + '11-dummy.netdev', + '25-dhcp-pd-downstream-test1.network', + '25-dhcp-pd-downstream-dummy97.network', + '12-dummy.netdev', + '25-dhcp-pd-downstream-dummy98.network', + '13-dummy.netdev', + '25-dhcp-pd-downstream-dummy99.network', + '80-6rd-tunnel.network', + ) start_networkd() self.wait_online('veth-peer:routable') @@ -9155,19 +10345,29 @@ def get_dhcp_6rd_prefix(link): # 6rd-prefix: 2001:db8::/32 # br-addresss: 10.0.0.1 - start_dnsmasq('--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:01', - ipv4_range='10.100.100.100,10.100.100.200', - ipv4_router='10.0.0.1') - self.wait_online('veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded', - 'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable') + start_dnsmasq( + '--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:01', + ipv4_range='10.100.100.100,10.100.100.200', + ipv4_router='10.0.0.1', + ) + self.wait_online( + 'veth99:routable', + 'test1:routable', + 'dummy98:routable', + 'dummy99:degraded', + 'veth97:routable', + 'veth97-peer:routable', + 'veth98:routable', + 'veth98-peer:routable', + ) # Check the DBus interface for assigned prefix information prefixInfo = get_dhcp_6rd_prefix('veth99') - self.assertEqual(prefixInfo['Prefix'], [32,1,13,184,0,0,0,0,0,0,0,0,0,0,0,0]) # 2001:db8:: + self.assertEqual(prefixInfo['Prefix'], [32, 1, 13, 184, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) # 2001:db8:: # fmt: skip self.assertEqual(prefixInfo['PrefixLength'], 32) self.assertEqual(prefixInfo['IPv4MaskLength'], 8) - self.assertEqual(prefixInfo['BorderRouters'], [[10,0,0,1]]) + self.assertEqual(prefixInfo['BorderRouters'], [[10, 0, 0, 1]]) # Test case for a downstream which appears later check_output('ip link add dummy97 type dummy') @@ -9182,30 +10382,56 @@ def get_dhcp_6rd_prefix(link): self.wait_online(f'{tunnel_name}:routable') - self.verify_dhcp4_6rd(tunnel_name, '10.100.100.1', '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', '(10.0.0.1|a00:1)') + self.verify_dhcp4_6rd( + tunnel_name, + '10.100.100.1', + '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', + '(10.0.0.1|a00:1)', + ) # Test case for reconfigure networkctl_reconfigure('dummy98', 'dummy99') self.wait_online('dummy98:routable', 'dummy99:degraded') - self.verify_dhcp4_6rd(tunnel_name, '10.100.100.1', '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', '(10.0.0.1|a00:1)') + self.verify_dhcp4_6rd( + tunnel_name, + '10.100.100.1', + '(10.100.100.1[0-9][0-9]|a64:64[6-9a-c][0-9a-f])', + '(10.0.0.1|a00:1)', + ) # Change the address range and (border) router, then if check the same tunnel is reused. stop_dnsmasq() - start_dnsmasq('--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:02', - ipv4_range='10.100.100.200,10.100.100.250', - ipv4_router='10.0.0.2') + start_dnsmasq( + '--dhcp-option=212,08:20:20:01:0d:b8:00:00:00:00:00:00:00:00:00:00:00:00:0a:00:00:02', + ipv4_range='10.100.100.200,10.100.100.250', + ipv4_router='10.0.0.2', + ) print('Wait for the DHCP lease to be renewed/rebind') time.sleep(120) - self.wait_online('veth99:routable', 'test1:routable', 'dummy97:routable', 'dummy98:routable', 'dummy99:degraded', - 'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable') + self.wait_online( + 'veth99:routable', + 'test1:routable', + 'dummy97:routable', + 'dummy98:routable', + 'dummy99:degraded', + 'veth97:routable', + 'veth97-peer:routable', + 'veth98:routable', + 'veth98-peer:routable', + ) + + self.verify_dhcp4_6rd( + tunnel_name, + '10.100.100.2', + '(10.100.100.2[0-5][0-9]|a64:64[c-f][0-9a-f])', + '(10.0.0.2|a00:2)', + ) - self.verify_dhcp4_6rd(tunnel_name, '10.100.100.2', '(10.100.100.2[0-5][0-9]|a64:64[c-f][0-9a-f])', '(10.0.0.2|a00:2)') class NetworkdIPv6PrefixTests(unittest.TestCase, Utilities): - def setUp(self): setup_common() @@ -9213,8 +10439,13 @@ def tearDown(self): tear_down_common() def test_ipv6_route_prefix(self): - copy_network_unit('25-veth.netdev', '25-ipv6ra-prefix-client.network', '25-ipv6ra-prefix.network', - '12-dummy.netdev', '25-ipv6ra-uplink.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6ra-prefix-client.network', + '25-ipv6ra-prefix.network', + '12-dummy.netdev', + '25-ipv6ra-uplink.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable', 'dummy98:routable') @@ -9239,7 +10470,7 @@ def test_ipv6_route_prefix(self): print('### ip -6 nexthop show dev veth-peer') output = check_output('ip -6 nexthop show dev veth-peer') print(output) - self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') # fmt: skip print('### ip -6 address show dev veth99') output = check_output('ip -6 address show dev veth99') @@ -9272,8 +10503,13 @@ def test_ipv6_route_prefix(self): self.assertEqual(prefix_length, 96) def test_ipv6_route_prefix_deny_list(self): - copy_network_unit('25-veth.netdev', '25-ipv6ra-prefix-client-deny-list.network', '25-ipv6ra-prefix.network', - '12-dummy.netdev', '25-ipv6ra-uplink.network') + copy_network_unit( + '25-veth.netdev', + '25-ipv6ra-prefix-client-deny-list.network', + '25-ipv6ra-prefix.network', + '12-dummy.netdev', + '25-ipv6ra-uplink.network', + ) start_networkd() self.wait_online('veth99:routable', 'veth-peer:routable', 'dummy98:routable') @@ -9295,7 +10531,7 @@ def test_ipv6_route_prefix_deny_list(self): print('### ip -6 nexthop show dev veth-peer') output = check_output('ip -6 nexthop show dev veth-peer') print(output) - self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') + self.assertRegex(output, r'id [0-9]* via fe80::1034:56ff:fe78:9abc dev veth-peer scope link proto ra') # fmt: skip print('### ip -6 address show dev veth99') output = check_output('ip -6 address show dev veth99') @@ -9311,8 +10547,8 @@ def test_ipv6_route_prefix_deny_list(self): print(output) self.assertIn('example.com', output) -class NetworkdMTUTests(unittest.TestCase, Utilities): +class NetworkdMTUTests(unittest.TestCase, Utilities): def setUp(self): setup_common() @@ -9339,7 +10575,7 @@ def check_mtu(self, mtu, ipv6_mtu=None, reset=True): self.reset_check_mtu(mtu, ipv6_mtu) def reset_check_mtu(self, mtu, ipv6_mtu=None): - ''' test setting mtu/ipv6_mtu with interface already up ''' + """test setting mtu/ipv6_mtu with interface already up""" stop_networkd() # note - changing the device mtu resets the ipv6 mtu @@ -9365,39 +10601,46 @@ def test_mtu_link(self): self.check_mtu('1600', reset=False) def test_ipv6_mtu(self): - ''' set ipv6 mtu without setting device mtu ''' + """set ipv6 mtu without setting device mtu""" copy_network_unit('12-dummy.netdev', '12-dummy.network.d/ipv6-mtu-1400.conf') self.check_mtu('1500', '1400') def test_ipv6_mtu_toolarge(self): - ''' try set ipv6 mtu over device mtu (it shouldn't work) ''' + """try set ipv6 mtu over device mtu (it shouldn't work)""" copy_network_unit('12-dummy.netdev', '12-dummy.network.d/ipv6-mtu-1550.conf') self.check_mtu('1500', '1500') def test_mtu_network_ipv6_mtu(self): - ''' set ipv6 mtu and set device mtu via network file ''' - copy_network_unit('12-dummy.netdev', '12-dummy.network.d/mtu.conf', '12-dummy.network.d/ipv6-mtu-1550.conf') + """set ipv6 mtu and set device mtu via network file""" + copy_network_unit( + '12-dummy.netdev', + '12-dummy.network.d/mtu.conf', + '12-dummy.network.d/ipv6-mtu-1550.conf', + ) self.check_mtu('1600', '1550') def test_mtu_netdev_ipv6_mtu(self): - ''' set ipv6 mtu and set device mtu via netdev file ''' + """set ipv6 mtu and set device mtu via netdev file""" copy_network_unit('12-dummy-mtu.netdev', '12-dummy.network.d/ipv6-mtu-1550.conf') self.check_mtu('1600', '1550', reset=False) def test_mtu_link_ipv6_mtu(self): - ''' set ipv6 mtu and set device mtu via link file ''' + """set ipv6 mtu and set device mtu via link file""" copy_network_unit('12-dummy.netdev', '12-dummy-mtu.link', '12-dummy.network.d/ipv6-mtu-1550.conf') self.check_mtu('1600', '1550', reset=False) -class NetworkdSysctlTest(unittest.TestCase, Utilities): +class NetworkdSysctlTest(unittest.TestCase, Utilities): def setUp(self): setup_common() def tearDown(self): tear_down_common() - @unittest.skipUnless(compare_kernel_version("6.12"), reason="On kernels <= 6.12, bpf_current_task_under_cgroup() isn't available for program types BPF_PROG_TYPE_CGROUP_SYSCTL") + @unittest.skipUnless( + compare_kernel_version('6.12'), + reason='On kernels <= 6.12, bpf_current_task_under_cgroup() is not available for program types BPF_PROG_TYPE_CGROUP_SYSCTL', + ) def test_sysctl_monitor(self): copy_network_unit('12-dummy.network', '12-dummy.netdev', '12-dummy.link') start_networkd() @@ -9414,26 +10657,78 @@ def test_sysctl_monitor(self): call('sysctl -w net.ipv6.conf.dummy98.hop_limit=4') call('sysctl -w net.ipv6.conf.dummy98.max_addresses=10') - log=read_networkd_log() + log = read_networkd_log() + # fmt: off self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv6/conf/dummy98/accept_ra' from '0' to '1', conflicting with our setting to '0'") self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv6/conf/dummy98/mtu' from '1550' to '1360', conflicting with our setting to '1550'") self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv4/conf/dummy98/promote_secondaries' from '1' to '0', conflicting with our setting to '1'") self.assertRegex(log, r"Foreign process 'sysctl\[\d+\]' changed sysctl '/proc/sys/net/ipv6/conf/dummy98/proxy_ndp' from '0' to '1', conflicting with our setting to '0'") + # fmt: on self.assertNotIn("changed sysctl '/proc/sys/net/ipv6/conf/dummy98/hop_limit'", log) self.assertNotIn("changed sysctl '/proc/sys/net/ipv6/conf/dummy98/max_addresses'", log) - self.assertNotIn("Sysctl monitor BPF returned error", log) + self.assertNotIn('Sysctl monitor BPF returned error', log) + if __name__ == '__main__': parser = argparse.ArgumentParser() - parser.add_argument('--build-dir', help='Path to build dir', dest='build_dir') - parser.add_argument('--source-dir', help='Path to source dir/git tree', dest='source_dir') - parser.add_argument('--valgrind', help='Enable valgrind', dest='use_valgrind', type=bool, nargs='?', const=True, default=use_valgrind) - parser.add_argument('--debug', help='Generate debugging logs', dest='enable_debug', type=bool, nargs='?', const=True, default=enable_debug) - parser.add_argument('--asan-options', help='ASAN options', dest='asan_options') - parser.add_argument('--lsan-options', help='LSAN options', dest='lsan_options') - parser.add_argument('--ubsan-options', help='UBSAN options', dest='ubsan_options') - parser.add_argument('--with-coverage', help='Loosen certain sandbox restrictions to make gcov happy', dest='with_coverage', type=bool, nargs='?', const=True, default=with_coverage) - parser.add_argument('--no-journal', help='Do not show journal of systemd-networkd on stop', dest='show_journal', action='store_false') + parser.add_argument( + '--build-dir', + help='Path to build dir', + dest='build_dir', + ) + parser.add_argument( + '--source-dir', + help='Path to source dir/git tree', + dest='source_dir', + ) + parser.add_argument( + '--valgrind', + help='Enable valgrind', + dest='use_valgrind', + type=bool, + nargs='?', + const=True, + default=use_valgrind, + ) + parser.add_argument( + '--debug', + help='Generate debugging logs', + dest='enable_debug', + type=bool, + nargs='?', + const=True, + default=enable_debug, + ) + parser.add_argument( + '--asan-options', + help='ASAN options', + dest='asan_options', + ) + parser.add_argument( + '--lsan-options', + help='LSAN options', + dest='lsan_options', + ) + parser.add_argument( + '--ubsan-options', + help='UBSAN options', + dest='ubsan_options', + ) + parser.add_argument( + '--with-coverage', + help='Loosen certain sandbox restrictions to make gcov happy', + dest='with_coverage', + type=bool, + nargs='?', + const=True, + default=with_coverage, + ) + parser.add_argument( + '--no-journal', + help='Do not show journal of systemd-networkd on stop', + dest='show_journal', + action='store_false', + ) ns, unknown_args = parser.parse_known_args(namespace=unittest) if ns.build_dir: @@ -9447,16 +10742,18 @@ def test_sysctl_monitor(self): udevadm_bin = os.path.join(ns.build_dir, 'udevadm') build_dir = ns.build_dir + # fmt: off if ns.source_dir: source_dir = ns.source_dir - assert os.path.exists(os.path.join(source_dir, "meson_options.txt")), f"{source_dir} doesn't appear to be a systemd source tree." - elif os.path.exists(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../meson_options.txt"))): - source_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../")) + assert os.path.exists(os.path.join(source_dir, 'meson_options.txt')), f"{source_dir} doesn't appear to be a systemd source tree." + elif os.path.exists(os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../meson_options.txt'))): + source_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../')) else: source_dir = None + # fmt: on if networkd_bin is None or resolved_bin is None or timesyncd_bin is None: - print("networkd tests require networkd/resolved/timesyncd to be enabled") + print('networkd tests require networkd/resolved/timesyncd to be enabled') sys.exit(77) use_valgrind = ns.use_valgrind @@ -9464,7 +10761,7 @@ def test_sysctl_monitor(self): asan_options = ns.asan_options lsan_options = ns.lsan_options ubsan_options = ns.ubsan_options - with_coverage = ns.with_coverage or "COVERAGE_BUILD_DIR" in os.environ + with_coverage = ns.with_coverage or 'COVERAGE_BUILD_DIR' in os.environ show_journal = ns.show_journal if use_valgrind: @@ -9500,6 +10797,6 @@ def test_sysctl_monitor(self): argv=[ sys.argv[0], *unknown_args, - *(["-k", match] if (match := os.getenv("TEST_MATCH_TESTCASE")) else []) + *(['-k', match] if (match := os.getenv('TEST_MATCH_TESTCASE')) else []), ], ) diff --git a/test/test-shutdown.py b/test/test-shutdown.py index d19a03742c246..1fe96c8828d73 100755 --- a/test/test-shutdown.py +++ b/test/test-shutdown.py @@ -13,27 +13,34 @@ def run(args): ret = 1 - logger = logging.getLogger("test-shutdown") + logger = logging.getLogger('test-shutdown') logfile = None if args.logfile: - logger.debug("Logging pexpect IOs to %s", args.logfile) + logger.debug('Logging pexpect IOs to %s', args.logfile) logfile = open(args.logfile, 'w') elif args.verbose: logfile = sys.stdout - logger.info("spawning test") - console = pexpect.spawn(args.command, args.arg, logfile=logfile, env={ - "TERM": "dumb", - }, encoding='utf-8', timeout=60) + logger.info('spawning test') + console = pexpect.spawn( + args.command, + args.arg, + logfile=logfile, + env={ + 'TERM': 'dumb', + }, + encoding='utf-8', + timeout=60, + ) - logger.debug("child pid %d", console.pid) + logger.debug('child pid %d', console.pid) try: - logger.info("waiting for login prompt") + logger.info('waiting for login prompt') console.expect('H login: ', 10) - logger.info("log in and start screen") + logger.info('log in and start screen') console.sendline('root') console.expect('bash.*# ', 10) console.sendline('screen') @@ -46,42 +53,42 @@ def run(args): console.sendline('systemctl is-system-running --wait') console.expect(r'\b(running|degraded)\b', 60) -# console.interact() + # console.interact() console.sendline('tty') console.expect(r'/dev/(pts/\d+)') pty = console.match.group(1) - logger.info("window 1 at tty %s", pty) + logger.info('window 1 at tty %s', pty) - logger.info("schedule reboot") + logger.info('schedule reboot') console.sendline('shutdown -r') console.expect("Reboot scheduled for (?P.*), use 'shutdown -c' to cancel", 2) date = console.match.group('date') - logger.info("reboot scheduled for %s", date) + logger.info('reboot scheduled for %s', date) console.sendcontrol('a') console.send('0') - logger.info("verify broadcast message") + logger.info('verify broadcast message') console.expect(f'Broadcast message from root@H on {pty}', 2) console.expect(f'The system will reboot at {date}', 2) - logger.info("check show output") + logger.info('check show output') console.sendline('shutdown --show') console.expect(f"Reboot scheduled for {date}, use 'shutdown -c' to cancel", 2) - logger.info("cancel shutdown") + logger.info('cancel shutdown') console.sendline('shutdown -c') console.sendcontrol('a') console.send('1') console.expect('System shutdown has been cancelled', 2) - logger.info("call for reboot") + logger.info('call for reboot') console.sendline('sleep 10; shutdown -r now') console.sendcontrol('a') console.send('0') - console.expect("The system will reboot now!", 12) + console.expect('The system will reboot now!', 12) - logger.info("waiting for reboot") + logger.info('waiting for reboot') console.expect('H login: ', 60) console.sendline('root') @@ -89,16 +96,16 @@ def run(args): console.sendline('> /testok') - logger.info("power off") + logger.info('power off') console.sendline('poweroff') - logger.info("expect termination now") + logger.info('expect termination now') console.expect(pexpect.EOF) ret = 0 except Exception as e: logger.error(e) - logger.info("killing child pid %d", console.pid) + logger.info('killing child pid %d', console.pid) # Ask systemd-nspawn to stop and release the container's resources properly. console.kill(signal.SIGTERM) @@ -116,12 +123,31 @@ def run(args): return ret + def main(): - parser = argparse.ArgumentParser(description='test logind shutdown feature') - parser.add_argument("-v", "--verbose", action="store_true", help="verbose") - parser.add_argument("--logfile", metavar='FILE', help="Save all test input/output to the given path") - parser.add_argument("command", help="command to run") - parser.add_argument("arg", nargs='*', help="args for command") + parser = argparse.ArgumentParser( + description='test logind shutdown feature', + ) + parser.add_argument( + '-v', + '--verbose', + action='store_true', + help='verbose', + ) + parser.add_argument( + '--logfile', + metavar='FILE', + help='Save all test input/output to the given path', + ) + parser.add_argument( + 'command', + help='command to run', + ) + parser.add_argument( + 'arg', + nargs='*', + help='args for command', + ) args = parser.parse_args() @@ -134,6 +160,7 @@ def main(): return run(args) + if __name__ == '__main__': sys.exit(main()) diff --git a/test/test-systemd-tmpfiles.py b/test/test-systemd-tmpfiles.py index 37a5e9ef3e4b2..5489701a73fee 100755 --- a/test/test-systemd-tmpfiles.py +++ b/test/test-systemd-tmpfiles.py @@ -6,13 +6,13 @@ # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. +import grp import os -import sys +import pwd import socket import subprocess +import sys import tempfile -import pwd -import grp from pathlib import Path try: @@ -20,7 +20,7 @@ except ImportError: id128 = None -EX_DATAERR = 65 # from sysexits.h +EX_DATAERR = 65 # from sysexits.h EXIT_TEST_SKIP = 77 try: @@ -34,26 +34,32 @@ # If /tmp isn't owned by either 'root' or the current user # systemd-tmpfiles will exit with "Detected unsafe path transition" # breaking this test -tmpowner = os.stat("/tmp").st_uid +tmpowner = os.stat('/tmp').st_uid if tmpowner != 0 and tmpowner != os.getuid(): print("Skip: /tmp is not owned by 'root' or current user") sys.exit(EXIT_TEST_SKIP) + def test_line(line, *, user, returncode=EX_DATAERR, extra={}): args = ['--user'] if user else [] print('Running {} on {!r}'.format(' '.join(exe_with_args + args), line)) - c = subprocess.run(exe_with_args + ['--create', '-'] + args, - input=line, stdout=subprocess.PIPE, universal_newlines=True, - **extra) + c = subprocess.run( + exe_with_args + ['--create', '-'] + args, + input=line, + stdout=subprocess.PIPE, + text=True, + **extra, + ) assert c.returncode == returncode, c + def test_invalids(*, user): test_line('asdfa', user=user) test_line('f "open quote', user=user) test_line('f closed quote""', user=user) test_line('Y /unknown/letter', user=user) test_line('w non/absolute/path', user=user) - test_line('s', user=user) # s is for short + test_line('s', user=user) # s is for short test_line('f!! /too/many/bangs', user=user) test_line('f++ /too/many/plusses', user=user) test_line('f+!+ /too/many/plusses', user=user) @@ -77,12 +83,18 @@ def test_invalids(*, user): test_line('h - - -', user=user) test_line('H - - -', user=user) + def test_uninitialized_t(): if os.getuid() == 0: return - test_line('w /foo - - - - "specifier for --user %t"', - user=True, returncode=0, extra={'env':{'HOME': os.getenv('HOME')}}) + test_line( + 'w /foo - - - - "specifier for --user %t"', + user=True, + returncode=0, + extra={'env': {'HOME': os.getenv('HOME')}}, + ) + def test_content(line, expected, *, user, extra={}, subpath='/arg', path_cb=None): d = tempfile.TemporaryDirectory(prefix='test-content.', dir=temp_dir.name) @@ -92,24 +104,25 @@ def test_content(line, expected, *, user, extra={}, subpath='/arg', path_cb=None spec = line.format(arg) test_line(spec, user=user, returncode=0, extra=extra) content = open(arg).read() - print('expect: {!r}\nactual: {!r}'.format(expected, content)) + print(f'expect: {expected!r}\nactual: {content!r}') assert content == expected + def test_valid_specifiers(*, user): test_content('f {} - - - - two words', 'two words', user=user) if id128 and os.path.isfile('/etc/machine-id'): try: - test_content('f {} - - - - %m', '{}'.format(id128.get_machine().hex), user=user) + test_content('f {} - - - - %m', f'{id128.get_machine().hex}', user=user) except AssertionError as e: print(e) print('/etc/machine-id: {!r}'.format(open('/etc/machine-id').read())) print('/proc/cmdline: {!r}'.format(open('/proc/cmdline').read())) print('skipping') - test_content('f {} - - - - %b', '{}'.format(id128.get_boot().hex), user=user) - test_content('f {} - - - - %H', '{}'.format(socket.gethostname()), user=user) - test_content('f {} - - - - %v', '{}'.format(os.uname().release), user=user) - test_content('f {} - - - - %U', '{}'.format(os.getuid() if user else 0), user=user) - test_content('f {} - - - - %G', '{}'.format(os.getgid() if user else 0), user=user) + test_content('f {} - - - - %b', f'{id128.get_boot().hex}', user=user) + test_content('f {} - - - - %H', f'{socket.gethostname()}', user=user) + test_content('f {} - - - - %v', f'{os.uname().release}', user=user) + test_content('f {} - - - - %U', f'{os.getuid() if user else 0}', user=user) + test_content('f {} - - - - %G', f'{os.getgid() if user else 0}', user=user) try: puser = pwd.getpwuid(os.getuid() if user else 0) @@ -117,7 +130,7 @@ def test_valid_specifiers(*, user): puser = None if puser: - test_content('f {} - - - - %u', '{}'.format(puser.pw_name), user=user) + test_content('f {} - - - - %u', f'{puser.pw_name}', user=user) try: pgroup = grp.getgrgid(os.getgid() if user else 0) @@ -125,52 +138,55 @@ def test_valid_specifiers(*, user): pgroup = None if pgroup: - test_content('f {} - - - - %g', '{}'.format(pgroup.gr_name), user=user) + test_content('f {} - - - - %g', f'{pgroup.gr_name}', user=user) # Note that %h is the only specifier in which we look the environment, # because we check $HOME. Should we even be doing that? - home = os.path.expanduser("~") - test_content('f {} - - - - %h', '{}'.format(home), user=user) + home = os.path.expanduser('~') + test_content('f {} - - - - %h', f'{home}', user=user) xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR') if xdg_runtime_dir is not None or not user: test_content('f {} - - - - %t', xdg_runtime_dir if user else '/run', - user=user) + user=user) # fmt: skip xdg_state_home = os.getenv('XDG_STATE_HOME') if xdg_state_home is None and user: - xdg_state_home = os.path.join(home, ".local/state") + xdg_state_home = os.path.join(home, '.local/state') test_content('f {} - - - - %S', xdg_state_home if user else '/var/lib', - user=user) + user=user) # fmt: skip xdg_cache_home = os.getenv('XDG_CACHE_HOME') if xdg_cache_home is None and user: - xdg_cache_home = os.path.join(home, ".cache") + xdg_cache_home = os.path.join(home, '.cache') test_content('f {} - - - - %C', xdg_cache_home if user else '/var/cache', - user=user) + user=user) # fmt: skip test_content('f {} - - - - %L', os.path.join(xdg_state_home, 'log') if user else '/var/log', - user=user) + user=user) # fmt: skip test_content('f {} - - - - %%', '%', user=user) + def mkfifo(parent, subpath): os.makedirs(parent, mode=0o755, exist_ok=True) first_component = subpath.split('/')[1] path = parent + '/' + first_component - print('path: {}'.format(path)) + print(f'path: {path}') os.mkfifo(path) + def mkdir(parent, subpath): first_component = subpath.split('/')[1] path = parent + '/' + first_component os.makedirs(path, mode=0o755, exist_ok=True) os.symlink(path, path + '/self', target_is_directory=True) + def symlink(parent, subpath): link_path = parent + '/link-target' os.makedirs(parent, mode=0o755, exist_ok=True) @@ -180,6 +196,7 @@ def symlink(parent, subpath): path = parent + '/' + first_component os.symlink(link_path, path, target_is_directory=True) + def file(parent, subpath): content = 'file-' + subpath.split('/')[1] path = parent + subpath @@ -187,6 +204,7 @@ def file(parent, subpath): with open(path, 'wb') as f: f.write(content.encode()) + def valid_symlink(parent, subpath): target = 'link-target' link_path = parent + target @@ -195,6 +213,7 @@ def valid_symlink(parent, subpath): path = parent + '/' + first_component os.symlink(target, path, target_is_directory=True) + def test_hard_cleanup(*, user): type_cbs = [None, file, mkdir, symlink] if 'mkfifo' in dir(os): @@ -209,29 +228,32 @@ def test_hard_cleanup(*, user): label = 'valid_symlink-deep' test_content('f= {} - - - - ' + label, label, user=user, subpath='/deep/1/2', path_cb=valid_symlink) + def test_base64(): - test_content('f~ {} - - - - UGlmZgpQYWZmClB1ZmYgCg==', "Piff\nPaff\nPuff \n", user=False) + test_content('f~ {} - - - - UGlmZgpQYWZmClB1ZmYgCg==', 'Piff\nPaff\nPuff \n', user=False) + def test_conditionalized_execute_bit(): - c = subprocess.run(exe_with_args + ['--version', '|', 'grep', '-F', '+ACL'], shell=True, stdout=subprocess.DEVNULL) + c = subprocess.run( + exe_with_args + ['--version', '|', 'grep', '-F', '+ACL'], shell=True, stdout=subprocess.DEVNULL + ) if c.returncode != 0: return 0 d = tempfile.TemporaryDirectory(prefix='test-acl.', dir=temp_dir.name) - temp = Path(d.name) / "cond_exec" + temp = Path(d.name) / 'cond_exec' temp.touch() temp.chmod(0o644) - test_line(f"a {temp} - - - - u:root:Xwr", user=False, returncode=0) - c = subprocess.run(["getfacl", "-Ec", temp], - stdout=subprocess.PIPE, check=True, text=True) - assert "user:root:rw-" in c.stdout + test_line(f'a {temp} - - - - u:root:Xwr', user=False, returncode=0) + c = subprocess.run(['getfacl', '-Ec', temp], stdout=subprocess.PIPE, check=True, text=True) + assert 'user:root:rw-' in c.stdout temp.chmod(0o755) - test_line(f"a+ {temp} - - - - u:root:Xwr,g:root:rX", user=False, returncode=0) - c = subprocess.run(["getfacl", "-Ec", temp], - stdout=subprocess.PIPE, check=True, text=True) - assert "user:root:rwx" in c.stdout and "group:root:r-x" in c.stdout + test_line(f'a+ {temp} - - - - u:root:Xwr,g:root:rX', user=False, returncode=0) + c = subprocess.run(['getfacl', '-Ec', temp], stdout=subprocess.PIPE, check=True, text=True) + assert 'user:root:rwx' in c.stdout and 'group:root:r-x' in c.stdout + if __name__ == '__main__': test_invalids(user=False) diff --git a/test/test-udev.py b/test/test-udev.py index 20b55de7d679d..da5d41efe0179 100755 --- a/test/test-udev.py +++ b/test/test-udev.py @@ -19,8 +19,9 @@ import dataclasses import functools +import grp import os -import pwd, grp +import pwd import re import stat import subprocess @@ -37,11 +38,11 @@ sys.exit(77) -SYS_SCRIPT = Path(__file__).with_name('sys-script.py') +SYS_SCRIPT = Path(__file__).with_name('sys-script.py') try: - UDEV_BIN = Path(os.environ['UDEV_RULE_RUNNER']) + UDEV_BIN = Path(os.environ['UDEV_RULE_RUNNER']) except KeyError: - UDEV_BIN = Path(__file__).parent / 'manual/test-udev-rule-runner' + UDEV_BIN = Path(__file__).parent / 'manual/test-udev-rule-runner' UDEV_BIN = UDEV_BIN.absolute() # Those will be set by the udev_setup() fixture @@ -53,11 +54,12 @@ rules_10k_tags = \ '\n'.join(f'KERNEL=="sda", TAG+="test{i + 1}"' - for i in range(10_000)) + for i in range(10_000)) # fmt: skip rules_10k_tags_continuation = \ ',\\\n'.join(('KERNEL=="sda"', - *(f'TAG+="test{i + 1}"' for i in range(10_000)))) + *(f'TAG+="test{i + 1}"' for i in range(10_000)))) # fmt: skip + @dataclasses.dataclass class Device: @@ -149,8 +151,10 @@ def check_remove(self) -> None: def listify(f): def wrap(*args, **kwargs): return list(f(*args, **kwargs)) + return functools.update_wrapper(wrap, f) + @listify def all_block_devs(exp_func) -> list[Device]: # Create a device list with all block devices under /sys @@ -167,9 +171,7 @@ def all_block_devs(exp_func) -> list[Device]: tgt = tgt[5:] exp, not_exp = exp_func(tgt) - yield Device(devpath=tgt, - exp_links=exp, - not_exp_links=not_exp) + yield Device(devpath=tgt, exp_links=exp, not_exp_links=not_exp) @dataclasses.dataclass @@ -200,71 +202,71 @@ def create_rules_file(self) -> None: UDEV_RULES.parent.mkdir(exist_ok=True, parents=True) UDEV_RULES.write_text(self.rules) + RULES = [ Rules.new( 'no rules', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', ), - rules = r""" + rules=r''' # - """), - + ''', + ), Rules.new( 'label test of scsi disc', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["boot_disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['boot_disk'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "label test of scsi disc", + 'label test of scsi disc', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["boot_disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['boot_disk'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "label test of scsi disc", + 'label test of scsi disc', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["boot_disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['boot_disk'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "label test of scsi partition", + 'label test of scsi partition', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" - """), - + ''', + ), Rules.new( - "label test of pattern match", + 'label test of pattern match', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1", "boot_disk1-4", "boot_disk1-5"], - not_exp_links = ["boot_disk1-1", "boot_disk1-2", "boot_disk1-3", "boot_disk1-6", "boot_disk1-7"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1', 'boot_disk1-4', 'boot_disk1-5'], + not_exp_links=['boot_disk1-1', 'boot_disk1-2', 'boot_disk1-3', 'boot_disk1-6', 'boot_disk1-7'], ), - - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="?ATA", SYMLINK+="boot_disk%n-1" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA?", SYMLINK+="boot_disk%n-2" SUBSYSTEMS=="scsi", ATTRS{vendor}=="A??", SYMLINK+="boot_disk%n" @@ -277,48 +279,61 @@ def create_rules_file(self) -> None: SUBSYSTEMS=="scsi", GOTO="skip-7" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n-7" LABEL="skip-7" - """), - + ''', + ), Rules.new( - "label test of multiple sysfs files", + 'label test of multiple sysfs files', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1"], - not_exp_links = ["boot_diskX1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1'], + not_exp_links=['boot_diskX1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS X ", SYMLINK+="boot_diskX%n" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="boot_disk%n" - """), - + ''', + ), Rules.new( - "label test of max sysfs files (skip invalid rule)", + 'label test of max sysfs files (skip invalid rule)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["boot_disk1", "boot_diskXY1"], - not_exp_links = ["boot_diskXX1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['boot_disk1', 'boot_diskXY1'], + not_exp_links=['boot_diskXX1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="1", SYMLINK+="boot_diskXY%n" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n" - """), - - Rules.new( - "SYMLINK tests", - Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["link1", "link2/foo", "link3/aaa/bbb", - "abs1", "abs2/foo", "abs3/aaa/bbb", - "default___replace_test/foo_aaa", - "string_escape___replace/foo_bbb", - "env_with_space", - "default/replace/mode_foo__hoge", - "replace_env_harder_foo__hoge", - "match", "unmatch"], - not_exp_links = ["removed1", "removed2", "removed3", "unsafe/../../path", "/nondev/path/will/be/refused"], - ), - rules = r""" + ''', + ), + Rules.new( + 'SYMLINK tests', + Device( + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=[ + 'link1', + 'link2/foo', + 'link3/aaa/bbb', + 'abs1', + 'abs2/foo', + 'abs3/aaa/bbb', + 'default___replace_test/foo_aaa', + 'string_escape___replace/foo_bbb', + 'env_with_space', + 'default/replace/mode_foo__hoge', + 'replace_env_harder_foo__hoge', + 'match', + 'unmatch', + ], + not_exp_links=[ + 'removed1', + 'removed2', + 'removed3', + 'unsafe/../../path', + '/nondev/path/will/be/refused', + ], + ), + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="removed1" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed1" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/./dev///removed2" @@ -336,98 +351,98 @@ def create_rules_file(self) -> None: SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", ENV{.HOGE}="replace/env/harder?foo;;hoge", SYMLINK+="%E{.HOGE}" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK=="link1", SYMLINK+="match" SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK!="removed1", SYMLINK+="unmatch" - """), - + ''', + ), Rules.new( - "catch device by *", + 'catch device by *', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0", "catch-all"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0', 'catch-all'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM*", SYMLINK+="modem/%n" KERNEL=="*", SYMLINK+="catch-all" - """), - + ''', + ), Rules.new( - "catch device by * - take 2", + 'catch device by * - take 2', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="*ACM1", SYMLINK+="bad" KERNEL=="*ACM0", SYMLINK+="modem/%n" - """), - + ''', + ), Rules.new( - "catch device by ?", + 'catch device by ?', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0"], - not_exp_links = ["modem/0-1", "modem/0-2"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0'], + not_exp_links=['modem/0-1', 'modem/0-2'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1" KERNEL=="ttyACM??", SYMLINK+="modem/%n-2" KERNEL=="ttyACM?", SYMLINK+="modem/%n" - """), - + ''', + ), Rules.new( - "catch device by character class", + 'catch device by character class', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem/0"], - not_exp_links = ["modem/0-1", "modem/0-2"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem/0'], + not_exp_links=['modem/0-1', 'modem/0-2'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[A-Z]*", SYMLINK+="modem/%n-1" KERNEL=="ttyACM?[0-9]", SYMLINK+="modem/%n-2" KERNEL=="ttyACM[0-9]*", SYMLINK+="modem/%n" - """), - + ''', + ), Rules.new( "don't replace kernel name", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "comment lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' # this is a comment KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "comment lines in config file with whitespace (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' # this is a comment with whitespace before the comment KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "whitespace only lines (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["whitespace"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['whitespace'], ), - rules = r""" + rules=r''' @@ -436,49 +451,49 @@ def create_rules_file(self) -> None: - """), - + ''', + ), Rules.new( "empty lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( "backslashed multi lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", \ SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "preserve backslashes, if they are not for a newline", + 'preserve backslashes, if they are not for a newline', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["aaa"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['aaa'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \101", RESULT=="A", SYMLINK+="aaa" - """), - + ''', + ), Rules.new( "stupid backslashed multi lines in config file (and don't replace kernel name)", Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' # \ @@ -490,1364 +505,1361 @@ def create_rules_file(self) -> None: KERNEL=="ttyACM0", \ SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "subdirectory handling", + 'subdirectory handling', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["sub/direct/ory/modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['sub/direct/ory/modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem" - """), - + ''', + ), Rules.new( - "parent device name match of scsi partition", + 'parent device name match of scsi partition', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["first_disk5"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['first_disk5'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n" - """), - + ''', + ), Rules.new( - "test substitution chars", + 'test substitution chars', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["Major:8:minor:5:kernelnumber:5:id:0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['Major:8:minor:5:kernelnumber:5:id:0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b" - """), - + ''', + ), Rules.new( - "import of shell-value returned from program", + 'import of shell-value returned from program', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node12345678"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node12345678'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", IMPORT{program}="/bin/echo -e ' TEST_KEY=12345678\n TEST_key2=98765'", SYMLINK+="node$env{TEST_KEY}" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "substitution of sysfs value (%s{file})", + 'substitution of sysfs value (%s{file})', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["disk-ATA-sda"], - not_exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['disk-ATA-sda'], + not_exp_links=['modem'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "program result substitution", + 'program result substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["special-device-5"], - not_exp_links = ["not"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['special-device-5'], + not_exp_links=['not'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="-special-*", SYMLINK+="not" SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="special-*", SYMLINK+="%c-%n" - """), - + ''', + ), Rules.new( - "program result substitution (newline removal)", + 'program result substitution (newline removal)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["newline_removed"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['newline_removed'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed" - """), - + ''', + ), Rules.new( - "program result substitution", + 'program result substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["test-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['test-0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c" - """), - + ''', + ), Rules.new( - "program with lots of arguments", + 'program with lots of arguments', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo9"], - not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo7", "foo8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo9'], + not_exp_links=['foo3', 'foo4', 'foo5', 'foo6', 'foo7', 'foo8'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}" - """), - + ''', + ), Rules.new( - "program with subshell", + 'program with subshell', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["bar9"], - not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo7", "foo8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['bar9'], + not_exp_links=['foo3', 'foo4', 'foo5', 'foo6', 'foo7', 'foo8'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed s/foo9/bar9/'", KERNEL=="sda5", SYMLINK+="%c{7}" - """), - + ''', + ), Rules.new( - "program arguments combined with apostrophes", + 'program arguments combined with apostrophes', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo7"], - not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo7'], + not_exp_links=['foo3', 'foo4', 'foo5', 'foo6', 'foo8'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4' 'foo5 foo6 foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}" - """), - + ''', + ), Rules.new( - "program arguments combined with escaped double quotes, part 1", + 'program arguments combined with escaped double quotes, part 1', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo2"], - not_exp_links = ["foo1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo2'], + not_exp_links=['foo1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c 'printf %%s \"foo1 foo2\" | grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "program arguments combined with escaped double quotes, part 2", + 'program arguments combined with escaped double quotes, part 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo2"], - not_exp_links = ["foo1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo2'], + not_exp_links=['foo1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c \"printf %%s 'foo1 foo2' | grep 'foo1 foo2'\"", KERNEL=="sda5", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "program arguments combined with escaped double quotes, part 3", + 'program arguments combined with escaped double quotes, part 3', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["foo2"], - not_exp_links = ["foo1", "foo3"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['foo2'], + not_exp_links=['foo1', 'foo3'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/bash -c 'printf \"%%s %%s\" \"foo1 foo2\" \"foo3\"| grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "characters before the %c{N} substitution", + 'characters before the %c{N} substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["my-foo9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['my-foo9'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}" - """), - + ''', + ), Rules.new( - "substitute the second to last argument", + 'substitute the second to last argument', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["my-foo8"], - not_exp_links = ["my-foo3", "my-foo4", "my-foo5", "my-foo6", "my-foo7", "my-foo9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['my-foo8'], + not_exp_links=['my-foo3', 'my-foo4', 'my-foo5', 'my-foo6', 'my-foo7', 'my-foo9'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}" - """), - + ''', + ), Rules.new( - "test substitution by variable name", + 'test substitution by variable name', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["Major:8-minor:5-kernelnumber:5-id:0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['Major:8-minor:5-kernelnumber:5-id:0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:$major-minor:$minor-kernelnumber:$number-id:$id" - """), - + ''', + ), Rules.new( - "test substitution by variable name 2", + 'test substitution by variable name 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["Major:8-minor:5-kernelnumber:5-id:0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['Major:8-minor:5-kernelnumber:5-id:0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="Major:$major-minor:%m-kernelnumber:$number-id:$id" - """), - + ''', + ), Rules.new( - "test substitution by variable name 3", + 'test substitution by variable name 3', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["850:0:0:05"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['850:0:0:05'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n" - """), - + ''', + ), Rules.new( - "test substitution by variable name 4", + 'test substitution by variable name 4', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["855"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['855'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major$minor$number" - """), - + ''', + ), Rules.new( - "test substitution by variable name 5", + 'test substitution by variable name 5', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["8550:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['8550:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major%m%n$id" - """), - + ''', + ), Rules.new( - "non matching SUBSYSTEMS for device with no parent", + 'non matching SUBSYSTEMS for device with no parent', Device( - "/devices/virtual/tty/console", - exp_links = ["TTY"], - not_exp_links = ["foo"], + '/devices/virtual/tty/console', + exp_links=['TTY'], + not_exp_links=['foo'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo" KERNEL=="console", SYMLINK+="TTY" - """), - + ''', + ), Rules.new( - "non matching SUBSYSTEMS", + 'non matching SUBSYSTEMS', Device( - "/devices/virtual/tty/console", - exp_links = ["TTY"], - not_exp_links = ["foo"], + '/devices/virtual/tty/console', + exp_links=['TTY'], + not_exp_links=['foo'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo" KERNEL=="console", SYMLINK+="TTY" - """), - + ''', + ), Rules.new( - "ATTRS match", + 'ATTRS match', Device( - "/devices/virtual/tty/console", - exp_links = ["foo", "TTY"], + '/devices/virtual/tty/console', + exp_links=['foo', 'TTY'], ), - rules = r""" + rules=r''' KERNEL=="console", SYMLINK+="TTY" ATTRS{dev}=="5:1", SYMLINK+="foo" - """), - + ''', + ), Rules.new( - "ATTR (empty file)", + 'ATTR (empty file)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["empty", "not-something"], - not_exp_links = ["something", "not-empty"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['empty', 'not-something'], + not_exp_links=['something', 'not-empty'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTR{test_empty_file}=="?*", SYMLINK+="something" KERNEL=="sda", ATTR{test_empty_file}!="", SYMLINK+="not-empty" KERNEL=="sda", ATTR{test_empty_file}=="", SYMLINK+="empty" KERNEL=="sda", ATTR{test_empty_file}!="?*", SYMLINK+="not-something" - """), - + ''', + ), Rules.new( - "ATTR (non-existent file)", + 'ATTR (non-existent file)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["non-existent", "wrong"], - not_exp_links = ["something", "empty", "not-empty", - "not-something", "something"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['non-existent', 'wrong'], + not_exp_links=['something', 'empty', 'not-empty', 'not-something', 'something'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTR{nofile}=="?*", SYMLINK+="something" KERNEL=="sda", ATTR{nofile}!="", SYMLINK+="not-empty" KERNEL=="sda", ATTR{nofile}=="", SYMLINK+="empty" KERNEL=="sda", ATTR{nofile}!="?*", SYMLINK+="not-something" KERNEL=="sda", TEST!="nofile", SYMLINK+="non-existent" KERNEL=="sda", SYMLINK+="wrong" - """), - + ''', + ), Rules.new( - "program and bus type match", + 'program and bus type match', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="usb", PROGRAM=="/bin/echo -n usb-%b", SYMLINK+="%c" SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n scsi-%b", SYMLINK+="%c" SUBSYSTEMS=="foo", PROGRAM=="/bin/echo -n foo-%b", SYMLINK+="%c" - """), - + ''', + ), Rules.new( - "sysfs parent hierarchy", + 'sysfs parent hierarchy', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem'], ), - rules = r""" + rules=r''' ATTRS{idProduct}=="007b", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "name test with ! in the name", + 'name test with ! in the name', Device( - "/devices/virtual/block/fake!blockdev0", - devnode = "fake/blockdev0", - exp_links = ["is/a/fake/blockdev0"], - not_exp_links = ["is/not/a/fake/blockdev0", "modem"], + '/devices/virtual/block/fake!blockdev0', + devnode='fake/blockdev0', + exp_links=['is/a/fake/blockdev0'], + not_exp_links=['is/not/a/fake/blockdev0', 'modem'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k" SUBSYSTEM=="block", SYMLINK+="is/a/%k" KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "name test with ! in the name, but no matching rule", + 'name test with ! in the name, but no matching rule', Device( - "/devices/virtual/block/fake!blockdev0", - devnode = "fake/blockdev0", - not_exp_links = ["modem"], + '/devices/virtual/block/fake!blockdev0', + devnode='fake/blockdev0', + not_exp_links=['modem'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK+="modem" - """), - + ''', + ), Rules.new( - "KERNELS rule", + 'KERNELS rule', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0"], - not_exp_links = ["no-match", "short-id", "not-scsi"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0'], + not_exp_links=['no-match', 'short-id', 'not-scsi'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="usb", KERNELS=="0:0:0:0", SYMLINK+="not-scsi" SUBSYSTEMS=="scsi", KERNELS=="0:0:0:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS==":0", SYMLINK+="short-id" SUBSYSTEMS=="scsi", KERNELS=="/0:0:0:0", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "KERNELS wildcard all", + 'KERNELS wildcard all', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0"], - not_exp_links = ["no-match", "before"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0'], + not_exp_links=['no-match', 'before'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="*:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS=="*:0:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNELS=="*:0:0:1", SYMLINK+="no-match" SUBSYSTEMS=="scsi", KERNEL=="0:0:0:0", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNELS=="*", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "KERNELS wildcard partial", + 'KERNELS wildcard partial', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0", "before"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0', 'before'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNELS=="*:0", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "KERNELS wildcard partial 2", + 'KERNELS wildcard partial 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["scsi-0:0:0:0", "before"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['scsi-0:0:0:0', 'before'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNELS=="*:0:0:0", SYMLINK+="scsi-0:0:0:0" - """), - + ''', + ), Rules.new( - "substitute attr with link target value (first match)", + 'substitute attr with link target value (first match)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["driver-is-sd"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['driver-is-sd'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", SYMLINK+="driver-is-$attr{driver}" - """), - + ''', + ), Rules.new( - "substitute attr with link target value (currently selected device)", + 'substitute attr with link target value (currently selected device)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["driver-is-ahci"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['driver-is-ahci'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="pci", SYMLINK+="driver-is-$attr{driver}" - """), - + ''', + ), Rules.new( - "ignore ATTRS attribute whitespace", + 'ignore ATTRS attribute whitespace', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ignored"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ignored'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE", SYMLINK+="ignored" - """), - + ''', + ), Rules.new( - "do not ignore ATTRS attribute whitespace", + 'do not ignore ATTRS attribute whitespace', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["matched-with-space"], - not_exp_links = ["wrong-to-ignore"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['matched-with-space'], + not_exp_links=['wrong-to-ignore'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="wrong-to-ignore" SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="matched-with-space" - """), - + ''', + ), Rules.new( - "permissions USER=bad GROUP=name", + 'permissions USER=bad GROUP=name', Device( - "/devices/virtual/tty/tty33", - exp_perms = "0:0:0600", + '/devices/virtual/tty/tty33', + exp_perms='0:0:0600', ), - rules = r""" + rules=r''' KERNEL=="tty33", OWNER="bad", GROUP="name" - """), - + ''', + ), Rules.new( - "permissions OWNER=1", + 'permissions OWNER=1', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "1::0600", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='1::0600', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1" - """), - + ''', + ), Rules.new( - "permissions GROUP=1", + 'permissions GROUP=1', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = ":1:0660", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms=':1:0660', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="1" - """), - + ''', + ), Rules.new( - "textual user id", + 'textual user id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "daemon::0600", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='daemon::0600', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="daemon" - """), - + ''', + ), Rules.new( - "textual group id", + 'textual group id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = ":daemon:0660", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms=':daemon:0660', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="daemon" - """), - + ''', + ), Rules.new( - "textual user/group id", + 'textual user/group id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "root:audio:0660", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='root:audio:0660', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="audio" - """), - + ''', + ), Rules.new( - "permissions MODE=0777", + 'permissions MODE=0777', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "::0777", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='::0777', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", MODE="0777" - """), - + ''', + ), Rules.new( - "permissions OWNER=1 GROUP=1 MODE=0777", + 'permissions OWNER=1 GROUP=1 MODE=0777', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1", GROUP="1", MODE="0777" - """), - + ''', + ), Rules.new( - "permissions OWNER to 1", + 'permissions OWNER to 1', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1::", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1::', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1" - """), - + ''', + ), Rules.new( - "permissions GROUP to 1", + 'permissions GROUP to 1', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = ":1:0660", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms=':1:0660', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="1" - """), - + ''', + ), Rules.new( - "permissions MODE to 0060", + 'permissions MODE to 0060', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "::0060", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='::0060', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", MODE="0060" - """), - + ''', + ), Rules.new( - "permissions OWNER, GROUP, MODE", + 'permissions OWNER, GROUP, MODE', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1", GROUP="1", MODE="0777" - """), - + ''', + ), Rules.new( - "permissions only rule", + 'permissions only rule', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", OWNER="1", GROUP="1", MODE="0777" KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n" - """), - + ''', + ), Rules.new( - "multiple permissions only rule", + 'multiple permissions only rule', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:1:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:1:0777', ), - rules = r""" + rules=r''' SUBSYSTEM=="tty", OWNER="1" SUBSYSTEM=="tty", GROUP="1" SUBSYSTEM=="tty", MODE="0777" KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n" - """), - + ''', + ), Rules.new( - "permissions only rule with override at SYMLINK+ rule", + 'permissions only rule with override at SYMLINK+ rule', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms = "1:2:0777", + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_perms='1:2:0777', ), - rules = r""" + rules=r''' SUBSYSTEM=="tty", OWNER="1" SUBSYSTEM=="tty", GROUP="1" SUBSYSTEM=="tty", MODE="0777" KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444" KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="2" - """), - + ''', + ), Rules.new( - "major/minor number test", + 'major/minor number test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - exp_major_minor = "8:0", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + exp_major_minor='8:0', ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node" - """), - + ''', + ), Rules.new( - "big major number test", + 'big major number test', Device( - "/devices/virtual/misc/misc-fake1", - exp_links = ["node"], - exp_major_minor = "4095:1", + '/devices/virtual/misc/misc-fake1', + exp_links=['node'], + exp_major_minor='4095:1', ), - rules = r""" + rules=r''' KERNEL=="misc-fake1", SYMLINK+="node" - """), - + ''', + ), Rules.new( - "big major and big minor number test", + 'big major and big minor number test', Device( - "/devices/virtual/misc/misc-fake89999", - exp_links = ["node"], - exp_major_minor = "4095:89999", + '/devices/virtual/misc/misc-fake89999', + exp_links=['node'], + exp_major_minor='4095:89999', ), - rules = r""" + rules=r''' KERNEL=="misc-fake89999", SYMLINK+="node" - """), - + ''', + ), Rules.new( - "multiple symlinks with format char", + 'multiple symlinks with format char', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["symlink1-0", "symlink2-ttyACM0", "symlink3-"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['symlink1-0', 'symlink2-ttyACM0', 'symlink3-'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK="symlink1-%n symlink2-%k symlink3-%b" - """), - + ''', + ), Rules.new( - "multiple symlinks with a lot of s p a c e s", + 'multiple symlinks with a lot of s p a c e s', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["one", "two"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['one', 'two'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK=" one two " - """), - + ''', + ), Rules.new( - "symlink with spaces in substituted variable", + 'symlink with spaces in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}="one two three" SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with leading space in substituted variable", + 'symlink with leading space in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three" SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with trailing space in substituted variable", + 'symlink with trailing space in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}="one two three " SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with lots of space in substituted variable", + 'symlink with lots of space in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three " SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with multiple spaces in substituted variable", + 'symlink with multiple spaces in substituted variable', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["name-one_two_three-end"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['name-one_two_three-end'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three " SYMLINK="name-$env{WITH_WS}-end" - """), - + ''', + ), Rules.new( - "symlink with space and var with space", + 'symlink with space and var with space', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["first", "name-one_two_three-end", - "another_symlink", "a", "b", "c"], - not_exp_links = [" "], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['first', 'name-one_two_three-end', 'another_symlink', 'a', 'b', 'c'], + not_exp_links=[' '], ), - rules = r""" + rules=r''' ENV{WITH_WS}=" one two three " SYMLINK=" first name-$env{WITH_WS}-end another_symlink a b c " - """), - + ''', + ), Rules.new( - "symlink with env which contain slash (see #19309)", + 'symlink with env which contain slash (see #19309)', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["first", "name-aaa_bbb_ccc-end", - "another_symlink", "a", "b", "c"], - not_exp_links = ["ame-aaa/bbb/ccc-end"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['first', 'name-aaa_bbb_ccc-end', 'another_symlink', 'a', 'b', 'c'], + not_exp_links=['ame-aaa/bbb/ccc-end'], ), - rules = r""" + rules=r''' ENV{WITH_SLASH}="aaa/bbb/ccc" OPTIONS="string_escape=replace", ENV{REPLACED}="$env{WITH_SLASH}" SYMLINK=" first name-$env{REPLACED}-end another_symlink a b c " - """), - + ''', + ), Rules.new( - "symlink creation (same directory)", + 'symlink creation (same directory)', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["modem0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['modem0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n" - """), - + ''', + ), Rules.new( - "multiple symlinks", + 'multiple symlinks', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["first-0", "second-0", "third-0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['first-0', 'second-0', 'third-0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n" - """), - + ''', + ), Rules.new( "symlink name '.'", Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', ), # we get a warning, but the process does not fail - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="." - """), - + ''', + ), Rules.new( - "symlink node to itself", + 'symlink node to itself', Device( - "/devices/virtual/tty/tty0", + '/devices/virtual/tty/tty0', ), # we get a warning, but the process does not fail - rules = r""" + rules=r''' KERNEL=="tty0", SYMLINK+="tty0" - """), - + ''', + ), Rules.new( - "symlink %n substitution", + 'symlink %n substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["symlink0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['symlink0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n" - """), - + ''', + ), Rules.new( - "symlink %k substitution", + 'symlink %k substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["symlink-ttyACM0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['symlink-ttyACM0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k" - """), - + ''', + ), Rules.new( - "symlink %M:%m substitution", + 'symlink %M:%m substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["major-166:0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['major-166:0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m" - """), - + ''', + ), Rules.new( - "symlink %b substitution", + 'symlink %b substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["symlink-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['symlink-0:0:0:0'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b" - """), - + ''', + ), Rules.new( - "symlink %c substitution", + 'symlink %c substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["test"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['test'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c" - """), - + ''', + ), Rules.new( - "symlink %c{N} substitution", + 'symlink %c{N} substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["test"], - not_exp_links = ["symlink", "this"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['test'], + not_exp_links=['symlink', 'this'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}" - """), - + ''', + ), Rules.new( - "symlink %c{N+} substitution", + 'symlink %c{N+} substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["test", "this"], - not_exp_links = ["symlink"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['test', 'this'], + not_exp_links=['symlink'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}" - """), - + ''', + ), Rules.new( - "symlink only rule with %c{N+}", + 'symlink only rule with %c{N+}', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["test", "this"], - not_exp_links = ["symlink"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['test', 'this'], + not_exp_links=['symlink'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}" - """), - + ''', + ), Rules.new( - "symlink %s{filename} substitution", + 'symlink %s{filename} substitution', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["166:0"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['166:0'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}" - """), - + ''', + ), Rules.new( - "program result substitution (numbered part of)", + 'program result substitution (numbered part of)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["link1", "link2"], - not_exp_links = ["node"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['link1', 'link2'], + not_exp_links=['node'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2", RESULT=="node *", SYMLINK+="%c{2} %c{3}" - """), - + ''', + ), Rules.new( - "program result substitution (numbered part of+)", + 'program result substitution (numbered part of+)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["link1", "link2", "link3", "link4"], - not_exp_links = ["node"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['link1', 'link2', 'link3', 'link4'], + not_exp_links=['node'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}" - """), - + ''', + ), Rules.new( - "SUBSYSTEM match test", + 'SUBSYSTEM match test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - not_exp_links = ["should_not_match", "should_not_match2"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + not_exp_links=['should_not_match', 'should_not_match2'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", SUBSYSTEM=="vc" SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", SUBSYSTEM=="block" SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match2", SUBSYSTEM=="vc" - """), - + ''', + ), Rules.new( - "DRIVERS match test", + 'DRIVERS match test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], - not_exp_links = ["should_not_match"] + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], + not_exp_links=['should_not_match'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", DRIVERS=="sd-wrong" SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", DRIVERS=="sd" - """), - + ''', + ), Rules.new( - "devnode substitution test", + 'devnode substitution test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node" - """), - + ''', + ), Rules.new( - "parent node name substitution test", + 'parent node name substitution test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["sda-part-1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['sda-part-1'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-%n" - """), - + ''', + ), Rules.new( - "udev_root substitution", + 'udev_root substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["start-/dev-end"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['start-/dev-end'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end" - """), - + ''', + ), Rules.new( # This is not supported any more - "last_rule option", + 'last_rule option', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["last", "very-last"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['last', 'very-last'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="very-last" - """), - + ''', + ), Rules.new( - "negation KERNEL!=", + 'negation KERNEL!=', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["match", "before"], - not_exp_links = ["matches-but-is-negated"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['match', 'before'], + not_exp_links=['matches-but-is-negated'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL!="sda1", SYMLINK+="matches-but-is-negated" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNEL!="xsda1", SYMLINK+="match" - """), - + ''', + ), Rules.new( - "negation SUBSYSTEM!=", + 'negation SUBSYSTEM!=', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["before", "not-anything"], - not_exp_links = ["matches-but-is-negated"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['before', 'not-anything'], + not_exp_links=['matches-but-is-negated'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", SUBSYSTEM=="block", KERNEL!="sda1", SYMLINK+="matches-but-is-negated" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", SUBSYSTEM!="anything", SYMLINK+="not-anything" - """), - + ''', + ), Rules.new( - "negation PROGRAM!= exit code", + 'negation PROGRAM!= exit code', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["before", "nonzero-program"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['before', 'nonzero-program'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" KERNEL=="sda1", PROGRAM!="/bin/false", SYMLINK+="nonzero-program" - """), - + ''', + ), Rules.new( - "ENV{} test", + 'ENV{} test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true"], - not_exp_links = ["bad", "wrong"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true'], + not_exp_links=['bad', 'wrong'], ), - rules = r""" + rules=r''' ENV{ENV_KEY_TEST}="test" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", SYMLINK+="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "ENV{} test", + 'ENV{} test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true"], - not_exp_links = ["bad", "wrong", "no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true'], + not_exp_links=['bad', 'wrong', 'no'], ), - rules = r""" + rules=r''' ENV{ENV_KEY_TEST}="test" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="yes", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sdax1", SYMLINK+="no" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sda1", SYMLINK+="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "ENV{} test (assign)", + 'ENV{} test (assign)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true", "before"], - not_exp_links = ["no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true', 'before'], + not_exp_links=['no'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="true" - """), - + ''', + ), Rules.new( - "ENV{} test (assign 2 times)", + 'ENV{} test (assign 2 times)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["true", "before"], - not_exp_links = ["no", "bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['true', 'before'], + not_exp_links=['no', 'bad'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="absolutely-$env{ASSIGN}" SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="bad" SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="absolutely-true", SYMLINK+="true" - """), - + ''', + ), Rules.new( - "ENV{} test (assign2)", + 'ENV{} test (assign2)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part"], - not_exp_links = ["disk"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part'], + not_exp_links=['disk'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["disk"], - not_exp_links = ["part"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['disk'], + not_exp_links=['part'], ), - rules = r""" + rules=r''' SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false" SUBSYSTEM=="block", KERNEL=="*[!0-9]", ENV{PARTITION}="false", ENV{MAINDEVICE}="true" ENV{MAINDEVICE}=="true", SYMLINK+="disk" SUBSYSTEM=="block", SYMLINK+="before" ENV{PARTITION}=="true", SYMLINK+="part" - """), - + ''', + ), Rules.new( - "untrusted string sanitize", + 'untrusted string sanitize', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["sane"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['sane'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e name; (/usr/bin/badprogram)", RESULT=="name_ _/usr/bin/badprogram_", SYMLINK+="sane" - """), - + ''', + ), Rules.new( "untrusted string sanitize (don't replace utf8)", Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["uber"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['uber'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xc3\xbcber" RESULT=="über", SYMLINK+="uber" - """), - + ''', + ), Rules.new( - "untrusted string sanitize (replace invalid utf8)", + 'untrusted string sanitize (replace invalid utf8)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["replaced"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['replaced'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xef\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced" - """), - + ''', + ), Rules.new( - "read sysfs value from parent device", + 'read sysfs value from parent device', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["serial-354172020305000"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['serial-354172020305000'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}" - """), - + ''', + ), Rules.new( - "match against empty key string", + 'match against empty key string', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - not_exp_links = ["not-1-ok", "not-2-ok", "not-3-ok"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + not_exp_links=['not-1-ok', 'not-2-ok', 'not-3-ok'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTRS{nothing}!="", SYMLINK+="not-1-ok" KERNEL=="sda", ATTRS{nothing}=="", SYMLINK+="not-2-ok" KERNEL=="sda", ATTRS{vendor}!="", SYMLINK+="ok" KERNEL=="sda", ATTRS{vendor}=="", SYMLINK+="not-3-ok" - """), - + ''', + ), Rules.new( - "check ACTION value", + 'check ACTION value', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - not_exp_links = ["unknown-not-ok"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + not_exp_links=['unknown-not-ok'], ), - rules = r""" + rules=r''' ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok" ACTION=="add", KERNEL=="sda", SYMLINK+="ok" - """), - + ''', + ), Rules.new( - "final assignment", + 'final assignment', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - exp_perms = "root:tty:0640", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + exp_perms='root:tty:0640', ), - rules = r""" + rules=r''' KERNEL=="sda", GROUP:="tty" KERNEL=="sda", GROUP="root", MODE="0640", SYMLINK+="ok" - """), - + ''', + ), Rules.new( - "final assignment 2", + 'final assignment 2', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["ok"], - exp_perms = "root:tty:0640", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['ok'], + exp_perms='root:tty:0640', ), - rules = r""" + rules=r''' KERNEL=="sda", GROUP:="tty" SUBSYSTEM=="block", MODE:="640" KERNEL=="sda", GROUP="root", MODE="0666", SYMLINK+="ok" - """), - + ''', + ), Rules.new( - "env substitution", + 'env substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["node-add-me"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['node-add-me'], ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="0666", SYMLINK+="node-$env{ACTION}-me" - """), - + ''', + ), Rules.new( - "reset list to current value", + 'reset list to current value', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["three"], - not_exp_links = ["two", "one"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['three'], + not_exp_links=['two', 'one'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="one" KERNEL=="ttyACM[0-9]*", SYMLINK+="two" KERNEL=="ttyACM[0-9]*", SYMLINK="three" - """), - + ''', + ), Rules.new( - "test empty SYMLINK+ (empty override)", + 'test empty SYMLINK+ (empty override)', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right"], - not_exp_links = ["wrong"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right'], + not_exp_links=['wrong'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong" KERNEL=="ttyACM[0-9]*", SYMLINK="" KERNEL=="ttyACM[0-9]*", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches", + 'test multi matches', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right", "before"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right', 'before'], ), - rules = r""" + rules=r''' KERNEL=="ttyACM*", SYMLINK+="before" KERNEL=="ttyACM*|nothing", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches 2", + 'test multi matches 2', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right", "before"], - not_exp_links = ["nomatch"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right', 'before'], + not_exp_links=['nomatch'], ), - rules = r""" + rules=r''' KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch" KERNEL=="ttyACM*", SYMLINK+="before" KERNEL=="dontknow*|ttyACM*|nothing*", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches 3", + 'test multi matches 3', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right"], - not_exp_links = ["nomatch", "wrong1", "wrong2"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right'], + not_exp_links=['nomatch', 'wrong1', 'wrong2'], ), - rules = r""" + rules=r''' KERNEL=="dontknow|nothing", SYMLINK+="nomatch" KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1" KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2" KERNEL=="dontknow|ttyACM0|nothing", SYMLINK+="right" - """), - + ''', + ), Rules.new( - "test multi matches 4", + 'test multi matches 4', Device( - "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links = ["right"], - not_exp_links = ["nomatch", "wrong1", "wrong2", "wrong3"], + '/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0', + exp_links=['right'], + not_exp_links=['nomatch', 'wrong1', 'wrong2', 'wrong3'], ), - rules = r""" + rules=r''' KERNEL=="dontknow|nothing", SYMLINK+="nomatch" KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1" KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2" KERNEL=="all|dontknow|ttyACM0", SYMLINK+="right" KERNEL=="ttyACM0a|nothing", SYMLINK+="wrong3" - """), - + ''', + ), Rules.new( - "test multi matches 5", + 'test multi matches 5', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="foo" TAGS=="|foo", SYMLINK+="found" TAGS=="|aaa", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 6", + 'test multi matches 6', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", ENV{HOGE}="" ENV{HOGE}=="|foo", SYMLINK+="found" ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 7", + 'test multi matches 7', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="foo" TAGS=="foo||bar", SYMLINK+="found" TAGS=="aaa||bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 8", + 'test multi matches 8', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", ENV{HOGE}="" ENV{HOGE}=="foo||bar", SYMLINK+="found" ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 9", + 'test multi matches 9', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found", "found2"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found', 'found2'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="foo" TAGS=="foo|", SYMLINK+="found" TAGS=="aaa|", SYMLINK+="bad" KERNEL=="sda", TAGS!="hoge", SYMLINK+="found2" KERNEL=="sda", TAGS!="foo", SYMLINK+="bad2" - """), - + ''', + ), Rules.new( - "test multi matches 10", + 'test multi matches 10', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", ENV{HOGE}="" ENV{HOGE}=="foo|", SYMLINK+="found" ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "test multi matches 11", + 'test multi matches 11', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG="c" TAGS=="foo||bar||c", SYMLINK+="found" TAGS=="aaa||bbb||ccc", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "TAG refuses invalid string", + 'TAG refuses invalid string', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["valid", "found"], - not_exp_links = ["empty", "invalid_char", "path", "bad", "bad2"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['valid', 'found'], + not_exp_links=['empty', 'invalid_char', 'path', 'bad', 'bad2'], ), - rules = r""" + rules=r''' KERNEL=="sda", TAG+="", TAG+="invalid.char", TAG+="path/is/also/invalid", TAG+="valid" TAGS=="", SYMLINK+="empty" TAGS=="invalid.char", SYMLINK+="invalid_char" @@ -1856,33 +1868,33 @@ def create_rules_file(self) -> None: TAGS=="valid|", SYMLINK+="found" TAGS=="aaa|", SYMLINK+="bad" TAGS=="aaa|bbb", SYMLINK+="bad2" - """), - + ''', + ), Rules.new( - "IMPORT parent test", + 'IMPORT parent test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["parent"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['parent'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["parentenv-parent_right"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['parentenv-parent_right'], ), - delay = 500000, # Serialized! We need to sleep here after adding sda - rules = r""" + delay=500000, # Serialized! We need to sleep here after adding sda + rules=r''' KERNEL=="sda1", IMPORT{parent}="PARENT*", SYMLINK+="parentenv-$env{PARENT_KEY}$env{WRONG_PARENT_KEY}" KERNEL=="sda", IMPORT{program}="/bin/echo -e 'PARENT_KEY=parent_right\nWRONG_PARENT_KEY=parent_wrong'" KERNEL=="sda", SYMLINK+="parent" - """), - + ''', + ), Rules.new( - "GOTO test", + 'GOTO test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["right"], - not_exp_links = ["wrong", "wrong2"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['right'], + not_exp_links=['wrong', 'wrong2'], ), - rules = r""" + rules=r''' KERNEL=="sda1", GOTO="TEST" KERNEL=="sda1", SYMLINK+="wrong" KERNEL=="sda1", GOTO="BAD" @@ -1890,207 +1902,207 @@ def create_rules_file(self) -> None: KERNEL=="sda1", SYMLINK+="right", LABEL="TEST", GOTO="end" KERNEL=="sda1", SYMLINK+="wrong2", LABEL="BAD" LABEL="end" - """), - + ''', + ), Rules.new( - "GOTO label does not exist", + 'GOTO label does not exist', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["right"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['right'], ), - rules = r""" + rules=r''' KERNEL=="sda1", GOTO="does-not-exist" KERNEL=="sda1", SYMLINK+="right", LABEL="exists" - """), - + ''', + ), Rules.new( - "SYMLINK+ compare test", + 'SYMLINK+ compare test', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["right", "link"], - not_exp_links = ["wrong"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['right', 'link'], + not_exp_links=['wrong'], ), - rules = r""" + rules=r''' KERNEL=="sda1", SYMLINK+="link" KERNEL=="sda1", SYMLINK=="link*", SYMLINK+="right" KERNEL=="sda1", SYMLINK=="nolink*", SYMLINK+="wrong" - """), - + ''', + ), Rules.new( - "invalid key operation", + 'invalid key operation', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["yes"], - not_exp_links = ["no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['yes'], + not_exp_links=['no'], ), - rules = r""" + rules=r''' KERNEL="sda1", SYMLINK+="no" KERNEL=="sda1", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "operator chars in attribute", + 'operator chars in attribute', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["yes"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['yes'], ), - rules = r""" + rules=r''' KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "overlong comment line", + 'overlong comment line', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["yes"], - not_exp_links = ["no"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['yes'], + not_exp_links=['no'], ), - rules = r""" + rules=r''' # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 KERNEL=="sda1", SYMLINK+=="no" KERNEL=="sda1", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "magic subsys/kernel lookup", + 'magic subsys/kernel lookup', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["00:16:41:e2:8d:ff"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['00:16:41:e2:8d:ff'], ), - rules = r""" + rules=r''' KERNEL=="sda", SYMLINK+="$attr{[net/eth0]address}" - """), - + ''', + ), Rules.new( - "TEST absolute path", + 'TEST absolute path', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["there"], - not_exp_links = ["notthere"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['there'], + not_exp_links=['notthere'], ), - rules = r""" + rules=r''' TEST=="/etc/passwd", SYMLINK+="there" TEST!="/etc/passwd", SYMLINK+="notthere" - """), - + ''', + ), Rules.new( - "TEST subsys/kernel lookup", + 'TEST subsys/kernel lookup', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["yes"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['yes'], ), - rules = r""" + rules=r''' KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes" - """), - + ''', + ), Rules.new( - "TEST relative path", + 'TEST relative path', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["relative"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['relative'], ), - rules = r""" + rules=r''' KERNEL=="sda", TEST=="size", SYMLINK+="relative" - """), - + ''', + ), Rules.new( - "TEST wildcard substitution (find queue/nr_requests)", + 'TEST wildcard substitution (find queue/nr_requests)', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found-subdir"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found-subdir'], ), - rules = r""" + rules=r''' KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir" - """), - + ''', + ), Rules.new( - "TEST MODE=0000", + 'TEST MODE=0000', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms = "0:0:0000", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_perms='0:0:0000', ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="0000" - """), - + ''', + ), Rules.new( - "TEST PROGRAM feeds OWNER, GROUP, MODE", + 'TEST PROGRAM feeds OWNER, GROUP, MODE', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms = "1:1:0400", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_perms='1:1:0400', ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="666" KERNEL=="sda", PROGRAM=="/bin/echo 1 1 0400", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}" - """), - + ''', + ), Rules.new( - "TEST PROGRAM feeds MODE with overflow", + 'TEST PROGRAM feeds MODE with overflow', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms = "0:0:0440", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_perms='0:0:0440', ), - rules = r""" + rules=r''' KERNEL=="sda", MODE="440" KERNEL=="sda", PROGRAM=="/bin/echo 0 0 0400letsdoabuffferoverflow0123456789012345789012345678901234567890", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}" - """), - + ''', + ), Rules.new( - "magic [subsys/sysname] attribute substitution", + 'magic [subsys/sysname] attribute substitution', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["sda-8741C4G-end"], - exp_perms = "0:0:0600", + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['sda-8741C4G-end'], + exp_perms='0:0:0600', ), - rules = r""" + rules=r''' KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end" - """), - + ''', + ), Rules.new( - "builtin path_id", + 'builtin path_id', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0'], ), - rules = r""" + rules=r''' KERNEL=="sda", IMPORT{builtin}="path_id" KERNEL=="sda", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}" - """), - + ''', + ), Rules.new( - "add and match tag", + 'add and match tag', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", TAG+="green" TAGS=="green", SYMLINK+="found" TAGS=="blue", SYMLINK+="bad" - """), - + ''', + ), Rules.new( "don't crash with lots of tags", Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], ), - rules = f""" + rules=f''' {rules_10k_tags} TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="found" - """), - + ''', + ), Rules.new( - "continuations", + 'continuations', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = f""" + rules=f''' {rules_10k_tags_continuation} TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="bad" KERNEL=="sda",\\ @@ -2104,16 +2116,16 @@ def create_rules_file(self) -> None: \\ TAG+="hoge4" TAGS=="hoge1", TAGS=="hoge2", TAGS=="hoge3", TAGS=="hoge4", SYMLINK+="found" - """), - + ''', + ), Rules.new( - "continuations with empty line", + 'continuations with empty line', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = r""" + rules=r''' # empty line finishes continuation KERNEL=="sda", TAG+="foo" \ @@ -2122,16 +2134,16 @@ def create_rules_file(self) -> None: KERNEL=="sdb", TAG+="bbb" TAGS=="foo", SYMLINK+="found" TAGS=="aaa", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "continuations with space only line", + 'continuations with space only line', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links = ["found"], - not_exp_links = ["bad"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda', + exp_links=['found'], + not_exp_links=['bad'], ), - rules = """ + rules=''' # space only line finishes continuation KERNEL=="sda", TAG+="foo" \\ \t @@ -2140,192 +2152,194 @@ def create_rules_file(self) -> None: KERNEL=="sdb", TAG+="bbb" TAGS=="foo", SYMLINK+="found" TAGS=="aaa", SYMLINK+="bad" - """), - + ''', + ), Rules.new( - "multiple devices", + 'multiple devices', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + exp_links=['part-10'], ), - rules = r""" + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" - """), - + ''', + ), Rules.new( - "multiple devices, same link name, positive prio", + 'multiple devices, same link name, positive prio', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - not_exp_links = ["partition"], - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + not_exp_links=['partition'], + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7", "partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7', 'partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - not_exp_links = ["partition"], - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + not_exp_links=['partition'], + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - not_exp_links = ["partition"], - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + not_exp_links=['partition'], + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - not_exp_links = ["partition"], - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + not_exp_links=['partition'], + exp_links=['part-10'], ), - repeat = 100, - rules = r""" + repeat=100, + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition" KERNEL=="*7", OPTIONS+="link_priority=10" - """), - + ''', + ), Rules.new( - "multiple devices, same link name, negative prio", + 'multiple devices, same link name, negative prio', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - not_exp_links = ["partition"], - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + not_exp_links=['partition'], + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7", "partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7', 'partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - not_exp_links = ["partition"], - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + not_exp_links=['partition'], + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - not_exp_links = ["partition"], - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + not_exp_links=['partition'], + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - not_exp_links = ["partition"], - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + not_exp_links=['partition'], + exp_links=['part-10'], ), - rules = r""" + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition" KERNEL!="*7", OPTIONS+="link_priority=-10" - """), - + ''', + ), Rules.new( - "multiple devices, same link name, positive prio, sleep", + 'multiple devices, same link name, positive prio, sleep', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["part-1"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['part-1'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links = ["part-5"], - not_exp_links = ["partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5', + exp_links=['part-5'], + not_exp_links=['partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - not_exp_links = ["partition"], - exp_links = ["part-6"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6', + not_exp_links=['partition'], + exp_links=['part-6'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links = ["part-7", "partition"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7', + exp_links=['part-7', 'partition'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - not_exp_links = ["partition"], - exp_links = ["part-8"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8', + not_exp_links=['partition'], + exp_links=['part-8'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - not_exp_links = ["partition"], - exp_links = ["part-9"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9', + not_exp_links=['partition'], + exp_links=['part-9'], ), Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - not_exp_links = ["partition"], - exp_links = ["part-10"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10', + not_exp_links=['partition'], + exp_links=['part-10'], ), - delay = 10000, - rules = r""" + delay=10000, + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition" KERNEL=="*7", OPTIONS+="link_priority=10" - """), - + ''', + ), Rules.new( 'all_block_devs', - device_generator = lambda: \ - all_block_devs(lambda name: (["blockdev"], None) if name.endswith('/sda6') else (None, None)), - repeat = 10, - rules = r""" + device_generator=lambda: all_block_devs( + lambda name: (['blockdev'], None) if name.endswith('/sda6') else (None, None) + ), + repeat=10, + rules=r''' SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sd*", SYMLINK+="blockdev" KERNEL=="sda6", OPTIONS+="link_priority=10" - """), - + ''', + ), Rules.new( - "case insensitive match", + 'case insensitive match', Device( - "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links = ["ok"], + '/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1', + exp_links=['ok'], ), - - rules = r""" + rules=r''' KERNEL==i"SDA1", SUBSYSTEMS==i"SCSI", ATTRS{vendor}==i"a?a", SYMLINK+="ok" - """), + ''', + ), ] + def fork_and_run_udev(action: str, rules: Rules) -> None: kinder = [] for k, device in enumerate(rules.devices): @@ -2351,13 +2365,11 @@ def environment_issue(): if os.getuid() != 0: return 'Must be root to run properly' - c = subprocess.run(['systemd-detect-virt', '-r', '-q'], - check=False) + c = subprocess.run(['systemd-detect-virt', '-r', '-q'], check=False) if c.returncode == 0: return 'Running in a chroot, skipping the test' - c = subprocess.run(['systemd-detect-virt', '-c', '-q'], - check=False) + c = subprocess.run(['systemd-detect-virt', '-c', '-q'], check=False) if c.returncode == 0: return 'Running in a container, skipping the test' @@ -2375,27 +2387,24 @@ def udev_setup(): _tmpdir = tempfile.TemporaryDirectory() tmpdir = Path(_tmpdir.name) - UDEV_RUN = tmpdir / 'run' + UDEV_RUN = tmpdir / 'run' UDEV_RULES = UDEV_RUN / 'udev-test.rules' udev_tmpfs = tmpdir / 'tmpfs' - UDEV_DEV = udev_tmpfs / 'dev' - UDEV_SYS = udev_tmpfs / 'sys' + UDEV_DEV = udev_tmpfs / 'dev' + UDEV_SYS = udev_tmpfs / 'sys' - subprocess.run(['umount', udev_tmpfs], - stderr=subprocess.DEVNULL, - check=False) + subprocess.run(['umount', udev_tmpfs], stderr=subprocess.DEVNULL, check=False) udev_tmpfs.mkdir(exist_ok=True, parents=True) - subprocess.check_call(['mount', '-v', - '-t', 'tmpfs', - '-o', 'rw,mode=0755,nosuid,noexec', - 'tmpfs', udev_tmpfs]) + subprocess.check_call( + ['mount', '-v', '-t', 'tmpfs', '-o', 'rw,mode=0755,nosuid,noexec', 'tmpfs', udev_tmpfs] + ) UDEV_DEV.mkdir(exist_ok=True) # setting group and mode of udev_dev ensures the tests work # even if the parent directory has setgid bit enabled. - os.chmod(UDEV_DEV,0o755) + os.chmod(UDEV_DEV, 0o755) os.chown(UDEV_DEV, 0, 0) os.mknod(UDEV_DEV / 'null', 0o600 | stat.S_IFCHR, os.makedev(1, 3)) @@ -2411,10 +2420,10 @@ def udev_setup(): os.chdir(tmpdir) - if subprocess.run([UDEV_BIN, 'check'], - check=False).returncode != 0: - pytest.skip(f'{UDEV_BIN} failed to set up the environment, skipping the test', - allow_module_level=True) + if subprocess.run([UDEV_BIN, 'check'], check=False).returncode != 0: + pytest.skip( + f'{UDEV_BIN} failed to set up the environment, skipping the test', allow_module_level=True + ) yield @@ -2423,7 +2432,7 @@ def udev_setup(): udev_tmpfs.rmdir() -@pytest.mark.parametrize("rules", RULES, ids=(rule.desc for rule in RULES)) +@pytest.mark.parametrize('rules', RULES, ids=(rule.desc for rule in RULES)) def test_udev(rules: Rules, udev_setup): assert udev_setup is None @@ -2441,6 +2450,7 @@ def test_udev(rules: Rules, udev_setup): for device in rules.devices: device.check_remove() + if __name__ == '__main__': issue = environment_issue() if issue: diff --git a/test/units/TEST-69-SHUTDOWN.py b/test/units/TEST-69-SHUTDOWN.py index 51569bf51ef34..a208e42f07847 100755 --- a/test/units/TEST-69-SHUTDOWN.py +++ b/test/units/TEST-69-SHUTDOWN.py @@ -10,56 +10,58 @@ def main(): # TODO: drop once https://bugs.debian.org/1075733 is fixed - with open("/usr/lib/os-release") as f: + with open('/usr/lib/os-release') as f: for line in f: - if line.startswith("ID="): - if "debian" in line or "ubuntu" in line: + if line.startswith('ID='): + if 'debian' in line or 'ubuntu' in line: sys.exit(77) - logger = logging.getLogger("test-shutdown") + logger = logging.getLogger('test-shutdown') consoles = [] for _ in range(2): # Use script to allocate a separate pseudo tty to run the login shell in. console = pexpect.spawn( - "script", ["--quiet", "--return", "--flush", "--command", "login -f root", "/dev/null"], + 'script', + ['--quiet', '--return', '--flush', '--command', 'login -f root', '/dev/null'], logfile=sys.stdout, - env={"TERM": "dumb"}, - encoding="utf-8", + env={'TERM': 'dumb'}, + encoding='utf-8', timeout=60, ) - logger.info("waiting for login prompt") - console.expect(".*# ", 10) + logger.info('waiting for login prompt') + console.expect('.*# ', 10) consoles += [console] - consoles[1].sendline("tty") - consoles[1].expect(r"/dev/(pts/\d+)") + consoles[1].sendline('tty') + consoles[1].expect(r'/dev/(pts/\d+)') pty = console.match.group(1) - logger.info("window 1 at tty %s", pty) + logger.info('window 1 at tty %s', pty) - logger.info("schedule reboot") - consoles[1].sendline("shutdown -r") + logger.info('schedule reboot') + consoles[1].sendline('shutdown -r') consoles[1].expect("Reboot scheduled for (?P.*), use 'shutdown -c' to cancel", 2) - date = consoles[1].match.group("date") - logger.info("reboot scheduled for %s", date) + date = consoles[1].match.group('date') + logger.info('reboot scheduled for %s', date) - logger.info("verify broadcast message") - consoles[0].expect(f"Broadcast message from root@H on {pty}", 2) - consoles[0].expect(f"The system will reboot at {date}!", 2) + logger.info('verify broadcast message') + consoles[0].expect(f'Broadcast message from root@H on {pty}', 2) + consoles[0].expect(f'The system will reboot at {date}!', 2) - logger.info("check show output") - consoles[1].sendline("shutdown --show") + logger.info('check show output') + consoles[1].sendline('shutdown --show') consoles[1].expect(f"Reboot scheduled for {date}, use 'shutdown -c' to cancel", 2) - logger.info("cancel shutdown") - consoles[1].sendline("shutdown -c") - consoles[0].expect("System shutdown has been cancelled", 2) + logger.info('cancel shutdown') + consoles[1].sendline('shutdown -c') + consoles[0].expect('System shutdown has been cancelled', 2) - consoles[0].sendline("> /testok") + consoles[0].sendline('> /testok') -if __name__ == "__main__": + +if __name__ == '__main__': main() # vim: sw=4 et diff --git a/tools/analyze-dump-sort.py b/tools/analyze-dump-sort.py index a464a14bd1229..da6094f9961e7 100755 --- a/tools/analyze-dump-sort.py +++ b/tools/analyze-dump-sort.py @@ -58,6 +58,7 @@ def sort_dump(sourcefile, destfile=None): destfile.flush() return destfile + def parse_args(): p = argparse.ArgumentParser(description=__doc__) p.add_argument('one') @@ -65,6 +66,7 @@ def parse_args(): p.add_argument('--user', action='store_true') return p.parse_args() + if __name__ == '__main__': opts = parse_args() @@ -73,8 +75,7 @@ def parse_args(): two = sort_dump(open(opts.two)) else: user = ['--user'] if opts.user else [] - two = subprocess.run(['systemd-analyze', 'dump', *user], - capture_output=True, text=True, check=True) + two = subprocess.run(['systemd-analyze', 'dump', *user], capture_output=True, text=True, check=True) two = sort_dump(two.stdout.splitlines()) with subprocess.Popen(['diff', '-U10', one.name, two.name], stdout=subprocess.PIPE) as diff: subprocess.Popen(['less'], stdin=diff.stdout) diff --git a/tools/catalog-report.py b/tools/catalog-report.py index 060b1aae869e1..396099dbe4a96 100755 --- a/tools/catalog-report.py +++ b/tools/catalog-report.py @@ -36,9 +36,13 @@ def log_entry(entry): if 'CODE_FILE' in entry: # some of our code was using 'CODE_FUNCTION' instead of 'CODE_FUNC' - print('{}:{} {}'.format(entry.get('CODE_FILE', '???'), - entry.get('CODE_LINE', '???'), - entry.get('CODE_FUNC', None) or entry.get('CODE_FUNCTION', '???'))) + print( + '{}:{} {}'.format( + entry.get('CODE_FILE', '???'), + entry.get('CODE_LINE', '???'), + entry.get('CODE_FUNC', None) or entry.get('CODE_FUNCTION', '???'), + ) + ) print(' {}'.format(entry.get('MESSAGE', 'no message!'))) for k, v in entry.items(): if k.startswith('CODE_') or k in {'MESSAGE_ID', 'MESSAGE'}: @@ -46,12 +50,13 @@ def log_entry(entry): print(f' {k}={v}') print() + if __name__ == '__main__': j = journal.Reader() logged = set() pattern = re.compile('@[A-Z0-9_]+@') - mids = { v:k for k,v in id128.__dict__.items() if k.startswith('SD_MESSAGE') } + mids = {v: k for k, v in id128.__dict__.items() if k.startswith('SD_MESSAGE')} for i, x in enumerate(j): if i % 1000 == 0: diff --git a/tools/check-efi-alignment.py b/tools/check-efi-alignment.py index abdeb22fdbdb3..b442a16f55621 100755 --- a/tools/check-efi-alignment.py +++ b/tools/check-efi-alignment.py @@ -15,22 +15,23 @@ def main(): pe = pefile.PE(sys.argv[1], fast_load=True) for section in pe.sections: - name = section.Name.rstrip(b"\x00").decode() + name = section.Name.rstrip(b'\x00').decode() file_addr = section.PointerToRawData virt_addr = section.VirtualAddress - print(f"{name:10s} file=0x{file_addr:08x} virt=0x{virt_addr:08x}") + print(f'{name:10s} file=0x{file_addr:08x} virt=0x{virt_addr:08x}') if file_addr % 512 != 0: - print(f"File address of {name} section is not aligned to 512 bytes", file=sys.stderr) + print(f'File address of {name} section is not aligned to 512 bytes', file=sys.stderr) return 1 if virt_addr % 512 != 0: - print(f"Virt address of {name} section is not aligned to 512 bytes", file=sys.stderr) + print(f'Virt address of {name} section is not aligned to 512 bytes', file=sys.stderr) return 1 + if __name__ == '__main__': if len(sys.argv) != 2: - print(f"Usage: {sys.argv[0]} pe-image") + print(f'Usage: {sys.argv[0]} pe-image') sys.exit(1) sys.exit(main()) diff --git a/tools/check-version-history.py b/tools/check-version-history.py index db32b6920ab52..9c8e8b23c1fdc 100755 --- a/tools/check-version-history.py +++ b/tools/check-version-history.py @@ -20,17 +20,14 @@ def find_undocumented_functions(pages, ignorelist): filename = os.path.basename(page) pagetree = tree.parse(page) - assert pagetree.getroot().tag == "refentry" + assert pagetree.getroot().tag == 'refentry' hist_section = pagetree.find("refsect1[title='History']") - for func in pagetree.findall(".//funcprototype/funcdef/function"): + for func in pagetree.findall('.//funcprototype/funcdef/function'): path = f"./refsynopsisdiv/funcsynopsis/funcprototype/funcdef/function[.='{func.text}']" assert pagetree.findall(path) == [func] - if ( - hist_section is None - or hist_section.find(f"para/function[.='{func.text}()']") is None - ): + if hist_section is None or hist_section.find(f"para/function[.='{func.text}()']") is None: if func.text not in ignorelist: undocumented.append((filename, func.text)) return undocumented @@ -39,22 +36,22 @@ def find_undocumented_functions(pages, ignorelist): def construct_path(element): tag = element.tag - if tag == "refentry": - return "." + if tag == 'refentry': + return '.' - predicate = "" - if tag == "varlistentry": - text = "".join(element.find("term").itertext()) + predicate = '' + if tag == 'varlistentry': + text = ''.join(element.find('term').itertext()) predicate = f'[term="{text}"]' - elif tag.startswith("refsect"): - text = "".join(element.find("title").itertext()) + elif tag.startswith('refsect'): + text = ''.join(element.find('title').itertext()) predicate = f'[title="{text}"]' - elif tag == "variablelist": + elif tag == 'variablelist': varlists = element.getparent().findall(tag) if len(varlists) > 1: - predicate = f"[{varlists.index(element)+1}]" + predicate = f'[{varlists.index(element) + 1}]' - return construct_path(element.getparent()) + "/" + tag + predicate + return construct_path(element.getparent()) + '/' + tag + predicate def find_undocumented_commands(pages, ignorelist): @@ -63,23 +60,23 @@ def find_undocumented_commands(pages, ignorelist): filename = os.path.basename(page) pagetree = tree.parse(page) - if pagetree.getroot().tag != "refentry": + if pagetree.getroot().tag != 'refentry': continue - for varlistentry in pagetree.findall("*//variablelist/varlistentry"): + for varlistentry in pagetree.findall('*//variablelist/varlistentry'): path = construct_path(varlistentry) assert pagetree.findall(path) == [varlistentry] - listitem = varlistentry.find("listitem") + listitem = varlistentry.find('listitem') parent = listitem if listitem is not None else varlistentry rev = parent.getchildren()[-1] if ( - rev.get("href") != "version-info.xml" and - not path.startswith(tuple(entry[1] for entry in ignorelist if entry[0] == filename)) - ): - undocumented.append((filename, path)) + rev.get('href') != 'version-info.xml' + and not path.startswith(tuple(entry[1] for entry in ignorelist if entry[0] == filename)) + ): # fmt: skip + undocumented.append((filename, path)) return undocumented @@ -90,54 +87,44 @@ def process_pages(pages): for page in pages: filename = os.path.basename(page) - if filename.startswith("org.freedesktop."): # dbus + if filename.startswith('org.freedesktop.'): # dbus continue - if ( - filename.startswith("sd_") - or filename.startswith("sd-") - or filename.startswith("udev_") - ): + if filename.startswith('sd_') or filename.startswith('sd-') or filename.startswith('udev_'): function_pages.append(page) continue command_pages.append(page) - undocumented_commands = find_undocumented_commands( - command_pages, command_ignorelist - ) - undocumented_functions = find_undocumented_functions( - function_pages, function_ignorelist - ) + undocumented_commands = find_undocumented_commands(command_pages, command_ignorelist) + undocumented_functions = find_undocumented_functions(function_pages, function_ignorelist) return undocumented_commands, undocumented_functions -if __name__ == "__main__": - with open(os.path.join(os.path.dirname(__file__), "command_ignorelist")) as f: +if __name__ == '__main__': + with open(os.path.join(os.path.dirname(__file__), 'command_ignorelist')) as f: command_ignorelist = [] - for l in f.read().splitlines(): - if l.startswith("#"): + for line in f.read().splitlines(): + if line.startswith('#'): continue - fname, path = l.split(" ", 1) - path = path.replace("\\n", "\n") + fname, path = line.split(' ', 1) + path = path.replace('\\n', '\n') command_ignorelist.append((fname, path)) - with open(os.path.join(os.path.dirname(__file__), "function_ignorelist")) as f: + with open(os.path.join(os.path.dirname(__file__), 'function_ignorelist')) as f: function_ignorelist = f.read().splitlines() undocumented_commands, undocumented_functions = process_pages(sys.argv[1:]) if undocumented_commands or undocumented_functions: for filename, func in undocumented_functions: - print( - f"Function {func}() in {filename} isn't documented in the History section." - ) + print(f"Function {func}() in {filename} isn't documented in the History section.") for filename, path in undocumented_commands: - print(filename, path, "is undocumented") + print(filename, path, 'is undocumented') if undocumented_commands: print( - "Hint: if you reorganized this part of the documentation, " - "please update tools/commands_ignorelist." + 'Hint: if you reorganized this part of the documentation, ' + 'please update tools/commands_ignorelist.' ) sys.exit(1) diff --git a/tools/dbus_exporter.py b/tools/dbus_exporter.py index 854c7a848bbcb..ce6f8dbf78e8f 100755 --- a/tools/dbus_exporter.py +++ b/tools/dbus_exporter.py @@ -11,14 +11,15 @@ def extract_interfaces_xml(output_dir, executable): # as glibc looks at /proc/self/exe when resolving RPATH env = os.environ.copy() if not os.path.exists('/proc/self'): - env["LD_ORIGIN_PATH"] = executable.parent.as_posix() + env['LD_ORIGIN_PATH'] = executable.parent.as_posix() proc = run( args=[executable.absolute(), '--bus-introspect', 'list'], stdout=PIPE, env=env, check=True, - universal_newlines=True) + text=True, + ) interface_names = (x.split()[1] for x in proc.stdout.splitlines()) @@ -28,19 +29,25 @@ def extract_interfaces_xml(output_dir, executable): stdout=PIPE, env=env, check=True, - universal_newlines=True) + text=True, + ) interface_file_name = output_dir / (interface_name + '.xml') interface_file_name.write_text(proc.stdout) interface_file_name.chmod(0o644) + def main(): parser = ArgumentParser() - parser.add_argument('output', - type=Path) - parser.add_argument('executables', - nargs='+', - type=Path) + parser.add_argument( + 'output', + type=Path, + ) + parser.add_argument( + 'executables', + nargs='+', + type=Path, + ) args = parser.parse_args() @@ -50,5 +57,6 @@ def main(): for exe in args.executables: extract_interfaces_xml(args.output, exe) + if __name__ == '__main__': main() diff --git a/tools/dump-auxv.py b/tools/dump-auxv.py index 1abacda9c1fab..1eb93b0125137 100755 --- a/tools/dump-auxv.py +++ b/tools/dump-auxv.py @@ -85,24 +85,43 @@ 'AT_L3_CACHEGEOMETRY' : 47, 'AT_MINSIGSTKSZ' : 51, # Stack needed for signal delivery -} -AT_AUXV_NAMES = {v:k for k,v in AT_AUXV.items()} +} # fmt: skip +AT_AUXV_NAMES = {v: k for k, v in AT_AUXV.items()} + @click.command(help=__doc__) -@click.option('-b', '--big-endian', 'endian', - flag_value='>', - help='Input is big-endian') -@click.option('-l', '--little-endian', 'endian', - flag_value='<', - help='Input is little-endian') -@click.option('-3', '--32', 'field_width', - flag_value=32, - help='Input is 32-bit') -@click.option('-6', '--64', 'field_width', - flag_value=64, - help='Input is 64-bit') -@click.argument('file', - type=click.File(mode='rb')) +@click.option( + '-b', + '--big-endian', + 'endian', + flag_value='>', + help='Input is big-endian', +) +@click.option( + '-l', + '--little-endian', + 'endian', + flag_value='<', + help='Input is little-endian', +) +@click.option( + '-3', + '--32', + 'field_width', + flag_value=32, + help='Input is 32-bit', +) +@click.option( + '-6', + '--64', + 'field_width', + flag_value=64, + help='Input is 64-bit', +) +@click.argument( + 'file', + type=click.File(mode='rb'), +) def dump(endian, field_width, file): data = file.read() @@ -111,7 +130,7 @@ def dump(endian, field_width, file): if endian is None: endian = '@' - width = {32:'II', 64:'QQ'}[field_width] + width = {32: 'II', 64: 'QQ'}[field_width] format_str = f'{endian}{width}' print(f'# {format_str=}') @@ -137,5 +156,6 @@ def dump(endian, field_width, file): if not seen_null: print('# array not terminated with AT_NULL') + if __name__ == '__main__': dump() diff --git a/tools/elf2efi.py b/tools/elf2efi.py index ed705beb3169f..566e88ab6cd4e 100755 --- a/tools/elf2efi.py +++ b/tools/elf2efi.py @@ -31,12 +31,12 @@ import time import typing from ctypes import ( + LittleEndianStructure, c_char, c_uint8, c_uint16, c_uint32, c_uint64, - LittleEndianStructure, sizeof, ) @@ -46,28 +46,28 @@ class PeCoffHeader(LittleEndianStructure): _fields_ = ( - ("Machine", c_uint16), - ("NumberOfSections", c_uint16), - ("TimeDateStamp", c_uint32), - ("PointerToSymbolTable", c_uint32), - ("NumberOfSymbols", c_uint32), - ("SizeOfOptionalHeader", c_uint16), - ("Characteristics", c_uint16), - ) + ('Machine', c_uint16), + ('NumberOfSections', c_uint16), + ('TimeDateStamp', c_uint32), + ('PointerToSymbolTable', c_uint32), + ('NumberOfSymbols', c_uint32), + ('SizeOfOptionalHeader', c_uint16), + ('Characteristics', c_uint16), + ) # fmt: skip class PeDataDirectory(LittleEndianStructure): _fields_ = ( - ("VirtualAddress", c_uint32), - ("Size", c_uint32), - ) + ('VirtualAddress', c_uint32), + ('Size', c_uint32), + ) # fmt: skip class PeRelocationBlock(LittleEndianStructure): _fields_ = ( - ("PageRVA", c_uint32), - ("BlockSize", c_uint32), - ) + ('PageRVA', c_uint32), + ('BlockSize', c_uint32), + ) # fmt: skip def __init__(self, PageRVA: int): super().__init__(PageRVA) @@ -76,64 +76,64 @@ def __init__(self, PageRVA: int): class PeRelocationEntry(LittleEndianStructure): _fields_ = ( - ("Offset", c_uint16, 12), - ("Type", c_uint16, 4), - ) + ('Offset', c_uint16, 12), + ('Type', c_uint16, 4), + ) # fmt: skip class PeOptionalHeaderStart(LittleEndianStructure): _fields_ = ( - ("Magic", c_uint16), - ("MajorLinkerVersion", c_uint8), - ("MinorLinkerVersion", c_uint8), - ("SizeOfCode", c_uint32), - ("SizeOfInitializedData", c_uint32), - ("SizeOfUninitializedData", c_uint32), - ("AddressOfEntryPoint", c_uint32), - ("BaseOfCode", c_uint32), - ) + ('Magic', c_uint16), + ('MajorLinkerVersion', c_uint8), + ('MinorLinkerVersion', c_uint8), + ('SizeOfCode', c_uint32), + ('SizeOfInitializedData', c_uint32), + ('SizeOfUninitializedData', c_uint32), + ('AddressOfEntryPoint', c_uint32), + ('BaseOfCode', c_uint32), + ) # fmt: skip class PeOptionalHeaderMiddle(LittleEndianStructure): _fields_ = ( - ("SectionAlignment", c_uint32), - ("FileAlignment", c_uint32), - ("MajorOperatingSystemVersion", c_uint16), - ("MinorOperatingSystemVersion", c_uint16), - ("MajorImageVersion", c_uint16), - ("MinorImageVersion", c_uint16), - ("MajorSubsystemVersion", c_uint16), - ("MinorSubsystemVersion", c_uint16), - ("Win32VersionValue", c_uint32), - ("SizeOfImage", c_uint32), - ("SizeOfHeaders", c_uint32), - ("CheckSum", c_uint32), - ("Subsystem", c_uint16), - ("DllCharacteristics", c_uint16), - ) + ('SectionAlignment', c_uint32), + ('FileAlignment', c_uint32), + ('MajorOperatingSystemVersion', c_uint16), + ('MinorOperatingSystemVersion', c_uint16), + ('MajorImageVersion', c_uint16), + ('MinorImageVersion', c_uint16), + ('MajorSubsystemVersion', c_uint16), + ('MinorSubsystemVersion', c_uint16), + ('Win32VersionValue', c_uint32), + ('SizeOfImage', c_uint32), + ('SizeOfHeaders', c_uint32), + ('CheckSum', c_uint32), + ('Subsystem', c_uint16), + ('DllCharacteristics', c_uint16), + ) # fmt: skip class PeOptionalHeaderEnd(LittleEndianStructure): _fields_ = ( - ("LoaderFlags", c_uint32), - ("NumberOfRvaAndSizes", c_uint32), - ("ExportTable", PeDataDirectory), - ("ImportTable", PeDataDirectory), - ("ResourceTable", PeDataDirectory), - ("ExceptionTable", PeDataDirectory), - ("CertificateTable", PeDataDirectory), - ("BaseRelocationTable", PeDataDirectory), - ("Debug", PeDataDirectory), - ("Architecture", PeDataDirectory), - ("GlobalPtr", PeDataDirectory), - ("TLSTable", PeDataDirectory), - ("LoadConfigTable", PeDataDirectory), - ("BoundImport", PeDataDirectory), - ("IAT", PeDataDirectory), - ("DelayImportDescriptor", PeDataDirectory), - ("CLRRuntimeHeader", PeDataDirectory), - ("Reserved", PeDataDirectory), - ) + ('LoaderFlags', c_uint32), + ('NumberOfRvaAndSizes', c_uint32), + ('ExportTable', PeDataDirectory), + ('ImportTable', PeDataDirectory), + ('ResourceTable', PeDataDirectory), + ('ExceptionTable', PeDataDirectory), + ('CertificateTable', PeDataDirectory), + ('BaseRelocationTable', PeDataDirectory), + ('Debug', PeDataDirectory), + ('Architecture', PeDataDirectory), + ('GlobalPtr', PeDataDirectory), + ('TLSTable', PeDataDirectory), + ('LoadConfigTable', PeDataDirectory), + ('BoundImport', PeDataDirectory), + ('IAT', PeDataDirectory), + ('DelayImportDescriptor', PeDataDirectory), + ('CLRRuntimeHeader', PeDataDirectory), + ('Reserved', PeDataDirectory), + ) # fmt: skip class PeOptionalHeader(LittleEndianStructure): @@ -141,47 +141,47 @@ class PeOptionalHeader(LittleEndianStructure): class PeOptionalHeader32(PeOptionalHeader): - _anonymous_ = ("Start", "Middle", "End") + _anonymous_ = ('Start', 'Middle', 'End') _fields_ = ( - ("Start", PeOptionalHeaderStart), - ("BaseOfData", c_uint32), - ("ImageBase", c_uint32), - ("Middle", PeOptionalHeaderMiddle), - ("SizeOfStackReserve", c_uint32), - ("SizeOfStackCommit", c_uint32), - ("SizeOfHeapReserve", c_uint32), - ("SizeOfHeapCommit", c_uint32), - ("End", PeOptionalHeaderEnd), - ) + ('Start', PeOptionalHeaderStart), + ('BaseOfData', c_uint32), + ('ImageBase', c_uint32), + ('Middle', PeOptionalHeaderMiddle), + ('SizeOfStackReserve', c_uint32), + ('SizeOfStackCommit', c_uint32), + ('SizeOfHeapReserve', c_uint32), + ('SizeOfHeapCommit', c_uint32), + ('End', PeOptionalHeaderEnd), + ) # fmt: skip class PeOptionalHeader32Plus(PeOptionalHeader): - _anonymous_ = ("Start", "Middle", "End") + _anonymous_ = ('Start', 'Middle', 'End') _fields_ = ( - ("Start", PeOptionalHeaderStart), - ("ImageBase", c_uint64), - ("Middle", PeOptionalHeaderMiddle), - ("SizeOfStackReserve", c_uint64), - ("SizeOfStackCommit", c_uint64), - ("SizeOfHeapReserve", c_uint64), - ("SizeOfHeapCommit", c_uint64), - ("End", PeOptionalHeaderEnd), - ) + ('Start', PeOptionalHeaderStart), + ('ImageBase', c_uint64), + ('Middle', PeOptionalHeaderMiddle), + ('SizeOfStackReserve', c_uint64), + ('SizeOfStackCommit', c_uint64), + ('SizeOfHeapReserve', c_uint64), + ('SizeOfHeapCommit', c_uint64), + ('End', PeOptionalHeaderEnd), + ) # fmt: skip class PeSection(LittleEndianStructure): _fields_ = ( - ("Name", c_char * 8), - ("VirtualSize", c_uint32), - ("VirtualAddress", c_uint32), - ("SizeOfRawData", c_uint32), - ("PointerToRawData", c_uint32), - ("PointerToRelocations", c_uint32), - ("PointerToLinenumbers", c_uint32), - ("NumberOfRelocations", c_uint16), - ("NumberOfLinenumbers", c_uint16), - ("Characteristics", c_uint32), - ) + ('Name', c_char * 8), + ('VirtualSize', c_uint32), + ('VirtualAddress', c_uint32), + ('SizeOfRawData', c_uint32), + ('PointerToRawData', c_uint32), + ('PointerToRelocations', c_uint32), + ('PointerToLinenumbers', c_uint32), + ('NumberOfRelocations', c_uint16), + ('NumberOfLinenumbers', c_uint16), + ('Characteristics', c_uint32), + ) # fmt: skip def __init__(self) -> None: super().__init__() @@ -195,30 +195,32 @@ def __init__(self) -> None: assert sizeof(PeOptionalHeader32) == 224 assert sizeof(PeOptionalHeader32Plus) == 240 +# fmt: off PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE -PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ +PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ +# fmt: on IGNORE_SECTIONS = [ - ".eh_frame", - ".eh_frame_hdr", - ".ARM.exidx", - ".relro_padding", - ".sframe", + '.eh_frame', + '.eh_frame_hdr', + '.ARM.exidx', + '.relro_padding', + '.sframe', ] IGNORE_SECTION_TYPES = [ - "SHT_DYNAMIC", - "SHT_DYNSYM", - "SHT_GNU_ATTRIBUTES", - "SHT_GNU_HASH", - "SHT_HASH", - "SHT_NOTE", - "SHT_REL", - "SHT_RELA", - "SHT_RELR", - "SHT_STRTAB", - "SHT_SYMTAB", + 'SHT_DYNAMIC', + 'SHT_DYNSYM', + 'SHT_GNU_ATTRIBUTES', + 'SHT_GNU_HASH', + 'SHT_HASH', + 'SHT_NOTE', + 'SHT_REL', + 'SHT_RELA', + 'SHT_RELR', + 'SHT_STRTAB', + 'SHT_SYMTAB', ] # EFI mandates 4KiB memory pages. @@ -227,7 +229,7 @@ def __init__(self) -> None: # Nobody cares about DOS headers, so put the PE header right after. PE_OFFSET = 64 -PE_MAGIC = b"PE\0\0" +PE_MAGIC = b'PE\0\0' def align_to(x: int, align: int) -> int: @@ -239,8 +241,7 @@ def align_down(x: int, align: int) -> int: def next_section_address(sections: list[PeSection]) -> int: - return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize, - SECTION_ALIGNMENT) + return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT) class BadSectionError(ValueError): @@ -256,29 +257,31 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: relro = None for elf_seg in file.iter_segments(): - if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT: - raise BadSectionError(f"ELF segment {elf_seg['p_type']} is not properly aligned" - f" ({elf_seg['p_align']} != {SECTION_ALIGNMENT})") - if elf_seg["p_type"] == "PT_GNU_RELRO": + if elf_seg['p_type'] == 'PT_LOAD' and elf_seg['p_align'] != SECTION_ALIGNMENT: + raise BadSectionError( + f'ELF segment {elf_seg["p_type"]} is not properly aligned' + f' ({elf_seg["p_align"]} != {SECTION_ALIGNMENT})' + ) + if elf_seg['p_type'] == 'PT_GNU_RELRO': relro = elf_seg for elf_s in file.iter_sections(): if ( - elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_ALLOC == 0 - or elf_s["sh_type"] in IGNORE_SECTION_TYPES + elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_ALLOC == 0 + or elf_s['sh_type'] in IGNORE_SECTION_TYPES or elf_s.name in IGNORE_SECTIONS - or elf_s["sh_size"] == 0 + or elf_s['sh_size'] == 0 ): continue - if elf_s["sh_type"] not in ["SHT_PROGBITS", "SHT_NOBITS"]: - raise BadSectionError(f"Unknown section {elf_s.name} with type {elf_s['sh_type']}") + if elf_s['sh_type'] not in ['SHT_PROGBITS', 'SHT_NOBITS']: + raise BadSectionError(f'Unknown section {elf_s.name} with type {elf_s["sh_type"]}') if elf_s.name == '.got': # FIXME: figure out why those sections are inserted - print("WARNING: Non-empty .got section", file=sys.stderr) + print('WARNING: Non-empty .got section', file=sys.stderr) - if elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_EXECINSTR: + if elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_EXECINSTR: rwx = PE_CHARACTERISTICS_RX - elif elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_WRITE: + elif elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_WRITE: rwx = PE_CHARACTERISTICS_RW else: rwx = PE_CHARACTERISTICS_R @@ -293,11 +296,11 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: if pe_s: # Insert padding to properly align the section. - pad_len = elf_s["sh_addr"] - pe_s.VirtualAddress - len(pe_s.data) + pad_len = elf_s['sh_addr'] - pe_s.VirtualAddress - len(pe_s.data) pe_s.data += bytearray(pad_len) + elf_s.data() else: pe_s = PeSection() - pe_s.VirtualAddress = elf_s["sh_addr"] + pe_s.VirtualAddress = elf_s['sh_addr'] pe_s.Characteristics = rwx pe_s.data = elf_s.data() @@ -306,8 +309,8 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]: def convert_sections( - file: elffile.ELFFile, - opt: PeOptionalHeader, + file: elffile.ELFFile, + opt: PeOptionalHeader, ) -> list[PeSection]: last_vma = (0, 0) sections = [] @@ -324,24 +327,26 @@ def convert_sections( pe_s.VirtualSize = len(pe_s.data) pe_s.SizeOfRawData = align_to(len(pe_s.data), FILE_ALIGNMENT) pe_s.Name = { - PE_CHARACTERISTICS_RX: b".text", - PE_CHARACTERISTICS_RW: b".data", - PE_CHARACTERISTICS_R: b".rodata", + PE_CHARACTERISTICS_RX: b'.text', + PE_CHARACTERISTICS_RW: b'.data', + PE_CHARACTERISTICS_R: b'.rodata', }[pe_s.Characteristics] # This can happen if not building with '-z separate-code'. if pe_s.VirtualAddress < sum(last_vma): - raise BadSectionError(f"Section {pe_s.Name.decode()!r} @0x{pe_s.VirtualAddress:x} overlaps" - f" previous section @0x{last_vma[0]:x}+0x{last_vma[1]:x}=@0x{sum(last_vma):x}") + raise BadSectionError( + f'Section {pe_s.Name.decode()!r} @0x{pe_s.VirtualAddress:x} overlaps' + f' previous section @0x{last_vma[0]:x}+0x{last_vma[1]:x}=@0x{sum(last_vma):x}' + ) last_vma = (pe_s.VirtualAddress, pe_s.VirtualSize) - if pe_s.Name == b".text": + if pe_s.Name == b'.text': opt.BaseOfCode = pe_s.VirtualAddress opt.SizeOfCode += pe_s.VirtualSize else: opt.SizeOfInitializedData += pe_s.VirtualSize - if pe_s.Name == b".data" and isinstance(opt, PeOptionalHeader32): + if pe_s.Name == b'.data' and isinstance(opt, PeOptionalHeader32): opt.BaseOfData = pe_s.VirtualAddress sections.append(pe_s) @@ -355,14 +360,14 @@ def copy_sections( input_names: str, sections: list[PeSection], ) -> None: - for name in input_names.split(","): + for name in input_names.split(','): elf_s = file.get_section_by_name(name) if not elf_s: continue if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0: - raise BadSectionError(f"ELF section {name} is not aligned") - if elf_s["sh_flags"] & (elf.constants.SH_FLAGS.SHF_EXECINSTR | elf.constants.SH_FLAGS.SHF_WRITE) != 0: - raise BadSectionError(f"ELF section {name} is not read-only data") + raise BadSectionError(f'ELF section {name} is not aligned') + if elf_s['sh_flags'] & (elf.constants.SH_FLAGS.SHF_EXECINSTR | elf.constants.SH_FLAGS.SHF_WRITE) != 0: # fmt: skip + raise BadSectionError(f'ELF section {name} is not read-only data') pe_s = PeSection() pe_s.Name = name.encode() @@ -381,18 +386,21 @@ def apply_elf_relative_relocation( sections: list[PeSection], addend_size: int, ) -> None: - [target] = [pe_s for pe_s in sections - if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data)] + [target] = [ + pe_s + for pe_s in sections + if pe_s.VirtualAddress <= reloc['r_offset'] < pe_s.VirtualAddress + len(pe_s.data) + ] - addend_offset = reloc["r_offset"] - target.VirtualAddress + addend_offset = reloc['r_offset'] - target.VirtualAddress if reloc.is_RELA(): - addend = reloc["r_addend"] + addend = reloc['r_addend'] else: addend = target.data[addend_offset : addend_offset + addend_size] - addend = int.from_bytes(addend, byteorder="little") + addend = int.from_bytes(addend, byteorder='little') - value = (image_base + addend).to_bytes(addend_size, byteorder="little") + value = (image_base + addend).to_bytes(addend_size, byteorder='little') target.data[addend_offset : addend_offset + addend_size] = value @@ -404,47 +412,44 @@ def convert_elf_reloc_table( pe_reloc_blocks: dict[int, PeRelocationBlock], ) -> None: NONE_RELOC = { - "EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_NONE"], - "EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"], - "EM_ARM": elf.enums.ENUM_RELOC_TYPE_ARM["R_ARM_NONE"], - "EM_LOONGARCH": 0, - "EM_RISCV": 0, - "EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_NONE"], - }[file["e_machine"]] + 'EM_386': elf.enums.ENUM_RELOC_TYPE_i386['R_386_NONE'], + 'EM_AARCH64': elf.enums.ENUM_RELOC_TYPE_AARCH64['R_AARCH64_NONE'], + 'EM_ARM': elf.enums.ENUM_RELOC_TYPE_ARM['R_ARM_NONE'], + 'EM_LOONGARCH': 0, + 'EM_RISCV': 0, + 'EM_X86_64': elf.enums.ENUM_RELOC_TYPE_x64['R_X86_64_NONE'], + }[file['e_machine']] # fmt: skip RELATIVE_RELOC = { - "EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_RELATIVE"], - "EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_RELATIVE"], - "EM_ARM": elf.enums.ENUM_RELOC_TYPE_ARM["R_ARM_RELATIVE"], - "EM_LOONGARCH": 3, - "EM_RISCV": 3, - "EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"], - }[file["e_machine"]] + 'EM_386': elf.enums.ENUM_RELOC_TYPE_i386['R_386_RELATIVE'], + 'EM_AARCH64': elf.enums.ENUM_RELOC_TYPE_AARCH64['R_AARCH64_RELATIVE'], + 'EM_ARM': elf.enums.ENUM_RELOC_TYPE_ARM['R_ARM_RELATIVE'], + 'EM_LOONGARCH': 3, + 'EM_RISCV': 3, + 'EM_X86_64': elf.enums.ENUM_RELOC_TYPE_x64['R_X86_64_RELATIVE'], + }[file['e_machine']] # fmt: skip for reloc in elf_reloc_table.iter_relocations(): - if reloc["r_info_type"] == NONE_RELOC: + if reloc['r_info_type'] == NONE_RELOC: continue - if reloc["r_info_type"] == RELATIVE_RELOC: - apply_elf_relative_relocation(reloc, - elf_image_base, - sections, - file.elfclass // 8) + if reloc['r_info_type'] == RELATIVE_RELOC: + apply_elf_relative_relocation(reloc, elf_image_base, sections, file.elfclass // 8) # Now that the ELF relocation has been applied, we can create a PE relocation. - block_rva = reloc["r_offset"] & ~0xFFF + block_rva = reloc['r_offset'] & ~0xFFF if block_rva not in pe_reloc_blocks: pe_reloc_blocks[block_rva] = PeRelocationBlock(block_rva) entry = PeRelocationEntry() - entry.Offset = reloc["r_offset"] & 0xFFF + entry.Offset = reloc['r_offset'] & 0xFFF # REL_BASED_HIGHLOW or REL_BASED_DIR64 entry.Type = 3 if file.elfclass == 32 else 10 pe_reloc_blocks[block_rva].entries.append(entry) continue - raise BadSectionError(f"Unsupported relocation {reloc}") + raise BadSectionError(f'Unsupported relocation {reloc}') def convert_elf_relocations( @@ -453,27 +458,29 @@ def convert_elf_relocations( sections: list[PeSection], minimum_sections: int, ) -> typing.Optional[PeSection]: - dynamic = file.get_section_by_name(".dynamic") + dynamic = file.get_section_by_name('.dynamic') if dynamic is None: - raise BadSectionError("ELF .dynamic section is missing") + raise BadSectionError('ELF .dynamic section is missing') - [flags_tag] = dynamic.iter_tags("DT_FLAGS_1") - if not flags_tag["d_val"] & elf.enums.ENUM_DT_FLAGS_1["DF_1_PIE"]: - raise ValueError("ELF file is not a PIE") + [flags_tag] = dynamic.iter_tags('DT_FLAGS_1') + if not flags_tag['d_val'] & elf.enums.ENUM_DT_FLAGS_1['DF_1_PIE']: + raise ValueError('ELF file is not a PIE') # This checks that the ELF image base is 0. - symtab = file.get_section_by_name(".symtab") + symtab = file.get_section_by_name('.symtab') if symtab: - exe_start = symtab.get_symbol_by_name("__executable_start") - if exe_start and exe_start[0]["st_value"] != 0: - raise ValueError("Unexpected ELF image base") - - opt.SizeOfHeaders = align_to(PE_OFFSET - + len(PE_MAGIC) - + sizeof(PeCoffHeader) - + sizeof(opt) - + sizeof(PeSection) * max(len(sections) + 1, minimum_sections), - FILE_ALIGNMENT) + exe_start = symtab.get_symbol_by_name('__executable_start') + if exe_start and exe_start[0]['st_value'] != 0: + raise ValueError('Unexpected ELF image base') + + opt.SizeOfHeaders = align_to( + PE_OFFSET + + len(PE_MAGIC) + + sizeof(PeCoffHeader) + + sizeof(opt) + + sizeof(PeSection) * max(len(sections) + 1, minimum_sections), + FILE_ALIGNMENT, + ) # We use the basic VMA layout from the ELF image in the PE image. This could cause the first # section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed @@ -482,23 +489,18 @@ def convert_elf_relocations( # the ELF portions of the image. segment_offset = 0 if sections[0].VirtualAddress < opt.SizeOfHeaders: - segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress, - SECTION_ALIGNMENT) + segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress, SECTION_ALIGNMENT) - opt.AddressOfEntryPoint = file["e_entry"] + segment_offset + opt.AddressOfEntryPoint = file['e_entry'] + segment_offset opt.BaseOfCode += segment_offset if isinstance(opt, PeOptionalHeader32): opt.BaseOfData += segment_offset pe_reloc_blocks: dict[int, PeRelocationBlock] = {} for reloc_type, reloc_table in dynamic.get_relocation_tables().items(): - if reloc_type not in ["REL", "RELA"]: - raise BadSectionError(f"Unsupported relocation type {reloc_type}") - convert_elf_reloc_table(file, - reloc_table, - opt.ImageBase + segment_offset, - sections, - pe_reloc_blocks) + if reloc_type not in ['REL', 'RELA']: + raise BadSectionError(f'Unsupported relocation type {reloc_type}') + convert_elf_reloc_table(file, reloc_table, opt.ImageBase + segment_offset, sections, pe_reloc_blocks) for pe_s in sections: pe_s.VirtualAddress += segment_offset @@ -524,7 +526,7 @@ def convert_elf_relocations( data += entry pe_reloc_s = PeSection() - pe_reloc_s.Name = b".reloc" + pe_reloc_s.Name = b'.reloc' pe_reloc_s.data = data pe_reloc_s.VirtualAddress = next_section_address(sections) pe_reloc_s.VirtualSize = len(data) @@ -543,9 +545,9 @@ def write_pe( opt: PeOptionalHeader, sections: list[PeSection], ) -> None: - file.write(b"MZ") + file.write(b'MZ') file.seek(0x3C, io.SEEK_SET) - file.write(PE_OFFSET.to_bytes(2, byteorder="little")) + file.write(PE_OFFSET.to_bytes(2, byteorder='little')) file.seek(PE_OFFSET, io.SEEK_SET) file.write(PE_MAGIC) file.write(coff) @@ -554,8 +556,10 @@ def write_pe( offset = opt.SizeOfHeaders for pe_s in sorted(sections, key=lambda s: s.VirtualAddress): if pe_s.VirtualAddress < opt.SizeOfHeaders: - raise BadSectionError(f"Section {pe_s.Name} @0x{pe_s.VirtualAddress:x} overlaps" - " PE headers ending at 0x{opt.SizeOfHeaders:x}") + raise BadSectionError( + f'Section {pe_s.Name} @0x{pe_s.VirtualAddress:x} overlaps' + ' PE headers ending at 0x{opt.SizeOfHeaders:x}' + ) pe_s.PointerToRawData = offset file.write(pe_s) @@ -573,20 +577,20 @@ def write_pe( def elf2efi(args: argparse.Namespace) -> None: file = elffile.ELFFile(args.ELF) if not file.little_endian: - raise ValueError("ELF file is not little-endian") - if file["e_type"] not in ["ET_DYN", "ET_EXEC"]: - raise ValueError(f"Unsupported ELF type {file['e_type']}") + raise ValueError('ELF file is not little-endian') + if file['e_type'] not in ['ET_DYN', 'ET_EXEC']: + raise ValueError(f'Unsupported ELF type {file["e_type"]}') pe_arch = { - "EM_386": 0x014C, - "EM_AARCH64": 0xAA64, - "EM_ARM": 0x01C2, - "EM_LOONGARCH": 0x6232 if file.elfclass == 32 else 0x6264, - "EM_RISCV": 0x5032 if file.elfclass == 32 else 0x5064, - "EM_X86_64": 0x8664, - }.get(file["e_machine"]) + 'EM_386': 0x014C, + 'EM_AARCH64': 0xAA64, + 'EM_ARM': 0x01C2, + 'EM_LOONGARCH': 0x6232 if file.elfclass == 32 else 0x6264, + 'EM_RISCV': 0x5032 if file.elfclass == 32 else 0x5064, + 'EM_X86_64': 0x8664, + }.get(file['e_machine']) # fmt: skip if pe_arch is None: - raise ValueError(f"Unsupported ELF architecture {file['e_machine']}") + raise ValueError(f'Unsupported ELF architecture {file["e_machine"]}') coff = PeCoffHeader() opt = PeOptionalHeader32() if file.elfclass == 32 else PeOptionalHeader32Plus() @@ -605,7 +609,7 @@ def elf2efi(args: argparse.Namespace) -> None: coff.Machine = pe_arch coff.NumberOfSections = len(sections) - coff.TimeDateStamp = int(os.environ.get("SOURCE_DATE_EPOCH") or time.time()) + coff.TimeDateStamp = int(os.environ.get('SOURCE_DATE_EPOCH') or time.time()) coff.SizeOfOptionalHeader = sizeof(opt) # EXECUTABLE_IMAGE|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED|DEBUG_STRIPPED # and (32BIT_MACHINE or LARGE_ADDRESS_AWARE) @@ -632,66 +636,64 @@ def elf2efi(args: argparse.Namespace) -> None: opt.NumberOfRvaAndSizes = N_DATA_DIRECTORY_ENTRIES if pe_reloc_s: - opt.BaseRelocationTable = PeDataDirectory( - pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize - ) + opt.BaseRelocationTable = PeDataDirectory(pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize) write_pe(args.PE, coff, opt, sections) def create_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(description="Convert ELF binaries to PE/EFI") + parser = argparse.ArgumentParser(description='Convert ELF binaries to PE/EFI') parser.add_argument( - "--version-major", + '--version-major', type=int, default=0, - help="Major image version of EFI image", + help='Major image version of EFI image', ) parser.add_argument( - "--version-minor", + '--version-minor', type=int, default=0, - help="Minor image version of EFI image", + help='Minor image version of EFI image', ) parser.add_argument( - "--efi-major", + '--efi-major', type=int, default=0, - help="Minimum major EFI subsystem version", + help='Minimum major EFI subsystem version', ) parser.add_argument( - "--efi-minor", + '--efi-minor', type=int, default=0, - help="Minimum minor EFI subsystem version", + help='Minimum minor EFI subsystem version', ) parser.add_argument( - "--subsystem", + '--subsystem', type=int, default=10, - help="PE subsystem", + help='PE subsystem', ) parser.add_argument( - "ELF", - type=argparse.FileType("rb"), - help="Input ELF file", + 'ELF', + type=argparse.FileType('rb'), + help='Input ELF file', ) parser.add_argument( - "PE", - type=argparse.FileType("wb"), - help="Output PE/EFI file", + 'PE', + type=argparse.FileType('wb'), + help='Output PE/EFI file', ) parser.add_argument( - "--minimum-sections", + '--minimum-sections', type=int, default=0, - help="Minimum number of sections to leave space for", + help='Minimum number of sections to leave space for', ) parser.add_argument( - "--copy-sections", + '--copy-sections', type=str, - default="", - help="Copy these sections if found", + default='', + help='Copy these sections if found', ) return parser @@ -701,5 +703,5 @@ def main() -> None: elf2efi(parser.parse_args()) -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/tools/fetch-distro.py b/tools/fetch-distro.py index 00b8eca4d1847..a94b80718687b 100755 --- a/tools/fetch-distro.py +++ b/tools/fetch-distro.py @@ -12,6 +12,7 @@ import subprocess from pathlib import Path + def parse_args(): p = argparse.ArgumentParser( description=__doc__, @@ -27,34 +28,35 @@ def parse_args(): default=True, ) p.add_argument( - '--update', '-u', + '--update', + '-u', action='store_true', default=False, ) p.add_argument('--profile') return p.parse_args() + def read_config(distro: str): cmd = ['mkosi', '--json', '-d', distro, '-f', 'summary'] if args.profile: cmd += ['--profile', args.profile] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') text = subprocess.check_output(cmd, text=True) data = json.loads(text) - images = {image["Image"]: image for image in data["Images"]} - return images["build"] + images = {image['Image']: image for image in data['Images']} + return images['build'] + def commit_file(distro: str, files: list[Path], commit: str, changes: str): - message = '\n'.join(( - f'mkosi: update {distro} commit reference to {commit}', - '', - changes)) + message = '\n'.join((f'mkosi: update {distro} commit reference to {commit}', '', changes)) cmd = ['git', 'commit', '-m', message, *(str(file) for file in files)] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) + def checkout_distro(args, distro: str, config: dict): url = config['Environment']['GIT_URL'] branch = config['Environment']['GIT_BRANCH'] @@ -74,28 +76,30 @@ def checkout_distro(args, distro: str, config: dict): reference = ['--reference-if-able=.'] if distro == 'debian' else [] cmd = [ - 'git', 'clone', url, + 'git', + 'clone', + url, f'--branch={branch}', *sparse, dest.as_posix(), *reference, ] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) # Sparse checkout if the package is in a subdirectory if subdir is not None: - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'sparse-checkout', 'set', - '--no-cone', f'{subdir}'] - print(f"+ {shlex.join(cmd)}") + cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'sparse-checkout', 'set', '--no-cone', f'{subdir}'] + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'checkout', 'HEAD'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) args.fetch = False # no need to fetch if we just cloned + def update_distro(args, distro: str, config: dict): branch = config['Environment']['GIT_BRANCH'] subdir = config['Environment'].get('GIT_SUBDIR') @@ -103,32 +107,46 @@ def update_distro(args, distro: str, config: dict): pkg_subdir = config['Environment']['PKG_SUBDIR'] if args.fetch: - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'fetch', 'origin', '-v', - f'{branch}:remotes/origin/{branch}'] - print(f"+ {shlex.join(cmd)}") + cmd = [ + 'git', + '-C', f'pkg/{pkg_subdir}', + 'fetch', + 'origin', + '-v', + f'{branch}:remotes/origin/{branch}', + ] # fmt: skip + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'switch', branch] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'log', '-n1', '--format=%H', - f'refs/remotes/origin/{branch}'] + cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'log', '-n1', '--format=%H', f'refs/remotes/origin/{branch}'] if subdir is not None: cmd += [f'{subdir}'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') new_commit = subprocess.check_output(cmd, text=True).strip() if old_commit == new_commit: print(f'{pkg_subdir}: commit {new_commit!s} is still fresh') return - cmd = ['git', '-C', f'pkg/{pkg_subdir}', 'log', '--graph', '--no-merges', - '--pretty=oneline', '--no-decorate', '--abbrev-commit', '--abbrev=10', - f'{old_commit}..{new_commit}'] + cmd = [ + 'git', + '-C', f'pkg/{pkg_subdir}', + 'log', + '--graph', + '--no-merges', + '--pretty=oneline', + '--no-decorate', + '--abbrev-commit', + '--abbrev=10', + f'{old_commit}..{new_commit}', + ] # fmt: skip if subdir is not None: cmd += [f'{subdir}'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') changes = subprocess.check_output(cmd, text=True).strip() conf_dir = Path('mkosi/mkosi.pkgenv/mkosi.conf.d') @@ -142,8 +160,8 @@ def update_distro(args, distro: str, config: dict): file.write_text(new) tocommit = [file] - if distro == "fedora": - packit = Path(".packit.yml") + if distro == 'fedora': + packit = Path('.packit.yml') s = packit.read_text() assert old_commit in s new = s.replace(old_commit, new_commit) @@ -155,6 +173,7 @@ def update_distro(args, distro: str, config: dict): else: raise ValueError(f'{distro}: hash {new_commit} not found under {conf_dir}') + if __name__ == '__main__': args = parse_args() diff --git a/tools/fetch-mkosi.py b/tools/fetch-mkosi.py index 6e6440d65ef9c..9a67538e21553 100755 --- a/tools/fetch-mkosi.py +++ b/tools/fetch-mkosi.py @@ -7,9 +7,9 @@ """ import argparse +import re import shlex import subprocess -import re from pathlib import Path URL = 'https://github.com/systemd/mkosi' @@ -17,6 +17,7 @@ CONFIG = Path('mkosi/mkosi.conf') WORKFLOWS = [Path('.github/workflows') / f for f in ['mkosi.yml', 'coverage.yml', 'linter.yml']] + def parse_args(): p = argparse.ArgumentParser( description=__doc__, @@ -26,59 +27,74 @@ def parse_args(): type=Path, ) p.add_argument( - '--update', '-u', + '--update', + '-u', action='store_true', default=False, ) return p.parse_args() + def read_config(): print(f'Reading {CONFIG}…') - matches = [m.group(1) - for line in open(CONFIG) - if (m := re.match('^MinimumVersion=commit:([a-z0-9]{40})$', - line.strip()))] + with open(CONFIG) as f: + matches = [ + m.group(1) + for line in f + if (m := re.match('^MinimumVersion=commit:([a-z0-9]{40})$', line.strip())) + ] assert len(matches) == 1 return matches[0] + def commit_file(files: list[Path], commit: str, changes: str): - message = '\n'.join(( - f'mkosi: update mkosi ref to {commit}', - '', - changes)) + message = '\n'.join((f'mkosi: update mkosi ref to {commit}', '', changes)) cmd = ['git', 'commit', '-m', message, *(str(file) for file in files)] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) + def checkout_mkosi(args): if args.dir.exists(): print(f'{args.dir} already exists.') return cmd = [ - 'git', 'clone', URL, + 'git', + 'clone', + URL, f'--branch={BRANCH}', args.dir.as_posix(), ] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') subprocess.check_call(cmd) + def update_mkosi(args): old_commit = read_config() cmd = ['git', '-C', args.dir.as_posix(), 'rev-parse', f'refs/remotes/origin/{BRANCH}'] - print(f"+ {shlex.join(cmd)}") + print(f'+ {shlex.join(cmd)}') new_commit = subprocess.check_output(cmd, text=True).strip() if old_commit == new_commit: print(f'mkosi: commit {new_commit!s} is still fresh') return - cmd = ['git', '-C', args.dir.as_posix(), 'log', '--graph', '--no-merges', - '--pretty=oneline', '--no-decorate', '--abbrev-commit', '--abbrev=10', - f'{old_commit}..{new_commit}'] - print(f"+ {shlex.join(cmd)}") + cmd = [ + 'git', + '-C', args.dir.as_posix(), + 'log', + '--graph', + '--no-merges', + '--pretty=oneline', + '--no-decorate', + '--abbrev-commit', + '--abbrev=10', + f'{old_commit}..{new_commit}', + ] # fmt: skip + print(f'+ {shlex.join(cmd)}') changes = subprocess.check_output(cmd, text=True).strip() for f in [CONFIG, *WORKFLOWS]: @@ -91,6 +107,7 @@ def update_mkosi(args): commit_file([CONFIG, *WORKFLOWS], new_commit, changes) + if __name__ == '__main__': args = parse_args() checkout_mkosi(args) diff --git a/tools/find-unused-library-symbols.py b/tools/find-unused-library-symbols.py index 47f96df2ee44f..4e40512eb5ee6 100755 --- a/tools/find-unused-library-symbols.py +++ b/tools/find-unused-library-symbols.py @@ -39,10 +39,10 @@ def get_exported_symbols(library_path): ['nm', '--dynamic', '--defined-only', '--extern-only', library_path], capture_output=True, text=True, - check=True + check=True, ) except subprocess.CalledProcessError as e: - print(f"Error: Failed to run nm on {library_path}: {e}", file=sys.stderr) + print(f'Error: Failed to run nm on {library_path}: {e}', file=sys.stderr) sys.exit(1) except FileNotFoundError: print("Error: 'nm' command not found. Please install binutils.", file=sys.stderr) @@ -79,10 +79,10 @@ def get_undefined_symbols(executable_path): ['nm', '--dynamic', '--undefined-only', executable_path], capture_output=True, text=True, - check=True + check=True, ) except subprocess.CalledProcessError as e: - print(f"Warning: Failed to run nm on {executable_path}: {e}", file=sys.stderr) + print(f'Warning: Failed to run nm on {executable_path}: {e}', file=sys.stderr) return set() except FileNotFoundError: print("Error: 'nm' command not found. Please install binutils.", file=sys.stderr) @@ -108,12 +108,7 @@ def verify_executable_links_library(executable_path, library_name): Returns True if the executable links against a library with the given name. """ try: - result = subprocess.run( - ['ldd', executable_path], - capture_output=True, - text=True, - check=True - ) + result = subprocess.run(['ldd', executable_path], capture_output=True, text=True, check=True) except (subprocess.CalledProcessError, FileNotFoundError): # If ldd fails or doesn't exist, we'll skip the verification return True @@ -133,18 +128,14 @@ def get_library_internal_references(library_path, exported_symbols): This uses objdump to look for relocations that reference the exported symbols. """ try: - result = subprocess.run( - ['objdump', '-R', library_path], - capture_output=True, - text=True, - check=True - ) + result = subprocess.run(['objdump', '-R', library_path], capture_output=True, text=True, check=True) except subprocess.CalledProcessError as e: - print(f"Warning: Failed to run objdump on {library_path}: {e}", file=sys.stderr) + print(f'Warning: Failed to run objdump on {library_path}: {e}', file=sys.stderr) return set() except FileNotFoundError: - print("Warning: 'objdump' command not found. Internal references won't be detected.", - file=sys.stderr) + print( + "Warning: 'objdump' command not found. Internal references won't be detected.", file=sys.stderr + ) return set() internal_refs = set() @@ -181,7 +172,7 @@ def find_unused_symbols(library_path, executable_paths, verify_linkage=True): exported_symbols = get_exported_symbols(library_path) if not exported_symbols: - print(f"Warning: No exported symbols found in {library_path}", file=sys.stderr) + print(f'Warning: No exported symbols found in {library_path}', file=sys.stderr) return set(), set(), set() # Collect all symbols used by the executables @@ -194,8 +185,7 @@ def find_unused_symbols(library_path, executable_paths, verify_linkage=True): for exe_path in executable_paths: # Optionally verify linkage if verify_linkage and not verify_executable_links_library(exe_path, library_name): - print(f"Warning: {exe_path} does not appear to link against {library_name}", - file=sys.stderr) + print(f'Warning: {exe_path} does not appear to link against {library_name}', file=sys.stderr) undefined_symbols = get_undefined_symbols(exe_path) # Only count symbols that are actually exported by our library @@ -209,31 +199,31 @@ def find_unused_symbols(library_path, executable_paths, verify_linkage=True): def main(): parser = argparse.ArgumentParser( - description='Find unused exported symbols in a shared library' + description='Find unused exported symbols in a shared library', ) parser.add_argument( 'library', - help='Path to the shared library to analyze' + help='Path to the shared library to analyze', ) parser.add_argument( 'executables', nargs='+', - help='Paths to executables that link against the library' + help='Paths to executables that link against the library', ) parser.add_argument( '--no-verify-linkage', action='store_true', - help='Skip verification that executables actually link against the library' + help='Skip verification that executables actually link against the library', ) parser.add_argument( '--show-used', action='store_true', - help='Also show used symbols' + help='Also show used symbols', ) parser.add_argument( '--stats-only', action='store_true', - help='Only show statistics, not individual symbols' + help='Only show statistics, not individual symbols', ) args = parser.parse_args() @@ -241,7 +231,7 @@ def main(): # Verify library exists library_path = Path(args.library) if not library_path.exists(): - print(f"Error: Library not found: {library_path}", file=sys.stderr) + print(f'Error: Library not found: {library_path}', file=sys.stderr) sys.exit(1) # Verify executables exist @@ -249,47 +239,45 @@ def main(): for exe in args.executables: exe_path = Path(exe) if not exe_path.exists(): - print(f"Warning: Executable not found: {exe_path}", file=sys.stderr) + print(f'Warning: Executable not found: {exe_path}', file=sys.stderr) else: executable_paths.append(str(exe_path)) if not executable_paths: - print("Error: No valid executables provided", file=sys.stderr) + print('Error: No valid executables provided', file=sys.stderr) sys.exit(1) # Analyze symbols unused, exported, used = find_unused_symbols( - str(library_path), - executable_paths, - verify_linkage=not args.no_verify_linkage + str(library_path), executable_paths, verify_linkage=not args.no_verify_linkage ) # Print results - print(f"Analysis of {library_path.name}") - print("=" * 70) - print(f"Total exported symbols: {len(exported)}") - print(f" (excluding public API symbols starting with 'sd_')") - print(f"Used symbols: {len(used)}") - print(f"Unused symbols: {len(unused)}") - print(f"Usage rate: {len(used)/len(exported)*100:.1f}%" if exported else "N/A") + print(f'Analysis of {library_path.name}') + print('=' * 70) + print(f'Total exported symbols: {len(exported)}') + print(" (excluding public API symbols starting with 'sd_')") + print(f'Used symbols: {len(used)}') + print(f'Unused symbols: {len(unused)}') + print(f'Usage rate: {len(used) / len(exported) * 100:.1f}%' if exported else 'N/A') print() if not args.stats_only: if unused: - print("Unused symbols:") - print("-" * 70) + print('Unused symbols:') + print('-' * 70) for symbol in sorted(unused): - print(f" {symbol}") + print(f' {symbol}') print() else: - print("All exported symbols are used!") + print('All exported symbols are used!') print() if args.show_used and used: - print("Used symbols:") - print("-" * 70) + print('Used symbols:') + print('-' * 70) for symbol in sorted(used): - print(f" {symbol}") + print(f' {symbol}') print() # Exit with non-zero if there are unused symbols (useful for CI) diff --git a/tools/gdb-sd_dump_hashmaps.py b/tools/gdb-sd_dump_hashmaps.py index 596ee8d90c32b..d6fc2c4bb8e85 100755 --- a/tools/gdb-sd_dump_hashmaps.py +++ b/tools/gdb-sd_dump_hashmaps.py @@ -4,38 +4,39 @@ import gdb + class sd_dump_hashmaps(gdb.Command): "dump systemd's hashmaps" def __init__(self): - super().__init__("sd_dump_hashmaps", gdb.COMMAND_DATA, gdb.COMPLETE_NONE) + super().__init__('sd_dump_hashmaps', gdb.COMMAND_DATA, gdb.COMPLETE_NONE) def invoke(self, arg, _from_tty): - d = gdb.parse_and_eval("hashmap_debug_list") - hashmap_type_info = gdb.parse_and_eval("hashmap_type_info") - uchar_t = gdb.lookup_type("unsigned char") - ulong_t = gdb.lookup_type("unsigned long") - debug_offset = gdb.parse_and_eval("(unsigned long)&((HashmapBase*)0)->debug") + d = gdb.parse_and_eval('hashmap_debug_list') + hashmap_type_info = gdb.parse_and_eval('hashmap_type_info') + uchar_t = gdb.lookup_type('unsigned char') + ulong_t = gdb.lookup_type('unsigned long') + debug_offset = gdb.parse_and_eval('(unsigned long)&((HashmapBase*)0)->debug') - print("type, hash, indirect, entries, max_entries, buckets, creator") + print('type, hash, indirect, entries, max_entries, buckets, creator') while d: - h = gdb.parse_and_eval(f"(HashmapBase*)((char*){int(d.cast(ulong_t))} - {debug_offset})") + h = gdb.parse_and_eval(f'(HashmapBase*)((char*){int(d.cast(ulong_t))} - {debug_offset})') - if h["has_indirect"]: - storage_ptr = h["indirect"]["storage"].cast(uchar_t.pointer()) - n_entries = h["indirect"]["n_entries"] - n_buckets = h["indirect"]["n_buckets"] + if h['has_indirect']: + storage_ptr = h['indirect']['storage'].cast(uchar_t.pointer()) + n_entries = h['indirect']['n_entries'] + n_buckets = h['indirect']['n_buckets'] else: - storage_ptr = h["direct"]["storage"].cast(uchar_t.pointer()) - n_entries = h["n_direct_entries"] - n_buckets = hashmap_type_info[h["type"]]["n_direct_buckets"] + storage_ptr = h['direct']['storage'].cast(uchar_t.pointer()) + n_entries = h['n_direct_entries'] + n_buckets = hashmap_type_info[h['type']]['n_direct_buckets'] - t = ["plain", "ordered", "set"][int(h["type"])] + t = ['plain', 'ordered', 'set'][int(h['type'])] - print(f'{t}, {h["hash_ops"]}, {bool(h["has_indirect"])}, {n_entries}, {d["max_entries"]}, {n_buckets}') + print(f'{t}, {h["hash_ops"]}, {bool(h["has_indirect"])}, {n_entries}, {d["max_entries"]}, {n_buckets}') # fmt: skip - if arg != "" and n_entries > 0: - dib_raw_addr = storage_ptr + hashmap_type_info[h["type"]]["entry_size"] * n_buckets + if arg != '' and n_entries > 0: + dib_raw_addr = storage_ptr + hashmap_type_info[h['type']]['entry_size'] * n_buckets histogram = {} for i in range(0, n_buckets): @@ -44,11 +45,11 @@ def invoke(self, arg, _from_tty): for dib in sorted(histogram): if dib != 255: - print(f"{dib:>3} {histogram[dib]:>8} {float(histogram[dib]/n_entries):.0%} of entries") + print(f'{dib:>3} {histogram[dib]:>8} {float(histogram[dib] / n_entries):.0%} of entries') # fmt: skip else: - print(f"{dib:>3} {histogram[dib]:>8} {float(histogram[dib]/n_buckets):.0%} of slots") - s = sum(dib*count for (dib, count) in histogram.items() if dib != 255) / n_entries - print(f"mean DIB of entries: {s}") + print(f'{dib:>3} {histogram[dib]:>8} {float(histogram[dib] / n_buckets):.0%} of slots') # fmt: skip + s = sum(dib * count for (dib, count) in histogram.items() if dib != 255) / n_entries + print(f'mean DIB of entries: {s}') blocks = [] current_len = 1 @@ -69,10 +70,11 @@ def invoke(self, arg, _from_tty): if len(blocks) > 1 and blocks[0][0] == blocks[0][1] and blocks[-1][0] == n_buckets - 1: blocks[0][1] += blocks[-1][1] blocks = blocks[0:-1] - print("max block: {}".format(max(blocks, key=lambda a: a[1]))) - print("sum block lens: {}".format(sum(b[1] for b in blocks))) - print("mean block len: {}".format(sum(b[1] for b in blocks) / len(blocks))) + print(f'max block: {max(blocks, key=lambda a: a[1])}') + print(f'sum block lens: {sum(b[1] for b in blocks)}') + print(f'mean block len: {sum(b[1] for b in blocks) / len(blocks)}') + + d = d['debug_list_next'] - d = d["debug_list_next"] sd_dump_hashmaps() diff --git a/tools/generate-gperfs.py b/tools/generate-gperfs.py index fc349b43faed5..e9e520247eb20 100755 --- a/tools/generate-gperfs.py +++ b/tools/generate-gperfs.py @@ -13,23 +13,23 @@ sys.exit(f'Usage: {sys.argv[0]} name prefix file [includes...]') name, prefix, file, *includes = sys.argv[1:] - includes = [f"#include {i}" for i in includes] + includes = [f'#include {i}' for i in includes] # Older versions of python don't allow backslashes # in f-strings so use chr(10) for newlines and chr(92) # for backslashes instead as a workaround. - print(f"""\ + print(f'''\ %{{ _Pragma("GCC diagnostic ignored {chr(92)}"-Wimplicit-fallthrough{chr(92)}"") #if __GNUC__ >= 15 _Pragma("GCC diagnostic ignored {chr(92)}"-Wzero-as-null-pointer-constant{chr(92)}"") #endif {chr(10).join(includes)} -%}}""") - print(f"""\ +%}}''') + print(f'''\ struct {name}_name {{ const char* name; int id; }}; %null-strings -%%""") +%%''') for line in open(file): - print("{0}, {1}{0}".format(line.rstrip(), prefix)) + print('{0}, {1}{0}'.format(line.rstrip(), prefix)) diff --git a/tools/list-discoverable-partitions.py b/tools/list-discoverable-partitions.py index a19bf1d6e2ee7..437be68d727f7 100755 --- a/tools/list-discoverable-partitions.py +++ b/tools/list-discoverable-partitions.py @@ -30,16 +30,15 @@ 'TILEGX': 'TILE-Gx', 'X86': 'x86', 'X86_64': 'amd64/x86_64', -} +} # fmt: skip TYPES = { - 'ROOT' : 'Root Partition', - 'ROOT_VERITY' : 'Root Verity Partition', - 'ROOT_VERITY_SIG' : 'Root Verity Signature Partition', - 'USR' : '`/usr/` Partition', - 'USR_VERITY' : '`/usr/` Verity Partition', - 'USR_VERITY_SIG' : '`/usr/` Verity Signature Partition', - + 'ROOT': 'Root Partition', + 'ROOT_VERITY': 'Root Verity Partition', + 'ROOT_VERITY_SIG': 'Root Verity Signature Partition', + 'USR': '`/usr/` Partition', + 'USR_VERITY': '`/usr/` Verity Partition', + 'USR_VERITY_SIG': '`/usr/` Verity Signature Partition', 'ESP': 'EFI System Partition', 'SRV': 'Server Data Partition', 'VAR': 'Variable Data Partition', @@ -49,7 +48,7 @@ 'USER_HOME': 'Per-user Home Partition', 'LINUX_GENERIC': 'Generic Linux Data Partition', 'XBOOTLDR': 'Extended Boot Loader Partition', -} +} # fmt: skip DESCRIPTIONS = { 'ROOT': ( @@ -57,56 +56,66 @@ 'On systems with matching architecture, the first partition with this type UUID on the disk ' 'containing the active EFI ESP is automatically mounted to the root directory `/`. ' 'If the partition is encrypted with LUKS or has dm-verity integrity data (see below), the ' - 'device mapper file will be named `/dev/mapper/root`.'), + 'device mapper file will be named `/dev/mapper/root`.', + ), 'USR': ( 'Any native, optionally in LUKS', - 'Similar semantics to root partition, but just the `/usr/` partition.'), + 'Similar semantics to root partition, but just the `/usr/` partition.', + ), 'ROOT_VERITY': ( 'A dm-verity superblock followed by hash data', 'Contains dm-verity integrity hash data for the matching root partition. If this feature is ' 'used the partition UUID of the root partition should be the first 128 bits of the root hash ' 'of the dm-verity hash data, and the partition UUID of this dm-verity partition should be the ' 'final 128 bits of it, so that the root partition and its Verity partition can be discovered ' - 'easily, simply by specifying the root hash.'), + 'easily, simply by specifying the root hash.', + ), 'USR_VERITY': ( 'A dm-verity superblock followed by hash data', - 'Similar semantics to root Verity partition, but just for the `/usr/` partition.'), + 'Similar semantics to root Verity partition, but just for the `/usr/` partition.', + ), 'ROOT_VERITY_SIG': ( 'A serialized JSON object, see below', - 'Contains a root hash and a PKCS#7 signature for it, permitting signed dm-verity GPT images.'), + 'Contains a root hash and a PKCS#7 signature for it, permitting signed dm-verity GPT images.', + ), 'USR_VERITY_SIG': ( 'A serialized JSON object, see below', - 'Similar semantics to root Verity signature partition, but just for the `/usr/` partition.'), - + 'Similar semantics to root Verity signature partition, but just for the `/usr/` partition.', + ), 'ESP': ( 'VFAT', 'The ESP used for the current boot is automatically mounted to `/efi/` (or `/boot/` as ' 'fallback), unless a different partition is mounted there (possibly via `/etc/fstab`, or ' 'because the Extended Boot Loader Partition — see below — exists) or the directory is ' 'non-empty on the root disk. This partition type is defined by the ' - '[UEFI Specification](http://www.uefi.org/specifications).'), + '[UEFI Specification](http://www.uefi.org/specifications).', + ), 'XBOOTLDR': ( 'Typically VFAT', 'The Extended Boot Loader Partition (XBOOTLDR) used for the current boot is automatically ' 'mounted to `/boot/`, unless a different partition is mounted there (possibly via ' '`/etc/fstab`) or the directory is non-empty on the root disk. This partition type ' 'is defined by the [Boot Loader ' - 'Specification](https://uapi-group.org/specifications/specs/boot_loader_specification).'), + 'Specification](https://uapi-group.org/specifications/specs/boot_loader_specification).', + ), 'SWAP': ( 'Swap, optionally in LUKS', 'All swap partitions on the disk containing the root partition are automatically enabled. ' 'If the partition is encrypted with LUKS, the device mapper file will be named ' - '`/dev/mapper/swap`. This partition type predates the Discoverable Partitions Specification.'), + '`/dev/mapper/swap`. This partition type predates the Discoverable Partitions Specification.', + ), 'HOME': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' 'automatically mounted to `/home/`. If the partition is encrypted with LUKS, the device ' - 'mapper file will be named `/dev/mapper/home`.'), + 'mapper file will be named `/dev/mapper/home`.', + ), 'SRV': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' 'automatically mounted to `/srv/`. If the partition is encrypted with LUKS, the device ' - 'mapper file will be named `/dev/mapper/srv`.'), + 'mapper file will be named `/dev/mapper/srv`.', + ), 'VAR': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' @@ -118,7 +127,8 @@ 'listed here) is inherently private to a specific installation and cannot possibly be ' 'shared between multiple OS installations on the same disk, and thus should be bound to ' 'a specific instance of the OS, identified by its machine ID. If the partition is ' - 'encrypted with LUKS, the device mapper file will be named `/dev/mapper/var`.'), + 'encrypted with LUKS, the device mapper file will be named `/dev/mapper/var`.', + ), 'TMP': ( 'Any native, optionally in LUKS', 'The first partition with this type UUID on the disk containing the root partition is ' @@ -127,19 +137,23 @@ 'is indeed `/var/tmp/`, not `/tmp/`. The latter is typically maintained in memory via ' '`tmpfs` and does not require a partition on disk. In some cases it might be ' 'desirable to make `/tmp/` persistent too, in which case it is recommended to make it ' - 'a symlink or bind mount to `/var/tmp/`, thus not requiring its own partition type UUID.'), + 'a symlink or bind mount to `/var/tmp/`, thus not requiring its own partition type UUID.', + ), 'USER_HOME': ( 'Any native, optionally in LUKS', 'A home partition of a user, managed by ' - '[`systemd-homed`](https://www.freedesktop.org/software/systemd/man/systemd-homed.html).'), + '[`systemd-homed`](https://www.freedesktop.org/software/systemd/man/systemd-homed.html).', + ), 'LINUX_GENERIC': ( 'Any native, optionally in LUKS', 'No automatic mounting takes place for other Linux data partitions. This partition type ' 'should be used for all partitions that carry Linux file systems. The installer needs ' 'to mount them explicitly via entries in `/etc/fstab`. Optionally, these partitions may ' - 'be encrypted with LUKS. This partition type predates the Discoverable Partitions Specification.'), + 'be encrypted with LUKS. This partition type predates the Discoverable Partitions Specification.', + ), } + def extract(file): for line in file: # print(line) @@ -148,7 +162,10 @@ def extract(file): continue name = line.split()[1] - if m2 := re.match(r'^(ROOT|USR)_([A-Z0-9]+|X86_64|PPC64_LE|MIPS_LE|MIPS64_LE)(|_VERITY|_VERITY_SIG)\s+SD_ID128_MAKE\((.*)\)', m.group(1)): + if m2 := re.match( + r'^(ROOT|USR)_([A-Z0-9]+|X86_64|PPC64_LE|MIPS_LE|MIPS64_LE)(|_VERITY|_VERITY_SIG)\s+SD_ID128_MAKE\((.*)\)', + m.group(1), + ): ptype, arch, suffix, u = m2.groups() u = uuid.UUID(u.replace(',', '')) assert arch in ARCHITECTURES, f'{arch} not in f{ARCHITECTURES}' @@ -165,6 +182,7 @@ def extract(file): else: raise ValueError(f'Failed to match: {m.group(1)}') + def generate(defines): prevtype = None @@ -188,6 +206,7 @@ def generate(defines): print(f'| _{tdesc}{adesc}_ | `{puuid}` `{name}` | {morea} | {moreb} |') + if __name__ == '__main__': known = extract(sys.stdin) generate(known) diff --git a/tools/make-directive-index.py b/tools/make-directive-index.py index 5398b452eff18..ddf07534ef30c 100755 --- a/tools/make-directive-index.py +++ b/tools/make-directive-index.py @@ -13,6 +13,7 @@ referring to {pages} individual manual pages. ''' + def _extract_directives(directive_groups, formatting, page): t = xml_parse(page) section = t.find('./refmeta/manvolnum').text @@ -21,12 +22,13 @@ def _extract_directives(directive_groups, formatting, page): storopt = directive_groups['options'] for variablelist in t.iterfind('.//variablelist'): klass = variablelist.attrib.get('class') - searchpath = variablelist.attrib.get('xpath','./varlistentry/term/varname') + searchpath = variablelist.attrib.get('xpath', './varlistentry/term/varname') storvar = directive_groups[klass or 'miscellaneous'] #