Categories
Technology UNIX

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:

#!/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:

# 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!

6 replies on “Replacing udev with mdev in Gentoo”

Update: Add ‘microcode‘ entry.

Known potential issues, compared to udev‘s /dev population:

  • /dev/md/* devices not created – but this is deprecated?
  • Loading i2c_dev doesn’t result in the creation of i2c-0;
  • autofs device not created, but udev shouldn’t be generating this?
  • /dev/sg* devices not created to match /dev/sd* (and others?);
  • /dev/disk, /dev/block, and /dev/char directories not created (but these are udev-specific: does anything rely on these?)

Update: /dev/md/* devices are now auto-created

… so it appears that udev was auto-creating /dev/sg* device-nodes even though the sg module wasn’t loaded (as with autofs, presumably).

The other two items save for i2c appear to be udev-specific constructs, which I’ve not found anything else using.

Update: My original mdev-based system has no problems with i2c, so it appears to a quirk specific to the second system…

Update: Ah – the first system does actually have an i2c device under /sys whilst the second doesn’t… and MAKEDEV was missing, breaking sensors-detect.

FWIW, I’m still booting in Suse 13.1 from disk w/separate usr.

I’m still using init scripts salvaged from 12.3 (and some before,
where systemd changes had already been made).

I do use ‘udev’ on my system, but not systemd (don’t know how long
this will work w/new changes going into the pair, and how long it will
be before systemd becomes a blocker).

But for now, in suse boot, it starts ‘boot’ scripts before
single user scripts i.e. levels=(B,1,2,3,5). Booting linux w/’S’
inserts you before ‘B’ has been run which is handy for ‘B’ debugging.
‘B’ is where /usr/ gets mounted …. right now, before udev (which
confuses it a bit — I think I need a post-udev-shutdown umount
to mirror my early mount.

But from udev’s point of view, /usr/ is mounted when it comes up, so it
appears happy so far….

System boots with the devtmpfs which works for mounting my root disks which
are str8 SCSI (sdc[xyz])… other disks are LVM but use lilo for boot and
it likes sectors so it seems happy w/sdc.

Still have my ethernet drivers named eth0-x…

I may get hit w/a big clue stick and have to rearchitect w/next upgrade (always the case, it seems), but for now, still working.

(Run vanilla kernel 3.15 (or whatever latest is … not my distro’s kernel
which has multiple customizations). I run my own versions of several
large SW packages to set my own options & optimizations.

One script I found useful to have:
> rootfs_dependency_checker.pl
fixes_enabled = , >=5013 (no)
(Start prelink-info analysis to find broken deps (backgrounded))
Scan file-system mount order Dependencies
(rootfs):/,
Order 1:/Media, /Share, /backups, /home, /homes, /misc, /net, /smb, /tmp, /usr, /var,
Order 2:/backups/Media, /usr/share,
find all libs
check for old versions in /usr
check for out of date root libs
check for faulty or unsafe symlinks
check obj prelink dependencies
Read prelink info…
check for obsolete root libs
num_objs=778, numlibs=4268

checks for objects on / w/deps on /usr (makes copies where needed).

Also tries to use the “prelink” script to check for any lib
deps not on the same device (prelink -l)…

Be happy to share script, but w/no guarantees(perlscript)…
It’s only had 1 user, (me) so hasn’t been generalized much beyond
that…

Still working on other scripts, though this issue isn’t top of my
interest list with 13.1 working (was for a few months after 13.1 came
out, and might be again when 13.2 comes out…priorities change! ;-).

Just thought I’d share my experiences and touch base to say hi,
and let you knowwthere are others out there working on dealing
w/some of these problems…

I’ve just added a Bluetooth dongle to a Raspberry Pi, and I’m not getting an ‘hci0‘ device. However, mdev is being called correctly and provided with the appropriate data – but no device-node is ever present and there doesn’t appear to be any further pertinent information below ‘/sys‘. I suspect that my minimal configuration is missing some undeclared module dependency (in the same way that ehci now needs ehci_pci in order to be useful, etc.) and this is holding things back, but further experimentation is needed.

I’ve found some references to ‘bluetooth.agent‘ (as well as a few other agent files) which I’ve never come across. If anyone could point me at these, I’d be very grateful!

P.S. mdev-enabled ebuilds now available from github.com

Leave a Reply