Replacing udev with mdev in Gentoo

Recent changes to udev mean that it is now a requirement to have the partition containing the /usr filesystem mounted prior to system boot, requiring usr and root to be on the same partition (which is Red Hat’s preferred solution), or to mount /usr prior to booting from an initrd.

I’ve successfully run Linux systems for many years without needing this additional complication, and I don’t plan to start changing the core boot process in order to comply with Red Hat’s (non-FHS compatible) vision of what a Linux system should look like.

The best alternative right now seems to be Busyboxmdev – a very simple hotplug agent and /dev tree maintenance tool which provides identical core functionality to udev.

However, the default configuration files provided with mdev are somewhat outdated and there isn’t much information out there documenting how to make the transition.

Please note: I’m looking at mdev from the point of view of a headless server where (for example) USB is available, but neither essential nor heavily used. A desktop system with many esoteric devices and running a graphical environment may need much more work to duplicate udev functionality to a usable level. mdev was never explicitly designed to run a system from, only to provide an initial or recovery-disk environment.

With Gentoo’s baselayout-2 base-system files installed and a ‘sysinit’ run-level, initial mounting of /proc and /sys is performed automatically, and so the only actions needed to commence using mdev are:

USE="-pam mdev static" emerge -v busybox
rc-update del udev sysinit
rc-update add mdev sysinit

(The ‘pam‘ USE-flag is incompatible with a statically-linked build)

Whilst this will allow the system to reboot successfully, there will be some odd device-nodes below /dev, and some nodes will be mis-named or not in the correct directories. ‘lsusb‘ will not work, for example. Additionally, interface renaming did not work for me without further tweaks.

When mdev is instructed to create sub-directories, it seems to create parent directories with permissions 777. A work-around is to pre-create known required parent directories:

--- /usr/portage/sys-apps/busybox/files/mdev.rc.1
+++ /etc/init.d/mdev
@@ -60,7 +60,7 @@ seed_dev()
        [ -e /proc/kcore ] && ln -snf /proc/kcore /dev/core
 
        # Create problematic directories
-       mkdir -p /dev/pts /dev/shm
+       mkdir -p /dev/pts /dev/shm /dev/cpu /dev/bus/usb
 }
 
 mount_it()


Update: A much more obvious way to handle this is in mdev rules, as below.

If interface renaming is required, then a new /etc/mactab is needed to map MAC addresses to names:

# Allow busybox' "nameif" to rename downed interfaces on the basis of their
# MAC addresses

ef0     00:0d:b9:xx:xx:xa
ef1     00:0d:b9:xx:xx:xb
ef2     00:0d:b9:xx:xx:xc

# NB: bridges use the MAC address of their primary slave - this will cause
# "nameif" to attempt to rename the bridge rather than the interface if a
# bridge is active when invoked.

… and even though mdev is instructed to call ‘nameif‘ when a new network device appears, I also had to add this function to ‘/etc/conf.d/net‘:

preup() {
        [ "${IFACE}" = "lo" ] && return 0

        [ ! -d /dev/.udev -a -n "$( echo "${IFACE}" | grep -v "^br[0-9]\+$" )" ] && {
                einfo "Calling 'nameif' to set interface names"
                /sbin/nameif
        }

        return 0
} # preup

The ‘usbdev‘ script provided with mdev handles old-style USB device names such as ‘/dev/usbdev1.1_ep00‘, but doesn’t handle entries such as ‘/sys/devices/pci0000:00/0000:00:0f.4/usb2/2-1‘. The following new ‘/lib/mdev/usb‘ script handles modern USB device names:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#!/bin/sh

if [ -w "kmsg" ]; then
        LOGFILE="kmsg"
else
        LOGFILE="mdev_usb.log"
fi

echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) mdev usb helper started as '$0' in '$( pwd )', MDEV '$MDEV', ACTION '$ACTION', DEVPATH '$DEVPATH', SUBSYSTEM '$SUBSYSTEM', SEQNUM '$SEQNUM'"

[ -n "$MDEV" ] || exit 0
[ -n "$DEVPATH" ] || exit 0
[ "$SUBSYSTEM" = "usb" ] || exit 0

# add zeros to device or bus
function add_zeros() {
        case "$( echo "$1" | wc -L )" in
                1)      echo "00$1"
                        ;;
                2)      echo "0$1"
                        ;;
                *)      echo "$1"
                        ;;
        esac
        return 0
}

# e.g. DEVPATH=/devices/pci0000:00/0000:00:0f.4/usb2/2-1, MDEV=2-1
BUS=""
USB_DEV=""
if [ -d /sys/devices ]; then
        BUS="$( add_zeros "$( cat "/sys$DEVPATH/busnum" 2>/dev/null )" )"
        USB_DEV="
$( add_zeros "$( cat "/sys$DEVPATH/devnum" 2>/dev/null )" )"
fi
if [ -z "
$BUS" ]; then
        BUS="
$( add_zeros "$( echo "$DEVPATH" | cut -d'/' -f 5 | sed 's/^usb//' )" )"
fi      
if [ -z "
$USB_DEV" ]; then
        USB_DEV="
$( add_zeros "$( echo "$MDEV" | cut -d'-' -f 1 )" )"
fi
#USB_FUNC="
$( add_zeros "$( echo "$MDEV" | cut -d'-' -f 2 )" )"

# try to load the proper driver for usb devices
case "
$ACTION" in
        add|"
")
                echo >>"
$LOGFILE" "$$ $( date +"%T.%N" ) Performing 'add' ACTION"

                # move usb device file
                if [ ! -d "
bus/usb/$BUS" ]; then
                        echo >>"
$LOGFILE" "$$ $( date +"%T.%N" ) WARNING USB bus directory 'bus/usb/$BUS' doesn't exist - is USB initialised?"
                        mkdir -p "bus/usb/$BUS" \
                                && echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) mkdir succeeded for '
bus/usb/$BUS'" \
                                || { echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) mkdir failed for '
bus/usb/$BUS'" ; exit 0 ; }
                fi
                mv "$MDEV" "bus/usb/$BUS/$USB_DEV" \
                        && echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) mv succeeded for '
bus/usb/$BUS/$USB_DEV'" \
                        || { echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) mv failed for '
bus/usb/$BUS/$USB_DEV'" ; exit 0 ; }
                ;;
        remove)
                echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) Performing '
remove' ACTION"

                # remove device file and possible empty dirs
                if rm -f "bus/usb/$BUS/$USB_DEV" 2>/dev/null \
                        && echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) rm -f '
bus/usb/$BUS/$USB_DEV' succeeded"
                then
                        rmdir -p "bus/usb/$BUS" 2>/dev/null \
                                && echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) rmdir -p '
bus/usb/$BUS' succeeded" \
                                || { echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) rmdir -p '
bus/usb/$BUS' failed" ; exit 0 ; }
                else
                        echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) rm -f '
bus/usb/$BUS/$USB_DEV' failed"
                        exit 0
                fi
                ;;
esac

echo >>"$LOGFILE" "$$ $( date +"%T.%N" ) usb helper completed successfully"

exit 0

Finally, to tie this all together, I use this ‘/etc/mdev.conf‘, which adds some additional device handling and also some clarification of the available syntax based on the Busybox source:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# Provide user, group, and mode information for devices.  If a regex matches
# the device name provided by sysfs, use the appropriate user:group and mode
# instead of the default 0:0 660.
#
# Syntax:
# [-]devicename_regex user:group mode [>|=path] [@|$|*cmd args...]
#
# Leading minus in 1st field means "don't stop on this line", otherwise
# search is stopped after the matching line is encountered.
# Leading @ allows specification as @major,minor[-minor2] for disambiguation
#
# =: move, >: move and create a symlink, !: don't create node
# @|$|*: run $cmd on delete, @cmd on create, *cmd on both

# support module loading on hotplug
$MODALIAS=.*            root:root       660 @/sbin/modprobe "$MODALIAS"

# null may already exist; therefore ownership has to be changed with command
null                    root:root       666 @/bin/chmod 666 $MDEV
zero                    root:root       666
full                    root:root       666
random                  root:root       644
urandom                 root:root       644
hwrandom                root:root       644
grsec                   root:root       660

kmem                    root:kmem       640
kmsg                    root:root       600
mem                     root:kmem       640
port                    root:kmem       640
# console may already exist; therefore ownership has to be changed with command
console                 root:tty        600 @/bin/chmod 600 $MDEV
ptmx                    root:tty        666
pty.*                   root:tty        660

-cpu.*                  root:root       755 @/bin/mkdir -pm 755 cpu ; /bin/rm $MDEV
cpu([0-9]+)             root:root       444 =cpu/%1/cpuid
-msr.*                  root:root       755 @/bin/mkdir -pm 755 cpu ; /bin/rm $MDEV
msr([0-9]+)             root:root       600 =cpu/%1/msr
microcode               root:root       600 =cpu/

# Typical devices

tty                     root:tty        666
tty[0-9]+               root:tty        620
vcsa?[0-9]*             root:tty        660
ttyS[0-9]+              root:uucp       660
ttyprintk               root:root       600

# block devices
ram([0-9]+)             root:disk       660 >rd/%1
loop([0-9]+)            root:disk       660 >loop/%1
sd[a-z].*               root:disk       660 */lib/mdev/usbdisk_link
#hd[a-z][0-9]*          root:disk       660 */lib/mdev/ide_links
md[0-9]+                root:disk       660 @/bin/mkdir -pm 755 md ; /bin/ln -sf ../$MDEV md/${MDEV/md}
#sr[0-9]+               root:cdrom      660 @/bin/ln -sf $MDEV cdrom
#fd[0-9]+               root:floppy     660
bsg/.*                  root:root       600 =bsg/

# net devices
-net/.*                 root:root       600 @/sbin/nameif
tun[0-9]*               root:root       666 =net/
tap[0-9]*               root:root       666 =net/

# i2c
i2c-([0-9]+)            root:root       600 >i2c/%1
i2c([0-9]+)             root:root       600 >i2c/%1

# usb bus devices
-usb.*                  root:usb        755 @/bin/mkdir -pm 755 bus/usb ; /bin/chmod 755 bus ; /bin/rm $MDEV
usb([0-9])              root:usb        664 =bus/usb/00%1/001
usb([1-9][0-9])         root:usb        664 =bus/usb/0%1/001
usb([1-9][0-9]{2})      root:usb        664 =bus/usb/%1/001
# usb devices
([0-9]+)-([0-9]+)       root:usb        664 */lib/mdev/usb
hiddev[0-9]+            root:root       600 =usb/
hidraw[0-9]+            root:root       600
# Traditionally, USB devices appeared as, e.g., '/dev/usbdev1.1_ep00'
usbdev[0-9]\.[0-9]      root:root       664 */lib/mdev/usbdev
usbdev[0-9]\.[0-9]_.*   root:root       664

# misc stuff
#misc/.*                nobody:nogroup    0 !
#rtc                    root:root       600 >misc/
rtc0                    root:root       600 @/bin/ln -sf $MDEV rtc

# input stuff
event[0-9]+             root:root       640 =input/
mice                    root:root       640 =input/
mouse[0-9]+             root:root       640 =input/
ts[0-9]+                root:root       600 =input/

# Less typical devices

fuse                    root:root       666

#ttyLTM[0-9]+           root:dialout    660 @/bin/ln -sf $MDEV modem
#ttySHSF[0-9]+          root:dialout    660 @/bin/ln -sf $MDEV modem
#slamr                  root:dialout    660 @/bin/ln -sf $MDEV slamr0
#slusb                  root:dialout    660 @/bin/ln -sf $MDEV slusb0

The other helpful aspect of this approach is that reverting to udev is simply a matter of removing ‘mdev‘ from the sysinit run-level, and re-adding ‘udev‘ in its place.

These are all the fixes needed for the PC Engines ALIX 2d13 router (now with integrated RTC – the previous 2c3 board died, and in the meantime the product was upgraded). I’ll update this post as I roll-out mdev to the Storage and Infrastructure servers. Please feel free to post your success (or otherwise 😉 in the comments below!