diff options
author | Anthony G. Basile <blueness@gentoo.org> | 2012-11-15 10:10:41 -0500 |
---|---|---|
committer | Anthony G. Basile <blueness@gentoo.org> | 2012-11-15 10:10:41 -0500 |
commit | 2944f347d087ff24ec808e4b70fe104a772a97a0 (patch) | |
tree | a5de4fbefe16ef359a526442fb41251f123399d5 /test | |
parent | 678b0b89572768b21d8b74360d55b75b233799c4 (diff) | |
parent | d025f1e4dca8fc1436aff76f9e6185fe3e728daa (diff) |
Fork of Original Code Base: anongit.freedesktop.org/systemd
This is the initial fork of the code base from freedsktop.org.
The code is provided here as a reference of the initial starting
point and for possible future checkouts after a large portion
of this code is removed.
Merge git://anongit.freedesktop.org/systemd/systemd
Diffstat (limited to 'test')
-rw-r--r-- | test/.gitignore | 3 | ||||
-rw-r--r-- | test/Makefile | 20 | ||||
-rw-r--r-- | test/README.testsuite | 35 | ||||
-rw-r--r-- | test/TEST-01-BASIC/Makefile | 10 | ||||
-rwxr-xr-x | test/TEST-01-BASIC/test.sh | 252 | ||||
l--------- | test/TEST-02-CRYPTSETUP/Makefile | 1 | ||||
-rwxr-xr-x | test/TEST-02-CRYPTSETUP/test.sh | 264 | ||||
-rw-r--r-- | test/a.service | 7 | ||||
-rw-r--r-- | test/b.service | 6 | ||||
-rw-r--r-- | test/c.service | 6 | ||||
-rw-r--r-- | test/d.service | 8 | ||||
-rw-r--r-- | test/e.service | 8 | ||||
-rw-r--r-- | test/f.service | 5 | ||||
-rw-r--r-- | test/g.service | 6 | ||||
-rw-r--r-- | test/h.service | 6 | ||||
-rwxr-xr-x | test/rule-syntax-check.py | 64 | ||||
-rwxr-xr-x | test/rules-test.sh | 15 | ||||
-rw-r--r-- | test/sys.tar.xz | bin | 0 -> 165116 bytes | |||
-rw-r--r-- | test/test-functions | 864 | ||||
-rwxr-xr-x | test/udev-test.pl | 1530 |
20 files changed, 3110 insertions, 0 deletions
diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000000..93c1f950fe --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,3 @@ +.testdir +test.log +/sys diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000000..987a32548f --- /dev/null +++ b/test/Makefile @@ -0,0 +1,20 @@ +# Just a little hook script to easy building when in this directory +.PHONY: all check clean + +all: + $(MAKE) -C .. + +clean: + @for i in TEST-[0-9]*; do \ + [ -d $$i ] || continue ; \ + [ -f $$i/Makefile ] || continue ; \ + make -C $$i clean ; \ + done + +check: + $(MAKE) -C .. all + @for i in TEST-[0-9]*; do \ + [ -d $$i ] || continue ; \ + [ -f $$i/Makefile ] || continue ; \ + make -C $$i all ; \ + done diff --git a/test/README.testsuite b/test/README.testsuite new file mode 100644 index 0000000000..0f96b984a9 --- /dev/null +++ b/test/README.testsuite @@ -0,0 +1,35 @@ +The extended testsuite only works with uid=0. It contains of several +subdirectories named "test/TEST-??-*", which are run one by one. + +To run the extended testsuite do the following: + +$ make all +$ cd test +$ sudo make clean check +... +make[1]: Entering directory `/mnt/data/harald/git/systemd/test/TEST-01-BASIC' +Making all in . +Making all in po +Making all in docs/libudev +Making all in docs/gudev +TEST: Basic systemd setup [OK] +make[1]: Leaving directory `/mnt/data/harald/git/systemd/test/TEST-01-BASIC' +... + +If one of the tests fails, then $subdir/test.log contains the log file of +the test. + +To debug a special testcase of the testsuite do: + +$ make all +$ cd test/TEST-01-BASIC +$ sudo make clean setup run + +If you want to log in the testsuite virtual machine, you can specify +additional kernel command line parameter with $DEBUGFAIL. + +$ sudo sh -c 'DEBUGFAIL="systemd.unit=multi-user.target" make clean setup run' + +you can even skip the "clean" and "setup" if you want to run the machine again. + +$ sudo sh -c 'DEBUGFAIL="systemd.unit=multi-user.target" make run' diff --git a/test/TEST-01-BASIC/Makefile b/test/TEST-01-BASIC/Makefile new file mode 100644 index 0000000000..5e89a29eff --- /dev/null +++ b/test/TEST-01-BASIC/Makefile @@ -0,0 +1,10 @@ +all: + @make -s --no-print-directory -C ../.. all + @basedir=../.. TEST_BASE_DIR=../ ./test.sh --all +setup: + @make --no-print-directory -C ../.. all + @basedir=../.. TEST_BASE_DIR=../ ./test.sh --setup +clean: + @basedir=../.. TEST_BASE_DIR=../ ./test.sh --clean +run: + @basedir=../.. TEST_BASE_DIR=../ ./test.sh --run diff --git a/test/TEST-01-BASIC/test.sh b/test/TEST-01-BASIC/test.sh new file mode 100755 index 0000000000..eb6a80a07c --- /dev/null +++ b/test/TEST-01-BASIC/test.sh @@ -0,0 +1,252 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="Basic systemd setup" + +KVERSION=${KVERSION-$(uname -r)} +KERNEL_VER=$(uname -r) + +# Uncomment this to debug failures +#DEBUGFAIL="systemd.unit=multi-user.target" +DEBUGTOOLS="df free ls stty cat ps ln ip route dmesg dhclient mkdir cp ping dhclient strace less grep id tty touch du sort" + +run_qemu() { + # TODO: qemu wrapper script: http://www.spinics.net/lists/kvm/msg72389.html + qemu-kvm \ + -hda $TESTDIR/rootdisk.img \ + -m 512M -nographic \ + -net none -kernel /boot/vmlinuz-$KERNEL_VER \ + -append "root=/dev/sda1 systemd.log_level=debug raid=noautodetect loglevel=2 init=/usr/lib/systemd/systemd ro console=ttyS0,115200n81 selinux=0 $DEBUGFAIL" || return 1 + + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + cp -a $TESTDIR/root/failed $TESTDIR + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root + cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + + +run_nspawn() { + systemd-nspawn -b -D $TESTDIR/nspawn-root --capability=CAP_AUDIT_CONTROL,CAP_AUDIT_WRITE /usr/lib/systemd/systemd + ret=1 + [[ -e $TESTDIR/nspawn-root/testok ]] && ret=0 + cp -a $TESTDIR/nspawn-root/failed $TESTDIR + cp -a $TESTDIR/nspawn-root/var/log/journal $TESTDIR + cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + + +test_run() { + if check_qemu ; then + run_qemu || return 1 + else + dwarn "can't run qemu-kvm, skipping" + fi + if check_nspawn; then + run_nspawn || return 1 + else + dwarn "can't run systemd-nspawn, skipping" + fi + return 0 +} + +test_setup() { + rm -f $TESTDIR/rootdisk.img + # Create the blank file to use as a root filesystem + dd if=/dev/null of=$TESTDIR/rootdisk.img bs=1M seek=200 + LOOPDEV=$(losetup --show -P -f $TESTDIR/rootdisk.img) + [ -b $LOOPDEV ] || return 1 + echo "LOOPDEV=$LOOPDEV" >> $STATEFILE + sfdisk -C 6400 -H 2 -S 32 -L $LOOPDEV <<EOF +,3200 +, +EOF + + mkfs.ext3 -L systemd ${LOOPDEV}p1 + echo -n test >$TESTDIR/keyfile + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + mkdir -p $TESTDIR/root/run + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + initdir=$TESTDIR/root + + # create the basic filesystem layout + setup_basic_dirs + + # install compiled files + (cd ../..; make DESTDIR=$initdir install) + + # remove unneeded documentation + rm -fr $initdir/usr/share/{man,doc,gtk-doc} + + # install possible missing libraries + for i in $initdir/{sbin,bin}/* $initdir/lib/systemd/*; do + inst_libs $i + done + + # make a journal directory + mkdir -p $initdir/var/log/journal + + # install some basic config files + inst /etc/sysconfig/init + inst /etc/passwd + inst /etc/shadow + inst /etc/group + inst /etc/shells + inst /etc/nsswitch.conf + inst /etc/pam.conf + inst /etc/securetty + inst /etc/os-release + inst /etc/localtime + # we want an empty environment + > $initdir/etc/environment + > $initdir/etc/machine-id + + # set the hostname + echo systemd-testsuite > $initdir/etc/hostname + + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + cat >$initdir/etc/fstab <<EOF +LABEL=systemd / ext3 rw 0 1 +EOF + + # setup the testsuite target + cat >$initdir/etc/systemd/system/testsuite.target <<EOF +[Unit] +Description=Testsuite target +Requires=multi-user.target +After=multi-user.target +Conflicts=rescue.target +AllowIsolate=yes +EOF + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/bin/bash -c 'set -x; systemctl --failed --no-legend --no-pager > /failed ; echo OK > /testok; while : ;do echo "testsuite service waiting for journal to move to /var/log/journal" > /dev/console ; for i in /var/log/journal/*;do [ -d "\$i" ] && echo "\$i" && break 2; done; sleep 1; done; sleep 1; exit 0;' +ExecStopPost=/usr/bin/systemctl poweroff +Type=oneshot +EOF + mkdir -p $initdir/etc/systemd/system/testsuite.target.wants + ln -fs ../testsuite.service $initdir/etc/systemd/system/testsuite.target.wants/testsuite.service + + # make the testsuite the default target + ln -fs testsuite.target $initdir/etc/systemd/system/default.target + mkdir -p $initdir/etc/rc.d + cat >$initdir/etc/rc.d/rc.local <<EOF +#!/bin/bash +exit 0 +EOF + chmod 0755 $initdir/etc/rc.d/rc.local + # install basic tools needed + dracut_install sh bash setsid loadkeys setfont \ + login sushell sulogin gzip sleep echo mount umount cryptsetup + dracut_install dmsetup modprobe + + # install libnss_files for login + inst_libdir_file "libnss_files*" + + # install dbus and pam + find \ + /etc/dbus-1 \ + /etc/pam.d \ + /etc/security \ + /lib64/security \ + /lib/security -xtype f \ + | while read file; do + inst $file + done + + # install dbus socket and service file + inst /usr/lib/systemd/system/dbus.socket + inst /usr/lib/systemd/system/dbus.service + + # install basic keyboard maps and fonts + for i in \ + /usr/lib/kbd/consolefonts/latarcyrheb-sun16* \ + /usr/lib/kbd/keymaps/include/* \ + /usr/lib/kbd/keymaps/i386/include/* \ + /usr/lib/kbd/keymaps/i386/qwerty/us.*; do + [[ -f $i ]] || continue + inst $i + done + + # some basic terminfo files + for _terminfodir in /lib/terminfo /etc/terminfo /usr/share/terminfo; do + [ -f ${_terminfodir}/l/linux ] && break + done + dracut_install -o ${_terminfodir}/l/linux + + # softlink mtab + ln -fs /proc/self/mounts $initdir/etc/mtab + + # install any Exec's from the service files + egrep -ho '^Exec[^ ]*=[^ ]+' $initdir/lib/systemd/system/*.service \ + | while read i; do + i=${i##Exec*=}; i=${i##-} + inst $i + done + + # install plymouth, if found... else remove plymouth service files + # if [ -x /usr/libexec/plymouth/plymouth-populate-initrd ]; then + # PLYMOUTH_POPULATE_SOURCE_FUNCTIONS="$TEST_BASE_DIR/test-functions" \ + # /usr/libexec/plymouth/plymouth-populate-initrd -t $initdir + # dracut_install plymouth plymouthd + # else + rm -f $initdir/{usr/lib,etc}/systemd/system/plymouth* $initdir/{usr/lib,etc}/systemd/system/*/plymouth* + # fi + + # some helper tools for debugging + [[ $DEBUGTOOLS ]] && dracut_install $DEBUGTOOLS + + # install ld.so.conf* and run ldconfig + cp -a /etc/ld.so.conf* $initdir/etc + ldconfig -r "$initdir" + ddebug "Strip binaeries" + find "$initdir" -perm +111 -type f | xargs strip --strip-unneeded | ddebug + + # copy depmod files + inst /lib/modules/$KERNEL_VER/modules.order + inst /lib/modules/$KERNEL_VER/modules.builtin + # generate module dependencies + if [[ -d $initdir/lib/modules/$KERNEL_VER ]] && \ + ! depmod -a -b "$initdir" $KERNEL_VER; then + dfatal "\"depmod -a $KERNEL_VER\" failed." + exit 1 + fi + ) + rm -fr $TESTDIR/nspawn-root + ddebug "cp -ar $TESTDIR/root $TESTDIR/nspawn-root" + cp -ar $TESTDIR/root $TESTDIR/nspawn-root + # we don't mount in the nspawn root + rm -fr $TESTDIR/nspawn-root/etc/fstab + + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +. $TEST_BASE_DIR/test-functions +do_test "$@" diff --git a/test/TEST-02-CRYPTSETUP/Makefile b/test/TEST-02-CRYPTSETUP/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-02-CRYPTSETUP/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile
\ No newline at end of file diff --git a/test/TEST-02-CRYPTSETUP/test.sh b/test/TEST-02-CRYPTSETUP/test.sh new file mode 100755 index 0000000000..790dc3074c --- /dev/null +++ b/test/TEST-02-CRYPTSETUP/test.sh @@ -0,0 +1,264 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +TEST_DESCRIPTION="cryptsetup systemd setup" + +KVERSION=${KVERSION-$(uname -r)} +KERNEL_VER=$(uname -r) + +# Uncomment this to debug failures +#DEBUGFAIL="systemd.unit=multi-user.target" +DEBUGTOOLS="df free ls stty cat ps ln ip route dmesg dhclient mkdir cp ping dhclient strace less grep id tty touch du sort" + +run_qemu() { + # TODO: qemu wrapper script: http://www.spinics.net/lists/kvm/msg72389.html + qemu-kvm \ + -hda $TESTDIR/rootdisk.img \ + -m 512M -nographic \ + -net none -kernel /boot/vmlinuz-$KERNEL_VER \ + -append "root=/dev/sda1 systemd.log_level=debug raid=noautodetect loglevel=2 init=/usr/lib/systemd/systemd ro console=ttyS0,115200n81 selinux=0 $DEBUGFAIL" || return 1 + + ret=1 + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + [[ -e $TESTDIR/root/testok ]] && ret=0 + cp -a $TESTDIR/root/failed $TESTDIR + cryptsetup luksOpen ${LOOPDEV}p2 varcrypt <$TESTDIR/keyfile + mount /dev/mapper/varcrypt $TESTDIR/root/var + cp -a $TESTDIR/root/var/log/journal $TESTDIR + umount $TESTDIR/root/var + umount $TESTDIR/root + cryptsetup luksClose /dev/mapper/varcrypt + cat $TESTDIR/failed + ls -l $TESTDIR/journal/*/*.journal + test -s $TESTDIR/failed && ret=$(($ret+1)) + return $ret +} + + +test_run() { + if check_qemu ; then + run_qemu || return 1 + else + dwarn "can't run qemu-kvm, skipping" + fi + return 0 +} + +test_setup() { + rm -f $TESTDIR/rootdisk.img + # Create the blank file to use as a root filesystem + dd if=/dev/null of=$TESTDIR/rootdisk.img bs=1M seek=200 + LOOPDEV=$(losetup --show -P -f $TESTDIR/rootdisk.img) + [ -b $LOOPDEV ] || return 1 + echo "LOOPDEV=$LOOPDEV" >> $STATEFILE + sfdisk -C 6400 -H 2 -S 32 -L $LOOPDEV <<EOF +,3200 +, +EOF + + mkfs.ext3 -L systemd ${LOOPDEV}p1 + echo -n test >$TESTDIR/keyfile + cryptsetup -q luksFormat ${LOOPDEV}p2 $TESTDIR/keyfile + cryptsetup luksOpen ${LOOPDEV}p2 varcrypt <$TESTDIR/keyfile + mkfs.ext3 -L var /dev/mapper/varcrypt + mkdir -p $TESTDIR/root + mount ${LOOPDEV}p1 $TESTDIR/root + mkdir -p $TESTDIR/root/run + mkdir -p $TESTDIR/root/var + mount /dev/mapper/varcrypt $TESTDIR/root/var + + # Create what will eventually be our root filesystem onto an overlay + ( + LOG_LEVEL=5 + initdir=$TESTDIR/root + + # create the basic filesystem layout + setup_basic_dirs + + # install compiled files + (cd ../..; make DESTDIR=$initdir install) + + # remove unneeded documentation + rm -fr $initdir/usr/share/{man,doc,gtk-doc} + + # install possible missing libraries + for i in $initdir/{sbin,bin}/* $initdir/lib/systemd/*; do + inst_libs $i + done + + # make a journal directory + mkdir -p $initdir/var/log/journal + + # install some basic config files + inst /etc/sysconfig/init + inst /etc/passwd + inst /etc/shadow + inst /etc/group + inst /etc/shells + inst /etc/nsswitch.conf + inst /etc/pam.conf + inst /etc/securetty + inst /etc/os-release + inst /etc/localtime + # we want an empty environment + > $initdir/etc/environment + > $initdir/etc/machine-id + + # set the hostname + echo systemd-testsuite > $initdir/etc/hostname + + eval $(udevadm info --export --query=env --name=/dev/mapper/varcrypt) + eval $(udevadm info --export --query=env --name=${LOOPDEV}p2) + + cat >$initdir/etc/crypttab <<EOF +$DM_NAME UUID=$ID_FS_UUID /etc/varkey +EOF + echo -n test > $initdir/etc/varkey + cat $initdir/etc/crypttab | ddebug + + cat >$initdir/etc/fstab <<EOF +LABEL=systemd / ext3 rw 0 1 +/dev/mapper/varcrypt /var ext3 defaults 0 1 +EOF + + # setup the testsuite target + cat >$initdir/etc/systemd/system/testsuite.target <<EOF +[Unit] +Description=Testsuite target +Requires=multi-user.target +After=multi-user.target +Conflicts=rescue.target +AllowIsolate=yes +EOF + + # setup the testsuite service + cat >$initdir/etc/systemd/system/testsuite.service <<EOF +[Unit] +Description=Testsuite service +After=multi-user.target + +[Service] +ExecStart=/bin/bash -c 'set -x; systemctl --failed --no-legend --no-pager > /failed ; echo OK > /testok; while : ;do systemd-cat echo "testsuite service waiting for /var/log/journal" ; echo "testsuite service waiting for journal to move to /var/log/journal" > /dev/console ; for i in /var/log/journal/*;do [ -d "\$i" ] && echo "\$i" && break 2; done; sleep 1; done; sleep 1; exit 0;' +ExecStopPost=/usr/bin/systemctl poweroff +Type=oneshot +EOF + mkdir -p $initdir/etc/systemd/system/testsuite.target.wants + ln -fs ../testsuite.service $initdir/etc/systemd/system/testsuite.target.wants/testsuite.service + + # make the testsuite the default target + ln -fs testsuite.target $initdir/etc/systemd/system/default.target + mkdir -p $initdir/etc/rc.d + cat >$initdir/etc/rc.d/rc.local <<EOF +#!/bin/bash +exit 0 +EOF + chmod 0755 $initdir/etc/rc.d/rc.local + # install basic tools needed + dracut_install sh bash setsid loadkeys setfont \ + login sushell sulogin gzip sleep echo mount umount cryptsetup + dracut_install dmsetup modprobe + + instmods dm_crypt =crypto + + type -P dmeventd >/dev/null && dracut_install dmeventd + + inst_libdir_file "libdevmapper-event.so*" + + inst_rules 10-dm.rules 13-dm-disk.rules 95-dm-notify.rules + + # install libnss_files for login + inst_libdir_file "libnss_files*" + + # install dbus and pam + find \ + /etc/dbus-1 \ + /etc/pam.d \ + /etc/security \ + /lib64/security \ + /lib/security -xtype f \ + | while read file; do + inst $file + done + + # install dbus socket and service file + inst /usr/lib/systemd/system/dbus.socket + inst /usr/lib/systemd/system/dbus.service + + # install basic keyboard maps and fonts + for i in \ + /usr/lib/kbd/consolefonts/latarcyrheb-sun16* \ + /usr/lib/kbd/keymaps/include/* \ + /usr/lib/kbd/keymaps/i386/include/* \ + /usr/lib/kbd/keymaps/i386/qwerty/us.*; do + [[ -f $i ]] || continue + inst $i + done + + # some basic terminfo files + for _terminfodir in /lib/terminfo /etc/terminfo /usr/share/terminfo; do + [ -f ${_terminfodir}/l/linux ] && break + done + dracut_install -o ${_terminfodir}/l/linux + + # softlink mtab + ln -fs /proc/self/mounts $initdir/etc/mtab + + # install any Exec's from the service files + egrep -ho '^Exec[^ ]*=[^ ]+' $initdir/lib/systemd/system/*.service \ + | while read i; do + i=${i##Exec*=}; i=${i##-} + inst $i + done + + # install plymouth, if found... else remove plymouth service files + # if [ -x /usr/libexec/plymouth/plymouth-populate-initrd ]; then + # PLYMOUTH_POPULATE_SOURCE_FUNCTIONS="$TEST_BASE_DIR/test-functions" \ + # /usr/libexec/plymouth/plymouth-populate-initrd -t $initdir + # dracut_install plymouth plymouthd + # else + rm -f $initdir/{usr/lib,etc}/systemd/system/plymouth* $initdir/{usr/lib,etc}/systemd/system/*/plymouth* + # fi + + # some helper tools for debugging + [[ $DEBUGTOOLS ]] && dracut_install $DEBUGTOOLS + + # install ld.so.conf* and run ldconfig + cp -a /etc/ld.so.conf* $initdir/etc + ldconfig -r "$initdir" + ddebug "Strip binaeries" + find "$initdir" -perm +111 -type f | xargs strip --strip-unneeded | ddebug + + # copy depmod files + inst /lib/modules/$KERNEL_VER/modules.order + inst /lib/modules/$KERNEL_VER/modules.builtin + # generate module dependencies + if [[ -d $initdir/lib/modules/$KERNEL_VER ]] && \ + ! depmod -a -b "$initdir" $KERNEL_VER; then + dfatal "\"depmod -a $KERNEL_VER\" failed." + exit 1 + fi + ) + rm -fr $TESTDIR/nspawn-root + ddebug "cp -ar $TESTDIR/root $TESTDIR/nspawn-root" + cp -ar $TESTDIR/root $TESTDIR/nspawn-root + # we don't mount in the nspawn root + rm -fr $TESTDIR/nspawn-root/etc/fstab + + ddebug "umount $TESTDIR/root/var" + umount $TESTDIR/root/var + cryptsetup luksClose /dev/mapper/varcrypt + ddebug "umount $TESTDIR/root" + umount $TESTDIR/root +} + +test_cleanup() { + umount $TESTDIR/root/var 2>/dev/null + [[ -b /dev/mapper/varcrypt ]] && cryptsetup luksClose /dev/mapper/varcrypt + umount $TESTDIR/root 2>/dev/null + [[ $LOOPDEV ]] && losetup -d $LOOPDEV + return 0 +} + +. $TEST_BASE_DIR/test-functions +do_test "$@" diff --git a/test/a.service b/test/a.service new file mode 100644 index 0000000000..4168d2d051 --- /dev/null +++ b/test/a.service @@ -0,0 +1,7 @@ +[Unit] +Description=A +Requires=b.service +Before=b.service + +[Service] +ExecStart=/bin/true diff --git a/test/b.service b/test/b.service new file mode 100644 index 0000000000..e03bae36be --- /dev/null +++ b/test/b.service @@ -0,0 +1,6 @@ +[Unit] +Description=B +Wants=f.service + +[Service] +ExecStart=/bin/true diff --git a/test/c.service b/test/c.service new file mode 100644 index 0000000000..e2f60a8fbf --- /dev/null +++ b/test/c.service @@ -0,0 +1,6 @@ +[Unit] +Description=C +Requires=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/d.service b/test/d.service new file mode 100644 index 0000000000..921fd2ee1b --- /dev/null +++ b/test/d.service @@ -0,0 +1,8 @@ +[Unit] +Description=D:Cyclic +After=b.service +Before=a.service +Requires=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/e.service b/test/e.service new file mode 100644 index 0000000000..5ba98c7c43 --- /dev/null +++ b/test/e.service @@ -0,0 +1,8 @@ +[Unit] +Description=E:Cyclic +After=b.service +Before=a.service +Wants=a.service + +[Service] +ExecStart=/bin/true diff --git a/test/f.service b/test/f.service new file mode 100644 index 0000000000..7dde681c17 --- /dev/null +++ b/test/f.service @@ -0,0 +1,5 @@ +[Unit] +Description=F + +[Service] +ExecStart=/bin/true diff --git a/test/g.service b/test/g.service new file mode 100644 index 0000000000..cbfa82a454 --- /dev/null +++ b/test/g.service @@ -0,0 +1,6 @@ +[Unit] +Description=G +Conflicts=e.service + +[Service] +ExecStart=/bin/true diff --git a/test/h.service b/test/h.service new file mode 100644 index 0000000000..74a7751cad --- /dev/null +++ b/test/h.service @@ -0,0 +1,6 @@ +[Unit] +Description=H +Wants=g.service + +[Service] +ExecStart=/bin/true diff --git a/test/rule-syntax-check.py b/test/rule-syntax-check.py new file mode 100755 index 0000000000..4b602a925a --- /dev/null +++ b/test/rule-syntax-check.py @@ -0,0 +1,64 @@ +#!/usr/bin/python +# Simple udev rules syntax checker +# +# (C) 2010 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +import re +import sys + +if len(sys.argv) < 2: + print >> sys.stderr, 'Usage: %s <rules file> [...]' % sys.argv[0] + sys.exit(2) + +no_args_tests = re.compile('(ACTION|DEVPATH|KERNELS?|NAME|SYMLINK|SUBSYSTEMS?|DRIVERS?|TAG|RESULT|TEST)\s*(?:=|!)=\s*"([^"]*)"$') +args_tests = re.compile('(ATTRS?|ENV|TEST){([a-zA-Z0-9/_.*%-]+)}\s*(?:=|!)=\s*"([^"]*)"$') +no_args_assign = re.compile('(NAME|SYMLINK|OWNER|GROUP|MODE|TAG|PROGRAM|RUN|LABEL|GOTO|WAIT_FOR|OPTIONS|IMPORT)\s*(?:\+=|:=|=)\s*"([^"]*)"$') +args_assign = re.compile('(ATTR|ENV|IMPORT|RUN){([a-zA-Z0-9/_.*%-]+)}\s*(=|\+=)\s*"([^"]*)"$') + +result = 0 +buffer = '' +for path in sys.argv[1:]: + lineno = 0 + for line in open(path): + lineno += 1 + + # handle line continuation + if line.endswith('\\\n'): + buffer += line[:-2] + continue + else: + line = buffer + line + buffer = '' + + # filter out comments and empty lines + line = line.strip() + if not line or line.startswith('#'): + continue + + for clause in line.split(','): + clause = clause.strip() + if not (no_args_tests.match(clause) or args_tests.match(clause) or + no_args_assign.match(clause) or args_assign.match(clause)): + + print('Invalid line %s:%i: %s' % (path, lineno, line)) + print(' clause:', clause) + print() + result = 1 + break + +sys.exit(result) diff --git a/test/rules-test.sh b/test/rules-test.sh new file mode 100755 index 0000000000..1e224ff8b5 --- /dev/null +++ b/test/rules-test.sh @@ -0,0 +1,15 @@ +#!/bin/sh +# Call the udev rule syntax checker on all rules that we ship +# +# (C) 2010 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> + +[ -n "$srcdir" ] || srcdir=`dirname $0`/.. + +# skip if we don't have python +type python >/dev/null 2>&1 || { + echo "$0: No python installed, skipping udev rule syntax check" + exit 0 +} + +$srcdir/test/rule-syntax-check.py `find $srcdir/rules -name '*.rules'` diff --git a/test/sys.tar.xz b/test/sys.tar.xz Binary files differnew file mode 100644 index 0000000000..49ee8027b2 --- /dev/null +++ b/test/sys.tar.xz diff --git a/test/test-functions b/test/test-functions new file mode 100644 index 0000000000..0587cd4feb --- /dev/null +++ b/test/test-functions @@ -0,0 +1,864 @@ +#!/bin/bash +# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- +# ex: ts=8 sw=4 sts=4 et filetype=sh +PATH=/sbin:/bin:/usr/sbin:/usr/bin +export PATH + +KERNEL_VER=${KERNEL_VER-$(uname -r)} +KERNEL_MODS="/lib/modules/$KERNEL_VER/" + +setup_basic_dirs() { + for d in usr/bin usr/sbin bin etc lib "$libdir" sbin tmp usr var var/log dev proc sys sysroot root run run/lock run/initramfs; do + if [ -L "/$d" ]; then + inst_symlink "/$d" + else + inst_dir "/$d" + fi + done + + ln -sfn /run "$initdir/var/run" + ln -sfn /run/lock "$initdir/var/lock" +} + +inst_libs() { + local _bin=$1 + local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)' + local _file _line + + LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do + [[ $_line = 'not a dynamic executable' ]] && break + + if [[ $_line =~ $_so_regex ]]; then + _file=${BASH_REMATCH[1]} + [[ -e ${initdir}/$_file ]] && continue + inst_library "$_file" + continue + fi + + if [[ $_line =~ not\ found ]]; then + dfatal "Missing a shared library required by $_bin." + dfatal "Run \"ldd $_bin\" to find out what it is." + dfatal "$_line" + dfatal "dracut cannot create an initrd." + exit 1 + fi + done +} + +import_testdir() { + STATEFILE=".testdir" + [[ -e $STATEFILE ]] && . $STATEFILE + if [[ -z "$TESTDIR" ]] || [[ ! -d "$TESTDIR" ]]; then + TESTDIR=$(mktemp --tmpdir=/var/tmp -d -t systemd-test.XXXXXX) + echo "TESTDIR=\"$TESTDIR\"" > $STATEFILE + export TESTDIR + fi +} + +## @brief Converts numeric logging level to the first letter of level name. +# +# @param lvl Numeric logging level in range from 1 to 6. +# @retval 1 if @a lvl is out of range. +# @retval 0 if @a lvl is correct. +# @result Echoes first letter of level name. +_lvl2char() { + case "$1" in + 1) echo F;; + 2) echo E;; + 3) echo W;; + 4) echo I;; + 5) echo D;; + 6) echo T;; + *) return 1;; + esac +} + +## @brief Internal helper function for _do_dlog() +# +# @param lvl Numeric logging level. +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +# +# @note This function is not supposed to be called manually. Please use +# dtrace(), ddebug(), or others instead which wrap this one. +# +# This function calls _do_dlog() either with parameter msg, or if +# none is given, it will read standard input and will use every line as +# a message. +# +# This enables: +# dwarn "This is a warning" +# echo "This is a warning" | dwarn +LOG_LEVEL=4 + +dlog() { + [ -z "$LOG_LEVEL" ] && return 0 + [ $1 -le $LOG_LEVEL ] || return 0 + local lvl="$1"; shift + local lvlc=$(_lvl2char "$lvl") || return 0 + + if [ $# -ge 1 ]; then + echo "$lvlc: $*" + else + while read line; do + echo "$lvlc: " "$line" + done + fi +} + +## @brief Logs message at TRACE level (6) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +dtrace() { + set +x + dlog 6 "$@" + [ -n "$debug" ] && set -x || : +} + +## @brief Logs message at DEBUG level (5) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +ddebug() { +# set +x + dlog 5 "$@" +# [ -n "$debug" ] && set -x || : +} + +## @brief Logs message at INFO level (4) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +dinfo() { + set +x + dlog 4 "$@" + [ -n "$debug" ] && set -x || : +} + +## @brief Logs message at WARN level (3) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +dwarn() { + set +x + dlog 3 "$@" + [ -n "$debug" ] && set -x || : +} + +## @brief Logs message at ERROR level (2) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +derror() { +# set +x + dlog 2 "$@" +# [ -n "$debug" ] && set -x || : +} + +## @brief Logs message at FATAL level (1) +# +# @param msg Message. +# @retval 0 It's always returned, even if logging failed. +dfatal() { + set +x + dlog 1 "$@" + [ -n "$debug" ] && set -x || : +} + + +# Generic substring function. If $2 is in $1, return 0. +strstr() { [ "${1#*$2*}" != "$1" ]; } + +# normalize_path <path> +# Prints the normalized path, where it removes any duplicated +# and trailing slashes. +# Example: +# $ normalize_path ///test/test// +# /test/test +normalize_path() { + shopt -q -s extglob + set -- "${1//+(\/)//}" + shopt -q -u extglob + echo "${1%/}" +} + +# convert_abs_rel <from> <to> +# Prints the relative path, when creating a symlink to <to> from <from>. +# Example: +# $ convert_abs_rel /usr/bin/test /bin/test-2 +# ../../bin/test-2 +# $ ln -s $(convert_abs_rel /usr/bin/test /bin/test-2) /usr/bin/test +convert_abs_rel() { + local __current __absolute __abssize __cursize __newpath + local -i __i __level + + set -- "$(normalize_path "$1")" "$(normalize_path "$2")" + + # corner case #1 - self looping link + [[ "$1" == "$2" ]] && { echo "${1##*/}"; return; } + + # corner case #2 - own dir link + [[ "${1%/*}" == "$2" ]] && { echo "."; return; } + + IFS="/" __current=($1) + IFS="/" __absolute=($2) + + __abssize=${#__absolute[@]} + __cursize=${#__current[@]} + + while [[ ${__absolute[__level]} == ${__current[__level]} ]] + do + (( __level++ )) + if (( __level > __abssize || __level > __cursize )) + then + break + fi + done + + for ((__i = __level; __i < __cursize-1; __i++)) + do + if ((__i > __level)) + then + __newpath=$__newpath"/" + fi + __newpath=$__newpath".." + done + + for ((__i = __level; __i < __abssize; __i++)) + do + if [[ -n $__newpath ]] + then + __newpath=$__newpath"/" + fi + __newpath=$__newpath${__absolute[__i]} + done + + echo "$__newpath" +} + + +# Install a directory, keeping symlinks as on the original system. +# Example: if /lib points to /lib64 on the host, "inst_dir /lib/file" +# will create ${initdir}/lib64, ${initdir}/lib64/file, +# and a symlink ${initdir}/lib -> lib64. +inst_dir() { + [[ -e ${initdir}/"$1" ]] && return 0 # already there + + local _dir="$1" _part="${1%/*}" _file + while [[ "$_part" != "${_part%/*}" ]] && ! [[ -e "${initdir}/${_part}" ]]; do + _dir="$_part $_dir" + _part=${_part%/*} + done + + # iterate over parent directories + for _file in $_dir; do + [[ -e "${initdir}/$_file" ]] && continue + if [[ -L $_file ]]; then + inst_symlink "$_file" + else + # create directory + mkdir -m 0755 -p "${initdir}/$_file" || return 1 + [[ -e "$_file" ]] && chmod --reference="$_file" "${initdir}/$_file" + chmod u+w "${initdir}/$_file" + fi + done +} + +# $1 = file to copy to ramdisk +# $2 (optional) Name for the file on the ramdisk +# Location of the image dir is assumed to be $initdir +# We never overwrite the target if it exists. +inst_simple() { + [[ -f "$1" ]] || return 1 + strstr "$1" "/" || return 1 + + local _src=$1 target="${2:-$1}" + if ! [[ -d ${initdir}/$target ]]; then + [[ -e ${initdir}/$target ]] && return 0 + [[ -L ${initdir}/$target ]] && return 0 + [[ -d "${initdir}/${target%/*}" ]] || inst_dir "${target%/*}" + fi + # install checksum files also + if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then + inst "${_src%/*}/.${_src##*/}.hmac" "${target%/*}/.${target##*/}.hmac" + fi + ddebug "Installing $_src" + cp --sparse=always -pfL "$_src" "${initdir}/$target" +} + +# find symlinks linked to given library file +# $1 = library file +# Function searches for symlinks by stripping version numbers appended to +# library filename, checks if it points to the same target and finally +# prints the list of symlinks to stdout. +# +# Example: +# rev_lib_symlinks libfoo.so.8.1 +# output: libfoo.so.8 libfoo.so +# (Only if libfoo.so.8 and libfoo.so exists on host system.) +rev_lib_symlinks() { + [[ ! $1 ]] && return 0 + + local fn="$1" orig="$(readlink -f "$1")" links='' + + [[ ${fn} =~ .*\.so\..* ]] || return 1 + + until [[ ${fn##*.} == so ]]; do + fn="${fn%.*}" + [[ -L ${fn} && $(readlink -f "${fn}") == ${orig} ]] && links+=" ${fn}" + done + + echo "${links}" +} + +# Same as above, but specialized to handle dynamic libraries. +# It handles making symlinks according to how the original library +# is referenced. +inst_library() { + local _src="$1" _dest=${2:-$1} _lib _reallib _symlink + strstr "$1" "/" || return 1 + [[ -e $initdir/$_dest ]] && return 0 + if [[ -L $_src ]]; then + # install checksum files also + if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then + inst "${_src%/*}/.${_src##*/}.hmac" "${_dest%/*}/.${_dest##*/}.hmac" + fi + _reallib=$(readlink -f "$_src") + inst_simple "$_reallib" "$_reallib" + inst_dir "${_dest%/*}" + [[ -d "${_dest%/*}" ]] && _dest=$(readlink -f "${_dest%/*}")/${_dest##*/} + ln -sfn $(convert_abs_rel "${_dest}" "${_reallib}") "${initdir}/${_dest}" + else + inst_simple "$_src" "$_dest" + fi + + # Create additional symlinks. See rev_symlinks description. + for _symlink in $(rev_lib_symlinks $_src) $(rev_lib_symlinks $_reallib); do + [[ ! -e $initdir/$_symlink ]] && { + ddebug "Creating extra symlink: $_symlink" + inst_symlink $_symlink + } + done +} + +# find a binary. If we were not passed the full path directly, +# search in the usual places to find the binary. +find_binary() { + if [[ -z ${1##/*} ]]; then + if [[ -x $1 ]] || { strstr "$1" ".so" && ldd $1 &>/dev/null; }; then + echo $1 + return 0 + fi + fi + + type -P $1 +} + +# Same as above, but specialized to install binary executables. +# Install binary executable, and all shared library dependencies, if any. +inst_binary() { + local _bin _target + _bin=$(find_binary "$1") || return 1 + _target=${2:-$_bin} + [[ -e $initdir/$_target ]] && return 0 + [[ -L $_bin ]] && inst_symlink $_bin $_target && return 0 + local _file _line + local _so_regex='([^ ]*/lib[^/]*/[^ ]*\.so[^ ]*)' + # I love bash! + LC_ALL=C ldd "$_bin" 2>/dev/null | while read _line; do + [[ $_line = 'not a dynamic executable' ]] && break + + if [[ $_line =~ $_so_regex ]]; then + _file=${BASH_REMATCH[1]} + [[ -e ${initdir}/$_file ]] && continue + inst_library "$_file" + continue + fi + + if [[ $_line =~ not\ found ]]; then + dfatal "Missing a shared library required by $_bin." + dfatal "Run \"ldd $_bin\" to find out what it is." + dfatal "$_line" + dfatal "dracut cannot create an initrd." + exit 1 + fi + done + inst_simple "$_bin" "$_target" +} + +# same as above, except for shell scripts. +# If your shell script does not start with shebang, it is not a shell script. +inst_script() { + local _bin + _bin=$(find_binary "$1") || return 1 + shift + local _line _shebang_regex + read -r -n 80 _line <"$_bin" + # If debug is set, clean unprintable chars to prevent messing up the term + [[ $debug ]] && _line=$(echo -n "$_line" | tr -c -d '[:print:][:space:]') + _shebang_regex='(#! *)(/[^ ]+).*' + [[ $_line =~ $_shebang_regex ]] || return 1 + inst "${BASH_REMATCH[2]}" && inst_simple "$_bin" "$@" +} + +# same as above, but specialized for symlinks +inst_symlink() { + local _src=$1 _target=${2:-$1} _realsrc + strstr "$1" "/" || return 1 + [[ -L $1 ]] || return 1 + [[ -L $initdir/$_target ]] && return 0 + _realsrc=$(readlink -f "$_src") + if ! [[ -e $initdir/$_realsrc ]]; then + if [[ -d $_realsrc ]]; then + inst_dir "$_realsrc" + else + inst "$_realsrc" + fi + fi + [[ ! -e $initdir/${_target%/*} ]] && inst_dir "${_target%/*}" + [[ -d ${_target%/*} ]] && _target=$(readlink -f ${_target%/*})/${_target##*/} + ln -sfn $(convert_abs_rel "${_target}" "${_realsrc}") "$initdir/$_target" +} + +# attempt to install any programs specified in a udev rule +inst_rule_programs() { + local _prog _bin + + if grep -qE 'PROGRAM==?"[^ "]+' "$1"; then + for _prog in $(grep -E 'PROGRAM==?"[^ "]+' "$1" | sed -r 's/.*PROGRAM==?"([^ "]+).*/\1/'); do + if [ -x /lib/udev/$_prog ]; then + _bin=/lib/udev/$_prog + else + _bin=$(find_binary "$_prog") || { + dinfo "Skipping program $_prog using in udev rule $(basename $1) as it cannot be found" + continue; + } + fi + + #dinfo "Installing $_bin due to it's use in the udev rule $(basename $1)" + dracut_install "$_bin" + done + fi +} + +# udev rules always get installed in the same place, so +# create a function to install them to make life simpler. +inst_rules() { + local _target=/etc/udev/rules.d _rule _found + + inst_dir "/lib/udev/rules.d" + inst_dir "$_target" + for _rule in "$@"; do + if [ "${rule#/}" = "$rule" ]; then + for r in /lib/udev/rules.d /etc/udev/rules.d; do + if [[ -f $r/$_rule ]]; then + _found="$r/$_rule" + inst_simple "$_found" + inst_rule_programs "$_found" + fi + done + fi + for r in '' ./ $dracutbasedir/rules.d/; do + if [[ -f ${r}$_rule ]]; then + _found="${r}$_rule" + inst_simple "$_found" "$_target/${_found##*/}" + inst_rule_programs "$_found" + fi + done + [[ $_found ]] || dinfo "Skipping udev rule: $_rule" + done +} + +# general purpose installation function +# Same args as above. +inst() { + local _x + + case $# in + 1) ;; + 2) [[ ! $initdir && -d $2 ]] && export initdir=$2 + [[ $initdir = $2 ]] && set $1;; + 3) [[ -z $initdir ]] && export initdir=$2 + set $1 $3;; + *) dfatal "inst only takes 1 or 2 or 3 arguments" + exit 1;; + esac + for _x in inst_symlink inst_script inst_binary inst_simple; do + $_x "$@" && return 0 + done + return 1 +} + +# install any of listed files +# +# If first argument is '-d' and second some destination path, first accessible +# source is installed into this path, otherwise it will installed in the same +# path as source. If none of listed files was installed, function return 1. +# On first successful installation it returns with 0 status. +# +# Example: +# +# inst_any -d /bin/foo /bin/bar /bin/baz +# +# Lets assume that /bin/baz exists, so it will be installed as /bin/foo in +# initramfs. +inst_any() { + local to f + + [[ $1 = '-d' ]] && to="$2" && shift 2 + + for f in "$@"; do + if [[ -e $f ]]; then + [[ $to ]] && inst "$f" "$to" && return 0 + inst "$f" && return 0 + fi + done + + return 1 +} + +# dracut_install [-o ] <file> [<file> ... ] +# Install <file> to the initramfs image +# -o optionally install the <file> and don't fail, if it is not there +dracut_install() { + local _optional=no + if [[ $1 = '-o' ]]; then + _optional=yes + shift + fi + while (($# > 0)); do + if ! inst "$1" ; then + if [[ $_optional = yes ]]; then + dinfo "Skipping program $1 as it cannot be found and is" \ + "flagged to be optional" + else + dfatal "Failed to install $1" + exit 1 + fi + fi + shift + done +} + +# Install a single kernel module along with any firmware it may require. +# $1 = full path to kernel module to install +install_kmod_with_fw() { + # no need to go further if the module is already installed + + [[ -e "${initdir}/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" ]] \ + && return 0 + + [[ -e "$initdir/.kernelmodseen/${1##*/}" ]] && return 0 + + if [[ $omit_drivers ]]; then + local _kmod=${1##*/} + _kmod=${_kmod%.ko} + _kmod=${_kmod/-/_} + if [[ "$_kmod" =~ $omit_drivers ]]; then + dinfo "Omitting driver $_kmod" + return 1 + fi + if [[ "${1##*/lib/modules/$KERNEL_VER/}" =~ $omit_drivers ]]; then + dinfo "Omitting driver $_kmod" + return 1 + fi + fi + + [ -d "$initdir/.kernelmodseen" ] && \ + > "$initdir/.kernelmodseen/${1##*/}" + + inst_simple "$1" "/lib/modules/$KERNEL_VER/${1##*/lib/modules/$KERNEL_VER/}" \ + || return $? + + local _modname=${1##*/} _fwdir _found _fw + _modname=${_modname%.ko*} + for _fw in $(modinfo -k $KERNEL_VER -F firmware $1 2>/dev/null); do + _found='' + for _fwdir in $fw_dir; do + if [[ -d $_fwdir && -f $_fwdir/$_fw ]]; then + inst_simple "$_fwdir/$_fw" "/lib/firmware/$_fw" + _found=yes + fi + done + if [[ $_found != yes ]]; then + if ! grep -qe "\<${_modname//-/_}\>" /proc/modules; then + dinfo "Possible missing firmware \"${_fw}\" for kernel module" \ + "\"${_modname}.ko\"" + else + dwarn "Possible missing firmware \"${_fw}\" for kernel module" \ + "\"${_modname}.ko\"" + fi + fi + done + return 0 +} + +# Do something with all the dependencies of a kernel module. +# Note that kernel modules depend on themselves using the technique we use +# $1 = function to call for each dependency we find +# It will be passed the full path to the found kernel module +# $2 = module to get dependencies for +# rest of args = arguments to modprobe +# _fderr specifies FD passed from surrounding scope +for_each_kmod_dep() { + local _func=$1 _kmod=$2 _cmd _modpath _options _found=0 + shift 2 + modprobe "$@" --ignore-install --show-depends $_kmod 2>&${_fderr} | ( + while read _cmd _modpath _options; do + [[ $_cmd = insmod ]] || continue + $_func ${_modpath} || exit $? + _found=1 + done + [[ $_found -eq 0 ]] && exit 1 + exit 0 + ) +} + +# filter kernel modules to install certain modules that meet specific +# requirements. +# $1 = search only in subdirectory of /kernel/$1 +# $2 = function to call with module name to filter. +# This function will be passed the full path to the module to test. +# The behavior of this function can vary depending on whether $hostonly is set. +# If it is, we will only look at modules that are already in memory. +# If it is not, we will look at all kernel modules +# This function returns the full filenames of modules that match $1 +filter_kernel_modules_by_path () ( + local _modname _filtercmd + if ! [[ $hostonly ]]; then + _filtercmd='find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra"' + _filtercmd+=' "$KERNEL_MODS/weak-updates" -name "*.ko" -o -name "*.ko.gz"' + _filtercmd+=' -o -name "*.ko.xz"' + _filtercmd+=' 2>/dev/null' + else + _filtercmd='cut -d " " -f 1 </proc/modules|xargs modinfo -F filename ' + _filtercmd+='-k $KERNEL_VER 2>/dev/null' + fi + for _modname in $(eval $_filtercmd); do + case $_modname in + *.ko) "$2" "$_modname" && echo "$_modname";; + *.ko.gz) gzip -dc "$_modname" > $initdir/$$.ko + $2 $initdir/$$.ko && echo "$_modname" + rm -f $initdir/$$.ko + ;; + *.ko.xz) xz -dc "$_modname" > $initdir/$$.ko + $2 $initdir/$$.ko && echo "$_modname" + rm -f $initdir/$$.ko + ;; + esac + done +) +find_kernel_modules_by_path () ( + if ! [[ $hostonly ]]; then + find "$KERNEL_MODS/kernel/$1" "$KERNEL_MODS/extra" "$KERNEL_MODS/weak-updates" \ + -name "*.ko" -o -name "*.ko.gz" -o -name "*.ko.xz" 2>/dev/null + else + cut -d " " -f 1 </proc/modules \ + | xargs modinfo -F filename -k $KERNEL_VER 2>/dev/null + fi +) + +filter_kernel_modules () { + filter_kernel_modules_by_path drivers "$1" +} + +find_kernel_modules () { + find_kernel_modules_by_path drivers +} + +# instmods [-c] <kernel module> [<kernel module> ... ] +# instmods [-c] <kernel subsystem> +# install kernel modules along with all their dependencies. +# <kernel subsystem> can be e.g. "=block" or "=drivers/usb/storage" +instmods() { + [[ $no_kernel = yes ]] && return + # called [sub]functions inherit _fderr + local _fderr=9 + local _check=no + if [[ $1 = '-c' ]]; then + _check=yes + shift + fi + + function inst1mod() { + local _ret=0 _mod="$1" + case $_mod in + =*) + if [ -f $KERNEL_MODS/modules.${_mod#=} ]; then + ( [[ "$_mpargs" ]] && echo $_mpargs + cat "${KERNEL_MODS}/modules.${_mod#=}" ) \ + | instmods + else + ( [[ "$_mpargs" ]] && echo $_mpargs + find "$KERNEL_MODS" -path "*/${_mod#=}/*" -printf '%f\n' ) \ + | instmods + fi + ;; + --*) _mpargs+=" $_mod" ;; + i2o_scsi) return ;; # Do not load this diagnostic-only module + *) + _mod=${_mod##*/} + # if we are already installed, skip this module and go on + # to the next one. + [[ -f "$initdir/.kernelmodseen/${_mod%.ko}.ko" ]] && return + + if [[ $omit_drivers ]] && [[ "$1" =~ $omit_drivers ]]; then + dinfo "Omitting driver ${_mod##$KERNEL_MODS}" + return + fi + # If we are building a host-specific initramfs and this + # module is not already loaded, move on to the next one. + [[ $hostonly ]] && ! grep -qe "\<${_mod//-/_}\>" /proc/modules \ + && ! echo $add_drivers | grep -qe "\<${_mod}\>" \ + && return + + # We use '-d' option in modprobe only if modules prefix path + # differs from default '/'. This allows us to use Dracut with + # old version of modprobe which doesn't have '-d' option. + local _moddirname=${KERNEL_MODS%%/lib/modules/*} + [[ -n ${_moddirname} ]] && _moddirname="-d ${_moddirname}/" + + # ok, load the module, all its dependencies, and any firmware + # it may require + for_each_kmod_dep install_kmod_with_fw $_mod \ + --set-version $KERNEL_VER ${_moddirname} $_mpargs + ((_ret+=$?)) + ;; + esac + return $_ret + } + + function instmods_1() { + local _mod _mpargs + if (($# == 0)); then # filenames from stdin + while read _mod; do + inst1mod "${_mod%.ko*}" || { + if [ "$_check" = "yes" ]; then + dfatal "Failed to install $_mod" + return 1 + fi + } + done + fi + while (($# > 0)); do # filenames as arguments + inst1mod ${1%.ko*} || { + if [ "$_check" = "yes" ]; then + dfatal "Failed to install $1" + return 1 + fi + } + shift + done + return 0 + } + + local _ret _filter_not_found='FATAL: Module .* not found.' + set -o pipefail + # Capture all stderr from modprobe to _fderr. We could use {var}>... + # redirections, but that would make dracut require bash4 at least. + eval "( instmods_1 \"\$@\" ) ${_fderr}>&1" \ + | while read line; do [[ "$line" =~ $_filter_not_found ]] && echo $line || echo $line >&2 ;done | derror + _ret=$? + set +o pipefail + return $_ret +} + +# inst_libdir_file [-n <pattern>] <file> [<file>...] +# Install a <file> located on a lib directory to the initramfs image +# -n <pattern> install non-matching files +inst_libdir_file() { + if [[ "$1" == "-n" ]]; then + local _pattern=$1 + shift 2 + for _dir in $libdirs; do + for _i in "$@"; do + for _f in "$_dir"/$_i; do + [[ "$_i" =~ $_pattern ]] || continue + [[ -e "$_i" ]] && dracut_install "$_i" + done + done + done + else + for _dir in $libdirs; do + for _i in "$@"; do + for _f in "$_dir"/$_i; do + [[ -e "$_f" ]] && dracut_install "$_f" + done + done + done + fi +} + +check_qemu() { + command -v qemu-kvm &>/dev/null && [[ -c /dev/kvm ]] +} + +check_nspawn() { + [[ -d /sys/fs/cgroup/systemd ]] +} + + +do_test() { + if [[ $UID != "0" ]]; then + echo "TEST: $TEST_DESCRIPTION [SKIPPED]: not root" >&2 + exit 0 + fi + +# Detect lib paths + [[ $libdir ]] || for libdir in /lib64 /lib; do + [[ -d $libdir ]] && libdirs+=" $libdir" && break + done + + [[ $usrlibdir ]] || for usrlibdir in /usr/lib64 /usr/lib; do + [[ -d $usrlibdir ]] && libdirs+=" $usrlibdir" && break + done + + import_testdir + + while (($# > 0)); do + case $1 in + --run) + echo "TEST RUN: $TEST_DESCRIPTION" + test_run + ret=$? + if [ $ret -eq 0 ]; then + echo "TEST RUN: $TEST_DESCRIPTION [OK]" + else + echo "TEST RUN: $TEST_DESCRIPTION [FAILED]" + fi + exit $ret;; + --setup) + echo "TEST SETUP: $TEST_DESCRIPTION" + test_setup + exit $?;; + --clean) + echo "TEST CLEANUP: $TEST_DESCRIPTION" + test_cleanup + rm -fr "$TESTDIR" + rm -f .testdir + exit $?;; + --all) + echo -n "TEST: $TEST_DESCRIPTION "; + ( + test_setup && test_run + ret=$? + test_cleanup + rm -fr "$TESTDIR" + rm -f .testdir + exit $ret + ) </dev/null >test.log 2>&1 + ret=$? + if [ $ret -eq 0 ]; then + rm test.log + echo "[OK]" + else + echo "[FAILED]" + echo "see $(pwd)/test.log" + fi + exit $ret;; + *) break ;; + esac + shift + done +} diff --git a/test/udev-test.pl b/test/udev-test.pl new file mode 100755 index 0000000000..a9f5db03cf --- /dev/null +++ b/test/udev-test.pl @@ -0,0 +1,1530 @@ +#!/usr/bin/perl + +# udev test +# +# Provides automated testing of the udev binary. +# The whole test is self contained in this file, except the matching sysfs tree. +# Simply extend the @tests array, to add a new test variant. +# +# Every test is driven by its own temporary config file. +# This program prepares the environment, creates the config and calls udev. +# +# udev parses the rules, looks at the provided sysfs and +# first creates and then removes the device node. +# After creation and removal the result is checked against the +# expected value and the result is printed. +# +# Copyright (C) 2004-2012 Kay Sievers <kay@vrfy.org> +# Copyright (C) 2004 Leann Ogasawara <ogasawara@osdl.org> + +use warnings; +use strict; + +my $udev_bin = "./test-udev"; +my $valgrind = 0; +my $udev_bin_valgrind = "valgrind --tool=memcheck --leak-check=yes --quiet $udev_bin"; +my $udev_dev = "test/dev"; +my $udev_run = "test/run"; +my $udev_rules_dir = "$udev_run/udev/rules.d"; +my $udev_rules = "$udev_rules_dir/udev-test.rules"; + +my @tests = ( + { + desc => "no rules", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "sda" , + exp_rem_error => "yes", + rules => <<EOF +# +EOF + }, + { + desc => "label test of scsi disc", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "boot_disk" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "label test of scsi disc", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "boot_disk" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "label test of scsi disc", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "boot_disk" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "label test of scsi partition", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "boot_disk1" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="boot_disk%n" +EOF + }, + { + desc => "label test of pattern match", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "boot_disk1" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="?ATA", SYMLINK+="boot_disk%n-1" +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA?", SYMLINK+="boot_disk%n-2" +SUBSYSTEMS=="scsi", ATTRS{vendor}=="A??", SYMLINK+="boot_disk%n" +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATAS", SYMLINK+="boot_disk%n-3" +EOF + }, + { + desc => "label test of multiple sysfs files", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "boot_disk1" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS X ", SYMLINK+="boot_diskX%n" +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", SYMLINK+="boot_disk%n" +EOF + }, + { + desc => "label test of max sysfs files (skip invalid rule)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "boot_disk1" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", ATTRS{queue_depth}=="32", SYMLINK+="boot_diskXX%n" +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", ATTRS{model}=="ST910021AS", ATTRS{scsi_level}=="6", ATTRS{rev}=="4.06", ATTRS{type}=="0", SYMLINK+="boot_disk%n" +EOF + }, + { + desc => "catch device by *", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem/0" , + rules => <<EOF +KERNEL=="ttyACM*", SYMLINK+="modem/%n" +EOF + }, + { + desc => "catch device by * - take 2", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem/0" , + rules => <<EOF +KERNEL=="*ACM1", SYMLINK+="bad" +KERNEL=="*ACM0", SYMLINK+="modem/%n" +EOF + }, + { + desc => "catch device by ?", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem/0" , + rules => <<EOF +KERNEL=="ttyACM??*", SYMLINK+="modem/%n-1" +KERNEL=="ttyACM??", SYMLINK+="modem/%n-2" +KERNEL=="ttyACM?", SYMLINK+="modem/%n" +EOF + }, + { + desc => "catch device by character class", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem/0" , + rules => <<EOF +KERNEL=="ttyACM[A-Z]*", SYMLINK+="modem/%n-1" +KERNEL=="ttyACM?[0-9]", SYMLINK+="modem/%n-2" +KERNEL=="ttyACM[0-9]*", SYMLINK+="modem/%n" +EOF + }, + { + desc => "replace kernel name", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "Handle comment lines in config file (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF +# this is a comment +KERNEL=="ttyACM0", SYMLINK+="modem" + +EOF + }, + { + desc => "Handle comment lines in config file with whitespace (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF + # this is a comment with whitespace before the comment +KERNEL=="ttyACM0", SYMLINK+="modem" + +EOF + }, + { + desc => "Handle whitespace only lines (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "whitespace" , + rules => <<EOF + + + + # this is a comment with whitespace before the comment +KERNEL=="ttyACM0", SYMLINK+="whitespace" + + + +EOF + }, + { + desc => "Handle empty lines in config file (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF + +KERNEL=="ttyACM0", SYMLINK+="modem" + +EOF + }, + { + desc => "Handle backslashed multi lines in config file (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF +KERNEL=="ttyACM0", \\ +SYMLINK+="modem" + +EOF + }, + { + desc => "preserve backslashes, if they are not for a newline", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "aaa", + rules => <<EOF +KERNEL=="ttyACM0", PROGRAM=="/bin/echo -e \\101", RESULT=="A", SYMLINK+="aaa" +EOF + }, + { + desc => "Handle stupid backslashed multi lines in config file (and replace kernel name)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF + +# +\\ + +\\ + +#\\ + +KERNEL=="ttyACM0", \\ + SYMLINK+="modem" + +EOF + }, + { + desc => "subdirectory handling", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "sub/direct/ory/modem" , + rules => <<EOF +KERNEL=="ttyACM0", SYMLINK+="sub/direct/ory/modem" +EOF + }, + { + desc => "parent device name match of scsi partition", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "first_disk5" , + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="first_disk%n" +EOF + }, + { + desc => "test substitution chars", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "Major:8:minor:5:kernelnumber:5:id:0:0:0:0" , + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:%M:minor:%m:kernelnumber:%n:id:%b" +EOF + }, + { + desc => "import of shell-value returned from program", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node12345678", + rules => <<EOF +SUBSYSTEMS=="scsi", IMPORT{program}="/bin/echo -e \' TEST_KEY=12345678\\n TEST_key2=98765\'", SYMLINK+="node\$env{TEST_KEY}" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "sustitution of sysfs value (%s{file})", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "disk-ATA-sda" , + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{vendor}=="ATA", SYMLINK+="disk-%s{vendor}-%k" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "program result substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "special-device-5" , + not_exp_name => "not" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="-special-*", SYMLINK+="not" +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n special-device", RESULT=="special-*", SYMLINK+="%c-%n" +EOF + }, + { + desc => "program result substitution (newline removal)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "newline_removed" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo test", RESULT=="test", SYMLINK+="newline_removed" +EOF + }, + { + desc => "program result substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "test-0:0:0:0" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n test-%b", RESULT=="test-0:0*", SYMLINK+="%c" +EOF + }, + { + desc => "program with lots of arguments", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "foo9" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="%c{7}" +EOF + }, + { + desc => "program with subshell", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "bar9" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/sh -c 'echo foo3 foo4 foo5 foo6 foo7 foo8 foo9 | sed s/foo9/bar9/'", KERNEL=="sda5", SYMLINK+="%c{7}" +EOF + }, + { + desc => "program arguments combined with apostrophes", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "foo7" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n 'foo3 foo4' 'foo5 foo6 foo7 foo8'", KERNEL=="sda5", SYMLINK+="%c{5}" +EOF + }, + { + desc => "characters before the %c{N} substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "my-foo9" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{7}" +EOF + }, + { + desc => "substitute the second to last argument", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "my-foo8" , + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo3 foo4 foo5 foo6 foo7 foo8 foo9", KERNEL=="sda5", SYMLINK+="my-%c{6}" +EOF + }, + { + desc => "test substitution by variable name", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="Major:\$major-minor:\$minor-kernelnumber:\$number-id:\$id" +EOF + }, + { + desc => "test substitution by variable name 2", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "Major:8-minor:5-kernelnumber:5-id:0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="Major:\$major-minor:%m-kernelnumber:\$number-id:\$id" +EOF + }, + { + desc => "test substitution by variable name 3", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "850:0:0:05" , + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="%M%m%b%n" +EOF + }, + { + desc => "test substitution by variable name 4", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "855" , + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major\$minor\$number" +EOF + }, + { + desc => "test substitution by variable name 5", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "8550:0:0:0" , + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", DEVPATH=="*/sda/*", SYMLINK+="\$major%m%n\$id" +EOF + }, + { + desc => "non matching SUBSYSTEMS for device with no parent", + devpath => "/devices/virtual/tty/console", + exp_name => "TTY", + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n foo", RESULT=="foo", SYMLINK+="foo" +KERNEL=="console", SYMLINK+="TTY" +EOF + }, + { + desc => "non matching SUBSYSTEMS", + devpath => "/devices/virtual/tty/console", + exp_name => "TTY" , + rules => <<EOF +SUBSYSTEMS=="foo", ATTRS{dev}=="5:1", SYMLINK+="foo" +KERNEL=="console", SYMLINK+="TTY" +EOF + }, + { + desc => "ATTRS match", + devpath => "/devices/virtual/tty/console", + exp_name => "foo" , + rules => <<EOF +KERNEL=="console", SYMLINK+="TTY" +ATTRS{dev}=="5:1", SYMLINK+="foo" +EOF + }, + { + desc => "ATTR (empty file)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "empty" , + rules => <<EOF +KERNEL=="sda", ATTR{test_empty_file}=="?*", SYMLINK+="something" +KERNEL=="sda", ATTR{test_empty_file}!="", SYMLINK+="not-empty" +KERNEL=="sda", ATTR{test_empty_file}=="", SYMLINK+="empty" +KERNEL=="sda", ATTR{test_empty_file}!="?*", SYMLINK+="not-something" +EOF + }, + { + desc => "ATTR (non-existent file)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "non-existent" , + rules => <<EOF +KERNEL=="sda", ATTR{nofile}=="?*", SYMLINK+="something" +KERNEL=="sda", ATTR{nofile}!="", SYMLINK+="not-empty" +KERNEL=="sda", ATTR{nofile}=="", SYMLINK+="empty" +KERNEL=="sda", ATTR{nofile}!="?*", SYMLINK+="not-something" +KERNEL=="sda", TEST!="nofile", SYMLINK+="non-existent" +KERNEL=="sda", SYMLINK+="wrong" +EOF + }, + { + desc => "program and bus type match", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "scsi-0:0:0:0" , + rules => <<EOF +SUBSYSTEMS=="usb", PROGRAM=="/bin/echo -n usb-%b", SYMLINK+="%c" +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n scsi-%b", SYMLINK+="%c" +SUBSYSTEMS=="foo", PROGRAM=="/bin/echo -n foo-%b", SYMLINK+="%c" +EOF + }, + { + desc => "sysfs parent hierarchy", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem" , + rules => <<EOF +ATTRS{idProduct}=="007b", SYMLINK+="modem" +EOF + }, + { + desc => "name test with ! in the name", + devpath => "/devices/virtual/block/fake!blockdev0", + exp_name => "is/a/fake/blockdev0" , + rules => <<EOF +SUBSYSTEMS=="scsi", SYMLINK+="is/not/a/%k" +SUBSYSTEM=="block", SYMLINK+="is/a/%k" +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "name test with ! in the name, but no matching rule", + devpath => "/devices/virtual/block/fake!blockdev0", + exp_name => "fake/blockdev0" , + exp_rem_error => "yes", + rules => <<EOF +KERNEL=="ttyACM0", SYMLINK+="modem" +EOF + }, + { + desc => "KERNELS rule", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "scsi-0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="usb", KERNELS=="0:0:0:0", SYMLINK+="not-scsi" +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:1", SYMLINK+="no-match" +SUBSYSTEMS=="scsi", KERNELS==":0", SYMLINK+="short-id" +SUBSYSTEMS=="scsi", KERNELS=="/0:0:0:0", SYMLINK+="no-match" +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="scsi-0:0:0:0" +EOF + }, + { + desc => "KERNELS wildcard all", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "scsi-0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="*:1", SYMLINK+="no-match" +SUBSYSTEMS=="scsi", KERNELS=="*:0:1", SYMLINK+="no-match" +SUBSYSTEMS=="scsi", KERNELS=="*:0:0:1", SYMLINK+="no-match" +SUBSYSTEMS=="scsi", KERNEL=="0:0:0:0", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNELS=="*", SYMLINK+="scsi-0:0:0:0" +EOF + }, + { + desc => "KERNELS wildcard partial", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "scsi-0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNELS=="*:0", SYMLINK+="scsi-0:0:0:0" +EOF + }, + { + desc => "KERNELS wildcard partial 2", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "scsi-0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNELS=="0:0:0:0", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNELS=="*:0:0:0", SYMLINK+="scsi-0:0:0:0" +EOF + }, + { + desc => "substitute attr with link target value (first match)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "driver-is-sd", + rules => <<EOF +SUBSYSTEMS=="scsi", SYMLINK+="driver-is-\$attr{driver}" +EOF + }, + { + desc => "substitute attr with link target value (currently selected device)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "driver-is-ahci", + rules => <<EOF +SUBSYSTEMS=="pci", SYMLINK+="driver-is-\$attr{driver}" +EOF + }, + { + desc => "ignore ATTRS attribute whitespace", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "ignored", + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE", SYMLINK+="ignored" +EOF + }, + { + desc => "do not ignore ATTRS attribute whitespace", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "matched-with-space", + rules => <<EOF +SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="wrong-to-ignore" +SUBSYSTEMS=="scsi", ATTRS{whitespace_test}=="WHITE SPACE ", SYMLINK+="matched-with-space" +EOF + }, + { + desc => "permissions USER=bad GROUP=name", + devpath => "/devices/virtual/tty/tty33", + exp_name => "tty33", + exp_perms => "0:0:0600", + rules => <<EOF +KERNEL=="tty33", OWNER="bad", GROUP="name" +EOF + }, + { + desc => "permissions OWNER=5000", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => "5000::0600", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="5000" +EOF + }, + { + desc => "permissions GROUP=100", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => ":100:0660", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="100" +EOF + }, + { + desc => "textual user id", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => "nobody::0600", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="nobody" +EOF + }, + { + desc => "textual group id", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => ":daemon:0660", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", GROUP="daemon" +EOF + }, + { + desc => "textual user/group id", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => "root:mail:0660", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="root", GROUP="mail" +EOF + }, + { + desc => "permissions MODE=0777", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => "::0777", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", MODE="0777" +EOF + }, + { + desc => "permissions OWNER=5000 GROUP=100 MODE=0777", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_perms => "5000:100:0777", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", OWNER="5000", GROUP="100", MODE="0777" +EOF + }, + { + desc => "permissions OWNER to 5000", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "5000::", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="5000" +EOF + }, + { + desc => "permissions GROUP to 100", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => ":100:0660", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="100" +EOF + }, + { + desc => "permissions MODE to 0060", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "::0060", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", MODE="0060" +EOF + }, + { + desc => "permissions OWNER, GROUP, MODE", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "5000:100:0777", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", OWNER="5000", GROUP="100", MODE="0777" +EOF + }, + { + desc => "permissions only rule", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "5000:100:0777", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", OWNER="5000", GROUP="100", MODE="0777" +KERNEL=="ttyUSX[0-9]*", OWNER="5001", GROUP="101", MODE="0444" +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n" +EOF + }, + { + desc => "multiple permissions only rule", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "3000:4000:0777", + rules => <<EOF +SUBSYSTEM=="tty", OWNER="3000" +SUBSYSTEM=="tty", GROUP="4000" +SUBSYSTEM=="tty", MODE="0777" +KERNEL=="ttyUSX[0-9]*", OWNER="5001", GROUP="101", MODE="0444" +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n" +EOF + }, + { + desc => "permissions only rule with override at SYMLINK+ rule", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "ttyACM0", + exp_perms => "3000:8000:0777", + rules => <<EOF +SUBSYSTEM=="tty", OWNER="3000" +SUBSYSTEM=="tty", GROUP="4000" +SUBSYSTEM=="tty", MODE="0777" +KERNEL=="ttyUSX[0-9]*", OWNER="5001", GROUP="101", MODE="0444" +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", GROUP="8000" +EOF + }, + { + desc => "major/minor number test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + exp_majorminor => "8:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node" +EOF + }, + { + desc => "big major number test", + devpath => "/devices/virtual/misc/misc-fake1", + exp_name => "node", + exp_majorminor => "4095:1", + rules => <<EOF +KERNEL=="misc-fake1", SYMLINK+="node" +EOF + }, + { + desc => "big major and big minor number test", + devpath => "/devices/virtual/misc/misc-fake89999", + exp_name => "node", + exp_majorminor => "4095:89999", + rules => <<EOF +KERNEL=="misc-fake89999", SYMLINK+="node" +EOF + }, + { + desc => "multiple symlinks with format char", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "symlink2-ttyACM0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK="symlink1-%n symlink2-%k symlink3-%b" +EOF + }, + { + desc => "multiple symlinks with a lot of s p a c e s", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "one", + not_exp_name => " ", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK=" one two " +EOF + }, + { + desc => "symlink creation (same directory)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "modem0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK="modem%n" +EOF + }, + { + desc => "multiple symlinks", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "second-0" , + rules => <<EOF +KERNEL=="ttyACM0", SYMLINK="first-%n second-%n third-%n" +EOF + }, + { + desc => "symlink name '.'", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => ".", + exp_add_error => "yes", + exp_rem_error => "yes", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="." +EOF + }, + { + desc => "symlink node to itself", + devpath => "/devices/virtual/tty/tty0", + exp_name => "link", + exp_add_error => "yes", + exp_rem_error => "yes", + option => "clean", + rules => <<EOF +KERNEL=="tty0", SYMLINK+="tty0" +EOF + }, + { + desc => "symlink %n substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "symlink0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink%n" +EOF + }, + { + desc => "symlink %k substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "symlink-ttyACM0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="symlink-%k" +EOF + }, + { + desc => "symlink %M:%m substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "major-166:0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="ttyACM%n", SYMLINK+="major-%M:%m" +EOF + }, + { + desc => "symlink %b substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "symlink-0:0:0:0", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="symlink-%b" +EOF + }, + { + desc => "symlink %c substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "test", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo test", SYMLINK+="%c" +EOF + }, + { + desc => "symlink %c{N} substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "test", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2}" +EOF + }, + { + desc => "symlink %c{N+} substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "this", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", PROGRAM=="/bin/echo symlink test this", SYMLINK+="%c{2+}" +EOF + }, + { + desc => "symlink only rule with %c{N+}", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "test", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/bin/echo link test this" SYMLINK+="%c{2+}" +EOF + }, + { + desc => "symlink %s{filename} substitution", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "166:0", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="%s{dev}" +EOF + }, + { + desc => "program result substitution (numbered part of)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "link1", + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2", RESULT=="node *", SYMLINK+="%c{2} %c{3}" +EOF + }, + { + desc => "program result substitution (numbered part of+)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda5", + exp_name => "link4", + rules => <<EOF +SUBSYSTEMS=="scsi", PROGRAM=="/bin/echo -n node link1 link2 link3 link4", RESULT=="node *", SYMLINK+="%c{2+}" +EOF + }, + { + desc => "SUBSYSTEM match test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", SUBSYSTEM=="vc" +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", SUBSYSTEM=="block" +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match2", SUBSYSTEM=="vc" +EOF + }, + { + desc => "DRIVERS match test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="should_not_match", DRIVERS=="sd-wrong" +SUBSYSTEMS=="scsi", KERNEL=="sda", SYMLINK+="node", DRIVERS=="sd" +EOF + }, + { + desc => "devnode substitution test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda", PROGRAM=="/usr/bin/test -b %N" SYMLINK+="node" +EOF + }, + { + desc => "parent node name substitution test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "sda-part-1", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="%P-part-1" +EOF + }, + { + desc => "udev_root substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "start-/dev-end", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="start-%r-end" +EOF + }, + { + desc => "last_rule option", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "last", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="last", OPTIONS="last_rule" +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="very-last" +EOF + }, + { + desc => "negation KERNEL!=", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "match", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL!="sda1", SYMLINK+="matches-but-is-negated" +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNEL!="xsda1", SYMLINK+="match" +EOF + }, + { + desc => "negation SUBSYSTEM!=", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "not-anything", + rules => <<EOF +SUBSYSTEMS=="scsi", SUBSYSTEM=="block", KERNEL!="sda1", SYMLINK+="matches-but-is-negated" +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" +SUBSYSTEMS=="scsi", SUBSYSTEM!="anything", SYMLINK+="not-anything" +EOF + }, + { + desc => "negation PROGRAM!= exit code", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "nonzero-program", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" +KERNEL=="sda1", PROGRAM!="/bin/false", SYMLINK+="nonzero-program" +EOF + }, + { + desc => "ENV{} test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "true", + rules => <<EOF +ENV{ENV_KEY_TEST}="test" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", SYMLINK+="true" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad" +EOF + }, + { + desc => "ENV{} test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "true", + rules => <<EOF +ENV{ENV_KEY_TEST}="test" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="go", SYMLINK+="wrong" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="yes", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sdax1", SYMLINK+="no" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="test", ENV{ACTION}=="add", ENV{DEVPATH}=="*/block/sda/sda1", SYMLINK+="true" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ENV_KEY_TEST}=="bad", SYMLINK+="bad" +EOF + }, + { + desc => "ENV{} test (assign)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "true", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no" +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="true", SYMLINK+="true" +EOF + }, + { + desc => "ENV{} test (assign 2 times)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "true", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="true" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}="absolutely-\$env{ASSIGN}" +SUBSYSTEMS=="scsi", KERNEL=="sda1", SYMLINK+="before" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="yes", SYMLINK+="no" +SUBSYSTEMS=="scsi", KERNEL=="sda1", ENV{ASSIGN}=="absolutely-true", SYMLINK+="true" +EOF + }, + { + desc => "ENV{} test (assign2)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "part", + rules => <<EOF +SUBSYSTEM=="block", KERNEL=="*[0-9]", ENV{PARTITION}="true", ENV{MAINDEVICE}="false" +SUBSYSTEM=="block", KERNEL=="*[!0-9]", ENV{PARTITION}="false", ENV{MAINDEVICE}="true" +ENV{MAINDEVICE}=="true", SYMLINK+="disk" +SUBSYSTEM=="block", SYMLINK+="before" +ENV{PARTITION}=="true", SYMLINK+="part" +EOF + }, + { + desc => "untrusted string sanitize", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "sane", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e name; (/usr/bin/badprogram)", RESULT=="name_ _/usr/bin/badprogram_", SYMLINK+="sane" +EOF + }, + { + desc => "untrusted string sanitize (don't replace utf8)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "uber", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xc3\\xbcber" RESULT=="\xc3\xbcber", SYMLINK+="uber" +EOF + }, + { + desc => "untrusted string sanitize (replace invalid utf8)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "replaced", + rules => <<EOF +SUBSYSTEMS=="scsi", KERNEL=="sda1", PROGRAM=="/bin/echo -e \\xef\\xe8garbage", RESULT=="__garbage", SYMLINK+="replaced" +EOF + }, + { + desc => "read sysfs value from parent device", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "serial-354172020305000", + rules => <<EOF +KERNEL=="ttyACM*", ATTRS{serial}=="?*", SYMLINK+="serial-%s{serial}" +EOF + }, + { + desc => "match against empty key string", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "ok", + rules => <<EOF +KERNEL=="sda", ATTRS{nothing}!="", SYMLINK+="not-1-ok" +KERNEL=="sda", ATTRS{nothing}=="", SYMLINK+="not-2-ok" +KERNEL=="sda", ATTRS{vendor}!="", SYMLINK+="ok" +KERNEL=="sda", ATTRS{vendor}=="", SYMLINK+="not-3-ok" +EOF + }, + { + desc => "check ACTION value", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "ok", + rules => <<EOF +ACTION=="unknown", KERNEL=="sda", SYMLINK+="unknown-not-ok" +ACTION=="add", KERNEL=="sda", SYMLINK+="ok" +EOF + }, + { + desc => "final assignment", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "ok", + exp_perms => "root:tty:0640", + rules => <<EOF +KERNEL=="sda", GROUP:="tty" +KERNEL=="sda", GROUP="not-ok", MODE="0640", SYMLINK+="ok" +EOF + }, + { + desc => "final assignment 2", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "ok", + exp_perms => "root:tty:0640", + rules => <<EOF +KERNEL=="sda", GROUP:="tty" +SUBSYSTEM=="block", MODE:="640" +KERNEL=="sda", GROUP="not-ok", MODE="0666", SYMLINK+="ok" +EOF + }, + { + desc => "env substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "node-add-me", + rules => <<EOF +KERNEL=="sda", MODE="0666", SYMLINK+="node-\$env{ACTION}-me" +EOF + }, + { + desc => "reset list to current value", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "three", + not_exp_name => "two", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="one" +KERNEL=="ttyACM[0-9]*", SYMLINK+="two" +KERNEL=="ttyACM[0-9]*", SYMLINK="three" +EOF + }, + { + desc => "test empty SYMLINK+ (empty override)", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "right", + not_exp_name => "wrong", + rules => <<EOF +KERNEL=="ttyACM[0-9]*", SYMLINK+="wrong" +KERNEL=="ttyACM[0-9]*", SYMLINK="" +KERNEL=="ttyACM[0-9]*", SYMLINK+="right" +EOF + }, + { + desc => "test multi matches", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "right", + rules => <<EOF +KERNEL=="ttyACM*", SYMLINK+="before" +KERNEL=="ttyACM*|nothing", SYMLINK+="right" +EOF + }, + { + desc => "test multi matches 2", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "right", + rules => <<EOF +KERNEL=="dontknow*|*nothing", SYMLINK+="nomatch" +KERNEL=="ttyACM*", SYMLINK+="before" +KERNEL=="dontknow*|ttyACM*|nothing*", SYMLINK+="right" +EOF + }, + { + desc => "test multi matches 3", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "right", + rules => <<EOF +KERNEL=="dontknow|nothing", SYMLINK+="nomatch" +KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1" +KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2" +KERNEL=="dontknow|ttyACM0|nothing", SYMLINK+="right" +EOF + }, + { + desc => "test multi matches 4", + devpath => "/devices/pci0000:00/0000:00:1d.7/usb5/5-2/5-2:1.0/tty/ttyACM0", + exp_name => "right", + rules => <<EOF +KERNEL=="dontknow|nothing", SYMLINK+="nomatch" +KERNEL=="dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong1" +KERNEL=="X|attyACM0|dontknow|ttyACM0a|nothing|attyACM0", SYMLINK+="wrong2" +KERNEL=="all|dontknow|ttyACM0", SYMLINK+="right" +KERNEL=="ttyACM0a|nothing", SYMLINK+="wrong3" +EOF + }, + { + desc => "IMPORT parent test sequence 1/2 (keep)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "parent", + option => "keep", + rules => <<EOF +KERNEL=="sda", IMPORT{program}="/bin/echo -e \'PARENT_KEY=parent_right\\nWRONG_PARENT_KEY=parent_wrong'" +KERNEL=="sda", SYMLINK+="parent" +EOF + }, + { + desc => "IMPORT parent test sequence 2/2 (keep)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "parentenv-parent_right", + option => "clean", + rules => <<EOF +KERNEL=="sda1", IMPORT{parent}="PARENT*", SYMLINK+="parentenv-\$env{PARENT_KEY}\$env{WRONG_PARENT_KEY}" +EOF + }, + { + desc => "GOTO test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "right", + rules => <<EOF +KERNEL=="sda1", GOTO="TEST" +KERNEL=="sda1", SYMLINK+="wrong" +KERNEL=="sda1", GOTO="BAD" +KERNEL=="sda1", SYMLINK+="", LABEL="NO" +KERNEL=="sda1", SYMLINK+="right", LABEL="TEST", GOTO="end" +KERNEL=="sda1", SYMLINK+="wrong2", LABEL="BAD" +LABEL="end" +EOF + }, + { + desc => "GOTO label does not exist", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "right", + rules => <<EOF +KERNEL=="sda1", GOTO="does-not-exist" +KERNEL=="sda1", SYMLINK+="right", +LABEL="exists" +EOF + }, + { + desc => "SYMLINK+ compare test", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "right", + not_exp_name => "wrong", + rules => <<EOF +KERNEL=="sda1", SYMLINK+="link" +KERNEL=="sda1", SYMLINK=="link*", SYMLINK+="right" +KERNEL=="sda1", SYMLINK=="nolink*", SYMLINK+="wrong" +EOF + }, + { + desc => "invalid key operation", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "yes", + rules => <<EOF +KERNEL="sda1", SYMLINK+="no" +KERNEL=="sda1", SYMLINK+="yes" +EOF + }, + { + desc => "operator chars in attribute", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "yes", + rules => <<EOF +KERNEL=="sda", ATTR{test:colon+plus}=="?*", SYMLINK+="yes" +EOF + }, + { + desc => "overlong comment line", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda/sda1", + exp_name => "yes", + rules => <<EOF +# 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 + # 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +KERNEL=="sda1", SYMLINK+=="no" +KERNEL=="sda1", SYMLINK+="yes" +EOF + }, + { + desc => "magic subsys/kernel lookup", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "00:16:41:e2:8d:ff", + rules => <<EOF +KERNEL=="sda", SYMLINK+="\$attr{[net/eth0]address}" +EOF + }, + { + desc => "TEST absolute path", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "there", + rules => <<EOF +TEST=="/etc/hosts", SYMLINK+="there" +TEST!="/etc/hosts", SYMLINK+="notthere" +EOF + }, + { + desc => "TEST subsys/kernel lookup", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "yes", + rules => <<EOF +KERNEL=="sda", TEST=="[net/eth0]", SYMLINK+="yes" +EOF + }, + { + desc => "TEST relative path", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "relative", + rules => <<EOF +KERNEL=="sda", TEST=="size", SYMLINK+="relative" +EOF + }, + { + desc => "TEST wildcard substitution (find queue/nr_requests)", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "found-subdir", + rules => <<EOF +KERNEL=="sda", TEST=="*/nr_requests", SYMLINK+="found-subdir" +EOF + }, + { + desc => "TEST MODE=0000", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "sda", + exp_perms => "0:0:0000", + exp_rem_error => "yes", + rules => <<EOF +KERNEL=="sda", MODE="0000" +EOF + }, + { + desc => "TEST PROGRAM feeds OWNER, GROUP, MODE", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "sda", + exp_perms => "5000:100:0400", + exp_rem_error => "yes", + rules => <<EOF +KERNEL=="sda", MODE="666" +KERNEL=="sda", PROGRAM=="/bin/echo 5000 100 0400", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}" +EOF + }, + { + desc => "TEST PROGRAM feeds MODE with overflow", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "sda", + exp_perms => "0:0:0440", + exp_rem_error => "yes", + rules => <<EOF +KERNEL=="sda", MODE="440" +KERNEL=="sda", PROGRAM=="/bin/echo 0 0 0400letsdoabuffferoverflow0123456789012345789012345678901234567890", OWNER="%c{1}", GROUP="%c{2}", MODE="%c{3}" +EOF + }, + { + desc => "magic [subsys/sysname] attribute substitution", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "sda-8741C4G-end", + exp_perms => "0:0:0600", + rules => <<EOF +KERNEL=="sda", PROGRAM="/bin/true create-envp" +KERNEL=="sda", ENV{TESTENV}="change-envp" +KERNEL=="sda", SYMLINK+="%k-%s{[dmi/id]product_name}-end" +EOF + }, + { + desc => "builtin path_id", + devpath => "/devices/pci0000:00/0000:00:1f.2/host0/target0:0:0/0:0:0:0/block/sda", + exp_name => "disk/by-path/pci-0000:00:1f.2-scsi-0:0:0:0", + rules => <<EOF +KERNEL=="sda", IMPORT{builtin}="path_id" +KERNEL=="sda", ENV{ID_PATH}=="?*", SYMLINK+="disk/by-path/\$env{ID_PATH}" +EOF + }, +); + +sub udev { + my ($action, $devpath, $rules) = @_; + + # create temporary rules + system("mkdir", "-p", "$udev_rules_dir"); + open CONF, ">$udev_rules" || die "unable to create rules file: $udev_rules"; + print CONF $$rules; + close CONF; + + if ($valgrind > 0) { + system("$udev_bin_valgrind $action $devpath"); + } else { + system("$udev_bin", "$action", "$devpath"); + } +} + +my $error = 0; + +sub permissions_test { + my($rules, $uid, $gid, $mode) = @_; + + my $wrong = 0; + my $userid; + my $groupid; + + $rules->{exp_perms} =~ m/^(.*):(.*):(.*)$/; + if ($1 ne "") { + if (defined(getpwnam($1))) { + $userid = int(getpwnam($1)); + } else { + $userid = $1; + } + if ($uid != $userid) { $wrong = 1; } + } + if ($2 ne "") { + if (defined(getgrnam($2))) { + $groupid = int(getgrnam($2)); + } else { + $groupid = $2; + } + if ($gid != $groupid) { $wrong = 1; } + } + if ($3 ne "") { + if (($mode & 07777) != oct($3)) { $wrong = 1; }; + } + if ($wrong == 0) { + print "permissions: ok\n"; + } else { + printf " expected permissions are: %s:%s:%#o\n", $1, $2, oct($3); + printf " created permissions are : %i:%i:%#o\n", $uid, $gid, $mode & 07777; + print "permissions: error\n"; + $error++; + sleep(1); + } +} + +sub major_minor_test { + my($rules, $rdev) = @_; + + my $major = ($rdev >> 8) & 0xfff; + my $minor = ($rdev & 0xff) | (($rdev >> 12) & 0xfff00); + my $wrong = 0; + + $rules->{exp_majorminor} =~ m/^(.*):(.*)$/; + if ($1 ne "") { + if ($major != $1) { $wrong = 1; }; + } + if ($2 ne "") { + if ($minor != $2) { $wrong = 1; }; + } + if ($wrong == 0) { + print "major:minor: ok\n"; + } else { + printf " expected major:minor is: %i:%i\n", $1, $2; + printf " created major:minor is : %i:%i\n", $major, $minor; + print "major:minor: error\n"; + $error++; + sleep(1); + } +} + +sub udev_setup { + system("rm", "-rf", "$udev_dev"); + mkdir($udev_dev) || die "unable to create udev_dev: $udev_dev\n"; + # setting group and mode of udev_dev ensures the tests work + # even if the parent directory has setgid bit enabled. + chown (0, 0, $udev_dev) || die "unable to chown $udev_dev\n"; + chmod (0755, $udev_dev) || die "unable to chmod $udev_dev\n"; + + system("rm", "-rf", "$udev_run"); +} + +sub run_test { + my ($rules, $number) = @_; + + print "TEST $number: $rules->{desc}\n"; + print "device \'$rules->{devpath}\' expecting node/link \'$rules->{exp_name}\'\n"; + + udev("add", $rules->{devpath}, \$rules->{rules}); + if (defined($rules->{not_exp_name})) { + if ((-e "$udev_dev/$rules->{not_exp_name}") || + (-l "$udev_dev/$rules->{not_exp_name}")) { + print "nonexistent: error \'$rules->{not_exp_name}\' not expected to be there\n"; + $error++; + sleep(1); + } + } + + if ((-e "$udev_dev/$rules->{exp_name}") || + (-l "$udev_dev/$rules->{exp_name}")) { + + my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, + $atime, $mtime, $ctime, $blksize, $blocks) = stat("$udev_dev/$rules->{exp_name}"); + + if (defined($rules->{exp_perms})) { + permissions_test($rules, $uid, $gid, $mode); + } + if (defined($rules->{exp_majorminor})) { + major_minor_test($rules, $rdev); + } + print "add: ok\n"; + } else { + print "add: error"; + if ($rules->{exp_add_error}) { + print " as expected\n"; + } else { + print "\n"; + system("tree", "$udev_dev"); + print "\n"; + $error++; + sleep(1); + } + } + + if (defined($rules->{option}) && $rules->{option} eq "keep") { + print "\n\n"; + return; + } + + udev("remove", $rules->{devpath}, \$rules->{rules}); + if ((-e "$udev_dev/$rules->{exp_name}") || + (-l "$udev_dev/$rules->{exp_name}")) { + print "remove: error"; + if ($rules->{exp_rem_error}) { + print " as expected\n"; + } else { + print "\n"; + system("tree", "$udev_dev"); + print "\n"; + $error++; + sleep(1); + } + } else { + print "remove: ok\n"; + } + + print "\n"; + + if (defined($rules->{option}) && $rules->{option} eq "clean") { + udev_setup(); + } + +} + +# only run if we have root permissions +# due to mknod restrictions +if (!($<==0)) { + print "Must have root permissions to run properly.\n"; + exit; +} + +udev_setup(); + +my $test_num = 1; +my @list; + +foreach my $arg (@ARGV) { + if ($arg =~ m/--valgrind/) { + $valgrind = 1; + printf("using valgrind\n"); + } else { + push(@list, $arg); + } +} + +if ($list[0]) { + foreach my $arg (@list) { + if (defined($tests[$arg-1]->{desc})) { + print "udev-test will run test number $arg:\n\n"; + run_test($tests[$arg-1], $arg); + } else { + print "test does not exist.\n"; + } + } +} else { + # test all + print "\nudev-test will run ".($#tests + 1)." tests:\n\n"; + + foreach my $rules (@tests) { + run_test($rules, $test_num); + $test_num++; + } +} + +print "$error errors occured\n\n"; + +# cleanup +system("rm", "-rf", "$udev_dev"); +system("rm", "-rf", "$udev_run"); + +if ($error > 0) { + exit(1); +} +exit(0); |