]> git.ipfire.org Git - thirdparty/systemd.git/blob - test/test-udev.py
Merge pull request #24570 from topimiettinen/nft-sets-v2
[thirdparty/systemd.git] / test / test-udev.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: LGPL-2.1-or-later
3
4 # pylint: disable=redefined-outer-name,no-else-return,multiple-imports
5 # pylint: disable=consider-using-with,global-statement
6
7 # udev test
8 #
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.
12 #
13 # Every test is driven by its own temporary config file.
14 # This program prepares the environment, creates the config and calls udev.
15 #
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.
19
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
31
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)
38
39
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()
46
47 # Those will be set by the udev_setup() fixture
48 UDEV_RUN = UDEV_RULES = UDEV_DEV = UDEV_SYS = None
49
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'
53
54 rules_10k_tags = \
55 '\n'.join(f'KERNEL=="sda", TAG+="test{i + 1}"'
56 for i in range(10_000))
57
58 rules_10k_tags_continuation = \
59 ',\\\n'.join(('KERNEL=="sda"',
60 *(f'TAG+="test{i + 1}"' for i in range(10_000))))
61
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
68
69 exp_perms: Optional[int] = None
70 exp_major_minor: Optional[str] = None
71
72 def check_permissions(self, st: os.stat_result) -> None:
73 if self.exp_perms is None:
74 return
75
76 user, group, mode = self.exp_perms.split(':')
77
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
84
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
91
92 if mode:
93 mode = int(mode, 8)
94 assert stat.S_IMODE(st.st_mode) == mode
95
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)
101
102 def get_devnode(self) -> Path:
103 suffix = self.devnode if self.devnode else self.devpath.split('/')[-1]
104 return UDEV_DEV / suffix
105
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)
110
111 def check_link_nonexistent(self, link: str, devnode: Path) -> None:
112 link = UDEV_DEV / link
113
114 try:
115 tgt = link.parent / link.readlink()
116 except FileNotFoundError:
117 return
118
119 assert not devnode.samefile(tgt)
120
121 def check_add(self) -> None:
122 print(f'check_add {self.devpath}')
123
124 devnode = self.get_devnode()
125 st = devnode.stat(follow_symlinks=False)
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)
129
130 for link in self.exp_links or []:
131 self.check_link_add(link, devnode)
132
133 for link in self.not_exp_links or []:
134 self.check_link_nonexistent(link, devnode)
135
136 def check_link_remove(self, link: str) -> None:
137 link = UDEV_DEV / link
138 with pytest.raises(FileNotFoundError):
139 link.readlink()
140
141 def check_remove(self) -> None:
142 devnode = self.get_devnode()
143 assert not devnode.exists()
144
145 for link in self.exp_links or []:
146 self.check_link_remove(link)
147
148
149 def listify(f):
150 def wrap(*args, **kwargs):
151 return list(f(*args, **kwargs))
152 return functools.update_wrapper(wrap, f)
153
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.
160
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
165
166 assert tgt.startswith('../../')
167 tgt = tgt[5:]
168
169 exp, not_exp = exp_func(tgt)
170 yield Device(devpath=tgt,
171 exp_links=exp,
172 not_exp_links=not_exp)
173
174
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
183
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 ''
188
189 assert bool(devices) ^ bool(device_generator)
190
191 return cls(desc, devices, rules, device_generator=device_generator, **kwargs)
192
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()
197
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)
202
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",
208 ),
209 Device(
210 "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1",
211 ),
212 rules = r"""
213 #
214 """),
215
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"],
221 ),
222 rules = r"""
223 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
224 KERNEL=="ttyACM0", SYMLINK+="modem"
225 """),
226
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"],
232 ),
233 rules = r"""
234 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
235 KERNEL=="ttyACM0", SYMLINK+="modem"
236 """),
237
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"],
243 ),
244 rules = r"""
245 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
246 KERNEL=="ttyACM0", SYMLINK+="modem"
247 """),
248
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"],
254 ),
255 rules = r"""
256 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n"
257 """),
258
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"],
265 ),
266
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 """),
281
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"],
288 ),
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 """),
293
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"],
300 ),
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 """),
306
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"],
320 ),
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 """),
340
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"],
346 ),
347 rules = r"""
348 KERNEL=="ttyACM*", SYMLINK+="modem/%n"
349 KERNEL=="*", SYMLINK+="catch-all"
350 """),
351
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"],
358 ),
359 rules = r"""
360 KERNEL=="*ACM1", SYMLINK+="bad"
361 KERNEL=="*ACM0", SYMLINK+="modem/%n"
362 """),
363
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"],
370 ),
371 rules = r"""
372 KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1"
373 KERNEL=="ttyACM??", SYMLINK+="modem/%n-2"
374 KERNEL=="ttyACM?", SYMLINK+="modem/%n"
375 """),
376
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"],
383 ),
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 """),
389
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"],
395 ),
396 rules = r"""
397 KERNEL=="ttyACM0", SYMLINK+="modem"
398 """),
399
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"],
405 ),
406 rules = r"""
407 # this is a comment
408 KERNEL=="ttyACM0", SYMLINK+="modem"
409
410 """),
411
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"],
417 ),
418 rules = r"""
419 # this is a comment with whitespace before the comment
420 KERNEL=="ttyACM0", SYMLINK+="modem"
421
422 """),
423
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"],
429 ),
430 rules = r"""
431
432
433
434 # this is a comment with whitespace before the comment
435 KERNEL=="ttyACM0", SYMLINK+="whitespace"
436
437
438
439 """),
440
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"],
446 ),
447 rules = r"""
448
449 KERNEL=="ttyACM0", SYMLINK+="modem"
450
451 """),
452
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"],
458 ),
459 rules = r"""
460 KERNEL=="ttyACM0", \
461 SYMLINK+="modem"
462
463 """),
464
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"],
470 ),
471 rules = r"""
472 KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \101", RESULT=="A", SYMLINK+="aaa"
473 """),
474
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"],
480 ),
481 rules = r"""
482
483 #
484 \
485
486 \
487
488 #\
489
490 KERNEL=="ttyACM0", \
491 SYMLINK+="modem"
492
493 """),
494
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"],
500 ),
501 rules = r"""
502 KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem"
503 """),
504
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"],
510 ),
511 rules = r"""
512 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n"
513 """),
514
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"],
520 ),
521 rules = r"""
522 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b"
523 """),
524
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"],
530 ),
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 """),
535
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"],
542 ),
543 rules = r"""
544 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k"
545 KERNEL=="ttyACM0", SYMLINK+="modem"
546 """),
547
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"],
554 ),
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 """),
559
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"],
565 ),
566 rules = r"""
567 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed"
568 """),
569
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"],
575 ),
576 rules = r"""
577 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c"
578 """),
579
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"],
586 ),
587 rules = r"""
588 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}"
589 """),
590
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"],
597 ),
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 """),
601
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"],
608 ),
609 rules = r"""
610 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4' 'foo5 foo6 foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}"
611 """),
612
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"],
619 ),
620 rules = r"""
621 SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'printf %%s \"foo1 foo2\" | grep \"foo1 foo2\"'", KERNEL=="sda5", SYMLINK+="%c{2}"
622 """),
623
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"],
630 ),
631 rules = r"""
632 SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c \"printf %%s 'foo1 foo2' | grep 'foo1 foo2'\"", KERNEL=="sda5", SYMLINK+="%c{2}"
633 """),
634
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"],
641 ),
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 """),
645
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"],
651 ),
652 rules = r"""
653 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}"
654 """),
655
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"],
662 ),
663 rules = r"""
664 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}"
665 """),
666
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"],
672 ),
673 rules = r"""
674 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:$major-minor:$minor-kernelnumber:$number-id:$id"
675 """),
676
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"],
682 ),
683 rules = r"""
684 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="Major:$major-minor:%m-kernelnumber:$number-id:$id"
685 """),
686
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"],
692 ),
693 rules = r"""
694 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n"
695 """),
696
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"],
702 ),
703 rules = r"""
704 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major$minor$number"
705 """),
706
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"],
712 ),
713 rules = r"""
714 SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="$major%m%n$id"
715 """),
716
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"],
723 ),
724 rules = r"""
725 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo"
726 KERNEL=="console", SYMLINK+="TTY"
727 """),
728
729 Rules.new(
730 "non matching SUBSYSTEMS",
731 Device(
732 "/devices/virtual/tty/console",
733 exp_links = ["TTY"],
734 not_exp_links = ["foo"],
735 ),
736 rules = r"""
737 SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo"
738 KERNEL=="console", SYMLINK+="TTY"
739 """),
740
741 Rules.new(
742 "ATTRS match",
743 Device(
744 "/devices/virtual/tty/console",
745 exp_links = ["foo", "TTY"],
746 ),
747 rules = r"""
748 KERNEL=="console", SYMLINK+="TTY"
749 ATTRS{dev}=="5:1", SYMLINK+="foo"
750 """),
751
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"],
758 ),
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 """),
765
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"],
773 ),
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 """),
782
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"],
788 ),
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 """),
794
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"],
800 ),
801 rules = r"""
802 ATTRS{idProduct}=="007b", SYMLINK+="modem"
803 """),
804
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"],
812 ),
813 rules = r"""
814 SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k"
815 SUBSYSTEM=="block", SYMLINK+="is/a/%k"
816 KERNEL=="ttyACM0", SYMLINK+="modem"
817 """),
818
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"],
825 ),
826 rules = r"""
827 KERNEL=="ttyACM0", SYMLINK+="modem"
828 """),
829
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"],
836 ),
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 """),
844
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"],
851 ),
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 """),
859
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"],
865 ),
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 """),
870
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"],
876 ),
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 """),
881
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"],
887 ),
888 rules = r"""
889 SUBSYSTEMS=="scsi", SYMLINK+="driver-is-$attr{driver}"
890 """),
891
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"],
897 ),
898 rules = r"""
899 SUBSYSTEMS=="pci", SYMLINK+="driver-is-$attr{driver}"
900 """),
901
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"],
907 ),
908 rules = r"""
909 SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE", SYMLINK+="ignored"
910 """),
911
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"],
918 ),
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 """),
923
924 Rules.new(
925 "permissions USER=bad GROUP=name",
926 Device(
927 "/devices/virtual/tty/tty33",
928 exp_perms = "0:0:0600",
929 ),
930 rules = r"""
931 KERNEL=="tty33", OWNER="bad", GROUP="name"
932 """),
933
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",
940 ),
941 rules = r"""
942 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1"
943 """),
944
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",
951 ),
952 rules = r"""
953 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="1"
954 """),
955
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",
962 ),
963 rules = r"""
964 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="daemon"
965 """),
966
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",
973 ),
974 rules = r"""
975 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="daemon"
976 """),
977
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",
984 ),
985 rules = r"""
986 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="audio"
987 """),
988
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",
995 ),
996 rules = r"""
997 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", MODE="0777"
998 """),
999
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",
1006 ),
1007 rules = r"""
1008 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="1", GROUP="1", MODE="0777"
1009 """),
1010
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::",
1016 ),
1017 rules = r"""
1018 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1"
1019 """),
1020
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",
1026 ),
1027 rules = r"""
1028 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="1"
1029 """),
1030
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",
1036 ),
1037 rules = r"""
1038 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", MODE="0060"
1039 """),
1040
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",
1046 ),
1047 rules = r"""
1048 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="1", GROUP="1", MODE="0777"
1049 """),
1050
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",
1056 ),
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 """),
1062
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",
1068 ),
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 """),
1076
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",
1082 ),
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 """),
1090
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",
1097 ),
1098 rules = r"""
1099 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node"
1100 """),
1101
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",
1108 ),
1109 rules = r"""
1110 KERNEL=="misc-fake1", SYMLINK+="node"
1111 """),
1112
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",
1119 ),
1120 rules = r"""
1121 KERNEL=="misc-fake89999", SYMLINK+="node"
1122 """),
1123
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-"],
1129 ),
1130 rules = r"""
1131 KERNEL=="ttyACM[0-9]*", SYMLINK="symlink1-%n symlink2-%k symlink3-%b"
1132 """),
1133
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 = [" "],
1140 ),
1141 rules = r"""
1142 KERNEL=="ttyACM[0-9]*", SYMLINK=" one two "
1143 """),
1144
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 = [" "],
1151 ),
1152 rules = r"""
1153 ENV{WITH_WS}="one two three"
1154 SYMLINK="name-$env{WITH_WS}-end"
1155 """),
1156
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 = [" "],
1163 ),
1164 rules = r"""
1165 ENV{WITH_WS}=" one two three"
1166 SYMLINK="name-$env{WITH_WS}-end"
1167 """),
1168
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 = [" "],
1175 ),
1176 rules = r"""
1177 ENV{WITH_WS}="one two three "
1178 SYMLINK="name-$env{WITH_WS}-end"
1179 """),
1180
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 = [" "],
1187 ),
1188 rules = r"""
1189 ENV{WITH_WS}=" one two three "
1190 SYMLINK="name-$env{WITH_WS}-end"
1191 """),
1192
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 = [" "],
1199 ),
1200 rules = r"""
1201 ENV{WITH_WS}=" one two three "
1202 SYMLINK="name-$env{WITH_WS}-end"
1203 """),
1204
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 = [" "],
1212 ),
1213 rules = r"""
1214 ENV{WITH_WS}=" one two three "
1215 SYMLINK=" first name-$env{WITH_WS}-end another_symlink a b c "
1216 """),
1217
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"],
1225 ),
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 """),
1231
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"],
1237 ),
1238 rules = r"""
1239 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n"
1240 """),
1241
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"],
1247 ),
1248 rules = r"""
1249 KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n"
1250 """),
1251
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",
1256 ),
1257 # we get a warning, but the process does not fail
1258 rules = r"""
1259 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="."
1260 """),
1261
1262 Rules.new(
1263 "symlink node to itself",
1264 Device(
1265 "/devices/virtual/tty/tty0",
1266 ),
1267 # we get a warning, but the process does not fail
1268 rules = r"""
1269 KERNEL=="tty0", SYMLINK+="tty0"
1270 """),
1271
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"],
1277 ),
1278 rules = r"""
1279 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n"
1280 """),
1281
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"],
1287 ),
1288 rules = r"""
1289 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k"
1290 """),
1291
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"],
1297 ),
1298 rules = r"""
1299 KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m"
1300 """),
1301
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"],
1307 ),
1308 rules = r"""
1309 SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b"
1310 """),
1311
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"],
1317 ),
1318 rules = r"""
1319 KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c"
1320 """),
1321
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"],
1328 ),
1329 rules = r"""
1330 KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}"
1331 """),
1332
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"],
1339 ),
1340 rules = r"""
1341 KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}"
1342 """),
1343
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"],
1350 ),
1351 rules = r"""
1352 SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}"
1353 """),
1354
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"],
1360 ),
1361 rules = r"""
1362 KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}"
1363 """),
1364
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"],
1371 ),
1372 rules = r"""
1373 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2", RESULT=="node *", SYMLINK+="%c{2} %c{3}"
1374 """),
1375
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"],
1382 ),
1383 rules = r"""
1384 SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}"
1385 """),
1386
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"],
1393 ),
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 """),
1399
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"]
1406 ),
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 """),
1411
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"],
1417 ),
1418 rules = r"""
1419 SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node"
1420 """),
1421
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"],
1427 ),
1428 rules = r"""
1429 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-%n"
1430 """),
1431
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"],
1437 ),
1438 rules = r"""
1439 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end"
1440 """),
1441
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"],
1448 ),
1449 rules = r"""
1450 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule"
1451 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="very-last"
1452 """),
1453
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"],
1460 ),
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 """),
1466
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"],
1473 ),
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 """),
1479
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"],
1485 ),
1486 rules = r"""
1487 SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before"
1488 KERNEL=="sda1", PROGRAM!="/bin/false", SYMLINK+="nonzero-program"
1489 """),
1490
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"],
1497 ),
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 """),
1504
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"],
1511 ),
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 """),
1519
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"],
1526 ),
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 """),
1533
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"],
1540 ),
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 """),
1549
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"],
1556 ),
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"],
1561 ),
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 """),
1569
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"],
1575 ),
1576 rules = r"""
1577 SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e name; (/usr/bin/badprogram)", RESULT=="name_ _/usr/bin/badprogram_", SYMLINK+="sane"
1578 """),
1579
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"],
1585 ),
1586 rules = r"""
1587 SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xc3\xbcber" RESULT=="über", SYMLINK+="uber"
1588 """),
1589
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"],
1595 ),
1596 rules = r"""
1597 SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \xef\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced"
1598 """),
1599
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"],
1605 ),
1606 rules = r"""
1607 KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}"
1608 """),
1609
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"],
1616 ),
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 """),
1623
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"],
1630 ),
1631 rules = r"""
1632 ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok"
1633 ACTION=="add", KERNEL=="sda", SYMLINK+="ok"
1634 """),
1635
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",
1642 ),
1643 rules = r"""
1644 KERNEL=="sda", GROUP:="tty"
1645 KERNEL=="sda", GROUP="root", MODE="0640", SYMLINK+="ok"
1646 """),
1647
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",
1654 ),
1655 rules = r"""
1656 KERNEL=="sda", GROUP:="tty"
1657 SUBSYSTEM=="block", MODE:="640"
1658 KERNEL=="sda", GROUP="root", MODE="0666", SYMLINK+="ok"
1659 """),
1660
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"],
1666 ),
1667 rules = r"""
1668 KERNEL=="sda", MODE="0666", SYMLINK+="node-$env{ACTION}-me"
1669 """),
1670
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"],
1677 ),
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 """),
1683
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"],
1690 ),
1691 rules = r"""
1692 KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong"
1693 KERNEL=="ttyACM[0-9]*", SYMLINK=""
1694 KERNEL=="ttyACM[0-9]*", SYMLINK+="right"
1695 """),
1696
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"],
1702 ),
1703 rules = r"""
1704 KERNEL=="ttyACM*", SYMLINK+="before"
1705 KERNEL=="ttyACM*|nothing", SYMLINK+="right"
1706 """),
1707
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"],
1714 ),
1715 rules = r"""
1716 KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch"
1717 KERNEL=="ttyACM*", SYMLINK+="before"
1718 KERNEL=="dontknow*|ttyACM*|nothing*", SYMLINK+="right"
1719 """),
1720
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"],
1727 ),
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 """),
1734
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"],
1741 ),
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 """),
1749
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"],
1756 ),
1757 rules = r"""
1758 KERNEL=="sda", TAG="foo"
1759 TAGS=="|foo", SYMLINK+="found"
1760 TAGS=="|aaa", SYMLINK+="bad"
1761 """),
1762
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"],
1769 ),
1770 rules = r"""
1771 KERNEL=="sda", ENV{HOGE}=""
1772 ENV{HOGE}=="|foo", SYMLINK+="found"
1773 ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
1774 """),
1775
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"],
1782 ),
1783 rules = r"""
1784 KERNEL=="sda", TAG="foo"
1785 TAGS=="foo||bar", SYMLINK+="found"
1786 TAGS=="aaa||bbb", SYMLINK+="bad"
1787 """),
1788
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"],
1795 ),
1796 rules = r"""
1797 KERNEL=="sda", ENV{HOGE}=""
1798 ENV{HOGE}=="foo||bar", SYMLINK+="found"
1799 ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
1800 """),
1801
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"],
1808 ),
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 """),
1816
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"],
1823 ),
1824 rules = r"""
1825 KERNEL=="sda", ENV{HOGE}=""
1826 ENV{HOGE}=="foo|", SYMLINK+="found"
1827 ENV{HOGE}=="aaa|bbb", SYMLINK+="bad"
1828 """),
1829
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"],
1836 ),
1837 rules = r"""
1838 KERNEL=="sda", TAG="c"
1839 TAGS=="foo||bar||c", SYMLINK+="found"
1840 TAGS=="aaa||bbb||ccc", SYMLINK+="bad"
1841 """),
1842
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"],
1849 ),
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 """),
1860
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"],
1866 ),
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"],
1870 ),
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 """),
1877
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"],
1884 ),
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 """),
1894
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"],
1900 ),
1901 rules = r"""
1902 KERNEL=="sda1", GOTO="does-not-exist"
1903 KERNEL=="sda1", SYMLINK+="right",
1904 LABEL="exists"
1905 """),
1906
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"],
1913 ),
1914 rules = r"""
1915 KERNEL=="sda1", SYMLINK+="link"
1916 KERNEL=="sda1", SYMLINK=="link*", SYMLINK+="right"
1917 KERNEL=="sda1", SYMLINK=="nolink*", SYMLINK+="wrong"
1918 """),
1919
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"],
1926 ),
1927 rules = r"""
1928 KERNEL="sda1", SYMLINK+="no"
1929 KERNEL=="sda1", SYMLINK+="yes"
1930 """),
1931
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"],
1937 ),
1938 rules = r"""
1939 KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes"
1940 """),
1941
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"],
1948 ),
1949 rules = r"""
1950 # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
1951 # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
1952 KERNEL=="sda1", SYMLINK+=="no"
1953 KERNEL=="sda1", SYMLINK+="yes"
1954 """),
1955
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"],
1961 ),
1962 rules = r"""
1963 KERNEL=="sda", SYMLINK+="$attr{[net/eth0]address}"
1964 """),
1965
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"],
1972 ),
1973 rules = r"""
1974 TEST=="/etc/passwd", SYMLINK+="there"
1975 TEST!="/etc/passwd", SYMLINK+="notthere"
1976 """),
1977
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"],
1983 ),
1984 rules = r"""
1985 KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes"
1986 """),
1987
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"],
1993 ),
1994 rules = r"""
1995 KERNEL=="sda", TEST=="size", SYMLINK+="relative"
1996 """),
1997
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"],
2003 ),
2004 rules = r"""
2005 KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir"
2006 """),
2007
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",
2013 ),
2014 rules = r"""
2015 KERNEL=="sda", MODE="0000"
2016 """),
2017
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",
2023 ),
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 """),
2028
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",
2034 ),
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 """),
2039
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",
2046 ),
2047 rules = r"""
2048 KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end"
2049 """),
2050
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"],
2056 ),
2057 rules = r"""
2058 KERNEL=="sda", IMPORT{builtin}="path_id"
2059 KERNEL=="sda", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/$env{ID_PATH}"
2060 """),
2061
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"],
2068 ),
2069 rules = r"""
2070 SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", TAG+="green"
2071 TAGS=="green", SYMLINK+="found"
2072 TAGS=="blue", SYMLINK+="bad"
2073 """),
2074
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"],
2080 ),
2081 rules = f"""
2082 {rules_10k_tags}
2083 TAGS=="test1", TAGS=="test500", TAGS=="test1234", TAGS=="test9999", TAGS=="test10000", SYMLINK+="found"
2084 """),
2085
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"],
2092 ),
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", \\
2103 \\
2104 \\
2105 TAG+="hoge4"
2106 TAGS=="hoge1", TAGS=="hoge2", TAGS=="hoge3", TAGS=="hoge4", SYMLINK+="found"
2107 """),
2108
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"],
2115 ),
2116 rules = r"""
2117 # empty line finishes continuation
2118 KERNEL=="sda", TAG+="foo" \
2119
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 """),
2126
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"],
2133 ),
2134 rules = """
2135 # space only line finishes continuation
2136 KERNEL=="sda", TAG+="foo" \\
2137 \t
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 """),
2144
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"],
2150 ),
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"],
2154 ),
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"],
2158 ),
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"],
2162 ),
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"],
2166 ),
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"],
2170 ),
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"],
2174 ),
2175 rules = r"""
2176 SUBSYSTEM=="block", SUBSYSTEMS=="scsi", KERNEL=="sda?*", ENV{DEVTYPE}=="partition", SYMLINK+="part-%n"
2177 """),
2178
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"],
2185 ),
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"],
2190 ),
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"],
2195 ),
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"],
2199 ),
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"],
2204 ),
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"],
2209 ),
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"],
2214 ),
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 """),
2221
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"],
2228 ),
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"],
2233 ),
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"],
2238 ),
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"],
2242 ),
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"],
2247 ),
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"],
2252 ),
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"],
2257 ),
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 """),
2263
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"],
2270 ),
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"],
2275 ),
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"],
2280 ),
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"],
2284 ),
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"],
2289 ),
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"],
2294 ),
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"],
2299 ),
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 """),
2306
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 """),
2316 ]
2317
2318 def fork_and_run_udev(action: str, rules: Rules) -> None:
2319 kinder = []
2320 for k, device in enumerate(rules.devices):
2321 # TODO: valgrind/gdb/strace
2322 cmd = [UDEV_BIN, action, device.devpath]
2323 if rules.delay:
2324 cmd += [f'{k * rules.delay}']
2325
2326 kinder += [subprocess.Popen(cmd)]
2327
2328 good = True
2329 for c in kinder:
2330 if not good:
2331 # once something fails, terminate all workers
2332 c.terminate()
2333 elif c.wait() != 0:
2334 good = False
2335
2336 assert good
2337
2338
2339 def environment_issue():
2340 if os.getuid() != 0:
2341 return 'Must be root to run properly'
2342
2343 c = subprocess.run(['systemd-detect-virt', '-r', '-q'],
2344 check=False)
2345 if c.returncode == 0:
2346 return 'Running in a chroot, skipping the test'
2347
2348 c = subprocess.run(['systemd-detect-virt', '-c', '-q'],
2349 check=False)
2350 if c.returncode == 0:
2351 return 'Running in a container, skipping the test'
2352
2353 return None
2354
2355
2356 @pytest.fixture(scope='module')
2357 def udev_setup():
2358 issue = environment_issue()
2359 if issue:
2360 pytest.skip(issue)
2361
2362 global UDEV_RUN, UDEV_RULES, UDEV_DEV, UDEV_SYS
2363
2364 _tmpdir = tempfile.TemporaryDirectory()
2365 tmpdir = Path(_tmpdir.name)
2366
2367 UDEV_RUN = tmpdir / 'run'
2368 UDEV_RULES = UDEV_RUN / 'udev-test.rules'
2369
2370 udev_tmpfs = tmpdir / 'tmpfs'
2371 UDEV_DEV = udev_tmpfs / 'dev'
2372 UDEV_SYS = udev_tmpfs / 'sys'
2373
2374 subprocess.run(['umount', udev_tmpfs],
2375 stderr=subprocess.DEVNULL,
2376 check=False)
2377 udev_tmpfs.mkdir(exist_ok=True, parents=True)
2378
2379 subprocess.check_call(['mount', '-v',
2380 '-t', 'tmpfs',
2381 '-o', 'rw,mode=0755,nosuid,noexec',
2382 'tmpfs', udev_tmpfs])
2383
2384 UDEV_DEV.mkdir(exist_ok=True)
2385 # setting group and mode of udev_dev ensures the tests work
2386 # even if the parent directory has setgid bit enabled.
2387 os.chmod(UDEV_DEV,0o755)
2388 os.chown(UDEV_DEV, 0, 0)
2389
2390 os.mknod(UDEV_DEV / 'null', 0o600 | stat.S_IFCHR, os.makedev(1, 3))
2391
2392 # check if we are permitted to create block device nodes
2393 sda = UDEV_DEV / 'sda'
2394 os.mknod(sda, 0o600 | stat.S_IFBLK, os.makedev(8, 0))
2395 sda.unlink()
2396
2397 subprocess.check_call([SYS_SCRIPT, UDEV_SYS.parent])
2398 subprocess.check_call(['rm', '-rf', UDEV_RUN])
2399 UDEV_RUN.mkdir(parents=True)
2400
2401 os.chdir(tmpdir)
2402
2403 if subprocess.run([UDEV_BIN, 'check'],
2404 check=False).returncode != 0:
2405 pytest.skip(f'{UDEV_BIN} failed to set up the environment, skipping the test',
2406 allow_module_level=True)
2407
2408 yield
2409
2410 subprocess.check_call(['rm', '-rf', UDEV_RUN])
2411 subprocess.check_call(['umount', '-v', udev_tmpfs])
2412 udev_tmpfs.rmdir()
2413
2414
2415 @pytest.mark.parametrize("rules", RULES, ids=(rule.desc for rule in RULES))
2416 def test_udev(rules: Rules, udev_setup):
2417 assert udev_setup is None
2418
2419 rules.create_rules_file()
2420 rules.generate_devices()
2421
2422 for _ in range(rules.repeat):
2423 fork_and_run_udev('add', rules)
2424
2425 for device in rules.devices:
2426 device.check_add()
2427
2428 fork_and_run_udev('remove', rules)
2429
2430 for device in rules.devices:
2431 device.check_remove()
2432
2433 if __name__ == '__main__':
2434 issue = environment_issue()
2435 if issue:
2436 print(issue, file=sys.stderr)
2437 sys.exit(77)
2438 sys.exit(pytest.main(sys.argv))