From fb5578b3c3e3a9aed28d573e0a1e549a30b61264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 4 May 2023 22:43:54 +0200 Subject: [PATCH 01/19] test-udev: add an optional timeout argument The tests wants to call some workers with a delay. This implements the delay directly in test-udev so that the caller can be simplified. Note that the argument is to be used by the other test file, so this is purposefully implemented in a simple way. --- src/test/test-udev.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/test/test-udev.c b/src/test/test-udev.c index 3ca132db3b..00ca79d0eb 100644 --- a/src/test/test-udev.c +++ b/src/test/test-udev.c @@ -18,6 +18,7 @@ #include "mkdir-label.h" #include "mount-util.h" #include "namespace-util.h" +#include "parse-util.h" #include "selinux-util.h" #include "signal-util.h" #include "string-util.h" @@ -92,9 +93,9 @@ static int run(int argc, char *argv[]) { test_setup_logging(LOG_INFO); - if (!IN_SET(argc, 2, 3)) + if (!IN_SET(argc, 2, 3, 4)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "This program needs one or two arguments, %d given", argc - 1); + "This program needs between one and three arguments, %d given", argc - 1); r = fake_filesystems(); if (r < 0) @@ -123,10 +124,18 @@ static int run(int argc, char *argv[]) { action = argv[1]; devpath = argv[2]; + if (argv[3]) { + unsigned us; + + r = safe_atou(argv[3], &us); + if (r < 0) + return log_error_errno(r, "Invalid delay '%s': %m", argv[3]); + usleep(us); + } + assert_se(udev_rules_load(&rules, RESOLVE_NAME_EARLY) == 0); - const char *syspath; - syspath = strjoina("/sys", devpath); + const char *syspath = strjoina("/sys", devpath); r = device_new_from_synthetic_event(&dev, syspath, action); if (r < 0) return log_debug_errno(r, "Failed to open device '%s'", devpath); From 7d3d147c4ac5c2b388e459a238d7acbaa50a05bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 4 May 2023 22:54:41 +0200 Subject: [PATCH 02/19] test_ukify: print message when skipping whole test file --- src/ukify/test/test_ukify.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index d221825019..6853205958 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -18,13 +18,15 @@ import textwrap try: import pytest -except ImportError: +except ImportError as e: + print(str(e), file=sys.stderr) sys.exit(77) try: # pyflakes: noqa import pefile # noqa -except ImportError: +except ImportError as e: + print(str(e), file=sys.stderr) sys.exit(77) # We import ukify.py, which is a template file. But only __version__ is From 083e2ba44572a1681bf0b1b6543f13b1e8797532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 May 2023 08:54:01 +0200 Subject: [PATCH 03/19] pid1: drop duplicate include --- src/core/job.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/job.h b/src/core/job.h index 30a7903aa3..df35e2a5b6 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -99,8 +99,6 @@ enum JobResult { _JOB_RESULT_INVALID = -EINVAL, }; -#include "unit.h" - struct JobDependency { /* Encodes that the 'subject' job needs the 'object' job in * some way. This structure is used only while building a transaction. */ From c4a090d60e09cd85a56ff91c46ca0f7b22dcd1a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 May 2023 09:03:12 +0200 Subject: [PATCH 04/19] Rewrite check-includes.pl in python --- LICENSES/README.md | 1 - tools/check-includes.pl | 23 ----------------------- tools/check-includes.py | 23 +++++++++++++++++++++++ 3 files changed, 23 insertions(+), 24 deletions(-) delete mode 100755 tools/check-includes.pl create mode 100755 tools/check-includes.py diff --git a/LICENSES/README.md b/LICENSES/README.md index d235b319d8..6174b8edc7 100644 --- a/LICENSES/README.md +++ b/LICENSES/README.md @@ -45,7 +45,6 @@ The following exceptions apply: * the following sources are licensed under the **CC0-1.0** license: - src/basic/siphash24.c - src/basic/siphash24.h - - tools/check-includes.pl * the following sources are licensed under the **MIT-0** license: - all examples under man/ - src/systemctl/systemd-sysv-install.SKELETON diff --git a/tools/check-includes.pl b/tools/check-includes.pl deleted file mode 100755 index c8bfcba8c0..0000000000 --- a/tools/check-includes.pl +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-License-Identifier: CC0-1.0 -#!/usr/bin/env perl -# -# checkincludes: Find files included more than once in (other) files. - -foreach $file (@ARGV) { - open(FILE, $file) or die "Cannot open $file: $!.\n"; - - my %includedfiles = (); - - while () { - if (m/^\s*#\s*include\s*[<"](\S*)[>"]/o) { - ++$includedfiles{$1}; - } - } - foreach $filename (keys %includedfiles) { - if ($includedfiles{$filename} > 1) { - print "$file: $filename is included more than once.\n"; - } - } - - close(FILE); -} diff --git a/tools/check-includes.py b/tools/check-includes.py new file mode 100755 index 0000000000..27d11b9d0a --- /dev/null +++ b/tools/check-includes.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +# pylint: disable=missing-docstring,invalid-name,unspecified-encoding,consider-using-with + +import re +import sys + +def check_file(filename): + seen = set() + good = True + for n, line in enumerate(open(filename)): + if m := re.match(r'^\s*#\s*include\s*[<"](\S*)[>"]', line): + include = m.group(1) + if include in seen: + print(f'{filename}:{n}: {line.strip()}') + good = False + seen.add(include) + return good + +if __name__ == '__main__': + good = all(check_file(name) for name in sys.argv[1:]) + sys.exit(0 if good else 1) From ba9ca60a88e1cf368ab59ee9c720caf0ec13a0b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 May 2023 09:33:12 +0200 Subject: [PATCH 05/19] meson: include .cc files in tags too We only have one, but it seems reasonable to not exclude it. Result tested with emacs. --- meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meson.build b/meson.build index 28086b16e6..1fd22c4f45 100644 --- a/meson.build +++ b/meson.build @@ -4726,7 +4726,7 @@ if git.found() all_files = run_command( env, '-u', 'GIT_WORK_TREE', git, '--git-dir=@0@/.git'.format(project_source_root), - 'ls-files', ':/*.[ch]', + 'ls-files', ':/*.[ch]', ':/*.cc', check : false) if all_files.returncode() == 0 all_files = files(all_files.stdout().split()) From 608923582980357c3feb3619231286e48c084f7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 May 2023 09:35:54 +0200 Subject: [PATCH 06/19] meson: add check-includes test to the test suite Let's just call it always. It is quite fast (meson says 0.12 s). --- meson.build | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 1fd22c4f45..9c35a701dd 100644 --- a/meson.build +++ b/meson.build @@ -4738,17 +4738,26 @@ if git.found() run_target( 'ctags', command : [env, 'ctags', '--tag-relative=never', '-o', '@0@/tags'.format(project_source_root)] + all_files) - endif -endif -if git.found() + ############################################ + + if want_tests != 'false' and conf.get('BUILD_MODE_DEVELOPER') == 1 + test('check-includes', + files('tools/check-includes.py'), + args: all_files, + env : ['PROJECT_SOURCE_ROOT=@0@'.format(project_source_root)]) + endif + endif + + #################################################### + git_contrib_sh = find_program('tools/git-contrib.sh') run_target( 'git-contrib', command : [git_contrib_sh]) -endif -if git.found() + #################################################### + git_head = run_command( git, '--git-dir=@0@/.git'.format(project_source_root), 'rev-parse', 'HEAD', From b0bd2ae8b3311f1cc811f0986072c09b293e8c07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 May 2023 09:36:00 +0200 Subject: [PATCH 07/19] meson: fix indentation --- meson.build | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index 9c35a701dd..25b5010264 100644 --- a/meson.build +++ b/meson.build @@ -2132,7 +2132,7 @@ endif conf.set10('ENABLE_UKIFY', want_ukify) ############################################################ -# + elf2efi_lds = project_source_root / 'tools/elf2efi.lds' elf2efi_py = find_program('tools/elf2efi.py') export_dbus_interfaces_py = find_program('tools/dbus_exporter.py') @@ -4462,10 +4462,10 @@ foreach test : tests message('@0@ is an unsafe test'.format(name)) elif want_tests != 'false' test(name, exe, - env : test_env, - timeout : test.get('timeout', 30), - suite : suite, - is_parallel : test.get('parallel', true)) + env : test_env, + timeout : test.get('timeout', 30), + suite : suite, + is_parallel : test.get('parallel', true)) endif endforeach From 495658d43c72d3a1496c8181bb1fcec84a76d66c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 May 2023 09:38:15 +0200 Subject: [PATCH 08/19] check-includes: print path relative to project root Instead of /home/zbyszek/src/systemd-work/build/../src/xdg-autostart-generator/xdg-autostart-service.h:11, print just src/xdg-autostart-generator/xdg-autostart-service.h:11. This is a bit annoying that this requires so much verbosity, but the output with the full names was too annoying. --- tools/check-includes.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/check-includes.py b/tools/check-includes.py index 27d11b9d0a..abca2882f0 100755 --- a/tools/check-includes.py +++ b/tools/check-includes.py @@ -3,9 +3,13 @@ # pylint: disable=missing-docstring,invalid-name,unspecified-encoding,consider-using-with +import os +import pathlib import re import sys +PROJECT_ROOT = pathlib.Path(os.getenv('PROJECT_SOURCE_ROOT', '.')) + def check_file(filename): seen = set() good = True @@ -13,6 +17,10 @@ def check_file(filename): if m := re.match(r'^\s*#\s*include\s*[<"](\S*)[>"]', line): include = m.group(1) if include in seen: + try: + filename = pathlib.Path(filename).resolve().relative_to(PROJECT_ROOT) + except ValueError: + pass print(f'{filename}:{n}: {line.strip()}') good = False seen.add(include) From f2c02d232eeb0c7d53089c586b72620383466e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 4 May 2023 22:40:38 +0200 Subject: [PATCH 09/19] test: rewrite udev-test.pl in Python MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I tried to keep this a 1:1 rewrite with the same field names. Nevertheless, some changes were made: - exp_add_error and exp_rem_error are dropped. Those fields meant that "./test-udev add " actually succeeded, but symlinks were not created, and exp_links was ignored and could contain bogus content. Instead, exp_links and not_exp_links are adjusted to not contain garbage and the tests check that "./test-udev add" succeeds and that the links are as expected from exp_links and not_exp_links. - cleanup was only used in one rule, and that rule was expected to fail, so cleanup wasn't actually necessary. So the cleanup field and the logic to call cleanup from individual tests is removed. - a bunch of fields were set, but didn't seem to be connected to any implementation: not_exp_name, not_exp_test. e62acc3159935781f05fa59c48e5a74e85c61ce2 did a rewrite of some of the tests and it seems that not_exp_test was added by mistake and not_exp_name was left behind by mistake. In Python, the field list is declared in the class, so it's harder to assign an unused attribute. Those uses were converted to not_exp_links. - in most rules, r"""…""" is used, so that escaping is not necessary. - the logic to generate devices was only used in one place, and the generator function also had provisions to handle arguments that were never given. all_block_devs() is made much simpler. - Descriptions that started with a capital letter were shortened and lowercased. - no special test case counting is done. pytest just counts the cases (Rules objects). - the output for failures is also removed. If something goes wrong, the user can use pytest --pdb or such to debug the issue. - perl version used a semaphore to manage udev runners, and would fork, optionally wait a bit, and then start the runner. In the python version, we just spawn them all and wait for them to exit. It's not very convenient to call fork() from python, so instead the runner was modified (in previous commit) to wait. The test can be called as: (cd build && sudo pytest -v ../test/udev-test.py) sudo meson test -C build udev-test.py -v I think this generally provides functionality that is close to the perl version. It seems some of the checks are now more fully implemented. Support for strace/gdb/valgrind is missing. Runtime goes down: 8.36 s → 5.78 s. --- test/meson.build | 9 +- test/udev-test.py | 2408 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2416 insertions(+), 1 deletion(-) create mode 100755 test/udev-test.py diff --git a/test/meson.build b/test/meson.build index b9992fae47..6af112d141 100644 --- a/test/meson.build +++ b/test/meson.build @@ -134,7 +134,7 @@ custom_target( if perl.found() udev_test_pl = find_program('udev-test.pl') if want_tests != 'false' - test('udev-test', + test('udev-test.pl', udev_test_pl, timeout : 180) endif @@ -142,6 +142,13 @@ else message('Skipping udev-test because perl is not available') endif +if want_tests != 'false' + test('udev-test.py', + files('udev-test.py'), + args : ['-v'], + timeout : 180) +endif + ############################################################ rpm = find_program('rpm', required : false) diff --git a/test/udev-test.py b/test/udev-test.py new file mode 100755 index 0000000000..5ddda3865f --- /dev/null +++ b/test/udev-test.py @@ -0,0 +1,2408 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: LGPL-2.1-or-later + +# pylint: disable=missing-docstring,redefined-outer-name,invalid-name +# pylint: disable=unspecified-encoding,no-else-return,line-too-long,too-many-lines +# pylint: disable=multiple-imports,too-many-instance-attributes,consider-using-with + +# udev test +# +# Provides automated testing of the udev binary. +# The whole test is self contained in this file, except the matching sysfs tree. +# Simply extend RULES to add a new test. +# +# Every test is driven by its own temporary config file. +# This program prepares the environment, creates the config and calls udev. +# +# udev parses the rules, looks at the provided sysfs and first creates and then +# removes the device node. After creation and removal the result is checked +# against the expected value and the result is printed. + +import dataclasses +import functools +import os +import pwd, grp +import re +import stat +import subprocess +import sys +import textwrap +from pathlib import Path +from typing import Optional + +try: + import pytest +except ImportError as e: + print(str(e), file=sys.stderr) + sys.exit(77) + + +# TODO: pass path to test-udev from outside +UDEV_BIN = './test-udev' +UDEV_RUN = Path('test/run') +UDEV_RULES_DIR = UDEV_RUN / 'udev/rules.d' +UDEV_RULES = UDEV_RULES_DIR / 'udev-test.rules' + +UDEV_RUN = Path('test/run') +UDEV_TMPFS = Path('test/tmpfs') +UDEV_DEV = UDEV_TMPFS / 'dev' +UDEV_SYS = UDEV_TMPFS / 'sys' + +# Relax sd-device's sysfs verification, since we want to provide a fake sysfs +# here that actually is a tmpfs. +os.environ['SYSTEMD_DEVICE_VERIFY_SYSFS'] = '0' + +rules_10k_tags = \ + '\n'.join(f'KERNEL=="sda", TAG+="test{i + 1}"' + for i in range(10_000)) + +rules_10k_tags_continuation = \ + ',\\\n'.join(('KERNEL=="sda"', + *(f'TAG+="test{i + 1}"' for i in range(10_000)))) + +@dataclasses.dataclass +class Device: + devpath: str + devnode: Optional[str] = None + exp_links: Optional[list[str]] = None + not_exp_links: Optional[list[str]] = None + + exp_perms: Optional[int] = None + exp_major_minor: Optional[str] = None + + def check_permissions(self, st: os.stat_result) -> None: + if self.exp_perms is None: + return + + user, group, mode = self.exp_perms.split(':') + + if user: + try: + uid = pwd.getpwnam(user).pw_uid + except KeyError: + uid = int(user) + assert uid == st.st_uid + + if group: + try: + gid = grp.getgrnam(group).gr_gid + except KeyError: + gid = int(group) + assert gid == st.st_gid + + if mode: + mode = int(mode, 8) + assert stat.S_IMODE(st.st_mode) == mode + + def check_major_minor(self, st: os.stat_result) -> None: + if not self.exp_major_minor: + return + minor, major = (int(x) for x in self.exp_major_minor.split(':')) + assert st.st_rdev == os.makedev(minor, major) + + def get_devnode(self) -> Path: + suffix = self.devnode if self.devnode else self.devpath.split('/')[-1] + return UDEV_DEV / suffix + + def check_link_add(self, link: str, devnode: Path) -> None: + link = UDEV_DEV / link + tgt = link.parent / link.readlink() + assert devnode.samefile(tgt) + + def check_link_nonexistent(self, link: str, devnode: Path) -> None: + link = UDEV_DEV / link + + try: + tgt = link.parent / link.readlink() + except FileNotFoundError: + return + + assert not devnode.samefile(tgt) + + def check_add(self) -> None: + print(f'check_add {self.devpath}') + + devnode = self.get_devnode() + st = devnode.stat(follow_symlinks=False) + assert stat.S_ISCHR(st.st_mode) or stat.S_ISBLK(st.st_mode) + self.check_permissions(st) + self.check_major_minor(st) + + for link in self.exp_links or []: + self.check_link_add(link, devnode) + + for link in self.not_exp_links or []: + self.check_link_nonexistent(link, devnode) + + def check_link_remove(self, link: str) -> None: + link = UDEV_DEV / link + with pytest.raises(FileNotFoundError): + link.readlink() + + def check_remove(self) -> None: + devnode = self.get_devnode() + assert not devnode.exists() + + for link in self.exp_links or []: + self.check_link_remove(link) + + +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 + # (except virtual devices and cd-roms) + # the optional argument exp_func returns expected and non-expected + # symlinks for the device. + + for p in UDEV_SYS.glob('dev/block/*'): + tgt = os.readlink(p) + if re.search('/virtual/ | /sr[0-9]*$', tgt, re.VERBOSE): + continue + + assert tgt.startswith('../../') + tgt = tgt[5:] + + exp, not_exp = exp_func(tgt) + yield Device(devpath=tgt, + exp_links=exp, + not_exp_links=not_exp) + + +@dataclasses.dataclass +class Rules: + desc: str + devices: list[Device] + rules: str + repeat: int = 1 + delay: Optional[int] = None + + @classmethod + def new(cls, desc: str, *devices, rules = None, **kwargs): + assert rules.startswith('\n') + rules = textwrap.dedent(rules[1:]) if rules else '' + return cls(desc, devices, rules, **kwargs) + + def create_rules_file(self) -> None: + # create temporary rules + 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", + ), + Device( + "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + ), + 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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" + KERNEL=="ttyACM0", SYMLINK+="modem" + """), + + 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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" + KERNEL=="ttyACM0", SYMLINK+="modem" + """), + + 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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" + KERNEL=="ttyACM0", SYMLINK+="modem" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" + """), + + Rules.new( + "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"], + ), + + 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" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATAS", SYMLINK+="boot_disk%n-3" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="AT?", SYMLINK+="boot_disk%n-4" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="??A", SYMLINK+="boot_disk%n-5" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", GOTO="skip-6" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n-6" + LABEL="skip-6" + 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", + 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"], + ), + 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)", + 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"], + ), + 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""" + 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" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed2" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="././removed3" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="/dev//./removed3/./" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="unsafe/../../path" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/nondev/path/will/be/refused" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="link1 .///link2/././/foo//./ .///link3/aaa/bbb" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/dev/abs1 /dev//./abs2///foo/./ ////dev/abs3/aaa/bbb" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="default?;;replace%%test/foo'aaa" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", SYMLINK+="string_escape replace/foo%%bbb" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ENV{.HOGE}="env with space", SYMLINK+="%E{.HOGE}" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ENV{.HOGE}="default/replace/mode?foo;;hoge", SYMLINK+="%E{.HOGE}" + 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 *", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_links = ["modem/0", "catch-all"], + ), + rules = r""" + KERNEL=="ttyACM*", SYMLINK+="modem/%n" + KERNEL=="*", SYMLINK+="catch-all" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="*ACM1", SYMLINK+="bad" + KERNEL=="*ACM0", SYMLINK+="modem/%n" + """), + + Rules.new( + "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"], + ), + 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", + 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"], + ), + 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"], + ), + 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"], + ), + 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"], + ), + 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"], + ), + rules = r""" + + + + # this is a comment with whitespace before the comment + KERNEL=="ttyACM0", SYMLINK+="whitespace" + + + + """), + + 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"], + ), + 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"], + ), + rules = r""" + KERNEL=="ttyACM0", \ + SYMLINK+="modem" + + """), + + Rules.new( + "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"], + ), + 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"], + ), + rules = r""" + + # + \ + + \ + + #\ + + KERNEL=="ttyACM0", \ + SYMLINK+="modem" + + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n" + """), + + Rules.new( + "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"], + ), + 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", + Device( + "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_links = ["node12345678"], + ), + 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})", + 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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k" + KERNEL=="ttyACM0", SYMLINK+="modem" + """), + + Rules.new( + "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"], + ), + 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)", + Device( + "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_links = ["newline_removed"], + ), + rules = r""" + SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c" + """), + + Rules.new( + "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"], + ), + 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", + 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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -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", + 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"], + ), + 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", + 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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf %%s \"foo1 foo2\" | grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}" + """), + + Rules.new( + "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"], + ), + rules = r""" +SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c \"printf %%s 'foo1 foo2' | grep 'foo1 foo2'\"", KERNEL=="sda5", SYMLINK+="%c{2}" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf \"%%s %%s\" \"foo1 foo2\" \"foo3\"| grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}" + """), + + Rules.new( + "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"], + ), + 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", + 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"], + ), + 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", + 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"], + ), + 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", + 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"], + ), + 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", + 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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major$minor$number" + """), + + Rules.new( + "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"], + ), + 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", + Device( + "/devices/virtual/tty/console", + exp_links = ["TTY"], + not_exp_links = ["foo"], + ), + rules = r""" + SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo" + KERNEL=="console", SYMLINK+="TTY" + """), + + Rules.new( + "non matching SUBSYSTEMS", + Device( + "/devices/virtual/tty/console", + exp_links = ["TTY"], + not_exp_links = ["foo"], + ), + rules = r""" + SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo" + KERNEL=="console", SYMLINK+="TTY" + """), + + Rules.new( + "ATTRS match", + Device( + "/devices/virtual/tty/console", + exp_links = ["foo", "TTY"], + ), + rules = r""" + KERNEL=="console", SYMLINK+="TTY" + ATTRS{dev}=="5:1", SYMLINK+="foo" + """), + + Rules.new( + "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"], + ), + 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)", + 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"], + ), + 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", + 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"], + ), + 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", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_links = ["modem"], + ), + rules = r""" + ATTRS{idProduct}=="007b", SYMLINK+="modem" + """), + + Rules.new( + "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"], + ), + 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", + Device( + "/devices/virtual/block/fake!blockdev0", + devnode = "fake/blockdev0", + not_exp_links = ["modem"], + ), + rules = r""" + KERNEL=="ttyACM0", SYMLINK+="modem" + """), + + Rules.new( + "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"], + ), + 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", + 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"], + ), + 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", + 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"], + ), + 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", + 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"], + ), + 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)", + Device( + "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_links = ["driver-is-sd"], + ), + rules = r""" + SUBSYSTEMS=="scsi", SYMLINK+="driver-is-$attr{driver}" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="pci", SYMLINK+="driver-is-$attr{driver}" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE", SYMLINK+="ignored" + """), + + Rules.new( + "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"], + ), + 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", + Device( + "/devices/virtual/tty/tty33", + exp_perms = "0:0:0600", + ), + rules = r""" + KERNEL=="tty33", OWNER="bad", GROUP="name" + """), + + Rules.new( + "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", + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1" + """), + + Rules.new( + "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", + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="1" + """), + + Rules.new( + "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", + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="daemon" + """), + + Rules.new( + "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", + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="daemon" + """), + + Rules.new( + "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", + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="audio" + """), + + Rules.new( + "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", + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", MODE="0777" + """), + + Rules.new( + "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", + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1", GROUP="1", MODE="0777" + """), + + Rules.new( + "permissions OWNER to 1", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_perms = "1::", + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1" + """), + + Rules.new( + "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", + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="1" + """), + + Rules.new( + "permissions MODE to 0060", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_perms = "::0060", + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", MODE="0060" + """), + + Rules.new( + "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", + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1", GROUP="1", MODE="0777" + """), + + Rules.new( + "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", + ), + 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", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_perms = "1:1:0777", + ), + 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", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_perms = "1:2:0777", + ), + 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", + 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", + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node" + """), + + Rules.new( + "big major number test", + Device( + "/devices/virtual/misc/misc-fake1", + exp_links = ["node"], + exp_major_minor = "4095:1", + ), + rules = r""" + KERNEL=="misc-fake1", SYMLINK+="node" + """), + + Rules.new( + "big major and big minor number test", + Device( + "/devices/virtual/misc/misc-fake89999", + exp_links = ["node"], + exp_major_minor = "4095:89999", + ), + rules = r""" + KERNEL=="misc-fake89999", SYMLINK+="node" + """), + + Rules.new( + "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-"], + ), + 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", + Device( + "/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""" + KERNEL=="ttyACM[0-9]*", SYMLINK=" one two " + """), + + Rules.new( + "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 = [" "], + ), + rules = r""" + ENV{WITH_WS}="one two three" + SYMLINK="name-$env{WITH_WS}-end" + """), + + Rules.new( + "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 = [" "], + ), + rules = r""" + ENV{WITH_WS}=" one two three" + SYMLINK="name-$env{WITH_WS}-end" + """), + + Rules.new( + "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 = [" "], + ), + rules = r""" + ENV{WITH_WS}="one two three " + SYMLINK="name-$env{WITH_WS}-end" + """), + + Rules.new( + "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 = [" "], + ), + rules = r""" + ENV{WITH_WS}=" one two three " + SYMLINK="name-$env{WITH_WS}-end" + """), + + Rules.new( + "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 = [" "], + ), + rules = r""" + ENV{WITH_WS}=" one two three " + SYMLINK="name-$env{WITH_WS}-end" + """), + + Rules.new( + "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 = [" "], + ), + 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)", + 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"], + ), + 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)", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_links = ["modem0"], + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n" + """), + + Rules.new( + "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"], + ), + 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", + ), + # we get a warning, but the process does not fail + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="." + """), + + Rules.new( + "symlink node to itself", + Device( + "/devices/virtual/tty/tty0", + ), + # we get a warning, but the process does not fail + rules = r""" + KERNEL=="tty0", SYMLINK+="tty0" + """), + + Rules.new( + "symlink %n substitution", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_links = ["symlink0"], + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n" + """), + + Rules.new( + "symlink %k substitution", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_links = ["symlink-ttyACM0"], + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b" + """), + + Rules.new( + "symlink %c substitution", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_links = ["test"], + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}" + """), + + Rules.new( + "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"], + ), + 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+)", + 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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}" + """), + + Rules.new( + "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"], + ), + 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", + 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"] + ), + 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", + Device( + "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_links = ["node"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-%n" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end" + """), + + Rules.new( + # This is not supported any more + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule" + SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="very-last" + """), + + Rules.new( + "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"], + ), + 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!=", + 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"], + ), + 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", + Device( + "/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""" + SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" + KERNEL=="sda1", PROGRAM!="/bin/false", SYMLINK+="nonzero-program" + """), + + Rules.new( + "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"], + ), + 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", + 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"], + ), + 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)", + 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"], + ), + 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)", + 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"], + ), + 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)", + 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"], + ), + 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"], + ), + 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", + Device( + "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_links = ["sane"], + ), + 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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xc3\xbcber" RESULT=="über", SYMLINK+="uber" + """), + + Rules.new( + "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"], + ), + rules = r""" + SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xef\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}" + """), + + Rules.new( + "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"], + ), + 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", + 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"], + ), + rules = r""" + ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok" + ACTION=="add", KERNEL=="sda", SYMLINK+="ok" + """), + + Rules.new( + "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", + ), + rules = r""" + KERNEL=="sda", GROUP:="tty" + KERNEL=="sda", GROUP="root", MODE="0640", SYMLINK+="ok" + """), + + Rules.new( + "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", + ), + rules = r""" + KERNEL=="sda", GROUP:="tty" + SUBSYSTEM=="block", MODE:="640" + KERNEL=="sda", GROUP="root", MODE="0666", SYMLINK+="ok" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda", MODE="0666", SYMLINK+="node-$env{ACTION}-me" + """), + + Rules.new( + "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"], + ), + 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)", + Device( + "/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""" + KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong" + KERNEL=="ttyACM[0-9]*", SYMLINK="" + KERNEL=="ttyACM[0-9]*", SYMLINK+="right" + """), + + Rules.new( + "test multi matches", + Device( + "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_links = ["right", "before"], + ), + rules = r""" + KERNEL=="ttyACM*", SYMLINK+="before" + KERNEL=="ttyACM*|nothing", SYMLINK+="right" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch" + KERNEL=="ttyACM*", SYMLINK+="before" + KERNEL=="dontknow*|ttyACM*|nothing*", SYMLINK+="right" + """), + + Rules.new( + "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"], + ), + 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", + 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"], + ), + 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", + 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"], + ), + rules = r""" + KERNEL=="sda", TAG="foo" + TAGS=="|foo", SYMLINK+="found" + TAGS=="|aaa", SYMLINK+="bad" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda", ENV{HOGE}="" + ENV{HOGE}=="|foo", SYMLINK+="found" + ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda", TAG="foo" + TAGS=="foo||bar", SYMLINK+="found" + TAGS=="aaa||bbb", SYMLINK+="bad" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda", ENV{HOGE}="" + ENV{HOGE}=="foo||bar", SYMLINK+="found" + ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" + """), + + Rules.new( + "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"], + ), + 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", + 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"], + ), + rules = r""" + KERNEL=="sda", ENV{HOGE}="" + ENV{HOGE}=="foo|", SYMLINK+="found" + ENV{HOGE}=="aaa|bbb", SYMLINK+="bad" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda", TAG="c" + TAGS=="foo||bar||c", SYMLINK+="found" + TAGS=="aaa||bbb||ccc", SYMLINK+="bad" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda", TAG+="", TAG+="invalid.char", TAG+="path/is/also/invalid", TAG+="valid" + TAGS=="", SYMLINK+="empty" + TAGS=="invalid.char", SYMLINK+="invalid_char" + TAGS=="path/is/also/invalid", SYMLINK+="path" + TAGS=="valid", SYMLINK+="valid" + TAGS=="valid|", SYMLINK+="found" + TAGS=="aaa|", SYMLINK+="bad" + TAGS=="aaa|bbb", SYMLINK+="bad2" + """), + + Rules.new( + "IMPORT parent test", + Device( + "/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"], + ), + 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", + 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"], + ), + rules = r""" + KERNEL=="sda1", GOTO="TEST" + KERNEL=="sda1", SYMLINK+="wrong" + KERNEL=="sda1", GOTO="BAD" + KERNEL=="sda1", SYMLINK+="", LABEL="NO" + KERNEL=="sda1", SYMLINK+="right", LABEL="TEST", GOTO="end" + KERNEL=="sda1", SYMLINK+="wrong2", LABEL="BAD" + LABEL="end" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda1", GOTO="does-not-exist" + KERNEL=="sda1", SYMLINK+="right", + LABEL="exists" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda1", SYMLINK+="link" + KERNEL=="sda1", SYMLINK=="link*", SYMLINK+="right" + KERNEL=="sda1", SYMLINK=="nolink*", SYMLINK+="wrong" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL="sda1", SYMLINK+="no" + KERNEL=="sda1", SYMLINK+="yes" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes" + """), + + Rules.new( + "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"], + ), + rules = r""" + # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + KERNEL=="sda1", SYMLINK+=="no" + KERNEL=="sda1", SYMLINK+="yes" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda", SYMLINK+="$attr{[net/eth0]address}" + """), + + Rules.new( + "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"], + ), + rules = r""" + TEST=="/etc/passwd", SYMLINK+="there" + TEST!="/etc/passwd", SYMLINK+="notthere" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes" + """), + + Rules.new( + "TEST relative path", + Device( + "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_links = ["relative"], + ), + rules = r""" + KERNEL=="sda", TEST=="size", SYMLINK+="relative" + """), + + Rules.new( + "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"], + ), + rules = r""" + KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir" + """), + + Rules.new( + "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", + ), + rules = r""" + KERNEL=="sda", MODE="0000" + """), + + Rules.new( + "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", + ), + 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", + Device( + "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_perms = "0:0:0440", + ), + 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", + 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", + ), + rules = r""" + KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end" + """), + + Rules.new( + "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"], + ), + 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", + 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"], + ), + 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"], + ), + rules = f""" + {rules_10k_tags} + TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="found" + """), + + Rules.new( + "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"], + ), + rules = f""" + {rules_10k_tags_continuation} + TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="bad" + KERNEL=="sda",\\ + # comment in continuation + TAG+="hoge1",\\ + # space before comment + TAG+="hoge2",\\ + # spaces before and after token are dropped + TAG+="hoge3", \\ + \\ + \\ + TAG+="hoge4" + TAGS=="hoge1", TAGS=="hoge2", TAGS=="hoge3", TAGS=="hoge4", SYMLINK+="found" + """), + + Rules.new( + "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"], + ), + rules = r""" + # empty line finishes continuation + KERNEL=="sda", TAG+="foo" \ + + KERNEL=="sdb", TAG+="hoge" + KERNEL=="sda", TAG+="aaa" \ + KERNEL=="sdb", TAG+="bbb" + TAGS=="foo", SYMLINK+="found" + TAGS=="aaa", SYMLINK+="bad" + """), + + Rules.new( + "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"], + ), + rules = """ + # space only line finishes continuation + KERNEL=="sda", TAG+="foo" \\ + \t + KERNEL=="sdb", TAG+="hoge" + KERNEL=="sda", TAG+="aaa" \\ + KERNEL=="sdb", TAG+="bbb" + TAGS=="foo", SYMLINK+="found" + TAGS=="aaa", SYMLINK+="bad" + """), + + Rules.new( + "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"], + ), + Device( + "/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"], + ), + Device( + "/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"], + ), + Device( + "/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"], + ), + rules = r""" + SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n" + """), + + Rules.new( + "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"], + ), + 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"], + ), + 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"], + ), + Device( + "/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"], + ), + 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"], + ), + 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"], + ), + 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", + 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"], + ), + 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"], + ), + 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"], + ), + Device( + "/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"], + ), + 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"], + ), + 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"], + ), + 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", + 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"], + ), + 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"], + ), + 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"], + ), + Device( + "/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"], + ), + 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"], + ), + 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"], + ), + 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', + *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" + """), +] + +def fork_and_run_udev(action: str, rules: Rules) -> None: + kinder = [] + for k, device in enumerate(rules.devices): + # TODO: valgrind/gdb/strace + cmd = [UDEV_BIN, action, device.devpath] + if rules.delay: + cmd += [f'{k * rules.delay}'] + + kinder += [subprocess.Popen(cmd)] + + good = True + for c in kinder: + if not good: + # once something fails, terminate all workers + c.terminate() + elif c.wait() != 0: + good = False + + assert good + + +def environment_issue(): + if os.getuid() != 0: + return 'Must be root to run properly' + + c = subprocess.run(['systemd-detect-virt', '-r', '-q'], + check=False) + if c.returncode == 0: + return 'Running in a chroot, skipping the test' + return None + + +@pytest.fixture(scope='module') +def udev_setup(): + issue = environment_issue() + if issue: + pytest.skip(issue) + + 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]) + + 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.chown(UDEV_DEV, 0, 0) + + os.mknod(UDEV_DEV / 'null', 0o600 | stat.S_IFCHR, os.makedev(1, 3)) + + # check if we are permitted to create block device nodes + sda = UDEV_DEV / 'sda' + os.mknod(sda, 0o600 | stat.S_IFBLK, os.makedev(8, 0)) + sda.unlink() + + subprocess.check_call(['cp', '-r', 'test/sys/', UDEV_SYS]) + subprocess.check_call(['rm', '-rf', UDEV_RUN]) + UDEV_RUN.mkdir(parents=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 + + subprocess.check_call(['rm', '-rf', UDEV_RUN]) + subprocess.check_call(['umount', '-v', UDEV_TMPFS]) + UDEV_TMPFS.rmdir() + + +@pytest.mark.parametrize("rules", RULES, ids=(rule.desc for rule in RULES)) +def test_udev(rules: Rules, udev_setup): + assert udev_setup is None + + rules.create_rules_file() + + for _ in range(rules.repeat): + fork_and_run_udev('add', rules) + + for device in rules.devices: + device.check_add() + + fork_and_run_udev('remove', rules) + + for device in rules.devices: + device.check_remove() + +if __name__ == '__main__': + issue = environment_issue() + if issue: + print(issue, file=sys.stderr) + sys.exit(77) + sys.exit(pytest.main(sys.argv)) From 09ea351b6fce2ac3be19fc8af40828b722cd6dc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 May 2023 08:49:18 +0200 Subject: [PATCH 10/19] test: drop udev-test.pl --- meson.build | 1 - test/meson.build | 13 +- test/udev-test.pl | 2911 --------------------------------------------- 3 files changed, 1 insertion(+), 2924 deletions(-) delete mode 100755 test/udev-test.pl diff --git a/meson.build b/meson.build index 25b5010264..224e6251d2 100644 --- a/meson.build +++ b/meson.build @@ -701,7 +701,6 @@ stat = find_program('stat') ln = find_program('ln') git = find_program('git', required : false) env = find_program('env') -perl = find_program('perl', required : false) rsync = find_program('rsync', required : false) diff = find_program('diff') find = find_program('find') diff --git a/test/meson.build b/test/meson.build index 6af112d141..8d6667b405 100644 --- a/test/meson.build +++ b/test/meson.build @@ -131,19 +131,8 @@ custom_target( output : 'sys', build_by_default : want_tests != 'false') -if perl.found() - udev_test_pl = find_program('udev-test.pl') - if want_tests != 'false' - test('udev-test.pl', - udev_test_pl, - timeout : 180) - endif -else - message('Skipping udev-test because perl is not available') -endif - if want_tests != 'false' - test('udev-test.py', + test('udev-test', files('udev-test.py'), args : ['-v'], timeout : 180) diff --git a/test/udev-test.pl b/test/udev-test.pl deleted file mode 100755 index 855c6aec3c..0000000000 --- a/test/udev-test.pl +++ /dev/null @@ -1,2911 +0,0 @@ -#!/usr/bin/env perl -# SPDX-License-Identifier: LGPL-2.1-or-later - -# udev test -# -# Provides automated testing of the udev binary. -# The whole test is self contained in this file, except the matching sysfs tree. -# Simply extend the @tests array, to add a new test variant. -# -# Every test is driven by its own temporary config file. -# This program prepares the environment, creates the config and calls udev. -# -# udev parses the rules, looks at the provided sysfs and -# first creates and then removes the device node. -# After creation and removal the result is checked against the -# expected value and the result is printed. -# -# Copyright © 2004 Leann Ogasawara - -use warnings; -use strict; - -BEGIN { - my $EXIT_TEST_SKIP = 77; - - unless (eval "use POSIX qw(WIFEXITED WEXITSTATUS); - use Cwd qw(getcwd abs_path); - use IPC::Semaphore; - use IPC::SysV qw(IPC_PRIVATE S_IRUSR S_IWUSR IPC_CREAT); - use Time::HiRes qw(usleep); 1") { - warn "Failed to import dependencies, skipping the test: $@"; - exit($EXIT_TEST_SKIP); - } -} - -# Relax sd-device's sysfs verification, since we want to provide a fake sysfs -# here that actually is a tmpfs. -$ENV{"SYSTEMD_DEVICE_VERIFY_SYSFS"}="0"; - -my $udev_bin = "./test-udev"; -my $valgrind = 0; -my $gdb = 0; -my $strace = 0; -my $udev_bin_valgrind = "valgrind --tool=memcheck --leak-check=yes --track-origins=yes --quiet $udev_bin"; -my $udev_bin_gdb = "gdb --args $udev_bin"; -my $udev_bin_strace = "strace -efile $udev_bin"; -my $udev_run = "test/run"; -my $udev_tmpfs = "test/tmpfs"; -my $udev_sys = "${udev_tmpfs}/sys"; -my $udev_dev = "${udev_tmpfs}/dev"; -my $udev_rules_dir = "$udev_run/udev/rules.d"; -my $udev_rules = "$udev_rules_dir/udev-test.rules"; -my $EXIT_TEST_SKIP = 77; - -my $rules_10k_tags = ""; -for (my $i = 1; $i <= 10000; ++$i) { - $rules_10k_tags .= 'KERNEL=="sda", TAG+="test' . $i . "\"\n"; -} - -my $rules_10k_tags_continuation = "KERNEL==\"sda\", \\\n"; -for (my $i = 1; $i < 10000; ++$i) { - $rules_10k_tags_continuation .= 'TAG+="test' . $i . "\",\\\n"; -} -$rules_10k_tags_continuation .= "TAG+=\"test10000\"\\n"; - -# Create a device list with all block devices under /sys -# (except virtual devices and cd-roms) -# the optional argument exp_func returns expected and non-expected -# symlinks for the device. -sub all_block_devs { - my ($exp_func) = @_; - my @devices; - - foreach my $bd (glob "$udev_sys/dev/block/*") { - my $tgt = readlink($bd); - my ($exp, $notexp) = (undef, undef); - - next if ($tgt =~ m!/virtual/! || $tgt =~ m!/sr[0-9]*$!); - - $tgt =~ s!^\.\./\.\.!!; - ($exp, $notexp) = $exp_func->($tgt) if defined($exp_func); - my $device = { - devpath => $tgt, - exp_links => $exp, - not_exp_links => $notexp, - }; - push(@devices, $device); - } - return \@devices; -} - -# This generator returns a suitable exp_func for use with -# all_block_devs(). -sub expect_for_some { - my ($pattern, $links, $donot) = @_; - my $_expect = sub { - my ($name) = @_; - - if ($name =~ /$pattern/) { - return ($links, undef); - } elsif ($donot) { - return (undef, $links); - } else { - return (undef, undef); - } - }; - return $_expect; -} - -my @tests = ( - { - desc => "no rules", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_rem_error => "yes", - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_rem_error => "yes", - }], - rules => < "label test of scsi disc", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["boot_disk"], - }], - rules => < "label test of scsi disc", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["boot_disk"], - }], - rules => < "label test of scsi disc", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["boot_disk"], - }], - rules => < "label test of scsi partition", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["boot_disk1"], - }], - rules => < "label test of pattern match", - devices => [ - { - devpath => "/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 => < "label test of multiple sysfs files", - devices => [ - { - devpath => "/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 => < "label test of max sysfs files (skip invalid rule)", - devices => [ - { - devpath => "/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 => < "SYMLINK tests", - devices => [ - { - devpath => "/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 => < "catch device by *", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["modem/0", "catch-all"], - }], - rules => < "catch device by * - take 2", - devices => [ - { - devpath => "/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 => < "catch device by ?", - devices => [ - { - devpath => "/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 => < "catch device by character class", - devices => [ - { - devpath => "/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 => < "don't replace kernel name", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["modem"], - }], - rules => < "Handle comment lines in config file (and don't replace kernel name)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["modem"], - }], - rules => < "Handle comment lines in config file with whitespace (and don't replace kernel name)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["modem"], - }], - rules => < "Handle whitespace only lines (and don't replace kernel name)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["whitespace"], - }], - rules => < "Handle empty lines in config file (and don't replace kernel name)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["modem"], - }], - rules => < "Handle backslashed multi lines in config file (and don't replace kernel name)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["modem"], - }], - rules => < "preserve backslashes, if they are not for a newline", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["aaa"], - }], - rules => < "Handle stupid backslashed multi lines in config file (and don't replace kernel name)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["modem"], - }], - rules => < "subdirectory handling", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["sub/direct/ory/modem"], - }], - rules => < "parent device name match of scsi partition", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links => ["first_disk5"], - }], - rules => < "test substitution chars", - devices => [ - { - devpath => "/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 => < "import of shell-value returned from program", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["node12345678"], - }], - rules => < "substitution of sysfs value (%s{file})", - devices => [ - { - devpath => "/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 => < "program result substitution", - devices => [ - { - devpath => "/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 => < "program result substitution (newline removal)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links => ["newline_removed"], - }], - rules => < "program result substitution", - devices => [ - { - devpath => "/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 => < "program with lots of arguments", - devices => [ - { - devpath => "/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 => < "program with subshell", - devices => [ - { - devpath => "/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 => < "program arguments combined with apostrophes", - devices => [ - { - devpath => "/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 => < "program arguments combined with escaped double quotes, part 1", - devices => [ - { - devpath => "/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 => < "program arguments combined with escaped double quotes, part 2", - devices => [ - { - devpath => "/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 => < "program arguments combined with escaped double quotes, part 3", - devices => [ - { - devpath => "/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 => < "characters before the %c{N} substitution", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links => ["my-foo9"], - }], - rules => < "substitute the second to last argument", - devices => [ - { - devpath => "/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 => < "test substitution by variable name", - devices => [ - { - devpath => "/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 => < "test substitution by variable name 2", - devices => [ - { - devpath => "/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 => < "test substitution by variable name 3", - devices => [ - { - devpath => "/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 => < "test substitution by variable name 4", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links => ["855"], - }], - rules => < "test substitution by variable name 5", - devices => [ - { - devpath => "/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 => < "non matching SUBSYSTEMS for device with no parent", - devices => [ - { - devpath => "/devices/virtual/tty/console", - exp_links => ["TTY"], - not_exp_links => ["foo"], - }], - rules => < "non matching SUBSYSTEMS", - devices => [ - { - devpath => "/devices/virtual/tty/console", - exp_links => ["TTY"], - not_exp_links => ["foo"], - }], - rules => < "ATTRS match", - devices => [ - { - devpath => "/devices/virtual/tty/console", - exp_links => ["foo", "TTY"], - }], - rules => < "ATTR (empty file)", - devices => [ - { - devpath => "/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 => < "ATTR (non-existent file)", - devices => [ - { - devpath => "/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 => < "program and bus type match", - devices => [ - { - devpath => "/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 => < "sysfs parent hierarchy", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["modem"], - }], - rules => < "name test with ! in the name", - devices => [ - { - devpath => "/devices/virtual/block/fake!blockdev0", - devnode => "fake/blockdev0", - exp_links => ["is/a/fake/blockdev0"], - not_exp_links => ["is/not/a/fake/blockdev0", "modem"], - }], - rules => < "name test with ! in the name, but no matching rule", - devices => [ - { - devpath => "/devices/virtual/block/fake!blockdev0", - devnode => "fake/blockdev0", - not_exp_links => ["modem"], - }], - rules => < "KERNELS rule", - devices => [ - { - devpath => "/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 => < "KERNELS wildcard all", - devices => [ - { - devpath => "/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 => < "KERNELS wildcard partial", - devices => [ - { - devpath => "/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 => < "KERNELS wildcard partial 2", - devices => [ - { - devpath => "/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 => < "substitute attr with link target value (first match)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["driver-is-sd"], - }], - rules => < "substitute attr with link target value (currently selected device)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["driver-is-ahci"], - }], - rules => < "ignore ATTRS attribute whitespace", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["ignored"], - }], - rules => < "do not ignore ATTRS attribute whitespace", - devices => [ - { - devpath => "/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 => < "permissions USER=bad GROUP=name", - devices => [ - { - devpath => "/devices/virtual/tty/tty33", - exp_perms => "0:0:0600", - }], - rules => < "permissions OWNER=1", - devices => [ - { - devpath => "/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 => < "permissions GROUP=1", - devices => [ - { - devpath => "/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 => < "textual user id", - devices => [ - { - devpath => "/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 => < "textual group id", - devices => [ - { - devpath => "/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 => < "textual user/group id", - devices => [ - { - devpath => "/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 => < "permissions MODE=0777", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["node"], - exp_perms => "::0777", - }], - rules => < "permissions OWNER=1 GROUP=1 MODE=0777", - devices => [ - { - devpath => "/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 => < "permissions OWNER to 1", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms => "1::", - }], - rules => < "permissions GROUP to 1", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms => ":1:0660", - }], - rules => < "permissions MODE to 0060", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms => "::0060", - }], - rules => < "permissions OWNER, GROUP, MODE", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms => "1:1:0777", - }], - rules => < "permissions only rule", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms => "1:1:0777", - }], - rules => < "multiple permissions only rule", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms => "1:1:0777", - }], - rules => < "permissions only rule with override at SYMLINK+ rule", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_perms => "1:2:0777", - }], - rules => < "major/minor number test", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["node"], - exp_majorminor => "8:0", - }], - rules => < "big major number test", - devices => [ - { - devpath => "/devices/virtual/misc/misc-fake1", - exp_links => ["node"], - exp_majorminor => "4095:1", - }], - rules => < "big major and big minor number test", - devices => [ - { - devpath => "/devices/virtual/misc/misc-fake89999", - exp_links => ["node"], - exp_majorminor => "4095:89999", - }], - rules => < "multiple symlinks with format char", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["symlink1-0", "symlink2-ttyACM0", "symlink3-"], - }], - rules => < "multiple symlinks with a lot of s p a c e s", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["one", "two"], - not_exp_links => [" "], - }], - rules => < "symlink with spaces in substituted variable", - devices => [ - { - devpath => "/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 => < "symlink with leading space in substituted variable", - devices => [ - { - devpath => "/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 => < "symlink with trailing space in substituted variable", - devices => [ - { - devpath => "/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 => < "symlink with lots of space in substituted variable", - devices => [ - { - devpath => "/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 => < "symlink with multiple spaces in substituted variable", - devices => [ - { - devpath => "/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 => < "symlink with space and var with space", - devices => [ - { - devpath => "/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 => < "symlink with env which contain slash (see #19309)", - devices => [ - { - devpath => "/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 => < "symlink creation (same directory)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["modem0"], - }], - rules => < "multiple symlinks", - devices => [ - { - devpath => "/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 => < "symlink name '.'", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["."], - exp_add_error => "yes", - exp_rem_error => "yes", - }], - rules => < "symlink node to itself", - devices => [ - { - devpath => "/devices/virtual/tty/tty0", - exp_links => ["link"], - exp_add_error => "yes", - exp_rem_error => "yes", - }], - option => "clean", - rules => < "symlink %n substitution", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["symlink0"], - }], - rules => < "symlink %k substitution", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["symlink-ttyACM0"], - }], - rules => < "symlink %M:%m substitution", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["major-166:0"], - }], - rules => < "symlink %b substitution", - devices => [ - { - devpath => "/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 => < "symlink %c substitution", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["test"], - }], - rules => < "symlink %c{N} substitution", - devices => [ - { - devpath => "/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 => < "symlink %c{N+} substitution", - devices => [ - { - devpath => "/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 => < "symlink only rule with %c{N+}", - devices => [ - { - devpath => "/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 => < "symlink %s{filename} substitution", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["166:0"], - }], - rules => < "program result substitution (numbered part of)", - devices => [ - { - devpath => "/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 => < "program result substitution (numbered part of+)", - devices => [ - { - devpath => "/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 => < "SUBSYSTEM match test", - devices => [ - { - devpath => "/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 => < "DRIVERS match test", - devices => [ - { - devpath => "/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 => < "devnode substitution test", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["node"], - }], - rules => < "parent node name substitution test", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["sda-part-1"], - }], - rules => < "udev_root substitution", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["start-/dev-end"], - }], - rules => < "last_rule option", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["last"], - not_exp_links => ["very-last"], - exp_nodev_error => "yes", - }], - rules => < "negation KERNEL!=", - devices => [ - { - devpath => "/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 => < "negation SUBSYSTEM!=", - devices => [ - { - devpath => "/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 => < "negation PROGRAM!= exit code", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["before", "nonzero-program"], - }], - rules => < "ENV{} test", - devices => [ - { - devpath => "/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 => < "ENV{} test", - devices => [ - { - devpath => "/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 => < "ENV{} test (assign)", - devices => [ - { - devpath => "/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 => < "ENV{} test (assign 2 times)", - devices => [ - { - devpath => "/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 => < "ENV{} test (assign2)", - devices => [ - { - devpath => "/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"], - }, - { - devpath => "/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 => < "untrusted string sanitize", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["sane"], - }], - rules => < "untrusted string sanitize (don't replace utf8)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["uber"], - }], - rules => < "untrusted string sanitize (replace invalid utf8)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["replaced"], - }], - rules => < "read sysfs value from parent device", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["serial-354172020305000"], - }], - rules => < "match against empty key string", - devices => [ - { - devpath => "/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 => < "check ACTION value", - devices => [ - { - devpath => "/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 => < "final assignment", - devices => [ - { - devpath => "/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 => < "final assignment 2", - devices => [ - { - devpath => "/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 => < "env substitution", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["node-add-me"], - }], - rules => < "reset list to current value", - devices => [ - { - devpath => "/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 => < "test empty SYMLINK+ (empty override)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["right"], - not_exp_links => ["wrong"], - }], - rules => < "test multi matches", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", - exp_links => ["right", "before"], - }], - rules => < "test multi matches 2", - devices => [ - { - devpath => "/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 => < "test multi matches 3", - devices => [ - { - devpath => "/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 => < "test multi matches 4", - devices => [ - { - devpath => "/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 => < "test multi matches 5", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found"], - not_exp_name => "bad", - }], - rules => < "test multi matches 6", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found"], - not_exp_name => "bad", - }], - rules => < "test multi matches 7", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found"], - not_exp_name => "bad", - }], - rules => < "test multi matches 8", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found"], - not_exp_name => "bad", - }], - rules => < "test multi matches 9", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found", "found2"], - not_exp_name => ["bad", "bad2"], - }], - rules => < "test multi matches 10", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found"], - not_exp_name => "bad", - }], - rules => < "test multi matches 11", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found"], - not_exp_name => "bad", - }], - rules => < "TAG refuses invalid string", - devices => [ - { - devpath => "/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 => < "IMPORT parent test", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["parent"], - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["parentenv-parent_right"], - }], - sleep_us => 500000, # Serialized! We need to sleep here after adding sda - rules => < "GOTO test", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["right"], - not_exp_test => ["wrong", "wrong2"], - }], - rules => < "GOTO label does not exist", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["right"], - }], - rules => < "SYMLINK+ compare test", - devices => [ - { - devpath => "/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 => < "invalid key operation", - devices => [ - { - devpath => "/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 => < "operator chars in attribute", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["yes"], - }], - rules => < "overlong comment line", - devices => [ - { - devpath => "/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 => < "magic subsys/kernel lookup", - devices => [ - { - devpath => "/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 => < "TEST absolute path", - devices => [ - { - devpath => "/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 => < "TEST subsys/kernel lookup", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["yes"], - }], - rules => < "TEST relative path", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["relative"], - }], - rules => < "TEST wildcard substitution (find queue/nr_requests)", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found-subdir"], - }], - rules => < "TEST MODE=0000", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms => "0:0:0000", - exp_rem_error => "yes", - }], - rules => < "TEST PROGRAM feeds OWNER, GROUP, MODE", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms => "1:1:0400", - }], - rules => < "TEST PROGRAM feeds MODE with overflow", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_perms => "0:0:0440", - exp_rem_error => "yes", - }], - rules => < "magic [subsys/sysname] attribute substitution", - devices => [ - { - devpath => "/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 => < "builtin path_id", - devices => [ - { - devpath => "/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 => < "add and match tag", - devices => [ - { - devpath => "/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 => < "don't crash with lots of tags", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found"], - }], - rules => $rules_10k_tags . < "continuations", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found"], - not_exp_name => "bad", - }], - rules => $rules_10k_tags_continuation . < "continuations with empty line", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found"], - not_exp_name => "bad", - - }], - rules => < "continuations with space only line", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", - exp_links => ["found"], - not_exp_name => "bad", - }], - rules => < "multiple devices", - devices => [ - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", - exp_links => ["part-1"], - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", - exp_links => ["part-5"], - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6", - exp_links => ["part-6"], - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links => ["part-7"], - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8", - exp_links => ["part-8"], - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9", - exp_links => ["part-9"], - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10", - exp_links => ["part-10"], - }, - ], - rules => < "multiple devices, same link name, positive prio", - repeat => 100, - devices => [ - { - devpath => "/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"], - }, - { - devpath => "/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"], - }, - { - devpath => "/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"], - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links => ["part-7", "partition"], - }, - { - devpath => "/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"], - }, - { - devpath => "/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"], - }, - { - devpath => "/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 => < "multiple devices, same link name, negative prio", - devices => [ - { - devpath => "/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"], - }, - { - devpath => "/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"], - }, - { - devpath => "/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"], - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links => ["part-7", "partition"], - }, - { - devpath => "/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"], - }, - { - devpath => "/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"], - }, - { - devpath => "/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 => < "multiple devices, same link name, positive prio, sleep", - devices => [ - { - devpath => "/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"], - }, - { - devpath => "/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"], - }, - { - devpath => "/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"], - }, - { - devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7", - exp_links => ["part-7", "partition"], - }, - { - devpath => "/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"], - }, - { - devpath => "/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"], - }, - { - devpath => "/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"], - }, - ], - sleep_us => 10000, - rules => < 'all_block_devs', - generator => expect_for_some("\\/sda6\$", ["blockdev"]), - repeat => 10, - rules => <$udev_rules" || die "unable to create rules file: $udev_rules"; - print CONF $$rules; - close CONF; -} - -sub udev { - my ($action, $devpath) = @_; - - if ($valgrind > 0) { - return system("$udev_bin_valgrind $action $devpath"); - } elsif ($gdb > 0) { - return system("$udev_bin_gdb $action $devpath"); - } elsif ($strace > 0) { - return system("$udev_bin_strace $action $devpath"); - } else { - return system("$udev_bin", "$action", "$devpath"); - } -} - -my $error = 0; -my $good = 0; -my $exp_good = 0; - -sub permissions_test { - my($rules, $uid, $gid, $mode) = @_; - - my $wrong = 0; - my $userid; - my $groupid; - - $rules->{exp_perms} =~ m/^(.*):(.*):(.*)$/; - if ($1 ne "") { - if (defined(getpwnam($1))) { - $userid = int(getpwnam($1)); - } else { - $userid = $1; - } - if ($uid != $userid) { $wrong = 1; } - } - if ($2 ne "") { - if (defined(getgrnam($2))) { - $groupid = int(getgrnam($2)); - } else { - $groupid = $2; - } - if ($gid != $groupid) { $wrong = 1; } - } - if ($3 ne "") { - if (($mode & 07777) != oct($3)) { $wrong = 1; }; - } - if ($wrong == 0) { - print "permissions: ok\n"; - $good++; - } else { - printf " expected permissions are: %s:%s:%#o\n", $1, $2, oct($3); - printf " created permissions are : %i:%i:%#o\n", $uid, $gid, $mode & 07777; - print "permissions: error\n"; - $error++; - sleep(1); - } -} - -sub major_minor_test { - my($rules, $rdev) = @_; - - my $major = ($rdev >> 8) & 0xfff; - my $minor = ($rdev & 0xff) | (($rdev >> 12) & 0xfff00); - my $wrong = 0; - - $rules->{exp_majorminor} =~ m/^(.*):(.*)$/; - if ($1 ne "") { - if ($major != $1) { $wrong = 1; }; - } - if ($2 ne "") { - if ($minor != $2) { $wrong = 1; }; - } - if ($wrong == 0) { - print "major:minor: ok\n"; - $good++; - } else { - printf " expected major:minor is: %i:%i\n", $1, $2; - printf " created major:minor is : %i:%i\n", $major, $minor; - print "major:minor: error\n"; - $error++; - sleep(1); - } -} - -sub udev_setup { - system("umount \"$udev_tmpfs\" 2>/dev/null"); - rmdir($udev_tmpfs); - mkdir($udev_tmpfs) || die "unable to create udev_tmpfs: $udev_tmpfs\n"; - - if (system("mount", "-o", "rw,mode=0755,nosuid,noexec", "-t", "tmpfs", "tmpfs", $udev_tmpfs)) { - warn "unable to mount tmpfs"; - return 0; - } - - mkdir($udev_dev) || die "unable to create udev_dev: $udev_dev\n"; - # setting group and mode of udev_dev ensures the tests work - # even if the parent directory has setgid bit enabled. - chown (0, 0, $udev_dev) || die "unable to chown $udev_dev\n"; - chmod (0755, $udev_dev) || die "unable to chmod $udev_dev\n"; - - if (system("mknod", $udev_dev . "/null", "c", "1", "3")) { - warn "unable to create $udev_dev/null"; - return 0; - } - - # check if we are permitted to create block device nodes - my $block_device_filename = $udev_dev . "/sda"; - if (system("mknod", $block_device_filename, "b", "8", "0")) { - warn "unable to create $block_device_filename"; - return 0; - } - unlink $block_device_filename; - - system("cp", "-r", "test/sys/", $udev_sys) && die "unable to copy test/sys"; - - system("rm", "-rf", "$udev_run"); - - if (!mkdir($udev_run)) { - warn "unable to create directory $udev_run"; - return 0; - } - - return 1; -} - -sub get_devnode { - my ($device) = @_; - my $devnode; - - if (defined($device->{devnode})) { - $devnode = "$udev_dev/$device->{devnode}"; - } else { - $devnode = "$device->{devpath}"; - $devnode =~ s!.*/!$udev_dev/!; - } - return $devnode; -} - -sub check_devnode { - my ($device) = @_; - my $devnode = get_devnode($device); - - my @st = lstat("$devnode"); - if (! (-b _ || -c _)) { - print "add $devnode: error\n"; - system("tree", "$udev_dev"); - $error++; - return undef; - } - - my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, - $atime, $mtime, $ctime, $blksize, $blocks) = @st; - - if (defined($device->{exp_perms})) { - permissions_test($device, $uid, $gid, $mode); - } - if (defined($device->{exp_majorminor})) { - major_minor_test($device, $rdev); - } - print "add $devnode: ok\n"; - $good++; - return $devnode; -} - -sub get_link_target { - my ($link) = @_; - - my $cwd = getcwd(); - my $dir = "$udev_dev/$link"; - my $tgt = readlink("$udev_dev/$link"); - $dir =~ s!/[^/]*$!!; - $tgt = abs_path("$dir/$tgt"); - $tgt =~ s!^$cwd/!!; - return $tgt; -} - -sub check_link_add { - my ($link, $devnode, $err_expected) = @_; - - my @st = lstat("$udev_dev/$link"); - if (-l _) { - my $tgt = get_link_target($link); - - if ($tgt ne $devnode) { - print "symlink $link: error, found -> $tgt\n"; - $error++; - system("tree", "$udev_dev"); - } else { - print "symlink $link: ok\n"; - $good++; - } - } else { - print "symlink $link: error"; - if ($err_expected) { - print " as expected\n"; - $good++; - } else { - print "\n"; - system("tree", "$udev_dev"); - print "\n"; - $error++; - sleep(1); - } - } -} - -sub check_link_nonexistent { - my ($link, $devnode, $err_expected) = @_; - - if ((-e "$udev_dev/$link") || (-l "$udev_dev/$link")) { - my $tgt = get_link_target($link); - - if ($tgt ne $devnode) { - print "nonexistent: '$link' points to other device (ok)\n"; - $good++; - } else { - print "nonexistent: error \'$link\' should not be there"; - if ($err_expected) { - print " (as expected)\n"; - $good++; - } else { - print "\n"; - system("tree", "$udev_dev"); - print "\n"; - $error++; - sleep(1); - } - } - } else { - print "nonexistent $link: ok\n"; - $good++; - } -} - -sub check_add { - my ($device) = @_; - my $devnode = check_devnode($device); - - if (defined($device->{exp_links})) { - foreach my $link (@{$device->{exp_links}}) { - check_link_add($link, $devnode, - $device->{exp_add_error}); - } - } - if (defined $device->{not_exp_links}) { - foreach my $link (@{$device->{not_exp_links}}) { - check_link_nonexistent($link, $devnode, - $device->{exp_nodev_error}); - } - } -} - -sub check_remove_devnode { - my ($device) = @_; - my $devnode = get_devnode($device); - - if (-e "$devnode") { - print "remove $devnode: error"; - print "\n"; - system("tree", "$udev_dev"); - print "\n"; - $error++; - sleep(1); - } else { - print "remove $devnode: ok\n"; - $good++; - } -} - -sub check_link_remove { - my ($link, $err_expected) = @_; - - if ((-e "$udev_dev/$link") || - (-l "$udev_dev/$link")) { - print "remove $link: error"; - if ($err_expected) { - print " as expected\n"; - $good++; - } else { - print "\n"; - system("tree", "$udev_dev"); - print "\n"; - $error++; - sleep(1); - } - } else { - print "remove $link: ok\n"; - $good++; - } -} - -sub check_remove { - my ($device) = @_; - - check_remove_devnode($device); - - return if (!defined($device->{exp_links})); - - foreach my $link (@{$device->{exp_links}}) { - check_link_remove($link, $device->{exp_rem_error}); - } -} - -sub run_udev { - my ($action, $dev, $sleep_us, $sema) = @_; - - # Notify main process that this worker has started - $sema->op(0, 1, 0); - - # Wait for start - $sema->op(0, 0, 0); - usleep($sleep_us) if defined ($sleep_us); - my $rc = udev($action, $dev->{devpath}); - exit $rc; -} - -sub fork_and_run_udev { - my ($action, $rules, $sema) = @_; - my @devices = @{$rules->{devices}}; - my $dev; - my $k = 0; - - $sema->setval(0, 1); - foreach $dev (@devices) { - my $pid = fork(); - - if (!$pid) { - run_udev($action, $dev, - defined($rules->{sleep_us}) ? $k * $rules->{sleep_us} : undef, - $sema); - } else { - $dev->{pid} = $pid; - } - $k++; - } - - # This operation waits for all workers to become ready, and - # starts them off when that's the case. - $sema->op(0, -($#devices + 2), 0); - - foreach $dev (@devices) { - my $rc; - my $pid; - - $pid = waitpid($dev->{pid}, 0); - if ($pid == -1) { - print "error waiting for pid dev->{pid}\n"; - } - if (WIFEXITED($?)) { - $rc = WEXITSTATUS($?); - - if ($rc) { - print "$udev_bin $action for $dev->{devpath} failed with code $rc\n"; - $error += 1; - } else { - $good++; - } - } - } -} - -sub run_test { - my ($rules, $number, $sema) = @_; - my $rc; - my @devices; - my $ntests; - my $cur_good = $good; - my $cur_error = $error; - - if (!defined $rules->{devices}) { - $rules->{devices} = all_block_devs($rules->{generator}); - } - @devices = @{$rules->{devices}}; - # For each device: exit status and devnode test for add & remove - $ntests += 4 * ($#devices + 1); - - foreach my $dev (@devices) { - $ntests += 2 * ($#{$dev->{exp_links}} + 1) - + ($#{$dev->{not_exp_links}} + 1) - + (defined $dev->{exp_perms} ? 1 : 0) - + (defined $dev->{exp_majorminor} ? 1 : 0); - } - if (defined $rules->{repeat}) { - $ntests *= $rules->{repeat}; - } - $exp_good += $ntests; - print "TEST $number: $rules->{desc}\n"; - create_rules(\$rules->{rules}); - - REPEAT: - fork_and_run_udev("add", $rules, $sema); - - foreach my $dev (@devices) { - check_add($dev); - } - - if (defined($rules->{option}) && $rules->{option} eq "keep") { - print "\n\n"; - return; - } - - fork_and_run_udev("remove", $rules, $sema); - - foreach my $dev (@devices) { - check_remove($dev); - } - - if (defined($rules->{repeat}) && --($rules->{repeat}) > 0) { - goto REPEAT; - } - printf "TEST $number: errors: %d good: %d/%d\n\n", $error-$cur_error, - $good-$cur_good, $ntests; - - if (defined($rules->{option}) && $rules->{option} eq "clean") { - udev_setup(); - } - -} - -sub cleanup { - system("rm", "-rf", "$udev_run"); - system("umount", "$udev_tmpfs"); - rmdir($udev_tmpfs); -} - -# only run if we have root permissions -# due to mknod restrictions -if (!($<==0)) { - print "Must have root permissions to run properly.\n"; - exit($EXIT_TEST_SKIP); -} - -# skip the test when running in a chroot -system("systemd-detect-virt", "-r", "-q"); -if ($? >> 8 == 0) { - print "Running in a chroot, skipping the test.\n"; - exit($EXIT_TEST_SKIP); -} - -if (!udev_setup()) { - warn "Failed to set up the environment, skipping the test"; - cleanup(); - exit($EXIT_TEST_SKIP); -} - -if (system($udev_bin, "check")) { - warn "$udev_bin failed to set up the environment, skipping the test"; - cleanup(); - exit($EXIT_TEST_SKIP); -} - -my $test_num = 1; -my @list; - -foreach my $arg (@ARGV) { - if ($arg =~ m/--valgrind/) { - $valgrind = 1; - printf("using valgrind\n"); - } elsif ($arg =~ m/--gdb/) { - $gdb = 1; - printf("using gdb\n"); - } elsif ($arg =~ m/--strace/) { - $strace = 1; - printf("using strace\n"); - } else { - push(@list, $arg); - } -} -my $sema = IPC::Semaphore->new(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR | IPC_CREAT); - -if ($list[0]) { - foreach my $arg (@list) { - if (defined($tests[$arg-1]->{desc})) { - print "udev-test will run test number $arg:\n\n"; - run_test($tests[$arg-1], $arg, $sema); - } else { - print "test does not exist.\n"; - } - } -} else { - # test all - print "\nudev-test will run ".($#tests + 1)." tests:\n\n"; - - foreach my $rules (@tests) { - run_test($rules, $test_num, $sema); - $test_num++; - } -} - -$sema->remove; -print "$error errors occurred. $good/$exp_good good results.\n\n"; - -cleanup(); - -if ($error > 0) { - exit(1); -} -exit(0); From 0454cf05d38d289474ca65c1917d414b2958f6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 5 May 2023 14:46:34 +0200 Subject: [PATCH 11/19] test: rework how udev-test is invoked As part of the build, we would populate build/test/sys/ using sys-script.py, and then udev-test.p[ly] would create a tmpfs instance on build/test/tmpfs and copy the sys tree to build/test/tmpfs/sys. Also, we had udev-test.p[ly] which called test-udev. test-udev was marked as a manual test and installed, but neither udev-test.p[ly] or sys-script.py were. test-udev is renamed to udev-rule-runner, which reduces confusion and frees up the test-udev name. udev-test.py is renamed to test-udev.py. All three files are now installed. test-udev.py is modified to internally call sys-script.py to set up the sys tree. Copying and creating it from scratch should take the same amount of time. We avoid having a magic directory, everything is now done underneath a temporary directory. test-udev.py is now a normal installed test, and run-unit-tests.py will pick it up. When test-udev.py is invoked from meson, the path to udev-rule-runner is passed via envvar; when it is invoked via run-unit-tests.py or directly, it looks for udev-rule-runner in a relative path. The goal of this whole change is to let Debian drop the 'udev' test. It called sys-script.py and udev-test.pl from the source directory and had to recreate a bunch of the logic. Now test-udev.py will now be called via 'upstream'. --- meson.build | 13 ++++ src/test/meson.build | 2 +- src/test/{test-udev.c => udev-rule-runner.c} | 10 +-- test/meson.build | 19 +++--- test/{udev-test.py => test-udev.py} | 64 ++++++++++++++------ 5 files changed, 71 insertions(+), 37 deletions(-) rename src/test/{test-udev.c => udev-rule-runner.c} (89%) rename test/{udev-test.py => test-udev.py} (98%) diff --git a/meson.build b/meson.build index 224e6251d2..f6c7279fd5 100644 --- a/meson.build +++ b/meson.build @@ -4415,6 +4415,7 @@ foreach test : simple_tests tests += { 'sources' : [test] } endforeach +TESTS = {} foreach test : tests sources = test.get('sources') condition = test.get('condition', '') @@ -4466,6 +4467,8 @@ foreach test : tests suite : suite, is_parallel : test.get('parallel', true)) endif + + TESTS += { name : exe } endforeach exe = executable( @@ -4527,6 +4530,16 @@ if want_tests != 'false' and static_libudev_pic test('test-libudev-static-sym', exe) endif +if want_tests != 'false' + udev_rule_runner = TESTS['udev-rule-runner'].full_path() + + test('test-udev', + test_udev_py, + args : ['-v'], + env : ['UDEV_RULE_RUNNER=@0@'.format(udev_rule_runner)], + timeout : 180) +endif + ############################################################ foreach fuzzer : simple_fuzzers diff --git a/src/test/meson.build b/src/test/meson.build index 8a5e47f004..8e76df624d 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -409,7 +409,7 @@ tests += [ 'timeout' : 120, }, { - 'sources' : files('test-udev.c'), + 'sources' : files('udev-rule-runner.c'), 'link_with' : [ libshared, libudevd_core, diff --git a/src/test/test-udev.c b/src/test/udev-rule-runner.c similarity index 89% rename from src/test/test-udev.c rename to src/test/udev-rule-runner.c index 00ca79d0eb..0b5938802a 100644 --- a/src/test/test-udev.c +++ b/src/test/udev-rule-runner.c @@ -62,11 +62,11 @@ static int fake_filesystems(void) { const char *error; bool ignore_mount_error; } fakefss[] = { - { "test/tmpfs/sys", "/sys", "Failed to mount test /sys", false }, - { "test/tmpfs/dev", "/dev", "Failed to mount test /dev", false }, - { "test/run", "/run", "Failed to mount test /run", false }, - { "test/run", "/etc/udev/rules.d", "Failed to mount empty /etc/udev/rules.d", true }, - { "test/run", UDEVLIBEXECDIR "/rules.d", "Failed to mount empty " UDEVLIBEXECDIR "/rules.d", true }, + { "tmpfs/sys", "/sys", "Failed to mount test /sys", false }, + { "tmpfs/dev", "/dev", "Failed to mount test /dev", false }, + { "run", "/run", "Failed to mount test /run", false }, + { "run", "/etc/udev/rules.d", "Failed to mount empty /etc/udev/rules.d", true }, + { "run", UDEVLIBEXECDIR "/rules.d", "Failed to mount empty " UDEVLIBEXECDIR "/rules.d", true }, }; int r; diff --git a/test/meson.build b/test/meson.build index 8d6667b405..f53971416e 100644 --- a/test/meson.build +++ b/test/meson.build @@ -123,19 +123,14 @@ endif ############################################################ -# prepare test/sys tree -sys_script_py = find_program('sys-script.py') -custom_target( - 'sys', - command : [sys_script_py, meson.current_build_dir()], - output : 'sys', - build_by_default : want_tests != 'false') +sys_script_py = files('sys-script.py') +test_udev_py = files('test-udev.py') -if want_tests != 'false' - test('udev-test', - files('udev-test.py'), - args : ['-v'], - timeout : 180) +if install_tests + install_data( + sys_script_py, + test_udev_py, + install_dir : unittestsdir) endif ############################################################ diff --git a/test/udev-test.py b/test/test-udev.py similarity index 98% rename from test/udev-test.py rename to test/test-udev.py index 5ddda3865f..86937205de 100755 --- a/test/udev-test.py +++ b/test/test-udev.py @@ -4,6 +4,7 @@ # pylint: disable=missing-docstring,redefined-outer-name,invalid-name # pylint: disable=unspecified-encoding,no-else-return,line-too-long,too-many-lines # pylint: disable=multiple-imports,too-many-instance-attributes,consider-using-with +# pylint: disable=global-statement # udev test # @@ -26,9 +27,10 @@ import re import stat import subprocess import sys +import tempfile import textwrap from pathlib import Path -from typing import Optional +from typing import Callable, Optional try: import pytest @@ -37,16 +39,15 @@ except ImportError as e: sys.exit(77) -# TODO: pass path to test-udev from outside -UDEV_BIN = './test-udev' -UDEV_RUN = Path('test/run') -UDEV_RULES_DIR = UDEV_RUN / 'udev/rules.d' -UDEV_RULES = UDEV_RULES_DIR / 'udev-test.rules' +SYS_SCRIPT = Path(__file__).with_name('sys-script.py') +try: + UDEV_BIN = Path(os.environ['UDEV_RULE_RUNNER']) +except KeyError: + UDEV_BIN = Path(__file__).parent / 'manual/udev-rule-runner' +UDEV_BIN = UDEV_BIN.absolute() -UDEV_RUN = Path('test/run') -UDEV_TMPFS = Path('test/tmpfs') -UDEV_DEV = UDEV_TMPFS / 'dev' -UDEV_SYS = UDEV_TMPFS / 'sys' +# Those will be set by the udev_setup() fixture +UDEV_RUN = UDEV_RULES = UDEV_DEV = UDEV_SYS = None # Relax sd-device's sysfs verification, since we want to provide a fake sysfs # here that actually is a tmpfs. @@ -178,14 +179,23 @@ class Rules: desc: str devices: list[Device] rules: str + device_generator: Callable = None repeat: int = 1 delay: Optional[int] = None @classmethod - def new(cls, desc: str, *devices, rules = None, **kwargs): + def new(cls, desc: str, *devices, rules=None, device_generator=None, **kwargs): assert rules.startswith('\n') rules = textwrap.dedent(rules[1:]) if rules else '' - return cls(desc, devices, rules, **kwargs) + + assert bool(devices) ^ bool(device_generator) + + return cls(desc, devices, rules, device_generator=device_generator, **kwargs) + + def generate_devices(self) -> None: + # We can't do this when the class is created, because setup is done later. + if self.device_generator: + self.devices = self.device_generator() def create_rules_file(self) -> None: # create temporary rules @@ -2298,7 +2308,8 @@ SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c \"printf %%s 'foo1 foo2' | grep 'foo1 f Rules.new( 'all_block_devs', - *all_block_devs(lambda name: (["blockdev"], None) if name.endswith('/sda6') else (None, None)), + 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" @@ -2344,15 +2355,27 @@ def udev_setup(): if issue: pytest.skip(issue) - subprocess.run(['umount', UDEV_TMPFS], + global UDEV_RUN, UDEV_RULES, UDEV_DEV, UDEV_SYS + + _tmpdir = tempfile.TemporaryDirectory() + tmpdir = Path(_tmpdir.name) + + 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' + + subprocess.run(['umount', udev_tmpfs], stderr=subprocess.DEVNULL, check=False) - UDEV_TMPFS.mkdir(exist_ok=True, parents=True) + udev_tmpfs.mkdir(exist_ok=True, parents=True) subprocess.check_call(['mount', '-v', '-t', 'tmpfs', '-o', 'rw,mode=0755,nosuid,noexec', - 'tmpfs', UDEV_TMPFS]) + 'tmpfs', udev_tmpfs]) UDEV_DEV.mkdir(exist_ok=True) # setting group and mode of udev_dev ensures the tests work @@ -2367,10 +2390,12 @@ def udev_setup(): os.mknod(sda, 0o600 | stat.S_IFBLK, os.makedev(8, 0)) sda.unlink() - subprocess.check_call(['cp', '-r', 'test/sys/', UDEV_SYS]) + subprocess.check_call([SYS_SCRIPT, UDEV_SYS.parent]) subprocess.check_call(['rm', '-rf', UDEV_RUN]) UDEV_RUN.mkdir(parents=True) + 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', @@ -2379,8 +2404,8 @@ def udev_setup(): yield subprocess.check_call(['rm', '-rf', UDEV_RUN]) - subprocess.check_call(['umount', '-v', UDEV_TMPFS]) - UDEV_TMPFS.rmdir() + subprocess.check_call(['umount', '-v', udev_tmpfs]) + udev_tmpfs.rmdir() @pytest.mark.parametrize("rules", RULES, ids=(rule.desc for rule in RULES)) @@ -2388,6 +2413,7 @@ def test_udev(rules: Rules, udev_setup): assert udev_setup is None rules.create_rules_file() + rules.generate_devices() for _ in range(rules.repeat): fork_and_run_udev('add', rules) From dcbbc7cef526c539da08299aee586902b7fedd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 6 May 2023 11:49:31 +0200 Subject: [PATCH 12/19] test/run-unit-tests, TEST-02: skip tests where the interpeter is not installed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the interpeter is missing, we get an exit code of 127. Let's treat those tests as skipped too. If we could run the test far enough so that it could do the check itself, it would return 77 anyway. $ test/asdf; echo $? exec: Failed to execute process 'test/asdf': The file specified the interpreter '/bin/asdf', which is not an executable command. 127 $ test/asdf; echo $? /usr/bin/env: ‘/bin/asdf’: No such file or directory 127 This should resolve the problem that TEST-02 fails or Debian's 'unit-tests' fail when python3 is not installed. Installing python3 via the mechanism that is used to construct TEST images, i.e. the dracut dependency chasing scheme, would be a lot of work for python with its modules in multiple locations and hundreds of little files. So I think it OK to just skip the test there, and also in other cases where python is not available. --- test/run-unit-tests.py | 3 +++ test/units/testsuite-02.sh | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/run-unit-tests.py b/test/run-unit-tests.py index 2d6709b703..7bb904ddd1 100755 --- a/test/run-unit-tests.py +++ b/test/run-unit-tests.py @@ -52,6 +52,9 @@ for test in tests: elif ex.returncode == 77: print(f'{YELLOW}SKIP: {name}{RESET_ALL}') total.skip += 1 + elif ex.returncode == 127: + print(f'{YELLOW}SKIP: {name} (no interpeter) {RESET_ALL}') + total.skip += 1 else: print(f'{RED}FAIL: {name}{RESET_ALL}') total.fail += 1 diff --git a/test/units/testsuite-02.sh b/test/units/testsuite-02.sh index 61f6d06397..211bd10547 100755 --- a/test/units/testsuite-02.sh +++ b/test/units/testsuite-02.sh @@ -24,7 +24,7 @@ function report_result() { local name="${1##*/}" local ret=$2 - if [[ $ret -ne 0 && $ret != 77 ]]; then + if [[ $ret -ne 0 && $ret != 77 && $ret != 127 ]]; then echo "$name failed with $ret" echo "$name" >>/failed-tests { @@ -32,7 +32,7 @@ function report_result() { cat "/$name.log" echo "--- $name end ---" } >>/failed - elif [[ $ret == 77 ]]; then + elif [[ $ret == 77 || $ret == 127 ]]; then echo "$name skipped" echo "$name" >>/skipped-tests { From e76ff43236b62240b04a43679c5bb5db58a407cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 7 May 2023 11:01:33 +0200 Subject: [PATCH 13/19] tools/check-includes: compat with Python 3.7 I thought that 3.8 is enough. But Centos8 CI chokes on the walrus. --- tools/check-includes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/check-includes.py b/tools/check-includes.py index abca2882f0..afb957a2e1 100755 --- a/tools/check-includes.py +++ b/tools/check-includes.py @@ -14,7 +14,8 @@ def check_file(filename): seen = set() good = True for n, line in enumerate(open(filename)): - if m := re.match(r'^\s*#\s*include\s*[<"](\S*)[>"]', line): + m = re.match(r'^\s*#\s*include\s*[<"](\S*)[>"]', line) + if m: include = m.group(1) if include in seen: try: From 7c0d79131b136cb8dd63f0847844be2d79f6994c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 7 May 2023 11:16:21 +0200 Subject: [PATCH 14/19] README: require python >= 3.7, clean up module descriptions libpython was added in 2cc86f094a8c316f7feb0336df3827a3264b116d, it seems because of python-systemd module that we built. But libpython by itself is not enough for actual python programs, and now we also list python itself, so let's drop libpython from the list. meson requires >= 3.7. We have CI that runs on CentOS8 with Python 3.6, but let's not provide official support for an EOL Python version. Individual distributions can provide backports, but we don't need to mention that in the user-facing docs. According to [1], 3.7 is on life support and 3.6 is EOL. [1] https://devguide.python.org/versions/ --- README | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README b/README index e60ff16ef1..d2d1def101 100644 --- a/README +++ b/README @@ -195,7 +195,6 @@ REQUIREMENTS: libgcrypt (optional) libqrencode (optional) libmicrohttpd (optional) - libpython (optional) libidn2 or libidn (optional) gnutls >= 3.1.4 (optional, >= 3.6.0 is required to support DNS-over-TLS with gnutls) openssl >= 1.1.0 (optional, required to support DNS-over-TLS with openssl) @@ -206,17 +205,17 @@ REQUIREMENTS: gperf docbook-xsl (optional, required for documentation) xsltproc (optional, required for documentation) + python >= 3.7 (required by meson too, >= 3.9 is required for ukify) python-jinja2 - python-pefile + python-pefile (optional, required for ukify) python-lxml (optional, required to build the indices) - python >= 3.5 + pyelftools (optional, required for systemd-boot) meson >= 0.53.2 ninja gcc >= 4.7 awk, sed, grep, and similar tools clang >= 10.0, llvm >= 10.0 (optional, required to build BPF programs from source code in C) - pyelftools (optional, required for systemd-boot) During runtime, you need the following additional dependencies: @@ -270,8 +269,9 @@ REQUIREMENTS: Additional packages are necessary to run some tests: - busybox (used by test/TEST-13-NSPAWN-SMOKE) - nc (used by test/TEST-12-ISSUE-3171) - - python3-pyparsing - - python3-evdev (used by hwdb parsing tests) + - python (test-udev which is installed is in python) + - python-pyparsing + - python-evdev (used by hwdb parsing tests) - strace (used by test/test-functions) - capsh (optional, used by test-execute) From 21ec66420acd85835913dc46c10543400fd3b878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 8 May 2023 11:38:25 +0200 Subject: [PATCH 15/19] test-udev: skip test on python3.6 --- test/test-udev.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-udev.py b/test/test-udev.py index 86937205de..7e9afc4fc8 100755 --- a/test/test-udev.py +++ b/test/test-udev.py @@ -19,7 +19,6 @@ # removes the device node. After creation and removal the result is checked # against the expected value and the result is printed. -import dataclasses import functools import os import pwd, grp @@ -33,6 +32,7 @@ from pathlib import Path from typing import Callable, Optional try: + import dataclasses # requires Python >= 3.7 import pytest except ImportError as e: print(str(e), file=sys.stderr) From b370f050f89595baa238a2eaf9ba558cfcb7e249 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 8 May 2023 12:24:37 +0200 Subject: [PATCH 16/19] test-execute: use bash instead of perl I think this is easier to grok too. --- test/test-execute/exec-specifier-interpolation.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test-execute/exec-specifier-interpolation.service b/test/test-execute/exec-specifier-interpolation.service index 4cb1b06518..2e8882c5ab 100644 --- a/test/test-execute/exec-specifier-interpolation.service +++ b/test/test-execute/exec-specifier-interpolation.service @@ -4,4 +4,4 @@ Description=https://github.com/systemd/systemd/issues/2637 [Service] Type=oneshot -ExecStart=/bin/sh -x -c "! test -x perl || perl -e 'exit(!(qq{%%U} eq qq{\\x25U}))'" +ExecStart=/bin/bash -x -c "[[ %%U == ?U ]]" From e11c9ba7bf4195c1c2bd9e00c98123ee3670ba1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 8 May 2023 12:48:37 +0200 Subject: [PATCH 17/19] tools/oss-fuzz: s/perl/awk/ The pattern is not exactly the same, but I don't think we need to be super-precise here. --- tools/oss-fuzz.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/oss-fuzz.sh b/tools/oss-fuzz.sh index b2a5900b1f..419805de7a 100755 --- a/tools/oss-fuzz.sh +++ b/tools/oss-fuzz.sh @@ -109,12 +109,12 @@ install -Dt "$OUT/src/shared/" \ # Most i386 libraries have to be brought to the runtime environment somehow. Ideally they # should be linked statically but since it isn't possible another way to keep them close # to the fuzz targets is used here. The dependencies are copied to "$OUT/src/shared" and -# then `rpath` is tweaked to make it possible for the linker to find them there. "$OUT/src/shared" +# then 'rpath' is tweaked to make it possible for the linker to find them there. "$OUT/src/shared" # is chosen because the runtime search path of all the fuzz targets already points to it # to load "libsystemd-shared" and "libsystemd-core". Stuff like that should be avoided on # x86_64 because it tends to break coverage reports, fuzz-introspector, CIFuzz and so on. if [[ "$ARCHITECTURE" == i386 ]]; then - for lib_path in $(ldd "$OUT"/src/shared/libsystemd-shared-*.so | perl -lne 'print $1 if m{=>\s+(/lib\S+)}'); do + for lib_path in $(ldd "$OUT"/src/shared/libsystemd-shared-*.so | awk '/=> \/lib/ { print $3 }'); do lib_name=$(basename "$lib_path") cp "$lib_path" "$OUT/src/shared" patchelf --set-rpath \$ORIGIN "$OUT/src/shared/$lib_name" From 711169905e75617eabf3934273aa37dac02c6458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 8 May 2023 13:43:03 +0200 Subject: [PATCH 18/19] test: use sed and grep instead of perl This gets rid of the all-but-one remaining uses of perl. I tested the new code on my machine, so I'm fairly confident that it works as expected. install_iscsi() has one majestic perl invocation, but we can't get rid of it easily: it extends the code of tgt-admin to print some list of files. Obviously this only works because tgt-admin is written in perl, and perl will be installed if tgt-admin is installed. install_iscsi() is used in TEST-64-UDEV-STORAGE conditionally if tgtadm is installed, so this can stay as is. --- test/test-functions | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/test/test-functions b/test/test-functions index cef2a780d4..2d303f4799 100644 --- a/test/test-functions +++ b/test/test-functions @@ -818,19 +818,19 @@ install_valgrind() { exit 1 fi - local valgrind_bins valgrind_libs valgrind_dbg_and_supp + local valgrind_bins valgrind_libs valgrind_supp - readarray -t valgrind_bins < <(strace -e execve valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if /^execve\("([^"]+)"/') + readarray -t valgrind_bins < <(strace -e execve valgrind /bin/true 2>&1 >/dev/null | + sed -r -n 's/execve\("([^"]*)".*/\1/p') image_install "${valgrind_bins[@]}" - readarray -t valgrind_libs < <(LD_DEBUG=files valgrind /bin/true 2>&1 >/dev/null | perl -lne 'print $1 if m{calling init: (/.*vgpreload_.*)}') + readarray -t valgrind_libs < <(LD_DEBUG=files valgrind /bin/true 2>&1 >/dev/null | + sed -r -n 's|.*calling init: (/.*vgpreload_.*)|\1|p') image_install "${valgrind_libs[@]}" - readarray -t valgrind_dbg_and_supp < <( - strace -e open valgrind /bin/true 2>&1 >/dev/null | - perl -lne 'if (my ($fname) = /^open\("([^"]+).*= (?!-)\d+/) { print $fname if $fname =~ /debug|\.supp$/ }' - ) - image_install "${valgrind_dbg_and_supp[@]}" + readarray -t valgrind_supp < <(strace -e open valgrind /bin/true 2>&1 >/dev/null | + sed -r -n 's,open\("([^"]*(/debug[^"]*|\.supp))".*= [0-9].*,\1,p') + image_install "${valgrind_supp[@]}" } create_valgrind_wrapper() { @@ -1455,16 +1455,13 @@ check_asan_reports() { ret=$((ret+1)) fi + # May 08 13:23:31 H testleak[2907148]: SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s). pids="$( - "$JOURNALCTL" -D "$root/var/log/journal" | perl -alne ' - BEGIN { - %services_to_ignore = ( - "dbus-daemon" => undef, - "dbus-broker-launch" => undef, - ); - } - print $2 if /\s(\S*)\[(\d+)\]:\s*SUMMARY:\s+\w+Sanitizer/ && !exists $services_to_ignore{$1}' + "$JOURNALCTL" -D "$root/var/log/journal" --grep 'SUMMARY: .*Sanitizer:' | + grep -v -E 'dbus-daemon|dbus-broker-launch' | + sed -r -n 's/.* .+\[([0-9]+)\]: SUMMARY:.*/\1/p' )" + if [[ -n "$pids" ]]; then ret=$((ret+1)) for pid in $pids; do From 98e2089f1b87406e03c5765bc0abd200e9b8d2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 8 May 2023 13:50:50 +0200 Subject: [PATCH 19/19] mkosi,ci: do not install perl --- .github/workflows/build_test.sh | 1 - .github/workflows/unit_tests.sh | 1 - mkosi.presets/00-base/mkosi.conf.d/10-arch.conf | 1 - mkosi.presets/00-base/mkosi.conf.d/10-centos-fedora.conf | 1 - 4 files changed, 4 deletions(-) diff --git a/.github/workflows/build_test.sh b/.github/workflows/build_test.sh index 40614f9084..35a0586b44 100755 --- a/.github/workflows/build_test.sh +++ b/.github/workflows/build_test.sh @@ -48,7 +48,6 @@ PACKAGES=( mold mount net-tools - perl python3-evdev python3-jinja2 python3-lxml diff --git a/.github/workflows/unit_tests.sh b/.github/workflows/unit_tests.sh index da99f993f1..562bafb614 100755 --- a/.github/workflows/unit_tests.sh +++ b/.github/workflows/unit_tests.sh @@ -19,7 +19,6 @@ ADDITIONAL_DEPS=( libtss2-dev libxkbcommon-dev libzstd-dev - perl python3-libevdev python3-pefile python3-pyelftools diff --git a/mkosi.presets/00-base/mkosi.conf.d/10-arch.conf b/mkosi.presets/00-base/mkosi.conf.d/10-arch.conf index 473d199718..c9ecf35350 100644 --- a/mkosi.presets/00-base/mkosi.conf.d/10-arch.conf +++ b/mkosi.presets/00-base/mkosi.conf.d/10-arch.conf @@ -23,7 +23,6 @@ BuildPackages= glib2 libxslt linux-api-headers - perl python python-jinja python-lxml diff --git a/mkosi.presets/00-base/mkosi.conf.d/10-centos-fedora.conf b/mkosi.presets/00-base/mkosi.conf.d/10-centos-fedora.conf index 090daf60db..ea31e4da74 100644 --- a/mkosi.presets/00-base/mkosi.conf.d/10-centos-fedora.conf +++ b/mkosi.presets/00-base/mkosi.conf.d/10-centos-fedora.conf @@ -29,7 +29,6 @@ BuildPackages= findutils libxslt pam-devel - perl-interpreter pkgconfig(audit) pkgconfig(blkid) pkgconfig(bzip2)