Automounter for /dev/da* devices


When it comes to using computers, an everyday task is to plug external devices, such as USB pen drives, Webcams or other external media into your machine. What we will talk about here, are two different alternatives of detecting and mounting usb devices to your computer. In this part we will specifically discuss:

1. Using the rc.d interface to mount usb devices at boot time

2. FreeBSD's device daemon - devd(8)

1. Making use of the rc.d interface:

There are two different layers to the rc.d interface. The first one is /etc/rc.d/. This folder holds a lot of scripts that control the startup mechanism of various system services e.g. the secure shell daemon - sshd or the network time protocol daemon ntpd which keeps the computers system clock up to date by syncronizing it with servers on the web. A service is controlled i.e. enabled or disabled by setting a variable in /etc/rc.conf to "YES" or "NO".

For example, to enable the sshd, the respective variable that should be set is called:

sshd_enable="YES" to enable to service

and

sshd_enable="NO" to disable the service.

A number of default settings which can be considered to be reasonable are already set in /etc/defaults/rc.conf.

Any setting in /etc/rc.conf overides the settings in /etc/defaults/rc.conf. By its nature, the whole rc.d framework is controlled by sh.

The scripts that are found in the folder mentioned above (/etc/rc.d/), are reserved for system programs that come with the base system by default. FreeBSD makes a strict division between the base system and 3rd party software. Any other other software, i.e. software which is not part of the base system is installed unser /usr/local/bin or /usr/local/sbin. For example the apache webserver is not part of the base system and thus installed under /usr/local. This is a distinction which Linux - I can't speak for all distributions here - doesn't make. Software which is installed via e.g. yum can be put under /bin, /sbin, /usr/bin or /usr/sbin.

To startup 3rd party applications at boot time, FreeBSD mirrors the /etc/rc.d/ structure under /usr/local/etc/rc.d/. This is the second layer of the rc.d framework. For any 3rd party application which the used desires to start at boot time once can place a script under /usr/local/etc/rc.d/ and set a "knob" in /etc/rc.conf to start the respective service.

Now, to automount /dev/da* devices making use of FreeBSD's rc-script functionality, you can place the following shell script:

#!/bin/sh

# Place this file in /usr/local/etc/rc.d/ to enable
# usb mounts at system startup

# PROVIDE: usbmount_2
# REQUIRE: LOGIN
# KEYWORD: shutdown

. /etc/rc.subr

name="usbmount_2.sh"
rcvar="usbmount_2_enable"

command="/usr/local/bin/${name}"

#start_cmd="${name}_start"
stop_cmd=":"

load_rc_config ${name}
run_rc_command "$1"

under /usr/local/etc/rc.d/ and set a variable:

usbmount2_enable="YES"

in /etc/rc.conf.

Apart from this, you need a progam that is triggered by your rc script. You can place usbmount2.sh in /usr/local/bin/.

#!/bin/sh

# does the file exist?

if [ -e /dev/da0s1 ] ; then

	# assume it's a ufs formatted drive
	mount /dev/da0s1 /mnt/ > /dev/null 2>&1

	if [ $? -eq 0 ] ; then
		echo "usbmount_2: mounted /dev/da0s1 as ufs on /mnt"
	else
		# not ufs, assume it's an msdosfs formatted drive
		mount -t msdosfs /dev/da0s1 /mnt/ > /dev/null 2>&1

		if [ $? -eq 0 ] ; then 
			echo "usbmount_2: mounted /dev/da0s1 as msdosfs on /mnt"
		fi
	fi
fi

The script is very simple in nature and there are probably nicer ways of scripting this, but it serves as a proof of concept. Don't forget to chown to root:wheel if you haven't already done so and make the script executable.

# chown root:wheel /usr/local/bin/usbmount2
# chmod +x /usr/local/bin/usbmount2

This last shell script is the actual program that the rc script executes, when the rc variable usbmount_2_enable is set to "YES" in /etc/rc.conf.

However, with this approach one is only able to detect those devices which are sensed and created by the kernel at boot time. Later changes to the system, like adding another device would remain unnoticed by the rc script.

If you want to do any further reading on the rc.d framework and the scripting mechanism you can follow this link.

2. The devd(8) approach:

For this reason, we will now talk about another approach to handle this matter, this is via FreeBSD's device daemon -> devd(8). devd is a little smarter. It is capable of both, i.e. detecting devices that are created at boot time and those that are plugged in to the system while it is running.

devd's configuration file is /etc/devd.conf. You can place the following code into devd.conf to trigger an event once a USB drive or basically any other new device has been attached to the system.

notify 100 {
	match	"system"	"DEVFS";
	match	"subsystem"	"CDEV";
	match	"type"		"CREATE";
	action	"/usr/local/bin/mount_drives.sh";
};

The action statement is what we will concern ourselves with here. It starts up a shell script - mount_drives.sh - which mounts the device to a mount point with its given filesystem.

#!/bin/sh

# global variables
DEV=/dev/da
NUMBER=0
DEVICE=
slice=s1

# our mount points
mnt_pt_ufs="/mnt/unix"		# ufs
mnt_pt_fat="/mnt/fat" 		# msdosfs
mnt_pt_ntfs="/mnt/win"		# ntfs

check_mnt_pts()
{
	# before attempting a mount, we need to check wether our mount
	# point exists. otherwise the call to mount would fail, even if
	# the filesystem specified is the correct one

	local mountpoints dir

	mountpoints="$mnt_pt_ufs $mnt_pt_fat $mnt_pf_ntfs"

	for dir in $mountpoints ; do
		if [ ! -d "${dir}" ] ; then
			mkdir ${dir}
		fi
	done
}

check_mnt_status()
{
	local dev mounts i

	dev=$1
	mounts=`mount | awk '{ print $1}'`

	# loop through all the allready mounted devices. 
	# in case the device we're trying to mount is allready 
	# mounted, we notify main() with a return value of TRUE = 0
	for i in $mounts ; do
		if [ "${i}" = "${dev}${slice}" ] ; then
			return 0
		fi
	done
	
	return 1
}

mount_it()
{
	local dev fs_ufs fs_fat fs_ntfs mnt_ufs mnt_fat mnt_ntfs

	dev=$1 ; fs_ufs="ufs" ; fs_fat="msdosfs" ; fs_ntfs="ntfs"
	mnt_ufs="-t ${fs_ufs} ${dev}${slice} ${mnt_pt_ufs}"
	mnt_fat="-t ${fs_fat} ${dev}${slice} ${mnt_pt_fat}"
	mnt_ntfs="-t ${fs_ntfs} ${dev}${slice} ${mnt_pt_ntfs}"

	# do our possible mount points exist?
	check_mnt_pts

	# we do not yet know what type of filesystem ${dev}${slice} is.
	# we iterate through possibilities, until we find the right one
	# and mount it on the respective mount point ${mnt_pt_*}

	if `mount ${mnt_ufs} > /dev/null 2>&1` ; then
		echo "mounted ${dev}${slice} as ${fs_ufs} on ${mnt_pt_ufs}"
	elif `mount ${mnt_fat} > /dev/null 2>&1` ; then
		echo "mounted ${dev}${slice} as ${fs_fat} on ${mnt_pt_fat}"
	elif `mount ${mnt_ntfs} > /dev/null 2>&1` ; then
		echo "mounted ${dev}${slice} as ${fs_ntfs} on ${mnt_pt_ntfs}"
	else
		echo "${dev}${slice} not mounted"
	fi
}

### entry point

while [ -c ${DEV}${NUMBER} ]
do
	DEVICE=${DEV}${NUMBER}
	NUMBER=$(($NUMBER + 1))

	# check if the device has allready been mounted,
	# if yes, go to the next one

	if `check_mnt_status ${DEVICE}` ; then
		continue
	fi
	mount_it ${DEVICE}
done

exit 0

This script as well, should be owned by root:wheel and be executable so issue:

# chown root:wheel /usr/local/bin/mount_drives.sh
# chmod +x /usr/local/bin/mount_drives.sh

on the command line.

Drawbacks to the devd(8) approach:

1) Imagine a user want's to attach two msdosfs formatted usb drivers to the system. Since /mnt/fat is allready mounted, mounting another fat is not possible. The shell script code should be edited towards a more dynamic approach, i.e. when /mnt/fat is mounted create /mnt/fat2 as an additional mount point. But as always, one has to stop somewhere, and with the way things are standing. This suffices for my purposes.

2) The script is limited, in terms of filesystem support. Only ufs, msdosfs(FAT) and ntfs are supported. I personally have hitherto not had the need to make use of any others, and it is always possible to extend the script's functionality.