update TODO
[systemd.io.git] / test / test-udev.py
blob68c48fd7907e96335d4cfa043a59008e34e334ac
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: LGPL-2.1-or-later
4 # pylint: disable=redefined-outer-name,no-else-return,multiple-imports
5 # pylint: disable=consider-using-with,global-statement
7 # udev test
9 # Provides automated testing of the udev binary.
10 # The whole test is self contained in this file, except the matching sysfs tree.
11 # Simply extend RULES to add a new test.
13 # Every test is driven by its own temporary config file.
14 # This program prepares the environment, creates the config and calls udev.
16 # udev parses the rules, looks at the provided sysfs and first creates and then
17 # removes the device node. After creation and removal the result is checked
18 # against the expected value and the result is printed.
20 import functools
21 import os
22 import pwd, grp
23 import re
24 import stat
25 import subprocess
26 import sys
27 import tempfile
28 import textwrap
29 from pathlib import Path
30 from typing import Callable, Optional
32 try:
33 import dataclasses # requires Python >= 3.7
34 import pytest
35 except ImportError as e:
36 print(str(e), file=sys.stderr)
37 sys.exit(77)
40 SYS_SCRIPT = Path(__file__).with_name('sys-script.py')
41 try:
42 UDEV_BIN = Path(os.environ['UDEV_RULE_RUNNER'])
43 except KeyError:
44 UDEV_BIN = Path(__file__).parent / 'manual/test-udev-rule-runner'
45 UDEV_BIN = UDEV_BIN.absolute()
47 # Those will be set by the udev_setup() fixture
48 UDEV_RUN = UDEV_RULES = UDEV_DEV = UDEV_SYS = None
50 # Relax sd-device's sysfs verification, since we want to provide a fake sysfs
51 # here that actually is a tmpfs.
52 os.environ['SYSTEMD_DEVICE_VERIFY_SYSFS'] = '0'
54 rules_10k_tags = \
55 '\n'.join(f'KERNEL=="sda", TAG+="test{i + 1}"'
56 for i in range(10_000))
58 rules_10k_tags_continuation = \
59 ',\\\n'.join(('KERNEL=="sda"',
60 *(f'TAG+="test{i + 1}"' for i in range(10_000))))
62 @dataclasses.dataclass
63 class Device:
64 devpath: str
65 devnode: Optional[str] = None
66 exp_links: Optional[list[str]] = None
67 not_exp_links: Optional[list[str]] = None
69 exp_perms: Optional[int] = None
70 exp_major_minor: Optional[str] = None
72 def check_permissions(self, st: os.stat_result) -> None:
73 if self.exp_perms is None:
74 return
76 user, group, mode = self.exp_perms.split(':')
78 if user:
79 try:
80 uid = pwd.getpwnam(user).pw_uid
81 except KeyError:
82 uid = int(user)
83 assert uid == st.st_uid
85 if group:
86 try:
87 gid = grp.getgrnam(group).gr_gid
88 except KeyError:
89 gid = int(group)
90 assert gid == st.st_gid
92 if mode:
93 mode = int(mode, 8)
94 assert stat.S_IMODE(st.st_mode) == mode
96 def check_major_minor(self, st: os.stat_result) -> None:
97 if not self.exp_major_minor:
98 return
99 minor, major = (int(x) for x in self.exp_major_minor.split(':'))
100 assert st.st_rdev == os.makedev(minor, major)
102 def get_devnode(self) -> Path:
103 suffix = self.devnode if self.devnode else self.devpath.split('/')[-1]
104 return UDEV_DEV / suffix
106 def check_link_add(self, link: str, devnode: Path) -> None:
107 link = UDEV_DEV / link
108 tgt = link.parent / link.readlink()
109 assert devnode.samefile(tgt)
111 def check_link_nonexistent(self, link: str, devnode: Path) -> None:
112 link = UDEV_DEV / link
114 try:
115 tgt = link.parent / link.readlink()
116 except FileNotFoundError:
117 return
119 assert not devnode.samefile(tgt)
121 def check_add(self) -> None:
122 print(f'check_add {self.devpath}')
124 devnode = self.get_devnode()
125 st = devnode.lstat()
126 assert stat.S_ISCHR(st.st_mode) or stat.S_ISBLK(st.st_mode)
127 self.check_permissions(st)
128 self.check_major_minor(st)
130 for link in self.exp_links or []:
131 self.check_link_add(link, devnode)
133 for link in self.not_exp_links or []:
134 self.check_link_nonexistent(link, devnode)
136 def check_link_remove(self, link: str) -> None:
137 link = UDEV_DEV / link
138 with pytest.raises(FileNotFoundError):
139 link.readlink()
141 def check_remove(self) -> None:
142 devnode = self.get_devnode()
143 assert not devnode.exists()
145 for link in self.exp_links or []:
146 self.check_link_remove(link)
149 def listify(f):
150 def wrap(*args, **kwargs):
151 return list(f(*args, **kwargs))
152 return functools.update_wrapper(wrap, f)
154 @listify
155 def all_block_devs(exp_func) -> list[Device]:
156 # Create a device list with all block devices under /sys
157 # (except virtual devices and cd-roms)
158 # the optional argument exp_func returns expected and non-expected
159 # symlinks for the device.
161 for p in UDEV_SYS.glob('dev/block/*'):
162 tgt = os.readlink(p)
163 if re.search('/virtual/ | /sr[0-9]*$', tgt, re.VERBOSE):
164 continue
166 assert tgt.startswith('../../')
167 tgt = tgt[5:]
169 exp, not_exp = exp_func(tgt)
170 yield Device(devpath=tgt,
171 exp_links=exp,
172 not_exp_links=not_exp)
175 @dataclasses.dataclass
176 class Rules:
177 desc: str
178 devices: list[Device]
179 rules: str
180 device_generator: Callable = None
181 repeat: int = 1
182 delay: Optional[int] = None
184 @classmethod
185 def new(cls, desc: str, *devices, rules=None, device_generator=None, **kwargs):
186 assert rules.startswith('\n')
187 rules = textwrap.dedent(rules[1:]) if rules else ''
189 assert bool(devices) ^ bool(device_generator)
191 return cls(desc, devices, rules, device_generator=device_generator, **kwargs)
193 def generate_devices(self) -> None:
194 # We can't do this when the class is created, because setup is done later.
195 if self.device_generator:
196 self.devices = self.device_generator()
198 def create_rules_file(self) -> None:
199 # create temporary rules
200 UDEV_RULES.parent.mkdir(exist_ok=True, parents=True)
201 UDEV_RULES.write_text(self.rules)
203 RULES = [
204 Rules.new(
205 'no rules',
206 Device(
207 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
209 Device(
210 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
212 rules = r"""
214 """),
216 Rules.new(
217 'label test of scsi disc',
218 Device(
219 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
220 exp_links = ["boot_disk"],
222 rules = r"""
223 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
224 KERNEL=="ttyACM0", SYMLINK+="modem"
225 """),
227 Rules.new(
228 "label test of scsi disc",
229 Device(
230 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
231 exp_links = ["boot_disk"],
233 rules = r"""
234 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
235 KERNEL=="ttyACM0", SYMLINK+="modem"
236 """),
238 Rules.new(
239 "label test of scsi disc",
240 Device(
241 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
242 exp_links = ["boot_disk"],
244 rules = r"""
245 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
246 KERNEL=="ttyACM0", SYMLINK+="modem"
247 """),
249 Rules.new(
250 "label test of scsi partition",
251 Device(
252 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
253 exp_links = ["boot_disk1"],
255 rules = r"""
256 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
257 """),
259 Rules.new(
260 "label test of pattern match",
261 Device(
262 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
263 exp_links = ["boot_disk1", "boot_disk1-4", "boot_disk1-5"],
264 not_exp_links = ["boot_disk1-1", "boot_disk1-2", "boot_disk1-3", "boot_disk1-6", "boot_disk1-7"],
267 rules = r"""
268 SUBSYSTEMS=="scsi", ATTRS{vendor}=="?ATA", SYMLINK+="boot_disk%n-1"
269 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA?", SYMLINK+="boot_disk%n-2"
270 SUBSYSTEMS=="scsi", ATTRS{vendor}=="A??", SYMLINK+="boot_disk%n"
271 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATAS", SYMLINK+="boot_disk%n-3"
272 SUBSYSTEMS=="scsi", ATTRS{vendor}=="AT?", SYMLINK+="boot_disk%n-4"
273 SUBSYSTEMS=="scsi", ATTRS{vendor}=="??A", SYMLINK+="boot_disk%n-5"
274 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", GOTO="skip-6"
275 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n-6"
276 LABEL="skip-6"
277 SUBSYSTEMS=="scsi", GOTO="skip-7"
278 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n-7"
279 LABEL="skip-7"
280 """),
282 Rules.new(
283 "label test of multiple sysfs files",
284 Device(
285 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
286 exp_links = ["boot_disk1"],
287 not_exp_links = ["boot_diskX1"],
289 rules = r"""
290 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS X ", SYMLINK+="boot_diskX%n"
291 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="boot_disk%n"
292 """),
294 Rules.new(
295 "label test of max sysfs files (skip invalid rule)",
296 Device(
297 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
298 exp_links = ["boot_disk1", "boot_diskXY1"],
299 not_exp_links = ["boot_diskXX1"],
301 rules = r"""
302 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"
303 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"
304 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n"
305 """),
307 Rules.new(
308 "SYMLINK tests",
309 Device(
310 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
311 exp_links = ["link1", "link2/foo", "link3/aaa/bbb",
312 "abs1", "abs2/foo", "abs3/aaa/bbb",
313 "default___replace_test/foo_aaa",
314 "string_escape___replace/foo_bbb",
315 "env_with_space",
316 "default/replace/mode_foo__hoge",
317 "replace_env_harder_foo__hoge",
318 "match", "unmatch"],
319 not_exp_links = ["removed1", "removed2", "removed3", "unsafe/../../path", "/nondev/path/will/be/refused"],
321 rules = r"""
322 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="removed1"
323 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed1"
324 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/./dev///removed2"
325 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="removed2"
326 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="././removed3"
327 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK-="/dev//./removed3/./"
328 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="unsafe/../../path"
329 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/nondev/path/will/be/refused"
330 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="link1 .///link2/././/foo//./ .///link3/aaa/bbb"
331 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="/dev/abs1 /dev//./abs2///foo/./ ////dev/abs3/aaa/bbb"
332 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="default?;;replace%%test/foo'aaa"
333 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", SYMLINK+="string_escape replace/foo%%bbb"
334 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ENV{.HOGE}="env with space", SYMLINK+="%E{.HOGE}"
335 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ENV{.HOGE}="default/replace/mode?foo;;hoge", SYMLINK+="%E{.HOGE}"
336 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", OPTIONS="string_escape=replace", ENV{.HOGE}="replace/env/harder?foo;;hoge", SYMLINK+="%E{.HOGE}"
337 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK=="link1", SYMLINK+="match"
338 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK!="removed1", SYMLINK+="unmatch"
339 """),
341 Rules.new(
342 "catch device by *",
343 Device(
344 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
345 exp_links = ["modem/0", "catch-all"],
347 rules = r"""
348 KERNEL=="ttyACM*", SYMLINK+="modem/%n"
349 KERNEL=="*", SYMLINK+="catch-all"
350 """),
352 Rules.new(
353 "catch device by * - take 2",
354 Device(
355 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
356 exp_links = ["modem/0"],
357 not_exp_links = ["bad"],
359 rules = r"""
360 KERNEL=="*ACM1", SYMLINK+="bad"
361 KERNEL=="*ACM0", SYMLINK+="modem/%n"
362 """),
364 Rules.new(
365 "catch device by ?",
366 Device(
367 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
368 exp_links = ["modem/0"],
369 not_exp_links = ["modem/0-1", "modem/0-2"],
371 rules = r"""
372 KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1"
373 KERNEL=="ttyACM??", SYMLINK+="modem/%n-2"
374 KERNEL=="ttyACM?", SYMLINK+="modem/%n"
375 """),
377 Rules.new(
378 "catch device by character class",
379 Device(
380 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
381 exp_links = ["modem/0"],
382 not_exp_links = ["modem/0-1", "modem/0-2"],
384 rules = r"""
385 KERNEL=="ttyACM[A-Z]*", SYMLINK+="modem/%n-1"
386 KERNEL=="ttyACM?[0-9]", SYMLINK+="modem/%n-2"
387 KERNEL=="ttyACM[0-9]*", SYMLINK+="modem/%n"
388 """),
390 Rules.new(
391 "don't replace kernel name",
392 Device(
393 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
394 exp_links = ["modem"],
396 rules = r"""
397 KERNEL=="ttyACM0", SYMLINK+="modem"
398 """),
400 Rules.new(
401 "comment lines in config file (and don't replace kernel name)",
402 Device(
403 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
404 exp_links = ["modem"],
406 rules = r"""
407 # this is a comment
408 KERNEL=="ttyACM0", SYMLINK+="modem"
410 """),
412 Rules.new(
413 "comment lines in config file with whitespace (and don't replace kernel name)",
414 Device(
415 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
416 exp_links = ["modem"],
418 rules = r"""
419 # this is a comment with whitespace before the comment
420 KERNEL=="ttyACM0", SYMLINK+="modem"
422 """),
424 Rules.new(
425 "whitespace only lines (and don't replace kernel name)",
426 Device(
427 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
428 exp_links = ["whitespace"],
430 rules = r"""
434 # this is a comment with whitespace before the comment
435 KERNEL=="ttyACM0", SYMLINK+="whitespace"
439 """),
441 Rules.new(
442 "empty lines in config file (and don't replace kernel name)",
443 Device(
444 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
445 exp_links = ["modem"],
447 rules = r"""
449 KERNEL=="ttyACM0", SYMLINK+="modem"
451 """),
453 Rules.new(
454 "backslashed multi lines in config file (and don't replace kernel name)",
455 Device(
456 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
457 exp_links = ["modem"],
459 rules = r"""
460 KERNEL=="ttyACM0", \
461 SYMLINK+="modem"
463 """),
465 Rules.new(
466 "preserve backslashes, if they are not for a newline",
467 Device(
468 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
469 exp_links = ["aaa"],
471 rules = r"""
472 KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \101", RESULT=="A", SYMLINK+="aaa"
473 """),
475 Rules.new(
476 "stupid backslashed multi lines in config file (and don't replace kernel name)",
477 Device(
478 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
479 exp_links = ["modem"],
481 rules = r"""
490 KERNEL=="ttyACM0", \
491 SYMLINK+="modem"
493 """),
495 Rules.new(
496 "subdirectory handling",
497 Device(
498 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
499 exp_links = ["sub/direct/ory/modem"],
501 rules = r"""
502 KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem"
503 """),
505 Rules.new(
506 "parent device name match of scsi partition",
507 Device(
508 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
509 exp_links = ["first_disk5"],
511 rules = r"""
512 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n"
513 """),
515 Rules.new(
516 "test substitution chars",
517 Device(
518 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
519 exp_links = ["Major:8:minor:5:kernelnumber:5:id:0:0:0:0"],
521 rules = r"""
522 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b"
523 """),
525 Rules.new(
526 "import of shell-value returned from program",
527 Device(
528 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
529 exp_links = ["node12345678"],
531 rules = r"""
532 SUBSYSTEMS=="scsi", IMPORT{program}="/bin/echo -e ' TEST_KEY=12345678\n TEST_key2=98765'", SYMLINK+="node$env{TEST_KEY}"
533 KERNEL=="ttyACM0", SYMLINK+="modem"
534 """),
536 Rules.new(
537 "substitution of sysfs value (%s{file})",
538 Device(
539 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
540 exp_links = ["disk-ATA-sda"],
541 not_exp_links = ["modem"],
543 rules = r"""
544 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k"
545 KERNEL=="ttyACM0", SYMLINK+="modem"
546 """),
548 Rules.new(
549 "program result substitution",
550 Device(
551 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
552 exp_links = ["special-device-5"],
553 not_exp_links = ["not"],
555 rules = r"""
556 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="-special-*", SYMLINK+="not"
557 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="special-*", SYMLINK+="%c-%n"
558 """),
560 Rules.new(
561 "program result substitution (newline removal)",
562 Device(
563 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
564 exp_links = ["newline_removed"],
566 rules = r"""
567 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed"
568 """),
570 Rules.new(
571 "program result substitution",
572 Device(
573 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
574 exp_links = ["test-0:0:0:0"],
576 rules = r"""
577 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c"
578 """),
580 Rules.new(
581 "program with lots of arguments",
582 Device(
583 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
584 exp_links = ["foo9"],
585 not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo7", "foo8"],
587 rules = r"""
588 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}"
589 """),
591 Rules.new(
592 "program with subshell",
593 Device(
594 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
595 exp_links = ["bar9"],
596 not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo7", "foo8"],
598 rules = r"""
599 SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed s/foo9/bar9/'", KERNEL=="sda5", SYMLINK+="%c{7}"
600 """),
602 Rules.new(
603 "program arguments combined with apostrophes",
604 Device(
605 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
606 exp_links = ["foo7"],
607 not_exp_links = ["foo3", "foo4", "foo5", "foo6", "foo8"],
609 rules = r"""
610 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4' 'foo5 foo6 foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}"
611 """),
613 Rules.new(
614 "program arguments combined with escaped double quotes, part 1",
615 Device(
616 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
617 exp_links = ["foo2"],
618 not_exp_links = ["foo1"],
620 rules = r"""
621 SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf %%s \"foo1 foo2\" | grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}"
622 """),
624 Rules.new(
625 "program arguments combined with escaped double quotes, part 2",
626 Device(
627 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
628 exp_links = ["foo2"],
629 not_exp_links = ["foo1"],
631 rules = r"""
632 SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c \"printf %%s 'foo1 foo2' | grep 'foo1 foo2'\"", KERNEL=="sda5", SYMLINK+="%c{2}"
633 """),
635 Rules.new(
636 "program arguments combined with escaped double quotes, part 3",
637 Device(
638 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
639 exp_links = ["foo2"],
640 not_exp_links = ["foo1", "foo3"],
642 rules = r"""
643 SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf \"%%s %%s\" \"foo1 foo2\" \"foo3\"| grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}"
644 """),
646 Rules.new(
647 "characters before the %c{N} substitution",
648 Device(
649 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
650 exp_links = ["my-foo9"],
652 rules = r"""
653 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}"
654 """),
656 Rules.new(
657 "substitute the second to last argument",
658 Device(
659 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
660 exp_links = ["my-foo8"],
661 not_exp_links = ["my-foo3", "my-foo4", "my-foo5", "my-foo6", "my-foo7", "my-foo9"],
663 rules = r"""
664 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}"
665 """),
667 Rules.new(
668 "test substitution by variable name",
669 Device(
670 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
671 exp_links = ["Major:8-minor:5-kernelnumber:5-id:0:0:0:0"],
673 rules = r"""
674 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:$major-minor:$minor-kernelnumber:$number-id:$id"
675 """),
677 Rules.new(
678 "test substitution by variable name 2",
679 Device(
680 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
681 exp_links = ["Major:8-minor:5-kernelnumber:5-id:0:0:0:0"],
683 rules = r"""
684 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="Major:$major-minor:%m-kernelnumber:$number-id:$id"
685 """),
687 Rules.new(
688 "test substitution by variable name 3",
689 Device(
690 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
691 exp_links = ["850:0:0:05"],
693 rules = r"""
694 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n"
695 """),
697 Rules.new(
698 "test substitution by variable name 4",
699 Device(
700 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
701 exp_links = ["855"],
703 rules = r"""
704 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major$minor$number"
705 """),
707 Rules.new(
708 "test substitution by variable name 5",
709 Device(
710 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
711 exp_links = ["8550:0:0:0"],
713 rules = r"""
714 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major%m%n$id"
715 """),
717 Rules.new(
718 "non matching SUBSYSTEMS for device with no parent",
719 Device(
720 "/devices/virtual/tty/console",
721 exp_links = ["TTY"],
722 not_exp_links = ["foo"],
724 rules = r"""
725 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo"
726 KERNEL=="console", SYMLINK+="TTY"
727 """),
729 Rules.new(
730 "non matching SUBSYSTEMS",
731 Device(
732 "/devices/virtual/tty/console",
733 exp_links = ["TTY"],
734 not_exp_links = ["foo"],
736 rules = r"""
737 SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo"
738 KERNEL=="console", SYMLINK+="TTY"
739 """),
741 Rules.new(
742 "ATTRS match",
743 Device(
744 "/devices/virtual/tty/console",
745 exp_links = ["foo", "TTY"],
747 rules = r"""
748 KERNEL=="console", SYMLINK+="TTY"
749 ATTRS{dev}=="5:1", SYMLINK+="foo"
750 """),
752 Rules.new(
753 "ATTR (empty file)",
754 Device(
755 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
756 exp_links = ["empty", "not-something"],
757 not_exp_links = ["something", "not-empty"],
759 rules = r"""
760 KERNEL=="sda", ATTR{test_empty_file}=="?*", SYMLINK+="something"
761 KERNEL=="sda", ATTR{test_empty_file}!="", SYMLINK+="not-empty"
762 KERNEL=="sda", ATTR{test_empty_file}=="", SYMLINK+="empty"
763 KERNEL=="sda", ATTR{test_empty_file}!="?*", SYMLINK+="not-something"
764 """),
766 Rules.new(
767 "ATTR (non-existent file)",
768 Device(
769 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
770 exp_links = ["non-existent", "wrong"],
771 not_exp_links = ["something", "empty", "not-empty",
772 "not-something", "something"],
774 rules = r"""
775 KERNEL=="sda", ATTR{nofile}=="?*", SYMLINK+="something"
776 KERNEL=="sda", ATTR{nofile}!="", SYMLINK+="not-empty"
777 KERNEL=="sda", ATTR{nofile}=="", SYMLINK+="empty"
778 KERNEL=="sda", ATTR{nofile}!="?*", SYMLINK+="not-something"
779 KERNEL=="sda", TEST!="nofile", SYMLINK+="non-existent"
780 KERNEL=="sda", SYMLINK+="wrong"
781 """),
783 Rules.new(
784 "program and bus type match",
785 Device(
786 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
787 exp_links = ["scsi-0:0:0:0"],
789 rules = r"""
790 SUBSYSTEMS=="usb", PROGRAM=="/bin/echo -n usb-%b", SYMLINK+="%c"
791 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n scsi-%b", SYMLINK+="%c"
792 SUBSYSTEMS=="foo", PROGRAM=="/bin/echo -n foo-%b", SYMLINK+="%c"
793 """),
795 Rules.new(
796 "sysfs parent hierarchy",
797 Device(
798 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
799 exp_links = ["modem"],
801 rules = r"""
802 ATTRS{idProduct}=="007b", SYMLINK+="modem"
803 """),
805 Rules.new(
806 "name test with ! in the name",
807 Device(
808 "/devices/virtual/block/fake!blockdev0",
809 devnode = "fake/blockdev0",
810 exp_links = ["is/a/fake/blockdev0"],
811 not_exp_links = ["is/not/a/fake/blockdev0", "modem"],
813 rules = r"""
814 SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k"
815 SUBSYSTEM=="block", SYMLINK+="is/a/%k"
816 KERNEL=="ttyACM0", SYMLINK+="modem"
817 """),
819 Rules.new(
820 "name test with ! in the name, but no matching rule",
821 Device(
822 "/devices/virtual/block/fake!blockdev0",
823 devnode = "fake/blockdev0",
824 not_exp_links = ["modem"],
826 rules = r"""
827 KERNEL=="ttyACM0", SYMLINK+="modem"
828 """),
830 Rules.new(
831 "KERNELS rule",
832 Device(
833 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
834 exp_links = ["scsi-0:0:0:0"],
835 not_exp_links = ["no-match", "short-id", "not-scsi"],
837 rules = r"""
838 SUBSYSTEMS=="usb", KERNELS=="0:0:0:0", SYMLINK+="not-scsi"
839 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:1", SYMLINK+="no-match"
840 SUBSYSTEMS=="scsi", KERNELS==":0", SYMLINK+="short-id"
841 SUBSYSTEMS=="scsi", KERNELS=="/0:0:0:0", SYMLINK+="no-match"
842 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="scsi-0:0:0:0"
843 """),
845 Rules.new(
846 "KERNELS wildcard all",
847 Device(
848 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
849 exp_links = ["scsi-0:0:0:0"],
850 not_exp_links = ["no-match", "before"],
852 rules = r"""
853 SUBSYSTEMS=="scsi", KERNELS=="*:1", SYMLINK+="no-match"
854 SUBSYSTEMS=="scsi", KERNELS=="*:0:1", SYMLINK+="no-match"
855 SUBSYSTEMS=="scsi", KERNELS=="*:0:0:1", SYMLINK+="no-match"
856 SUBSYSTEMS=="scsi", KERNEL=="0:0:0:0", SYMLINK+="before"
857 SUBSYSTEMS=="scsi", KERNELS=="*", SYMLINK+="scsi-0:0:0:0"
858 """),
860 Rules.new(
861 "KERNELS wildcard partial",
862 Device(
863 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
864 exp_links = ["scsi-0:0:0:0", "before"],
866 rules = r"""
867 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before"
868 SUBSYSTEMS=="scsi", KERNELS=="*:0", SYMLINK+="scsi-0:0:0:0"
869 """),
871 Rules.new(
872 "KERNELS wildcard partial 2",
873 Device(
874 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
875 exp_links = ["scsi-0:0:0:0", "before"],
877 rules = r"""
878 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before"
879 SUBSYSTEMS=="scsi", KERNELS=="*:0:0:0", SYMLINK+="scsi-0:0:0:0"
880 """),
882 Rules.new(
883 "substitute attr with link target value (first match)",
884 Device(
885 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
886 exp_links = ["driver-is-sd"],
888 rules = r"""
889 SUBSYSTEMS=="scsi", SYMLINK+="driver-is-$attr{driver}"
890 """),
892 Rules.new(
893 "substitute attr with link target value (currently selected device)",
894 Device(
895 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
896 exp_links = ["driver-is-ahci"],
898 rules = r"""
899 SUBSYSTEMS=="pci", SYMLINK+="driver-is-$attr{driver}"
900 """),
902 Rules.new(
903 "ignore ATTRS attribute whitespace",
904 Device(
905 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
906 exp_links = ["ignored"],
908 rules = r"""
909 SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE", SYMLINK+="ignored"
910 """),
912 Rules.new(
913 "do not ignore ATTRS attribute whitespace",
914 Device(
915 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
916 exp_links = ["matched-with-space"],
917 not_exp_links = ["wrong-to-ignore"],
919 rules = r"""
920 SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="wrong-to-ignore"
921 SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="matched-with-space"
922 """),
924 Rules.new(
925 "permissions USER=bad GROUP=name",
926 Device(
927 "/devices/virtual/tty/tty33",
928 exp_perms = "0:0:0600",
930 rules = r"""
931 KERNEL=="tty33", OWNER="bad", GROUP="name"
932 """),
934 Rules.new(
935 "permissions OWNER=1",
936 Device(
937 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
938 exp_links = ["node"],
939 exp_perms = "1::0600",
941 rules = r"""
942 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1"
943 """),
945 Rules.new(
946 "permissions GROUP=1",
947 Device(
948 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
949 exp_links = ["node"],
950 exp_perms = ":1:0660",
952 rules = r"""
953 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="1"
954 """),
956 Rules.new(
957 "textual user id",
958 Device(
959 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
960 exp_links = ["node"],
961 exp_perms = "daemon::0600",
963 rules = r"""
964 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="daemon"
965 """),
967 Rules.new(
968 "textual group id",
969 Device(
970 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
971 exp_links = ["node"],
972 exp_perms = ":daemon:0660",
974 rules = r"""
975 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="daemon"
976 """),
978 Rules.new(
979 "textual user/group id",
980 Device(
981 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
982 exp_links = ["node"],
983 exp_perms = "root:audio:0660",
985 rules = r"""
986 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="audio"
987 """),
989 Rules.new(
990 "permissions MODE=0777",
991 Device(
992 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
993 exp_links = ["node"],
994 exp_perms = "::0777",
996 rules = r"""
997 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", MODE="0777"
998 """),
1000 Rules.new(
1001 "permissions OWNER=1 GROUP=1 MODE=0777",
1002 Device(
1003 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1004 exp_links = ["node"],
1005 exp_perms = "1:1:0777",
1007 rules = r"""
1008 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1", GROUP="1", MODE="0777"
1009 """),
1011 Rules.new(
1012 "permissions OWNER to 1",
1013 Device(
1014 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1015 exp_perms = "1::",
1017 rules = r"""
1018 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1"
1019 """),
1021 Rules.new(
1022 "permissions GROUP to 1",
1023 Device(
1024 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1025 exp_perms = ":1:0660",
1027 rules = r"""
1028 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="1"
1029 """),
1031 Rules.new(
1032 "permissions MODE to 0060",
1033 Device(
1034 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1035 exp_perms = "::0060",
1037 rules = r"""
1038 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", MODE="0060"
1039 """),
1041 Rules.new(
1042 "permissions OWNER, GROUP, MODE",
1043 Device(
1044 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1045 exp_perms = "1:1:0777",
1047 rules = r"""
1048 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1", GROUP="1", MODE="0777"
1049 """),
1051 Rules.new(
1052 "permissions only rule",
1053 Device(
1054 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1055 exp_perms = "1:1:0777",
1057 rules = r"""
1058 KERNEL=="ttyACM[0-9]*", OWNER="1", GROUP="1", MODE="0777"
1059 KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444"
1060 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n"
1061 """),
1063 Rules.new(
1064 "multiple permissions only rule",
1065 Device(
1066 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1067 exp_perms = "1:1:0777",
1069 rules = r"""
1070 SUBSYSTEM=="tty", OWNER="1"
1071 SUBSYSTEM=="tty", GROUP="1"
1072 SUBSYSTEM=="tty", MODE="0777"
1073 KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444"
1074 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n"
1075 """),
1077 Rules.new(
1078 "permissions only rule with override at SYMLINK+ rule",
1079 Device(
1080 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1081 exp_perms = "1:2:0777",
1083 rules = r"""
1084 SUBSYSTEM=="tty", OWNER="1"
1085 SUBSYSTEM=="tty", GROUP="1"
1086 SUBSYSTEM=="tty", MODE="0777"
1087 KERNEL=="ttyUSX[0-9]*", OWNER="2", GROUP="2", MODE="0444"
1088 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="2"
1089 """),
1091 Rules.new(
1092 "major/minor number test",
1093 Device(
1094 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1095 exp_links = ["node"],
1096 exp_major_minor = "8:0",
1098 rules = r"""
1099 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node"
1100 """),
1102 Rules.new(
1103 "big major number test",
1104 Device(
1105 "/devices/virtual/misc/misc-fake1",
1106 exp_links = ["node"],
1107 exp_major_minor = "4095:1",
1109 rules = r"""
1110 KERNEL=="misc-fake1", SYMLINK+="node"
1111 """),
1113 Rules.new(
1114 "big major and big minor number test",
1115 Device(
1116 "/devices/virtual/misc/misc-fake89999",
1117 exp_links = ["node"],
1118 exp_major_minor = "4095:89999",
1120 rules = r"""
1121 KERNEL=="misc-fake89999", SYMLINK+="node"
1122 """),
1124 Rules.new(
1125 "multiple symlinks with format char",
1126 Device(
1127 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1128 exp_links = ["symlink1-0", "symlink2-ttyACM0", "symlink3-"],
1130 rules = r"""
1131 KERNEL=="ttyACM[0-9]*", SYMLINK="symlink1-%n symlink2-%k symlink3-%b"
1132 """),
1134 Rules.new(
1135 "multiple symlinks with a lot of s p a c e s",
1136 Device(
1137 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1138 exp_links = ["one", "two"],
1139 not_exp_links = [" "],
1141 rules = r"""
1142 KERNEL=="ttyACM[0-9]*", SYMLINK=" one two "
1143 """),
1145 Rules.new(
1146 "symlink with spaces in substituted variable",
1147 Device(
1148 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1149 exp_links = ["name-one_two_three-end"],
1150 not_exp_links = [" "],
1152 rules = r"""
1153 ENV{WITH_WS}="one two three"
1154 SYMLINK="name-$env{WITH_WS}-end"
1155 """),
1157 Rules.new(
1158 "symlink with leading space in substituted variable",
1159 Device(
1160 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1161 exp_links = ["name-one_two_three-end"],
1162 not_exp_links = [" "],
1164 rules = r"""
1165 ENV{WITH_WS}=" one two three"
1166 SYMLINK="name-$env{WITH_WS}-end"
1167 """),
1169 Rules.new(
1170 "symlink with trailing space in substituted variable",
1171 Device(
1172 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1173 exp_links = ["name-one_two_three-end"],
1174 not_exp_links = [" "],
1176 rules = r"""
1177 ENV{WITH_WS}="one two three "
1178 SYMLINK="name-$env{WITH_WS}-end"
1179 """),
1181 Rules.new(
1182 "symlink with lots of space in substituted variable",
1183 Device(
1184 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1185 exp_links = ["name-one_two_three-end"],
1186 not_exp_links = [" "],
1188 rules = r"""
1189 ENV{WITH_WS}=" one two three "
1190 SYMLINK="name-$env{WITH_WS}-end"
1191 """),
1193 Rules.new(
1194 "symlink with multiple spaces in substituted variable",
1195 Device(
1196 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1197 exp_links = ["name-one_two_three-end"],
1198 not_exp_links = [" "],
1200 rules = r"""
1201 ENV{WITH_WS}=" one two three "
1202 SYMLINK="name-$env{WITH_WS}-end"
1203 """),
1205 Rules.new(
1206 "symlink with space and var with space",
1207 Device(
1208 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1209 exp_links = ["first", "name-one_two_three-end",
1210 "another_symlink", "a", "b", "c"],
1211 not_exp_links = [" "],
1213 rules = r"""
1214 ENV{WITH_WS}=" one two three "
1215 SYMLINK=" first name-$env{WITH_WS}-end another_symlink a b c "
1216 """),
1218 Rules.new(
1219 "symlink with env which contain slash (see #19309)",
1220 Device(
1221 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1222 exp_links = ["first", "name-aaa_bbb_ccc-end",
1223 "another_symlink", "a", "b", "c"],
1224 not_exp_links = ["ame-aaa/bbb/ccc-end"],
1226 rules = r"""
1227 ENV{WITH_SLASH}="aaa/bbb/ccc"
1228 OPTIONS="string_escape=replace", ENV{REPLACED}="$env{WITH_SLASH}"
1229 SYMLINK=" first name-$env{REPLACED}-end another_symlink a b c "
1230 """),
1232 Rules.new(
1233 "symlink creation (same directory)",
1234 Device(
1235 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1236 exp_links = ["modem0"],
1238 rules = r"""
1239 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n"
1240 """),
1242 Rules.new(
1243 "multiple symlinks",
1244 Device(
1245 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1246 exp_links = ["first-0", "second-0", "third-0"],
1248 rules = r"""
1249 KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n"
1250 """),
1252 Rules.new(
1253 "symlink name '.'",
1254 Device(
1255 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1257 # we get a warning, but the process does not fail
1258 rules = r"""
1259 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="."
1260 """),
1262 Rules.new(
1263 "symlink node to itself",
1264 Device(
1265 "/devices/virtual/tty/tty0",
1267 # we get a warning, but the process does not fail
1268 rules = r"""
1269 KERNEL=="tty0", SYMLINK+="tty0"
1270 """),
1272 Rules.new(
1273 "symlink %n substitution",
1274 Device(
1275 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1276 exp_links = ["symlink0"],
1278 rules = r"""
1279 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n"
1280 """),
1282 Rules.new(
1283 "symlink %k substitution",
1284 Device(
1285 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1286 exp_links = ["symlink-ttyACM0"],
1288 rules = r"""
1289 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k"
1290 """),
1292 Rules.new(
1293 "symlink %M:%m substitution",
1294 Device(
1295 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1296 exp_links = ["major-166:0"],
1298 rules = r"""
1299 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m"
1300 """),
1302 Rules.new(
1303 "symlink %b substitution",
1304 Device(
1305 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1306 exp_links = ["symlink-0:0:0:0"],
1308 rules = r"""
1309 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b"
1310 """),
1312 Rules.new(
1313 "symlink %c substitution",
1314 Device(
1315 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1316 exp_links = ["test"],
1318 rules = r"""
1319 KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c"
1320 """),
1322 Rules.new(
1323 "symlink %c{N} substitution",
1324 Device(
1325 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1326 exp_links = ["test"],
1327 not_exp_links = ["symlink", "this"],
1329 rules = r"""
1330 KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}"
1331 """),
1333 Rules.new(
1334 "symlink %c{N+} substitution",
1335 Device(
1336 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1337 exp_links = ["test", "this"],
1338 not_exp_links = ["symlink"],
1340 rules = r"""
1341 KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}"
1342 """),
1344 Rules.new(
1345 "symlink only rule with %c{N+}",
1346 Device(
1347 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1348 exp_links = ["test", "this"],
1349 not_exp_links = ["symlink"],
1351 rules = r"""
1352 SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}"
1353 """),
1355 Rules.new(
1356 "symlink %s{filename} substitution",
1357 Device(
1358 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1359 exp_links = ["166:0"],
1361 rules = r"""
1362 KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}"
1363 """),
1365 Rules.new(
1366 "program result substitution (numbered part of)",
1367 Device(
1368 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
1369 exp_links = ["link1", "link2"],
1370 not_exp_links = ["node"],
1372 rules = r"""
1373 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2", RESULT=="node *", SYMLINK+="%c{2} %c{3}"
1374 """),
1376 Rules.new(
1377 "program result substitution (numbered part of+)",
1378 Device(
1379 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
1380 exp_links = ["link1", "link2", "link3", "link4"],
1381 not_exp_links = ["node"],
1383 rules = r"""
1384 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}"
1385 """),
1387 Rules.new(
1388 "SUBSYSTEM match test",
1389 Device(
1390 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1391 exp_links = ["node"],
1392 not_exp_links = ["should_not_match", "should_not_match2"],
1394 rules = r"""
1395 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", SUBSYSTEM=="vc"
1396 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", SUBSYSTEM=="block"
1397 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match2", SUBSYSTEM=="vc"
1398 """),
1400 Rules.new(
1401 "DRIVERS match test",
1402 Device(
1403 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1404 exp_links = ["node"],
1405 not_exp_links = ["should_not_match"]
1407 rules = r"""
1408 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", DRIVERS=="sd-wrong"
1409 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", DRIVERS=="sd"
1410 """),
1412 Rules.new(
1413 "devnode substitution test",
1414 Device(
1415 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1416 exp_links = ["node"],
1418 rules = r"""
1419 SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node"
1420 """),
1422 Rules.new(
1423 "parent node name substitution test",
1424 Device(
1425 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1426 exp_links = ["sda-part-1"],
1428 rules = r"""
1429 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-%n"
1430 """),
1432 Rules.new(
1433 "udev_root substitution",
1434 Device(
1435 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1436 exp_links = ["start-/dev-end"],
1438 rules = r"""
1439 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end"
1440 """),
1442 Rules.new(
1443 # This is not supported any more
1444 "last_rule option",
1445 Device(
1446 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1447 exp_links = ["last", "very-last"],
1449 rules = r"""
1450 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule"
1451 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="very-last"
1452 """),
1454 Rules.new(
1455 "negation KERNEL!=",
1456 Device(
1457 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1458 exp_links = ["match", "before"],
1459 not_exp_links = ["matches-but-is-negated"],
1461 rules = r"""
1462 SUBSYSTEMS=="scsi", KERNEL!="sda1", SYMLINK+="matches-but-is-negated"
1463 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
1464 SUBSYSTEMS=="scsi", KERNEL!="xsda1", SYMLINK+="match"
1465 """),
1467 Rules.new(
1468 "negation SUBSYSTEM!=",
1469 Device(
1470 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1471 exp_links = ["before", "not-anything"],
1472 not_exp_links = ["matches-but-is-negated"],
1474 rules = r"""
1475 SUBSYSTEMS=="scsi", SUBSYSTEM=="block", KERNEL!="sda1", SYMLINK+="matches-but-is-negated"
1476 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
1477 SUBSYSTEMS=="scsi", SUBSYSTEM!="anything", SYMLINK+="not-anything"
1478 """),
1480 Rules.new(
1481 "negation PROGRAM!= exit code",
1482 Device(
1483 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1484 exp_links = ["before", "nonzero-program"],
1486 rules = r"""
1487 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
1488 KERNEL=="sda1", PROGRAM!="/bin/false", SYMLINK+="nonzero-program"
1489 """),
1491 Rules.new(
1492 "ENV{} test",
1493 Device(
1494 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1495 exp_links = ["true"],
1496 not_exp_links = ["bad", "wrong"],
1498 rules = r"""
1499 ENV{ENV_KEY_TEST}="test"
1500 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong"
1501 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", SYMLINK+="true"
1502 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad"
1503 """),
1505 Rules.new(
1506 "ENV{} test",
1507 Device(
1508 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1509 exp_links = ["true"],
1510 not_exp_links = ["bad", "wrong", "no"],
1512 rules = r"""
1513 ENV{ENV_KEY_TEST}="test"
1514 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong"
1515 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="yes", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sdax1", SYMLINK+="no"
1516 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sda1", SYMLINK+="true"
1517 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad"
1518 """),
1520 Rules.new(
1521 "ENV{} test (assign)",
1522 Device(
1523 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1524 exp_links = ["true", "before"],
1525 not_exp_links = ["no"],
1527 rules = r"""
1528 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
1529 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no"
1530 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
1531 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="true"
1532 """),
1534 Rules.new(
1535 "ENV{} test (assign 2 times)",
1536 Device(
1537 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1538 exp_links = ["true", "before"],
1539 not_exp_links = ["no", "bad"],
1541 rules = r"""
1542 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true"
1543 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="absolutely-$env{ASSIGN}"
1544 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
1545 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no"
1546 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="bad"
1547 SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="absolutely-true", SYMLINK+="true"
1548 """),
1550 Rules.new(
1551 "ENV{} test (assign2)",
1552 Device(
1553 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1554 exp_links = ["part"],
1555 not_exp_links = ["disk"],
1557 Device(
1558 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1559 exp_links = ["disk"],
1560 not_exp_links = ["part"],
1562 rules = r"""
1563 SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false"
1564 SUBSYSTEM=="block", KERNEL=="*[!0-9]", ENV{PARTITION}="false", ENV{MAINDEVICE}="true"
1565 ENV{MAINDEVICE}=="true", SYMLINK+="disk"
1566 SUBSYSTEM=="block", SYMLINK+="before"
1567 ENV{PARTITION}=="true", SYMLINK+="part"
1568 """),
1570 Rules.new(
1571 "untrusted string sanitize",
1572 Device(
1573 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1574 exp_links = ["sane"],
1576 rules = r"""
1577 SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e name; (/usr/bin/badprogram)", RESULT=="name_ _/usr/bin/badprogram_", SYMLINK+="sane"
1578 """),
1580 Rules.new(
1581 "untrusted string sanitize (don't replace utf8)",
1582 Device(
1583 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1584 exp_links = ["uber"],
1586 rules = r"""
1587 SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xc3\xbcber" RESULT=="über", SYMLINK+="uber"
1588 """),
1590 Rules.new(
1591 "untrusted string sanitize (replace invalid utf8)",
1592 Device(
1593 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1594 exp_links = ["replaced"],
1596 rules = r"""
1597 SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xef\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced"
1598 """),
1600 Rules.new(
1601 "read sysfs value from parent device",
1602 Device(
1603 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1604 exp_links = ["serial-354172020305000"],
1606 rules = r"""
1607 KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}"
1608 """),
1610 Rules.new(
1611 "match against empty key string",
1612 Device(
1613 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1614 exp_links = ["ok"],
1615 not_exp_links = ["not-1-ok", "not-2-ok", "not-3-ok"],
1617 rules = r"""
1618 KERNEL=="sda", ATTRS{nothing}!="", SYMLINK+="not-1-ok"
1619 KERNEL=="sda", ATTRS{nothing}=="", SYMLINK+="not-2-ok"
1620 KERNEL=="sda", ATTRS{vendor}!="", SYMLINK+="ok"
1621 KERNEL=="sda", ATTRS{vendor}=="", SYMLINK+="not-3-ok"
1622 """),
1624 Rules.new(
1625 "check ACTION value",
1626 Device(
1627 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1628 exp_links = ["ok"],
1629 not_exp_links = ["unknown-not-ok"],
1631 rules = r"""
1632 ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok"
1633 ACTION=="add", KERNEL=="sda", SYMLINK+="ok"
1634 """),
1636 Rules.new(
1637 "final assignment",
1638 Device(
1639 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1640 exp_links = ["ok"],
1641 exp_perms = "root:tty:0640",
1643 rules = r"""
1644 KERNEL=="sda", GROUP:="tty"
1645 KERNEL=="sda", GROUP="root", MODE="0640", SYMLINK+="ok"
1646 """),
1648 Rules.new(
1649 "final assignment 2",
1650 Device(
1651 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1652 exp_links = ["ok"],
1653 exp_perms = "root:tty:0640",
1655 rules = r"""
1656 KERNEL=="sda", GROUP:="tty"
1657 SUBSYSTEM=="block", MODE:="640"
1658 KERNEL=="sda", GROUP="root", MODE="0666", SYMLINK+="ok"
1659 """),
1661 Rules.new(
1662 "env substitution",
1663 Device(
1664 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1665 exp_links = ["node-add-me"],
1667 rules = r"""
1668 KERNEL=="sda", MODE="0666", SYMLINK+="node-$env{ACTION}-me"
1669 """),
1671 Rules.new(
1672 "reset list to current value",
1673 Device(
1674 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1675 exp_links = ["three"],
1676 not_exp_links = ["two", "one"],
1678 rules = r"""
1679 KERNEL=="ttyACM[0-9]*", SYMLINK+="one"
1680 KERNEL=="ttyACM[0-9]*", SYMLINK+="two"
1681 KERNEL=="ttyACM[0-9]*", SYMLINK="three"
1682 """),
1684 Rules.new(
1685 "test empty SYMLINK+ (empty override)",
1686 Device(
1687 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1688 exp_links = ["right"],
1689 not_exp_links = ["wrong"],
1691 rules = r"""
1692 KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong"
1693 KERNEL=="ttyACM[0-9]*", SYMLINK=""
1694 KERNEL=="ttyACM[0-9]*", SYMLINK+="right"
1695 """),
1697 Rules.new(
1698 "test multi matches",
1699 Device(
1700 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1701 exp_links = ["right", "before"],
1703 rules = r"""
1704 KERNEL=="ttyACM*", SYMLINK+="before"
1705 KERNEL=="ttyACM*|nothing", SYMLINK+="right"
1706 """),
1708 Rules.new(
1709 "test multi matches 2",
1710 Device(
1711 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1712 exp_links = ["right", "before"],
1713 not_exp_links = ["nomatch"],
1715 rules = r"""
1716 KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch"
1717 KERNEL=="ttyACM*", SYMLINK+="before"
1718 KERNEL=="dontknow*|ttyACM*|nothing*", SYMLINK+="right"
1719 """),
1721 Rules.new(
1722 "test multi matches 3",
1723 Device(
1724 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1725 exp_links = ["right"],
1726 not_exp_links = ["nomatch", "wrong1", "wrong2"],
1728 rules = r"""
1729 KERNEL=="dontknow|nothing", SYMLINK+="nomatch"
1730 KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1"
1731 KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2"
1732 KERNEL=="dontknow|ttyACM0|nothing", SYMLINK+="right"
1733 """),
1735 Rules.new(
1736 "test multi matches 4",
1737 Device(
1738 "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0",
1739 exp_links = ["right"],
1740 not_exp_links = ["nomatch", "wrong1", "wrong2", "wrong3"],
1742 rules = r"""
1743 KERNEL=="dontknow|nothing", SYMLINK+="nomatch"
1744 KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1"
1745 KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2"
1746 KERNEL=="all|dontknow|ttyACM0", SYMLINK+="right"
1747 KERNEL=="ttyACM0a|nothing", SYMLINK+="wrong3"
1748 """),
1750 Rules.new(
1751 "test multi matches 5",
1752 Device(
1753 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1754 exp_links = ["found"],
1755 not_exp_links = ["bad"],
1757 rules = r"""
1758 KERNEL=="sda", TAG="foo"
1759 TAGS=="|foo", SYMLINK+="found"
1760 TAGS=="|aaa", SYMLINK+="bad"
1761 """),
1763 Rules.new(
1764 "test multi matches 6",
1765 Device(
1766 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1767 exp_links = ["found"],
1768 not_exp_links = ["bad"],
1770 rules = r"""
1771 KERNEL=="sda", ENV{HOGE}=""
1772 ENV{HOGE}=="|foo", SYMLINK+="found"
1773 ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
1774 """),
1776 Rules.new(
1777 "test multi matches 7",
1778 Device(
1779 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1780 exp_links = ["found"],
1781 not_exp_links = ["bad"],
1783 rules = r"""
1784 KERNEL=="sda", TAG="foo"
1785 TAGS=="foo||bar", SYMLINK+="found"
1786 TAGS=="aaa||bbb", SYMLINK+="bad"
1787 """),
1789 Rules.new(
1790 "test multi matches 8",
1791 Device(
1792 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1793 exp_links = ["found"],
1794 not_exp_links = ["bad"],
1796 rules = r"""
1797 KERNEL=="sda", ENV{HOGE}=""
1798 ENV{HOGE}=="foo||bar", SYMLINK+="found"
1799 ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
1800 """),
1802 Rules.new(
1803 "test multi matches 9",
1804 Device(
1805 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1806 exp_links = ["found", "found2"],
1807 not_exp_links = ["bad"],
1809 rules = r"""
1810 KERNEL=="sda", TAG="foo"
1811 TAGS=="foo|", SYMLINK+="found"
1812 TAGS=="aaa|", SYMLINK+="bad"
1813 KERNEL=="sda", TAGS!="hoge", SYMLINK+="found2"
1814 KERNEL=="sda", TAGS!="foo", SYMLINK+="bad2"
1815 """),
1817 Rules.new(
1818 "test multi matches 10",
1819 Device(
1820 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1821 exp_links = ["found"],
1822 not_exp_links = ["bad"],
1824 rules = r"""
1825 KERNEL=="sda", ENV{HOGE}=""
1826 ENV{HOGE}=="foo|", SYMLINK+="found"
1827 ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
1828 """),
1830 Rules.new(
1831 "test multi matches 11",
1832 Device(
1833 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1834 exp_links = ["found"],
1835 not_exp_links = ["bad"],
1837 rules = r"""
1838 KERNEL=="sda", TAG="c"
1839 TAGS=="foo||bar||c", SYMLINK+="found"
1840 TAGS=="aaa||bbb||ccc", SYMLINK+="bad"
1841 """),
1843 Rules.new(
1844 "TAG refuses invalid string",
1845 Device(
1846 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1847 exp_links = ["valid", "found"],
1848 not_exp_links = ["empty", "invalid_char", "path", "bad", "bad2"],
1850 rules = r"""
1851 KERNEL=="sda", TAG+="", TAG+="invalid.char", TAG+="path/is/also/invalid", TAG+="valid"
1852 TAGS=="", SYMLINK+="empty"
1853 TAGS=="invalid.char", SYMLINK+="invalid_char"
1854 TAGS=="path/is/also/invalid", SYMLINK+="path"
1855 TAGS=="valid", SYMLINK+="valid"
1856 TAGS=="valid|", SYMLINK+="found"
1857 TAGS=="aaa|", SYMLINK+="bad"
1858 TAGS=="aaa|bbb", SYMLINK+="bad2"
1859 """),
1861 Rules.new(
1862 "IMPORT parent test",
1863 Device(
1864 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1865 exp_links = ["parent"],
1867 Device(
1868 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1869 exp_links = ["parentenv-parent_right"],
1871 delay = 500000, # Serialized! We need to sleep here after adding sda
1872 rules = r"""
1873 KERNEL=="sda1", IMPORT{parent}="PARENT*", SYMLINK+="parentenv-$env{PARENT_KEY}$env{WRONG_PARENT_KEY}"
1874 KERNEL=="sda", IMPORT{program}="/bin/echo -e 'PARENT_KEY=parent_right\nWRONG_PARENT_KEY=parent_wrong'"
1875 KERNEL=="sda", SYMLINK+="parent"
1876 """),
1878 Rules.new(
1879 "GOTO test",
1880 Device(
1881 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1882 exp_links = ["right"],
1883 not_exp_links = ["wrong", "wrong2"],
1885 rules = r"""
1886 KERNEL=="sda1", GOTO="TEST"
1887 KERNEL=="sda1", SYMLINK+="wrong"
1888 KERNEL=="sda1", GOTO="BAD"
1889 KERNEL=="sda1", SYMLINK+="", LABEL="NO"
1890 KERNEL=="sda1", SYMLINK+="right", LABEL="TEST", GOTO="end"
1891 KERNEL=="sda1", SYMLINK+="wrong2", LABEL="BAD"
1892 LABEL="end"
1893 """),
1895 Rules.new(
1896 "GOTO label does not exist",
1897 Device(
1898 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1899 exp_links = ["right"],
1901 rules = r"""
1902 KERNEL=="sda1", GOTO="does-not-exist"
1903 KERNEL=="sda1", SYMLINK+="right",
1904 LABEL="exists"
1905 """),
1907 Rules.new(
1908 "SYMLINK+ compare test",
1909 Device(
1910 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1911 exp_links = ["right", "link"],
1912 not_exp_links = ["wrong"],
1914 rules = r"""
1915 KERNEL=="sda1", SYMLINK+="link"
1916 KERNEL=="sda1", SYMLINK=="link*", SYMLINK+="right"
1917 KERNEL=="sda1", SYMLINK=="nolink*", SYMLINK+="wrong"
1918 """),
1920 Rules.new(
1921 "invalid key operation",
1922 Device(
1923 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1924 exp_links = ["yes"],
1925 not_exp_links = ["no"],
1927 rules = r"""
1928 KERNEL="sda1", SYMLINK+="no"
1929 KERNEL=="sda1", SYMLINK+="yes"
1930 """),
1932 Rules.new(
1933 "operator chars in attribute",
1934 Device(
1935 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1936 exp_links = ["yes"],
1938 rules = r"""
1939 KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes"
1940 """),
1942 Rules.new(
1943 "overlong comment line",
1944 Device(
1945 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
1946 exp_links = ["yes"],
1947 not_exp_links = ["no"],
1949 rules = r"""
1950 # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
1951 # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
1952 KERNEL=="sda1", SYMLINK+=="no"
1953 KERNEL=="sda1", SYMLINK+="yes"
1954 """),
1956 Rules.new(
1957 "magic subsys/kernel lookup",
1958 Device(
1959 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1960 exp_links = ["00:16:41:e2:8d:ff"],
1962 rules = r"""
1963 KERNEL=="sda", SYMLINK+="$attr{[net/eth0]address}"
1964 """),
1966 Rules.new(
1967 "TEST absolute path",
1968 Device(
1969 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1970 exp_links = ["there"],
1971 not_exp_links = ["notthere"],
1973 rules = r"""
1974 TEST=="/etc/passwd", SYMLINK+="there"
1975 TEST!="/etc/passwd", SYMLINK+="notthere"
1976 """),
1978 Rules.new(
1979 "TEST subsys/kernel lookup",
1980 Device(
1981 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1982 exp_links = ["yes"],
1984 rules = r"""
1985 KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes"
1986 """),
1988 Rules.new(
1989 "TEST relative path",
1990 Device(
1991 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
1992 exp_links = ["relative"],
1994 rules = r"""
1995 KERNEL=="sda", TEST=="size", SYMLINK+="relative"
1996 """),
1998 Rules.new(
1999 "TEST wildcard substitution (find queue/nr_requests)",
2000 Device(
2001 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2002 exp_links = ["found-subdir"],
2004 rules = r"""
2005 KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir"
2006 """),
2008 Rules.new(
2009 "TEST MODE=0000",
2010 Device(
2011 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2012 exp_perms = "0:0:0000",
2014 rules = r"""
2015 KERNEL=="sda", MODE="0000"
2016 """),
2018 Rules.new(
2019 "TEST PROGRAM feeds OWNER, GROUP, MODE",
2020 Device(
2021 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2022 exp_perms = "1:1:0400",
2024 rules = r"""
2025 KERNEL=="sda", MODE="666"
2026 KERNEL=="sda", PROGRAM=="/bin/echo 1 1 0400", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}"
2027 """),
2029 Rules.new(
2030 "TEST PROGRAM feeds MODE with overflow",
2031 Device(
2032 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2033 exp_perms = "0:0:0440",
2035 rules = r"""
2036 KERNEL=="sda", MODE="440"
2037 KERNEL=="sda", PROGRAM=="/bin/echo 0 0 0400letsdoabuffferoverflow0123456789012345789012345678901234567890", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}"
2038 """),
2040 Rules.new(
2041 "magic [subsys/sysname] attribute substitution",
2042 Device(
2043 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2044 exp_links = ["sda-8741C4G-end"],
2045 exp_perms = "0:0:0600",
2047 rules = r"""
2048 KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end"
2049 """),
2051 Rules.new(
2052 "builtin path_id",
2053 Device(
2054 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2055 exp_links = ["disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0"],
2057 rules = r"""
2058 KERNEL=="sda", IMPORT{builtin}="path_id"
2059 KERNEL=="sda", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}"
2060 """),
2062 Rules.new(
2063 "add and match tag",
2064 Device(
2065 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2066 exp_links = ["found"],
2067 not_exp_links = ["bad"],
2069 rules = r"""
2070 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", TAG+="green"
2071 TAGS=="green", SYMLINK+="found"
2072 TAGS=="blue", SYMLINK+="bad"
2073 """),
2075 Rules.new(
2076 "don't crash with lots of tags",
2077 Device(
2078 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2079 exp_links = ["found"],
2081 rules = f"""
2082 {rules_10k_tags}
2083 TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="found"
2084 """),
2086 Rules.new(
2087 "continuations",
2088 Device(
2089 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2090 exp_links = ["found"],
2091 not_exp_links = ["bad"],
2093 rules = f"""
2094 {rules_10k_tags_continuation}
2095 TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="bad"
2096 KERNEL=="sda",\\
2097 # comment in continuation
2098 TAG+="hoge1",\\
2099 # space before comment
2100 TAG+="hoge2",\\
2101 # spaces before and after token are dropped
2102 TAG+="hoge3", \\
2105 TAG+="hoge4"
2106 TAGS=="hoge1", TAGS=="hoge2", TAGS=="hoge3", TAGS=="hoge4", SYMLINK+="found"
2107 """),
2109 Rules.new(
2110 "continuations with empty line",
2111 Device(
2112 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2113 exp_links = ["found"],
2114 not_exp_links = ["bad"],
2116 rules = r"""
2117 # empty line finishes continuation
2118 KERNEL=="sda", TAG+="foo" \
2120 KERNEL=="sdb", TAG+="hoge"
2121 KERNEL=="sda", TAG+="aaa" \
2122 KERNEL=="sdb", TAG+="bbb"
2123 TAGS=="foo", SYMLINK+="found"
2124 TAGS=="aaa", SYMLINK+="bad"
2125 """),
2127 Rules.new(
2128 "continuations with space only line",
2129 Device(
2130 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda",
2131 exp_links = ["found"],
2132 not_exp_links = ["bad"],
2134 rules = """
2135 # space only line finishes continuation
2136 KERNEL=="sda", TAG+="foo" \\
2138 KERNEL=="sdb", TAG+="hoge"
2139 KERNEL=="sda", TAG+="aaa" \\
2140 KERNEL=="sdb", TAG+="bbb"
2141 TAGS=="foo", SYMLINK+="found"
2142 TAGS=="aaa", SYMLINK+="bad"
2143 """),
2145 Rules.new(
2146 "multiple devices",
2147 Device(
2148 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
2149 exp_links = ["part-1"],
2151 Device(
2152 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
2153 exp_links = ["part-5"],
2155 Device(
2156 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6",
2157 exp_links = ["part-6"],
2159 Device(
2160 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7",
2161 exp_links = ["part-7"],
2163 Device(
2164 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8",
2165 exp_links = ["part-8"],
2167 Device(
2168 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9",
2169 exp_links = ["part-9"],
2171 Device(
2172 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10",
2173 exp_links = ["part-10"],
2175 rules = r"""
2176 SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n"
2177 """),
2179 Rules.new(
2180 "multiple devices, same link name, positive prio",
2181 Device(
2182 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
2183 exp_links = ["part-1"],
2184 not_exp_links = ["partition"],
2186 Device(
2187 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
2188 exp_links = ["part-5"],
2189 not_exp_links = ["partition"],
2191 Device(
2192 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6",
2193 not_exp_links = ["partition"],
2194 exp_links = ["part-6"],
2196 Device(
2197 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7",
2198 exp_links = ["part-7", "partition"],
2200 Device(
2201 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8",
2202 not_exp_links = ["partition"],
2203 exp_links = ["part-8"],
2205 Device(
2206 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9",
2207 not_exp_links = ["partition"],
2208 exp_links = ["part-9"],
2210 Device(
2211 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10",
2212 not_exp_links = ["partition"],
2213 exp_links = ["part-10"],
2215 repeat = 100,
2216 rules = r"""
2217 SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n"
2218 SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition"
2219 KERNEL=="*7", OPTIONS+="link_priority=10"
2220 """),
2222 Rules.new(
2223 "multiple devices, same link name, negative prio",
2224 Device(
2225 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
2226 exp_links = ["part-1"],
2227 not_exp_links = ["partition"],
2229 Device(
2230 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
2231 exp_links = ["part-5"],
2232 not_exp_links = ["partition"],
2234 Device(
2235 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6",
2236 not_exp_links = ["partition"],
2237 exp_links = ["part-6"],
2239 Device(
2240 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7",
2241 exp_links = ["part-7", "partition"],
2243 Device(
2244 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8",
2245 not_exp_links = ["partition"],
2246 exp_links = ["part-8"],
2248 Device(
2249 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9",
2250 not_exp_links = ["partition"],
2251 exp_links = ["part-9"],
2253 Device(
2254 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10",
2255 not_exp_links = ["partition"],
2256 exp_links = ["part-10"],
2258 rules = r"""
2259 SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n"
2260 SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition"
2261 KERNEL!="*7", OPTIONS+="link_priority=-10"
2262 """),
2264 Rules.new(
2265 "multiple devices, same link name, positive prio, sleep",
2266 Device(
2267 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
2268 exp_links = ["part-1"],
2269 not_exp_links = ["partition"],
2271 Device(
2272 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5",
2273 exp_links = ["part-5"],
2274 not_exp_links = ["partition"],
2276 Device(
2277 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda6",
2278 not_exp_links = ["partition"],
2279 exp_links = ["part-6"],
2281 Device(
2282 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda7",
2283 exp_links = ["part-7", "partition"],
2285 Device(
2286 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda8",
2287 not_exp_links = ["partition"],
2288 exp_links = ["part-8"],
2290 Device(
2291 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda9",
2292 not_exp_links = ["partition"],
2293 exp_links = ["part-9"],
2295 Device(
2296 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda10",
2297 not_exp_links = ["partition"],
2298 exp_links = ["part-10"],
2300 delay = 10000,
2301 rules = r"""
2302 SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n"
2303 SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="partition"
2304 KERNEL=="*7", OPTIONS+="link_priority=10"
2305 """),
2307 Rules.new(
2308 'all_block_devs',
2309 device_generator = lambda: \
2310 all_block_devs(lambda name: (["blockdev"], None) if name.endswith('/sda6') else (None, None)),
2311 repeat = 10,
2312 rules = r"""
2313 SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sd*", SYMLINK+="blockdev"
2314 KERNEL=="sda6", OPTIONS+="link_priority=10"
2315 """),
2317 Rules.new(
2318 "case insensitive match",
2319 Device(
2320 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
2321 exp_links = ["ok"],
2324 rules = r"""
2325 KERNEL==i"SDA1", SUBSYSTEMS==i"SCSI", ATTRS{vendor}==i"a?a", SYMLINK+="ok"
2326 """),
2329 def fork_and_run_udev(action: str, rules: Rules) -> None:
2330 kinder = []
2331 for k, device in enumerate(rules.devices):
2332 # TODO: valgrind/gdb/strace
2333 cmd = [UDEV_BIN, action, device.devpath]
2334 if rules.delay:
2335 cmd += [f'{k * rules.delay}']
2337 kinder += [subprocess.Popen(cmd)]
2339 good = True
2340 for c in kinder:
2341 if not good:
2342 # once something fails, terminate all workers
2343 c.terminate()
2344 elif c.wait() != 0:
2345 good = False
2347 assert good
2350 def environment_issue():
2351 if os.getuid() != 0:
2352 return 'Must be root to run properly'
2354 c = subprocess.run(['systemd-detect-virt', '-r', '-q'],
2355 check=False)
2356 if c.returncode == 0:
2357 return 'Running in a chroot, skipping the test'
2359 c = subprocess.run(['systemd-detect-virt', '-c', '-q'],
2360 check=False)
2361 if c.returncode == 0:
2362 return 'Running in a container, skipping the test'
2364 return None
2367 @pytest.fixture(scope='module')
2368 def udev_setup():
2369 issue = environment_issue()
2370 if issue:
2371 pytest.skip(issue)
2373 global UDEV_RUN, UDEV_RULES, UDEV_DEV, UDEV_SYS
2375 _tmpdir = tempfile.TemporaryDirectory()
2376 tmpdir = Path(_tmpdir.name)
2378 UDEV_RUN = tmpdir / 'run'
2379 UDEV_RULES = UDEV_RUN / 'udev-test.rules'
2381 udev_tmpfs = tmpdir / 'tmpfs'
2382 UDEV_DEV = udev_tmpfs / 'dev'
2383 UDEV_SYS = udev_tmpfs / 'sys'
2385 subprocess.run(['umount', udev_tmpfs],
2386 stderr=subprocess.DEVNULL,
2387 check=False)
2388 udev_tmpfs.mkdir(exist_ok=True, parents=True)
2390 subprocess.check_call(['mount', '-v',
2391 '-t', 'tmpfs',
2392 '-o', 'rw,mode=0755,nosuid,noexec',
2393 'tmpfs', udev_tmpfs])
2395 UDEV_DEV.mkdir(exist_ok=True)
2396 # setting group and mode of udev_dev ensures the tests work
2397 # even if the parent directory has setgid bit enabled.
2398 os.chmod(UDEV_DEV,0o755)
2399 os.chown(UDEV_DEV, 0, 0)
2401 os.mknod(UDEV_DEV / 'null', 0o600 | stat.S_IFCHR, os.makedev(1, 3))
2403 # check if we are permitted to create block device nodes
2404 sda = UDEV_DEV / 'sda'
2405 os.mknod(sda, 0o600 | stat.S_IFBLK, os.makedev(8, 0))
2406 sda.unlink()
2408 subprocess.check_call([SYS_SCRIPT, UDEV_SYS.parent])
2409 subprocess.check_call(['rm', '-rf', UDEV_RUN])
2410 UDEV_RUN.mkdir(parents=True)
2412 os.chdir(tmpdir)
2414 if subprocess.run([UDEV_BIN, 'check'],
2415 check=False).returncode != 0:
2416 pytest.skip(f'{UDEV_BIN} failed to set up the environment, skipping the test',
2417 allow_module_level=True)
2419 yield
2421 subprocess.check_call(['rm', '-rf', UDEV_RUN])
2422 subprocess.check_call(['umount', '-v', udev_tmpfs])
2423 udev_tmpfs.rmdir()
2426 @pytest.mark.parametrize("rules", RULES, ids=(rule.desc for rule in RULES))
2427 def test_udev(rules: Rules, udev_setup):
2428 assert udev_setup is None
2430 rules.create_rules_file()
2431 rules.generate_devices()
2433 for _ in range(rules.repeat):
2434 fork_and_run_udev('add', rules)
2436 for device in rules.devices:
2437 device.check_add()
2439 fork_and_run_udev('remove', rules)
2441 for device in rules.devices:
2442 device.check_remove()
2444 if __name__ == '__main__':
2445 issue = environment_issue()
2446 if issue:
2447 print(issue, file=sys.stderr)
2448 sys.exit(77)
2449 sys.exit(pytest.main(sys.argv))