2012-06-13

Linux 的 udev 是個有趣又有用的玩具 (5en) : Smallest Working Bourne-Shell Automounter

How to write your own shell script automounter
This article presents a minimal working example of udev-auto-mounter. It consists only of a 50-line Bourne-shell script and a one-line universal hook put in the directory /etc/udev/rules.d/. The shell script acts as an universal UESR (Udev-Event Service Routine). This probably is the world's simplest, smallest, most portable and flexible auto-mounting system for Linux and the most convenient to use relying only on X11, udev, and some of the most essential system utilities.

Features

It features:
  1. automatic pop-up terminal emulator for X with an interactive shell pwd'ed at the mount-point for each new block partition added to the system,
  2. automatic unmount after this window is closed.
That is, all one has to do manually is to
  1. plug in the mass storage device,
  2. close the pop-up window,
  3. unplug the mass storage device.
This seems to me the most natural design of a "good automounter".  It is fast, easy to understand, install, maintain, and is easily extendable.

This automounter responds to SD/MMC cards, USB thumb-drives, PCMCIA/CF memory cards and any partition of a newly inserted block device as soon as it is "seen" as an uevent "block-add-partition" by the udev-system.

Another remarkable feature of this shell-script is that it does not launch the x-terminal and shell as root, but the user-id owning the process xinit(1). So, instead of seeing a # prompt, you'll see a $ prompt right in the root directory of the newly mounted medium as shown in the following picture.

Whenever you want to unmount the partition, instead of the hassle looking for and clicking at some invisible tiny icon, you simply close the window by typing Ctrl-D or exit and unplug the device as soon as an acoustic signal is heard.

Due to the homogeneous structure, this automounter practically mounts any type of filesystem including vfat, ntfs, ntfs-3g, hfs, hfsplus, ext2, ext3, ... as long as it is supported by the system.  This automounter relies only on X11, udev, and some of the most essential utilities and is therefore very portable.

A typical session when the automounter kicks in is described as follows:e
  1. manually plug in a USB-thumb-drive (udev-event: block-partition-add, acoustic signal)
  2. auto-pop-up window (auto-mkdir, auto-mount, auto-chdir, $ user prompt)
  3. manually close the window (auto-chdir, auto-umount, acoustic signal, auto-rmdir)
  4. manually unplug USB-thubm drive (udev-event: block-partition-remove, acoustic signal)
where "block", "partiton", "add"/"removed" are the values of the variables $SUBSYSTEM, $DEVTYPE, and $ACTION passed over by the udev-system respectively.

For example, if you plug in a USB thumb-drive containing four partitions of vfat, ntfs, ext2 filesystems and a linux swap partition, three separate windows will pop-up as shown in the following picture. They are automatically mounted at /mnt/sda1_vfat/, /mnt/sda2_ntfs/, and /mnt/sda3_ext2/ respectively.  The USB-device is then ready to be removed after closing all of these windows.


Prerequisites

X11, root privilege, Linux kernel version greater than 2.6, udevd(8), aplay(1), xterm(1), bash(1), ps(1), mkdir(1), rmdir(1), chmod(1), sudo(8), basename(1), mount(8), umount(8).

Installation
  1. ngCut and paste the script in the "tinym1" section and save it in a file, say, in your home directory, as "/home/nobody/tinym1".
    $ chmod 755 /home/nobody/tinym1
    Note that it may an arbitrary filename you like and it can be put anywhere you like, not necessarily in the PATH.  Do not forget to change nobody to the user name under which you're working with.
  2. Edit the shell-script tinym1, and point the variables SND_INSERT, SND_REMOVE, and SND_UMOUNTED to your beloved sound files for acoustic aids.  Be sure that they be playable by aplay(1).  Do not forget to quote the full path within the quotation marks ", if the paths contain spaces.  It is safe to skip this step if acoustic aids are not desirable or you're such a minimalist and do not wish to install aplay(1) which usually comes with the Debian package alsa-utils.  On the other hand, any sound file player will do if it does not require a tty.  For example, "mplayer -really-quiet -noconsolecontrols" may be used to replace "aplay -q" in the shell script.
  3. Login as root,  create the file /etc/udev/rules.d/z99_hook.rules and put a single line: (Do not forget to change nobody to the user name under which you're working with.)
    # echo 'PROGRAM=="/usr/bin/test -f /tmp/.X0-lock", RUN+="/home/nobody/tinym1"' > /etc/udev/rules.d/z99_hook.rules
    # /etc/init.d/udev reload
And you're done. From now on, the script ~/tinym1 is called whenever a new partiton of a block device is registered by the udev-system. Try unplugging the AC power cord of your laptop, and you'll see the lines
tinym1/2487: skipped power_supply change tinym1/2488: skipped power_supply change
logged in the newly created file /tmp/tinym1.log. For catching this "power_supply" uevent, see 〈Linux 的 udev 是個有趣又有用的玩具(3)〉.

Trouble Shooti $ng

The hook /etc/udev/rules.d/z99_hook.rules as well as the following shell script tinym1 has been tested on Debian Etch and Lenny with kernel versions 2.6.27.62 and 2.6.32.59 under window managers vtwm and fluxbox.  Since this auto-mounting system has been made as simple and portable as possible, it should also work on other desktops, or even distros other than Debian. Check the logfile /tmp/tinym1.log to inquire what happened in case of unexpected problems.  Any report of oops, opinion, suggestion will definitely help and is highly appreciated.

The Smallest Bourne-Shell Script Automounter: tinym1

Variables marked in red are free to edit.

#!/bin/sh
# my_script example 5-1 (tinym1)

PROG="`basename $0`/$SEQNUM"
SND_INSERT="/xp/WINDOWS/Media/Windows XP Pop-up Blocked.wav"
SND_REMOVE="/xp/WINDOWS/Media/chimes.wav"
SND_UMOUNTED="/xp/WINDOWS/Media/notify.wav"
XTERM=xterm
SHELL=bash

LOGFILE=/tmp/tinym1.log
exec 3>> $LOGFILE && exec >& 3 && exec 2>&1


if [ "$SUBSYSTEM" = "block" -a "$DEVTYPE" = "partition" ]
then
    echo ""
    echo "$PROG: uevent $SUBSYSTEM $ACTION $DEVTYPE $DEVNAME $ID_FS_TYPE"

    if [ "$ACTION" = "add" ]
    then

        echo "$PROG: aplay -q $SND_INSERT &"
        aplay -q "$SND_INSERT" &

#        XUSER=`ps aux | grep -e xinit| grep -v grep | awk '{print $1}'`
        XUSER=`ps -C xinit -o user --no-headers `
        echo "$PROG: export DISPLAY=:0.0"
        export DISPLAY=:0.0

        KNAME="`basename $DEVNAME`"
        MPOINT="/mnt/${KNAME}_${ID_FS_TYPE}"

        echo "$PROG: mkdir $MPOINT"
        mkdir $MPOINT

        echo "$PROG: mount -t $ID_FS_TYPE $DEVNAME $MPOINT"
        if mount -t "$ID_FS_TYPE" "$DEVNAME" "$MPOINT"
        then

            echo "$PROG: cd $MPOINT/"
            cd "$MPOINT/"

            echo "$PROG: sudo -H -u $XUSER $XTERM -e $SHELL"
            sudo -H -u $XUSER / $XTERM -e $SHELL

            echo "$PROG: cd -"
            cd -

            echo "$PROG: umount $MPOINT/ && aplay -q $SND_UMOUNTED"
            umount "$MPOINT/" && aplay -q "$SND_UMOUNTED"

        else

          echo "$PROG: failed mounting ID_FS_TYPE=\"$ID_FS_TYPE\""

        fi

        echo "$PROG: rmdir $MPOINT/"
        rmdir "$MPOINT/"

        echo "$PROG: exiting..."

    elif [ "$ACTION" = "remove" ]
    then
        echo "$PROG: aplay -q $SND_REMOVE &"
        aplay -q "$SND_REMOVE" &

        echo "$PROG: exiting..."

    fi

else
    echo "$PROG: skipped uevent $SUBSYSTEM $ACTION $DEVTYPE $DEVNAME $ID_FS_TYPE"
fi

exit $?


Explanations

The hook (udev-rule) /etc/udev/rules.d/z99_hook.rules does a trick to RUN the automounter-script tinym1 only when the X windows system is up by testing the existence of the file "/tmp/.X0-lock". This is to avoid messing with the boot-process once this automounter is installed and used regularily.

The shell script, which is called by the hook on every uevent, make use of only 6 environment variables passed by the udev-system, i.e. $SUBSYSTEM, $ACTION, $DEVTYPE, $DEVNAME, $ID_FS_TYPE, and $SEQNUM.  Not even a kernel-name "%k" is required as an argument of the "RUN+=" statement in the udev-rule.

The following sample logfile (/tmp/tinym1.log) has been partially reordered for readability. The lines belonging to the same event (with the same sequence number) are normally interleavedly logged because it is usually a competition situation writing to the logfile.
tinym1/2118: block add /dev/sda1 vfat
tinym1/2118: aplay -q /xp/WINDOWS/Media/Windows XP Pop-up Blocked.wav &
tinym1/2118: export DISPLAY=:0.0
tinym1/2118: mkdir /mnt/sda1_vfat
tinym1/2118: mount -t vfatAY /dev/sda1 /mnt/sda1_vfat
tinym1/2118: cd /mnt/sda1_vfat/
tinym1/2118: sudo -H -u chen xterm -e bash
tinym1/2118: cd -
/
tinym1/2118: umount /mnt/sda1_vfat/ && aplay -q /xp/WINDOWS/Media/notify.wav
tinym1/2118: rmdir /mnt/sda1_vfat/
tinym1/2106: skipped usb add usb_device /dev/1-1
tinym1/2107: skipped usb add usb_interface
tinym1/2108: skipped scsi_host add
tinym1/2109: skipped usb_endpoint add /dev/usbdev1.9_ep01
tinym1/2110: skipped usb_endpoint add /dev/usbdev1.9_ep82
tinym1/2111: skipped usb_endpoint add /dev/usbdev1.9_ep83
tinym1/2113: skipped usb_endpoint add /dev/usbdev1.9_ep00
tinym1/2112: skipped usb_device add /dev/bus/usb/001/009
tinym1/2122: skipped bdi add
tinym1/2114: skipped scsi add scsi_device
tinym1/2115: skipped scsi_disk add
tinym1/2116: skipped scsi change scsi_device
tinym1/2117: skipped block add disk /dev/sda
tinym1/2118: block add /dev/sda1 vfat
tinym1/2118: aplay -q /xp/WINDOWS/Media/Windows XP Pop-up Blocked.wav &
tinym1/2118: export DISPLAY=:0.0
tinym1/2118: mkdir /mnt/sda1_vfat
tinym1/2118: mount -t vfat /dev/sda1 /mnt/sda1_vfat
tinym1/2118: cd /mnt/sda1_vfat/
tinym1/2118: sudo -H -u chen xterm -e bash
tinym1/2118: cd -
/
tinym1/2118: umount /mnt/sda1_vfat/ && aplay -q /xp/WINDOWS/Media/notify.wav
tinym1/2118: rmdir /mnt/sda1_vfat/
tinym1/2118: exiting...
tinym1/2123: skipped scsi_device add
tinym1/2119: block add /dev/sda2 ntfs
tinym1/2119: aplay -q /xp/WINDOWS/Media/Windows XP Pop-up Blocked.wav &
tinym1/2119: export DISPLinAY=:0.0
tinym1/2119: mkdir /mnt/sda2_ntfs
tinym1/2119: mount -t ntfs /dev/sda2 /mnt/sda2_ntfs
tinym1/2119: cd /mnt/sda2_ntfs/
tinym1/2119: sudo -H -u chen xterm -e bash
tinym1/2119: cd -
/
tinym1/2119: umount /mnt/sda2_ntfs/ && aplay -q /xp/WINDOWS/Media/notify.wav
tinym1/2119: rmdir /mnt/sda2_ntfs/
tinym1/2119: exiting...
tinym1/2121: block add /dev/sda4 swap
tinym1/2121: aplay -q /xp/WINDOWS/Media/Windows XP Pop-up Blocked.wav &
tinym1/2121: export DISPLAY=:0.0
tinym1/2121: mkdir /mnt/sda4_swap
tinym1/2121: mount -t swap /dev/sda4 /mnt/sda4_swap
mount: unknown filesystem type 'swap'
tinym1/2121: failed mounting ID_FS_TYPE="swap"
tinym1/2121: rmdir /mnt/sda4_swap/
tinym1/2121: exiting...
tinym1/2120: block add /dev/sda3 ext2
tinym1/2120: aplay -q /xp/WINDOWS/Media/Windows XP Pop-up Blocked.wav &
tinym1/2124: skipped bdi add
tinym1/2120: export DISPLAY=:0.0
tinym1/2120: mkdir /mnt/sda3_ext2
tinym1/2120: mount -t ext2 /dev/sda3 /mnt/sda3_ext2
tinym1/2120: cd /mnt/sda3_ext2/
tinym1/2120: sudo -H -u chen xterm -e bash
tinym1/2120: cd -
/
tinym1/2120: umount /mnt/sda3_ext2/ && aplay -q /xp/WINDOWS/Media/notify.wav
tinym1/2120: rmdir /mnt/sda3_ext2/
tinym1/2120: exiting...in
tinym1/2125: skipped bdi remove
tinym1/2126: skipped usb_endpoint remove /dev/usbdev1.9_ep01
tinym1/2127: skipped usb_endpoint remove /dev/usbdev1.9_ep82
tinym1/2128: skipped usb_endpoint remove /dev/usbdev1.9_ep83
tinym1/2129: skipped scsi_device remove
tinym1/2130: skipped scsi_disk remove
tinym1/2131: block remove /dev/sda4 swap
tinym1/2131: aplay -q /xp/WINDOWS/Media/chimes.wav &
tinym1/2132: block remove /dev/sda3 ext2
tinym1/2132: aplay -q /xp/WINDOWS/Media/chimes.wav &
tinym1/2131: exiting...
tinym1/2132: exiting...
tinym1/2133: block remove /dev/sda2 ntfs
tinym1/2133: aplay -q /xp/WINDOWS/Media/chimes.wav &
tinym1/2134: block remove /dev/sda1 vfat
tinym1/2134: aplay -q /xp/WINDOWS/Media/chimes.wav &
tinym1/2133: exiting...
tinym1/2134: exiting...
tinym1/2135: skipped bdi remove
tinym1/2137: skipped scsi remove scsi_device
tinym1/2138: skipped scsi_host remove
tinym1/2140: skipped usb_endpoint remove /dev/usbdev1.9_ep00
tinym1/2141: skipped usb_device remove /dev/bus/usb/001/009
tinym1/2136: skipped block remove disk /dev/sda
tinym1/2139: skipped usb remove usb_interface
tinym1/2142: skipped usb remove usb_device /dev/1-1
tinym1/2143: skipped usb add usb_device /dev/1-1
tinym1/2144: skipped usb add usb_interface
tinym1/2145: skipped scsi_host add
tinym1/2146: skipped usb_endpoint add /dev/usbdev1.10_ep01
tinym1/2147: skipped usb_endpoint add /dev/usbdev1.10_ep82
tinym1/2148: skipped usb_device add /dev/bus/usb/001/010
tinym1/2149: skipped usb_endpoint add /dev/usbdev1.10_ep00
tinym1/2150: skipped scsi add scsi_device
tinym1/2151: skipped scsi_disk add
tinym1/2155: skipped bdi add
tinym1/2152: skipped block add disk /dev/sda
tinym1/2156: skipped scsi_device add
tinym1/2154: block add /dev/sda2 hfsplus
tinym1/2154: aplay -q /xp/WINDOWS/Media/Windows XP Pop-up Blocked.wav &
tinym1/2154: export DISPLAY=:0.0
tinym1/2154: mkdir /mnt/sda2_hfsplus
tinym1/2154: mount -t hfsplus /dev/sda2 /mnt/sda2_hfsplus
tinym1/2154: cd /mnt/sda2_hfsplus/
tinym1/2154: sudo -H -u chen xterm -e bash

tinym1/2154: cd -
/
tinym1/2154: umount /mnt/sda2_hfsplus/ && aplay -q /xp/WINDOWS/Media/notify.wav
tinym1/2154: rmdir /mnt/sda2_hfsplus/
tinym1/2154: exiting...
tinym1/2157: skipped usb_endpoint remove /dev/usbdev1.10_ep01
tinym1/2158: skipped usb_endpoint remove /dev/usbdev1.10_ep82
tinym1/2159: skipped scsi_device remove
tinym1/2160: skipped scsi_disk remove
tinym1/2161: block remove /dev/sda2 hfsplus
tinym1/2161: aplay -q /xp/WINDOWS/Media/chimes.wav &
tinym1/2162: block remove /dev/sda1 vfat
tinym1/2162: aplay -q /xp/WINDOWS/Media/chimes.wav &
tinym1/2161: exiting...
tinym1/2163: skipped bdi remove
tinym1/2162: exiting...
tinym1/2165: skipped scsi remove scsi_device
tinym1/2166: skipped scsi_host remove
tinym1/2168: skipped usb_endpoint remove /dev/usbdev1.10_ep00
tinym1/2169: skipped usb_device remove /dev/bus/usb/001/010
tinym1/2164: skipped block remove disk /dev/sda
tinym1/2167: skipped usb remove usb_interface
tinym1/2170: skipped usb remove usb_device /dev/1-1

The udev-event of sequence numbers ($SEQNUM) from 2106 to 2117 are ignored but logged in the logfile /tmp/tinym1.log.

The udev-event 2118, for example, was triggered upon detection of a new ($ACTION==add) partition ($DEVTYPE) /dev/sda1 ($DEVNAME) on a block ($SUBSYSTEM) device of filesystem type vfat ($ID_FS_TYPE). The script automatically launched aplay(1), mkdir(1), mount(8), cd, sudo(8), xterm(1). After termination of xterm (e.g. by typing Ctrl-D or "exit"), it tried to get away from that directory (cd -), and subsequently umount(8), aplay(1), and rmdir(1).  As you can see, the mass storage device is ready for safe unplug as soon as the sound file "notify.wav" started to play, as long as this device contains only one partition and only one pop-up window has been opened.  It is not necessary to wait for the sound file to play to the end!

The event 2119 of a NTFS filesystem in the /dev/sda2 partition was handled exactly in the same way. If you are using the ntfs-3g as I do, you may like to override the variable ID_FS_TYPE by inserting the following codes at a proper location in the script:
if [ "$ID_FS_TYPE" = "ntfs" ] && which ntfs-3g > /dev/null then ID_FS_TYPE="ntfs-3g" fi
The event 2120 was triggered by a ext2 filesystem in /dev/sda3 which is no sweat at all. However, root privilege is required to write on the partition.

The event numbered 2121 was triggered by a Linux swap partition of ID 0x82 in /dev/sda4. The script tried to mount it, failed,
mount: unknown filesystem type 'swap'
and terminated without trying to launch a terminal emulator on X.

The event 2154 shows that this script is also capable of mounting a hfsplus filesystem embedded in a EFI(Extensible Firmware Interface) GPT(Globally Unique Identifier Partition Table) disk, even if it was originally formatted by a MacBook Air notebook computer.  However, this script does not do anything specific for GPT nor for hfsplus filesystem.  It simply works homogeneously as long as the kernel and the system support it. See also the related article 〈在 Linux 讀取 GPT 與 hfsplus 檔案系統〉(Accessing GPT and hfsplus Filesystem on Linux) which should be comprehensible for an English reader.

Projects
  • Try defining the variable XTERM to your favourite terminal emulator, such as "uxterm", "aterm", "rxvt", "crxvt"; or even "crxvt -im xcin", "crxvt -im gcin" if you are using xcin(1)/gc〈〉in(1) as chinese input method and Big5 character set (used in Taiwan). However, the additional line for the locale
    export LC_CTYPE="zh_TW.Big5"
     is required for xcin(1)/gcin(1) to start.
  • Try changing the variable SHELL to the shell of your choice, such as ash, ksh, csh, tcsh, etc.
  • Try inserting the following code segment:
    if [ "$ID_FS_TYPE" = "ntfs" ] && which ntfs-3g > /dev/null then ID_FS_TYPE="ntfs-3g" fi
    at a proper location of the script, so that mount -t ntfs-3g is executed instead of mount -t ntfs.
  • What you might also like to hack on the script is to insert various mount options (mount -o) according to the filesystem type ($ID_FS_TYPE), so that the mounted volume is readily writable.
  • What if you've got some usb thumb-drives on which no partition table is found, instead, a filesystem is made directly beginning from the first sector of the whole disk, for instance, msdos on /dev/sda?  What if it's an optical drive containing a iso9660 or udf filesystem? Well, in this case, this script will not respond at all.  However, that's what the udev utility program /lib/udev/vol_id is good for.  Stay tuned, the story is to be continued...
Security Issue

Do not apply this automounter on public service machines without modification. It has been created for mere demonstration purpose, where the shell-script is always run with root privilege and is fragile against malicious attack.


Related Articles

2011-11-04 Linux 的 udev 是個有趣又有用的玩具(1)
2011-11-11 Linux 的 udev 是個有趣又有用的玩具(2)
2011-11-11 Linux 的 udev 是個有趣又有用的玩具(3)
2011-11-21 Linux 的 udev 是個有趣又有用的玩具(4)
2011-12-06 Linux Udev: How to uniquely ring a bell or run a script upon hardware insertion
2012-04-02 在 Linux 讀取 GPT 與 hfsplus 檔案系統

沒有留言:

張貼留言